从Java / C#角度理解C ++编译器

我是一名经验丰富的Java / C#程序员,我最近开始学习C ++。 问题是,我无法理解如何构造各种头文件和代码文件。 这似乎主要是由于我对编译器如何将所有内容链接在一起缺乏了解。 我曾尝试过阅读一些教科书,但我的先入之见在我的Java和C#知识上有很大的色彩。 例如,我很难掌握方法等可以在命名空间中定义而不是仅在类定义中定义的事实。

我发现了很多C ++ – > Java / C#指南,但实际上没有其他方法可行。 是否有任何好的资源可以简化Java / C# – > C ++转换,特别是在理解编译过程方面?

C ++ FAQ是关于C ++所有特性的优秀资源,但它可能比你正在寻找的更先进 – 大多数问题(不仅仅是答案)对于相当有经验的C ++开发人员来说都是神秘的。

我想如果你谷歌的C ++教程,你将能够找到一些东西。 您可能还想尝试学习汇编语言(或至少快速了解微处理器中实际发生的事情),因为C和C ++在执行操作时都非常接近硬件。 这是他们的速度和力量来自的地方,但它的代价是Java提供的一些更好的抽象。

我可以尝试回答上面提到的具体问题,但我不知道我会做得多好。

理解头文件和cpp文件之间关系的关键之一是理解“翻译单元”的想法。 Java类文件可以被视为转换单元,因为它是编译为二进制forms的基本单元。 在C ++中,几乎每个cpp文件都是一个翻译单元(如果你做了奇怪的事情,也有例外)。

头文件可以包含在多个翻译单元中(并且必须包括在使用标头中定义的任何内容的任何地方)。 #include指令字面上只进行文本替换 – 包含文件的内容逐字插入#include指令所在的位置。 您通常希望在头文件中定义类接口,并在cpp文件中定义实现。 这是因为您不希望将实现细节公开给可能包含标题的其他翻译单元。 在C ++中,包括类在内的所有内容都不是真正丰富的对象,而只是编译器赋予其意义的内存块…通过将相同的头信息编译到每个转换单元中,编译器保证所有转换单元都具有同样理解一大块记忆所代表的东西。 由于编译后缺乏丰富的数据,reflection之类的东西是不可能的。

C ++构建过程的第二步是链接,这是链接器获取所有已编译的转换单元并查找转换单元中使用但未在其中定义的符号(通常是函数调用,但也包括变量)的位置。 然后,它查找另一个定义该符号并将它们“链接”在一起的转换单元,以便对特定函数的所有调用都定向到定义它的转换单元。

在类方法的情况下,必须通过类实例调用它们,类实例在幕后只是指向一块内存的指针。 当编译器看到这些类型的方法调用时,它会输出调用函数的代码,隐式地将指针(称为this指针)作为第一个参数传递给函数。 您可以拥有不属于类的函数(不是方法,正如您所说,因为方法恰当是类的成员函数,因此没有类就不能存在),因为链接器没有类的概念。 它将看到一个定义函数的转换单元,另一个调用函数并将它们绑定在一起的转换单元。

这最终比我预期的要长很多,当然是过于简单化了,但是根据我的知识和提供的详细程度,它是准确的……希望它有所帮助。 至少它应该给你一些谷歌搜索的起点。

当我第一次开始使用C时,这让我很困惑。 书籍没有很好地描述标题与代码文件的正确使用。

编译器通过加载每个.cpp文件并独立于所有其他文件进行编译来工作。 编译的第一步是加载#include语句引用的所有头文件。 你可以想到它在整个foo.h中进行文本插入,只要有#include“foo.h”。

这对于如何构建文件有什么影响? 头文件应该包含其他.cpp文件所需的程序的任何部分。 作为一般规则,实现不应该在头文件中。 这会引起问题。 头文件应包括类,函数和全局变量的声明(如果必须使用它们)。

我实际上建议远离有关C ++编译器的解释并查看C编译器的解释。 根据我的经验,可以更好地解释这些问题,避免将您与OOP问题混淆。 寻找有关C单独编译的材料。 我会把你推荐给我母校的一本很棒的小册子,但它不是英文的。

C编译和Java / C#之间的主要区别在于编译不会创建已解析的实体。 换句话说,当您使用Java进行编译时,编译器会为任何引用的类查找已编译的类文件,并确保所有内容都可用且一致。 基本假设是,当您最终运行程序时,这些文件也将可用。

另一方面,编译的C文件只是一个“承诺”。 它依赖于依赖关系看起来像什么的声明(以函数声明的forms),但不保证这些都是在任何地方定义的。 您需要做的最困难的思维模式切换是将C文件视为不仅仅是该文件,而是将该文件与其包含的所有内容(即预处理器生成的内容)聚合在一起。 换句话说,编译器没有看到头文件,它似乎是一个大文件。 编译器在生成的目标文件中跟踪“仍然缺失”的所有内容。 稍后,在链接时,链接器通过尝试使用来自不同目标文件的材料填充所有空白来解决此问题。

您可能想知道为什么编译和链接也是分开的(因为我没有看到任何post解释它,并且它是导致很多混淆的原因而不知道事情的根本原因)。

链接和编译是单独完成的,因为(并且可能有多个原因)需要进行库调用。 如果您定义了它或者它的任何类型,那些在这些头文件中实现函数原型的代码是已经编译并作为目标代码存在的库的一部分。 如果要使用一个巨大的编译过程,那么你需要拥有这些库调用的源代码,以及编译期间的更多时间,因为你还要编译库代码。