我正在跟随一份教程学习curses,其中所有的C代码都是在main()
之前原型函数内部定义,然后再进行定义。在我的C++学习中,我听说过函数原型,但从未尝试过,并且据我所知,它对代码编译的影响不大。这更多是程序员个人选择吗?如果是,那么为什么它被包含在C中呢?
我正在跟随一份教程学习curses,其中所有的C代码都是在main()
之前原型函数内部定义,然后再进行定义。在我的C++学习中,我听说过函数原型,但从未尝试过,并且据我所知,它对代码编译的影响不大。这更多是程序员个人选择吗?如果是,那么为什么它被包含在C中呢?
在C语言中,最初并未包括函数原型。当您调用函数时,编译器只是相信该函数存在,并接受您提供的参数类型。如果您获取参数的顺序、数量或类型有误,则很遗憾——您的代码将在运行时以可能难以理解的方式失败。
后来的C语言版本添加了函数原型以解决这些问题。在某些情况下,您的参数会被隐式转换为声明类型,或者作为与原型不兼容的标记,并且编译器可以标记类型顺序和数量错误为错误。这具有启用可变参数函数及其所需特殊参数处理的副作用。
请注意,在C语言中(与C ++不同),声明为foo_t func()
的函数与声明为foo_t func(void)
的函数不同。后者被原型化为没有参数的函数。前者声明了一个没有原型的函数。
x()
时,程序知道该函数的存在。这样,y()
就知道有一个x()
函数存在。由于C语言采用自上而下的编译方式,因此需要事先定义函数。x();
y();
main(){
}
y(){
x();
}
x(){
...
more code ...
maybe even y();
}
gcc -Wall -pedantic -std=c89 x.c
会产生10个警告。 - Jens函数原型是编译器编写早期的遗留物。过去,编译器需要对源文件进行多次扫描以进行编译被认为效率极低。
在C语言中,在某些情况下,以一种方式引用函数在语法上等同于引用变量:考虑获取指向函数的指针与获取指向变量的指针。在编译器的中间表示中,这两者在语义上是不同的,但从上下文中无法确定标识符是变量、函数名还是无效的标识符。
由于从上下文中无法确定,没有函数原型,每次编译一个源文件时,编译器都需要对其进行额外的扫描。这将为任何编译添加额外的O(n)因素(即,如果编译是O(m),现在将是O(m*n)),其中n是项目中文件的数量。在大型项目中,编译已经需要数小时,拥有双通道编译器是非常不可取的。
提前声明所有函数将允许编译器在扫描文件时构建函数表,并能够确定它遇到的标识符是引用函数还是变量。
由于这个原因,C(以及C++)编译器在编译方面非常高效。它允许您拥有这样一种情况,即可以在单独的.h文件中定义迭代器类,其中包括父容器类。由于您已经在迭代器中包含了父标题,因此无法拥有像"getIterator()"这样的方法,因为返回类型必须是迭代器类,因此它需要在父标题中包含迭代器头文件,从而创建循环包含(一个包含另一个,另一个再次包含自己等)。
如果将迭代器类原型放置在父容器内部,则可以拥有这样的方法,而无需包含迭代器头文件。这只起作用,因为您仅仅是说这样的对象存在并将被定义。
当然,有一些解决方法,例如使用预编译头文件,但我认为这样做不太优雅,并带来一堆缺点。当然,这是C++,而不是C。然而,在实践中,您可能会遇到这样一种情况,想以这种方式排列代码,除了类之外。