![088037d116501bc95f6c41481f9549ef.png](https://i-blog.csdnimg.cn/blog_migrate/00ebaf25deb0b180ad4de11a344fdc67.png)
文 | Edward
![f4ab0264e01f3d7d84782662a66a226b.png](https://i-blog.csdnimg.cn/blog_migrate/7beb95ba8386588748226cba7d6f49cf.png)
4.2 函数声明
当编译器检测到一个函数调用时,它产生代码传递参数,并且调用这个函数,等函数运行完成之后,接收到这个函数的返回值(如果函数有返回值)。但是编译器是如何知道函数接收到的是什么类型和多少数量的参数呢?如何知道该函数的返回值(如果有的话)类型呢?。这就是关于函数声明的问题了,之前4.1小节里面已经简单地讲述过,本小节将详细地来讨论这个问题。
如果前面所述的,编译器在调用这个函数时,根本就不清楚这个函数的返回值类型,形式参数的数量和类型,那么在调用这个函数时,编译器便会假设你调用函数时候,形式参数的类型和数量都是正确的,同时它也会假定函数会返回一个整数值。对于那些返回值并非整形的函数而言,这种隐藏函数特征的方式无异于如履薄冰,经常会导致错误。因此最为安全的办法就是将代码里面的函数特定信息全部一五一十地提供给编译器。那么函数的特定信息有哪些?如何一五一十地将函数的特定信息提供给编译器呢?
首先,函数的特定信息包含4个维度的信息,第一个为函数名,第二个为函数的返回值类型,第三个为函数的形式参数数量,第四个为函数的形式参数类型。只要将函数这四个维度的信息提供给编译器,编译器便会对这个函数“登记”,以便于后续它被调用时,有详细的信息可以提供给编译器参考。
其次,如何将这个函数信息提供给编译器。这里主要有两条途径。
第一条为,如果函数和调用者都在同一个源文件(同一个.c)文件中,并且,在这个函数被调用之前就已经定义好,即函数写在调用它的函数前面,那么编译器一早就会记住这个函数的具体信息,包括函数名,返回值,形参数量和类型。接着,编译器就可以检查后续所有调用者(同一源文件中)的信息是否正确。如图4-2-1所示。
![cc93aeaee9ffa622c9924a90ed46adef.png](https://i-blog.csdnimg.cn/blog_migrate/b36aea6d70bf747ca30849d4b773ffe7.png)
图4-2-1 函数在调用之前定义
在图4-2-1,我们定义了一个数值奇偶验证的函数,在main函数中,输入一个数字,并且调用这个数值奇偶验证函数,由于这个函数的定义和main函数在同一个源文件里面,且个数值奇偶验证函数定义在调用它的函数(main函数)之前,所以这样写函数,编译器也是认可,且安全的。
第二种向编译器提供函数信息的方法是使用函数原型。函数原型总结了函数定义的起始部分的声明,向编译器提供了有关该函数被调用时的完整信息。使用函数原型最为方便的办法,是将函数原型置于一个独立的文件,当其他源文件需要调用这个函数时,使用#include指令包含该文件。这个是关于函数原型定义的标准方式,但是由于这里需要使用多个C语言源文件,在使用IDE时,直接向工程里面添加源文件,接着指定这些源文件的路径即可,但是我们这里使用的是独立的C语言编译器,因此需要额外的文件来支撑这些源文件的依赖关系。在GCC中,这种源文件依赖关系的描述被称为Makefile。
接下来的内容,我们来一步一步讲解多个源文件的编译,以及简单Makefile的编写。
首先,举的例子还是之前的奇偶验证。我们按照函数原型那一节写的内容,将EvenOrParity函数写在独立的源文件中,先建立一个额外的源文件,叫做NumChk.c这个源文件主要放置奇偶校验函数的源代码。
另外,由于在C语言中include宏定义只能包含.h类型的文件(头文件)。因此还需要定义一个NumChk.h文件,这里需要注意的是,大家以后在写多个源文件的程序时,一个.c文件一般对应一个同名的.h文件。紧接着,还需要创建一个写主函数的APP文件,我们在这里就命名为app.c。最后,之前说了,这些文件在编译的时候,需要一个文件来描述它们的依赖关系,因此,我们再创建一个叫做“Makefile”的依赖关系文件。这里需要注意的是,这个文件的名字一般以“makefile”或者“Makefile”为主,后面任何后缀名都不需要。创建好文件的目录如图4-2-2所示。
![103f1e9d16ec87b6606a327a9bbbf86f.png](https://i-blog.csdnimg.cn/blog_migrate/6b33ded02d6207573c6fbf4bdff8bf0b.png)
图4-2-2 多源文件目录
接下来,我们需要将奇偶验证函数整个搬到NumChk.c里面,并且在NumChk.h文件里面,需要将函数的声明写在里面。如图4-2-3所示。
![3a06d116170561b71d9a96b58ab93c49.png](https://i-blog.csdnimg.cn/blog_migrate/9555439ba6baad70468bd8d207cc4e56.png)
图4-2-3 功能函数头文件和函数体的实现
在图4-2-3,NumChk.h头文件中的前面几个宏定义,我们目前先不看,后面会详细讲述,需要我们关心的有两个地方:
第一个地方是,为什么之前包含“stdio.h”时,使用尖括号“<>”,而现在包含“NumChk.h”时,使用双引号?这里需要大家注意,一般C语言包含编译器提供的头文件时,用尖括号“<>”,而包含自定义的头文件时,用双引号。这是给编译器区分的,一旦编译器遇到尖括号“<>”,便会到编译器指定的路径里面去找相应名称的头文件,如果找不到,便会报错。而编译器遇到双引号“”””时,便会到用户指定的目录里面去找,仅此区别。
第二个地方,就是函数原型的声明,函数原型声明的方式非常简单,只要将函数名的信息,包含返回值,形式参数类型,形式参数数量,原封不动地写到包含的头文件里即可,注意不能忘记加上后面的分号“;”。
接下来,在app.c里面写main函数,首先要在main函数里面把奇偶检验函数的头文件“NumChk.h”包含进去,同时,由于main函数会用到printf这个C语言库函数,因此也需要包含“stdio.h”头文件。如图4-2-4所示。
![3d6c318a052a31589beb8e9927934776.png](https://i-blog.csdnimg.cn/blog_migrate/c0210075bc66cc1b91b10eb8424fca80.png)
图4-2-4 main函数调用
到此为止,我们实现的代码完全写完,此时,如果你用VS Code打开这个文件夹时,在“EvenOrParity”函数上面右击,选择“转到声明”,就会跳转到“NumChk.h”文件里的函数声明上,如果点击“转到定义”,就会跳转到“NumChk.c”中的函数定义上。以后大家看到函数需要查看时也可以使用这种办法。
虽然代码都已经写完,但是目前的各个.c,.h,文件都是独立的,C语言编译器的文件包含机制是,在编译之前,首先会把包含的头文件中的函数声明和全局变量声明进行记录。并且在单独的.c和.h文件编译时,C编译器都能将其编译成独立的.o文件,但是这些.o文件该怎么去链接,C语言一无所知,此时Makefile的作用就展现出来了。(这个也是Windows程序员理解不了的东西)
一个工程中的源文件不计其数,其按类型、功能、模块分别放在若干个目录中,Makefile定义了一系列的规则来指定哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为Makefile就像一个Shell脚本一样,也可以执行操作系统的命令。
我们先来编写一个最简单的Makefile,Makefile具体怎么写,什么意思,我们后面再讲。编写好的Makefile如图4-2-5所示。大家一开始做程序的时候,不明白Makefile什么意思没关系,先按照本书提供的Makefile写下来,后续我们再来讨论其含义。
![87b2d82b458771d580b04c8960ed9f79.png](https://i-blog.csdnimg.cn/blog_migrate/9da9e122088fd449d08fb7059101fa74.png)
图4-2-5 Makefile
注意,Makefile是一种类似于脚本语言的东西,因此这里的首行缩进一定不能丢掉,要不然脚本语言解释器就识别不了此脚本了。
写完Makefile文件之后,就可以编译工程了,在Linux里面,只需要用终端打开Makefile所在的文件夹,然后再输入“make”命令即可编译。而我们的MinGW里面需要输入“mingw32-make.exe”才可以编译,编译完成后,我们发现有一个可执行文件被生成了,如图4-2-6所示。
![b368189da26cd20dc1755323439311e2.png](https://i-blog.csdnimg.cn/blog_migrate/7c0b76908d921bfc82e70677c0f36e8c.png)
图4-2-6 Make编译工程
接下来验证该程序,如图4-2-7所示。
![2c15c2d365fbc3de0c57aa918607068f.png](https://i-blog.csdnimg.cn/blog_migrate/337e9c93e137b4a2fddfcc1977a24bb2.png)
图4-2-7 程序运行结果
++++参考书目++++
今天本小节的内容,部分参考了《C与指针》,很推荐大家看看,特别适合于有C语言基础的童鞋。
购买链接:
![3d95cbc81918778a7dd040dfaa9d97c8.gif](https://i-blog.csdnimg.cn/blog_migrate/8718a3aaf44fa6c7f4d45a3718246c9b.gif)
![08e8c444ba6dd5cbf28642a8f2006dee.png](https://i-blog.csdnimg.cn/blog_migrate/5e81459a46b9b099dbc9970d9916dea6.png)