由于历史原因,C 中的函数可以以旧方式声明,无需原型,如下:
type function()
或带有原型的新方法,如:
type function(type parameter)
type function(type parameter, type parameter)
type function(type parameter, type parameter, type parameter)
… and so on
因此,给定参数类型的函数声明被称为具有原型。这会影响调用函数时参数的准备。使用原型,参数被转换为参数的声明类型。在没有原型的情况下,使用称为默认参数提升的规则将参数转换为默认类型。 (另外,如果一个函数只是被声明而不是定义,参数的名称可以省略,留下一个只列出类型的原型,如type function(type, type, type)。)
由于旧语法,要声明一个带有原型的函数说它没有参数,你需要明确地说void:type function(void)。 (这点在C++中不同,它不必支持旧语法。在C++中,type function()是一个没有参数的原型。)
此外,原型可以通过将,... 放在一个或多个常规参数之后来指定变量参数:
type function(type parameter,...)
在这种情况下,第一个参数将转换为参数类型,但与 ... 对应的参数将使用默认参数提升进行转换。 printf 就是这样声明的。
默认的参数提升是:
- 比
int 窄的整数(从技术上讲,等级较低)如果可以表示源类型的所有值,则提升为int,否则提升为unsigned int。
-
float 参数转换为 double。
对于默认参数提升中的位域也有一些挑剔,我不能说这在我的代码中曾经出现过。
历史
在旧的 C 语法中,函数会被定义为:
type function(name0, name1, name2)
type name0;
type name1;
type name2;
它会在没有原型的情况下被声明,就像type function()一样。这意味着调用者不知道参数的实际类型。但是你不能只为char 参数传递一个char 值或为short 参数传递一个short 值,因为C 试图变得灵活,以便它可以在多种类型的计算机上工作,它有规则关于 char 和 short 值在表达式中被提升为 int。此外,像'X' 这样的字符常量的类型为int,而不是char,因此,如果有人用foo('X') 调用函数,编译器将不知道foo 是否真的只需要char 而不是int。因此,默认参数提升是为了匹配整数提升。
后来的 C 版本通过提供一种在对调用者可见的声明中声明参数类型的方法来解决此问题,因此参数始终与参数匹配(并且编译器有更多可用于提供错误消息的信息)。但是还是要支持旧语法,才能编译旧代码。
更多
使用 C 标准中的短语“表示被调用函数的表达式”是因为您可以通过指针调用函数,而不仅仅是它们的名称。例如,我们可以这样写:
int (*FunctionPointer)() = (int (*)()) printf;
然后使用FunctionPointer("Hello, world.\n"); 调用printf。那么“表示被调用函数的表达式”就是FunctionPointer,尽管printf 有原型,但它没有原型。 (没有充分的理由使用printf 函数执行此操作,但一些深奥的代码可能会做一些令人讨厌的事情。)
你最初可以声明一个没有原型的函数:
int foo();
然后添加原型:
int foo(float x, char *y);
编译器将合并声明,生成的foo 将有一个原型。