上节课咱们学了调用函数,大家发现一个有意思的事没有?就是printf和scanf这些函数,我们在main函数里直接就调用了,编译器也没报错。
但是你定义一个自己的函数,如果在main后面实现,编译器就报错说不认识。
凭啥?这不公平啊!
原因特简单:编译器是个死脑筋,它从上往下看代码,看到的才认,没看到的就说不认识。
你想想,编译器读到main里面调用add的时候,它还没看到后面add长啥样呢,它心里想的是“这add是个啥玩意儿?没见过,报错!”
而printf为啥能直接用?因为你写了#include <stdio.h>,这个头文件里面提前告诉编译器了——“printf这个函数长这样,你先认着,具体的实现别处找去”。
所以今天咱们就讲一件事:怎么让你的函数也能像printf一样,在定义之前就能被调用。
这就是函数声明,也叫函数原型。
语法特别简单:把函数定义的第一行复制下来,后面加个分号,就行了。
比如你定义了一个函数:
int add(int a, int b) { return a + b; }
那它的声明就是:
int add(int a, int b);
你看,一模一样,就是把后面的大括号和函数体换成个分号,完事。
这叫函数原型——它就像这个函数的“身份证”,告诉编译器:这个函数叫啥名,接收啥参数,返回啥类型。具体怎么做的不重要,重要的是让编译器知道“有这个函数存在”。
有了这个声明,你就能把函数定义写在调用处的后面了:
#include <stdio.h> int add(int a, int b); // 声明:先告诉编译器有这号人 int main() { int z = add(3, 5); // 这时候编译器认识add了 printf("%d\n", z); return 0; } int add(int a, int b) { // 定义:具体实现放后面 return a + b; }
你看,编译器读到main里调用add的时候,往前翻了翻,看到了声明“哦,add是这个样子的,行,我知道了”,就不会报错了。后面再看到真正的定义的时候,它心里有数了。
有人可能要问了:那我在前面就直接把定义写了,不就不用声明了吗?
没错,确实不用。很多小项目就是这么干的——把函数定义都写在main前面,省事儿。
但是在实际开发中,你基本不可能把所有函数都堆在main前面。为啥?
第一,代码一多,你就得把函数分组放在不同文件里。比如一个文件专门放数学计算函数,一个文件专门放输入输出函数,一个文件是主程序。这时候你的main要用别的文件里的函数,就得靠头文件里的声明来“打招呼”。
第二,如果两个函数互相调用,比如A调用B,B又调用A。那你把谁放前面都不对——放A前面,编译器不认识B;放B前面,编译器不认识A。唯一的解决办法就是:把两个函数的声明都写在最前面,定义随便放哪儿都行。
函数声明其实可以写得比定义更“随性”一些。你看:
定义的时候,形参名是必须写的,因为你后面函数体里要用。
声明的时候,形参名可以省略,只写类型就够了。
int add(int, int); // 合法,参数名可以省略
这两种写法都行。不过个人建议写上名字,因为看得更清楚——别人一看就知道“哦,第一个是a,第二个是b”,心里有谱。
还有一个很重要的概念:声明和定义是两码事。
-
声明:告诉编译器“有这个函数存在,长这个样子”。不分配内存,不生成代码,就是个“口头告知”。
-
定义:真正把函数体写出来,分配内存,生成机器码,函数实实在在地存在了。
你可以声明无数次,但只能定义一次。
打个比方:声明就像是你在学校公告栏贴个通知——“下周学校要办运动会”。你可以在校门口贴一张,食堂贴一张,教学楼贴一张,贴一百张都没问题,大家知道有这个事儿了。但是运动会真正办起来,只能办一次,不可能校门口办一场、食堂再办一场。
再说说#include和函数声明的关系。
实际上,你每次写#include <stdio.h>,就是把stdio.h这个头文件里的内容全部“复制粘贴”到你的代码里。而这个头文件里面有什么?有大量函数的声明。printf、scanf、strlen这些函数的声明全在里面。
所以你看,你早就用过函数声明了,只是以前不知道这回事而已。
#include <stdio.h> // 这里面就装着一大堆函数声明 int main() { printf("Hello"); // 因为stdio.h里已经声明了printf,编译器认识它 return 0; }
等你学了多文件编程,你也会自己写头文件(.h文件),把一些函数声明放进去,然后让别人#include你的头文件,就能用你的函数了。
最后,给同学们总结一下这块的核心要点:
什么时候需要函数声明? 当你要调用的函数定义在调用处后面的时候,就需要在前面先声明一下。
函数原型怎么写? 把函数的第一行(返回值类型、函数名、参数列表)抄下来,后面加个分号就行。
声明和定义有啥区别? 声明是“告诉编译器有这个函数”,定义是“真正把这个函数写出来”。声明可以有多次,定义只能有一次。
声明里的参数名可以省略不写。
好了,同学们可以自己试试了。写一个函数,定义在main后面,然后在前面加个声明,看能不能编译通过。再把声明删了,看看编译器报什么错。亲手试一次,比背十遍都管用。