相信C语言初学者常会在前辈留下的项目,或者一些开源项目中看到以 void 作为参数的函数,例如 void fun(void),虽然我们都明白在C语言中,“void”表示空,但是按理说,如果函数不需要参数,直接空着就行:void fun(),为什么还要多此一举的写上 void 呢?
C语言函数参数的定义方式
大多数C语言教程在谈到自定义函数时,一般都将函数的“参数类型列表”放在“()”中,例如:
void f(int a, float b){
...
}
C语言没有理由将“无参数函数”作为特例处理,因此从“通用性”来看,定义C语言函数时,“()”内应该始终有“参数类型列表”,如果函数不需要使用参数,应该指定为 void,而不是空着。
但是在C语言程序开发中,void fun(); 这种定义函数的方式又的确是可行的,难道C语言真的将“无参数函数”作为特例处理了吗?
其实不是的,在C语言中,定义函数时,指定参数还有另外一种方式(标识符列表方式):
void f(a, b)
int a;
float b;
{
...
}
以标识符列表方式定义函数不常见,但的确是可用的,下面是一段完整的测试C语言代码,请看:
#include <stdio.h>
void f(a, b)
int a;
float b;
{
printf("%d, %f\n", a, b);
}
int main()
{
f();
f(1);
f(2, 3.14);
return 0;
}
编译并执行这段C语言代码,得到如下输出:
# gcc t.c -Wall
# ./a.out
1, 0.000000
1, -inf
2, 3.140000
可见,以“标识符列表方式”定义C语言函数,在调用时,函数的表现很像不定参数函数,未明确传递值的参数的值是未定义的。
另外,定义C语言函数时,标识符列表可以省去,但是参数类型列表不可以。因此,void fun(); 这种定义函数的方式其实是“标识符列表”被省去的一种特殊。
正如前面所讨论的,以“标识符列表方式”定义的C语言函数表现很像不定参数函数,因此 void fun(); 定义的函数,传递给其任意多的参数都是允许的:
void fun()
{...}
fun();
fun(1,3,4); //合法
而 void fun(void); 这种指定参数类型列表的定义方式就不同了,它限制了 fun() 函数不能接收任何参数:
void fun(void)
{...}
fun();
fun(1,2,3); //非法
标识符列表方式定义函数
现在我们已经知道,以参数类型列表方式定义C语言函数时,“()”内必须指定参数类型——如果不需要参数,需要指定其为 void。
因此,void fun(); 这种定义方式其实是以标识符列表方式定义的,只不过参数的类型和数目被省去了,因此调用者在调用 fun() 函数时,必须事先知道参数类型和数目。
如果传递给函数的参数是任意的(这对于以“标识符列表方式”定义的函数是允许的),那么就可能导致函数出现不预期的行为。例如函数的堆栈可能会被破坏,因为编译器并不限定传递的参数个数,函数在获得控制权后可能会出现不同的内存布局。
一般不推荐使用“标识符列表”方式定义C语言函数,这种方式在以前比较流行,如今有一些存在于许多历史遗留的代码中。
参数类型列表方式定义函数
以参数类型列表方式定义C语言函数是今天的主流,这种方式更加安全,因为它严格限定调用者传递给函数的参数类型和个数。例如 void fun(void); 函数在被调用时,只能以无参数方式调用 fun()。
另外,以参数类型列表方式定义的C语言函数,编译器能够准确知道参数的数据类型,因此函数可以对接收到的参数做隐式的类型转换,还可以对参数做一些类型提升(即所谓的默认参数提升),例如 char 类型提升为 int。
最后
顺便说一下,如果某个源文件中出现了省略的标识符列表方式定义的函数,和参数列表方式定义的函数,那么C语言原型将以参数列表方式定义的为准,请看下面这段C语言代码:
void f();
void f(int a)
{
...
}
原因也是简单的,void f(); 定义的函数 f() 可以接收任意多的参数,而 void f(int a); 定义的函数 f() 只能接收一个 int 型的参数,编译器为了不违背二者,只好让函数 f() 只接受一个 int 型参数了。