为什么需要前瞻性声明?

可能重复:
C ++应该消除头文件吗?

在像C#和Java这样的语言中,没有必要在使用它之前声明(例如)一个类。 如果我理解正确,这是因为编译器对代码进行了两次传递。 在第一个中它只是“收集可用信息”,在第二个中它检查代码是否正确。

在C和C ++中,编译器只进行一次传递,因此当时所有东西都需要可用。

所以我的问题基本上就是为什么不用C和C ++这样做。 它不会消除头文件的需求吗?

简短的回答是,计算能力和资源在定义C的时间与Java在25年后出现的时间之间呈指数级增长。

答案越长……

编译单元的最大大小 – 编译器在单个块中处理的代码块 – 将受编译计算机具有的内存量的限制。 为了处理您在机器代码中键入的符号,编译器需要将所有符号保存在查找表中,并在代码中遇到它们时引用它们。

当C在1972年创建时,计算资源更加稀缺并且溢价很高 – 在大多数系统中都不能立即存储复杂程序的整个符号表所需的内存。 固定存储也很昂贵,并且非常慢,因此虚拟内存或在磁盘上存储符号表部分等想法根本不允许在合理的时间范围内进行编译。

解决这个问题的最佳方法是将代码分成更小的部分,方法是人为地排除符号表中哪些部分需要提前编译单元。 向程序员强加一个相当小的任务来宣告他使用什么节省了计算机搜索整个程序以获得程序员可以使用的任何东西的巨大努力。

它还使编译器不必在每个源文件上进行两次传递:第一个用于索引内部的所有符号,第二个用于解析引用并查找它们。 当您处理磁带时,在几秒钟内测量搜索时间,读取吞吐量以每秒字节数(不是千字节或兆字节)来衡量,这非常有意义。

C ++虽然在大约17年后创建,但被定义为C的超集,因此必须使用相同的机制。

当Java在1995年推出时,平均计算机有足够的内存,即使对于复杂的项目,也只有一个符号表,不再是一个重要的负担。 Java并不是为了与C语言向后兼容而设计的,因此它不需要采用传统机制。 C#同样没有受到阻碍。

结果,他们的设计师选择将符号声明的划分负担从程序员身上移开并再次放在计算机上,因为它的成本与编译的总工作量成正比。

一句话:编译器技术已经取得了进展,无需进行前向声明。 加上计算机的速度要快几千倍,因此可以进行额外的计算以处理缺少前向声明。

C和C ++较旧,在需要保存每个CPU周期时都是标准化的。

不,它不会消除头文件。 它将消除使用标头在同一文件中声明类/函数的要求。 标头的主要原因是不要在同一个文件中声明事物。 标头的主要原因是声明在其他文件中定义的内容。

无论好坏,C(和C ++)的语义规则都要求“单一通道”式行为。 例如,考虑这样的代码:

int i; int f() { i = 1; int i = 2; } 

i=1分配给全局而不是 f()定义的全局 。 这是因为在分配时,尚未看到i的本地定义,因此不予考虑。 您仍然可以使用两遍编译器来遵循这些规则,但这样做可能并非易事。 我没有检查他们的规格确定,但我的直接猜测是Java和C#在这方面与C和C ++不同。

编辑:由于评论说我的猜测不正确,我做了一些检查。 根据Java语言参考,§14.4.2,Java似乎遵循与C ++相同的规则(稍微不同,但不是很多。

至少在我阅读C#语言规范时 ,(警告:Word文件)然而,它不同的。 它(§3.7.1)说:“在局部变量声明(第8.5.1节)中声明的局部变量的范围是声明发生的块。”

这似乎表明在C#中,局部变量应该在声明它的整个块中可见,因此使用类似于我给出的示例的代码,赋值将是局部变量,而不是全局变量。

所以,我的猜测是正确的一半:Java遵循(在这方面几乎和C ++一样,但C#没有。

这是因为C / C ++中的编译模块较小。 在C / C ++中,每个.c / .cpp文件都是单独编译的,从而创建了一个.obj模块。 因此,编译器需要有关在其他编译模块中声明的类型和变量的信息。 此信息以正向声明的forms提供,通常在头文件中提供。

另一方面,C#将几个.cs文件一次编译到一个大的编译模块中。

实际上,当从C#程序引用不同的编译模块时,编译器需要像C ++编译器一样知道声明(类型名称等)。 此信息直接从编译的模块中获取。 在C ++中,相同的信息被明确分开(这就是为什么你不能从C ++中找到变量名 – 编译的DLL,但可以从.NET程序集中确定它)。

C ++中的前向声明是一种提供有关当前编译源可能用于编译器的其他代码片段的元数据的方法,因此它可以生成正确的代码。

该元数据可以来自链接库/组件的作者。 但是,它也可以自动生成(例如,有些工具可以为COM对象生成C ++头文件)。 无论如何,C ++表达元数据的方式是通过您需要包含在源代码中的头文件。

C#/ .Net在编译时也使用类似的元数据。 但是,当应用它所应用的程序集时,该元数据会自动生成,并且通常会嵌入到该元数据中。 因此,当您在C#项目中引用一个程序集时,您实际上是在告诉编译器“请在此程序集中查找您需要的元数据”。

换句话说,C#中的元数据生成和消费对开发人员来说更加透明,使他们能够专注于真正重要的事情 – 编写自己的代码。

使用与程序集捆绑的代码的元数据也有其他好处。 reflection,代码发出,动态序列化 – 它们都依赖于元数据,以便能够在运行时生成正确的代码。

与此类似的C ++模拟将是RTTI,尽管由于不兼容的实现而没有被广泛采用。

来自Eric Lippert,C#内部所有内容的博主: http : //blogs.msdn.com/ericlippert/archive/2010/02/04/how-many-passes.aspx :

C#语言不要求在使用之前发生声明,这对用户和编译器编写器也有两个影响。 […]

对编译器编写器的影响是我们必须有一个“双通”编译器。 在第一遍中,我们寻找声明并忽略主体。 一旦我们收集了我们从C ++中的头文件中获得的声明中的所有信息,我们将对代码进行第二次传递并为主体生成IL。

总而言之,使用某些东西不需要在C#中声明它,而在C ++中则是如此。 这意味着在C ++中,您需要显式声明事物,并且使用头文件这样做更方便和安全,因此您不会违反一个定义规则 。