我们不仅可以调用C标准库提供的函数,也可以定义自己的函数,事实上我们已经这么做了:我们定义了main函数。例如:
- int main(void)
- {
- int hour = 11;
- int minute = 59;
- printf("%d and %d hours\n", hour, minute / 60);
- return 0;
- }
main函数的特殊之处在于执行程序时它自动被操作系统调用,操作系统就认准了main这个名字,除了名字特殊之外,main函数和别的函数没有区别。
main函数不带任何参数,参数列表应该写成void。main函数的返回值是int型的,return 0;这个语句表示返回值是0,main函数是被操作系统调用的,通常程序执行成功就返回0,在执行过程中出错就返回一个非0值。比如我们将main函数中的return语句改为return 4;再执行它,执行结束后可以在Shell中看到它的退出状态(Exit Status):
$ ./a.out
11 and 0 hours
$ echo $? //在Shell中查看main的退出状态命令
4
$?是Shell中的一个特殊变量,表示上一条命令的退出状态。
关于main函数需要注意两点:
(1)old规范中main函数不写返回值,默认返回int型。但是编译器无法检查程序中可能存在的Bug,增加了调试难度。所以main函数中必须明确写出返回值。
(2)其实操作系统在调用
main函数时是传参数的,main函数最标准的形式应该是int main(int argc, char *argv[])。如果不使用系统传进来的两个参数,C标准也允许int main(void)这种写法。但除了这两种形式之外,定义main函数的其它写法都是错误的。
我们先从不带参数也没有返回值的函数开始学习定义和使用函数:
例 3.2. 最简单的自定义函数
#include void newline(void) { printf("\n"); } int main(void) { printf("First Line.\n"); newline(); printf("Second Line.\n"); return 0; }
函数原型(Prototype)、函数声明、函数定义概念区分:
例 3.3. 较简单的自定义函数
#include void newline(void) { printf("\n"); } void threeline(void) { newline(); newline(); newline(); } int main(void) { printf("Three lines:\n"); threeline(); printf("Another three lines.\n"); threeline(); return 0; }
函数原型:比如void threeline(void)这一行,声明了一个函数的名字、参数类型和个数、返回值类型,这称为函数原型。
函数声明:在代码中可以单独写一个函数原型,后面加;号结束,而不写函数体,例如:
void threeline(void);
这种写法只能叫函数声明而不能叫函数定义,只有带函数体的声明才叫定义。
函数定义:只有分配存储空间的变量声明才叫变量定义,其实函数也是一样,编译器只有见到函数定义才会生成指令,而指令在程序运行时当然也要占存储空间。
如果在调用函数之前没有声明会怎么样呢?有的读者也许碰到过这种情况,我可以解释一下,但绝不推荐这种写法。比如按上面的顺序定义这三个函数,但是把开头的两行声明去掉:
#include int main(void) { printf("Three lines:\n"); threeline(); printf("Another three lines.\n"); threeline(); return 0; } void newline(void) { printf("\n"); } void threeline(void) { newline(); newline(); newline(); }编译时会报警告:
$ gcc main.c main.c:17: warning: conflicting types for ‘threeline’ main.c:6: warning: previous implicit declaration of ‘threeline’ was here但仍然能编译通过,运行结果也对。这里涉及到的规则称为函数的隐式声明(Implicit Declaration),在
main函数中调用threeline时并没有声明它,编译器认为此处隐式声明了int threeline(void);,隐式声明的函数返回值类型都是int,由于我们调用这个函数时没有传任何参数,所以编译器认为这个隐式声明的参数类型是void,这样函数的参数和返回值类型都确定下来了,编译器根据这些信息为函数调用生成相应的指令。然后编译器接着往下看,看到threeline函数的原型是void threeline(void),和先前的隐式声明的返回值类型不符,所以报警告。好在我们也没用到这个函数的返回值,所以执行结果仍然正确。