最近在写嵌入式开发以及给大一同学答疑的时候又碰到我的老朋友了——C/C++,抽空来写一写遇到的问题及想法。
大家在学习C/C++的时候,经常遇到把一些函数拆分到多个文件中的情况。对于一个C/C++函数,可以拆分成声明和定义两个部分,比如:
int function(int); //此处为声明int function(int num){return num;} //此处为定义// 若无第一行代码 第二行为声明并定义
在C/C++某些标准版本中,要求函数必须
先声明再调用
,也就是说,我们可以把整个函数写在调用语句之前,也可以仅把声明写在函数调用之前。
在学习C/C++的时候,在拆分成多个文件的时候(拆分.h和.c/cpp),一般都在头文件中进行函数/变量声明、宏、结构体声明,而在C文件中去进行变量定义、函数实现。老师在实验的时候也是这样要求的。不过为什么要这样呢?
我问过老师,老师说是可以
更快的编译
,问过同学,同学说是
方便阅读
,看程序的时候一眼就能看到有什么函数。我想,这些应该都不是本质的原因。
毕竟拆分声明和定义个人认为是一件很麻烦的事情。后来我又详细的研究了这件事,以及做了一些测试。首先,我们来说,
把声明和定义都写在.h文件中行不行?
结论是大部分情况下,
行
。
举个例子,现在我们有三个函数段,我们要将他们分别放入独立的文件。还有一个main函数用来调用他们。(此处省略include)
第一个函数outputInt用来输出一个数字,代码如下:
void outputInt(int num){ printf("%d",num); }
第二个函数outputIncreasedInt用来输出一个数字+1,代码如下:
void outputIncreasedInt(int num){ outputInt(num+1); }
第三个函数outputDecreasedInt用来输出一个数字-1,代码如下:
void outputDecreasedInt(int num){ outputInt(num-1); }
(这两个函数在下文简写为+1-1函数)
main函数对他们进行调用,代码如下:
int main(){ outputIncreasedInt(5); outputInt(6); outputDecreasedInt(7); return 0;}
然后,我们将其分为四种情况,进行测试。(添加#define #ifdef等用于防止多次包含的代码)
声明定义都放在.h里,共有四个文件。
结果:编译通过,运行正常。
声明定义分别放置在.h和.c中,共七个文件。
结果:编译通过,运行正常。
outputInt函数声明定义分别放置在.h和.c中,+1-1输出函数声明定义都放在.h里,共五个文件。
结果:编译通过,运行正常。
outputInt函数声明定义都放在.h里,+1-1输出函数声明定义分别放置在.h和.c中,共六个文件。
结果:链接错误,如图。
(可以通过在void outputInt前面加上static解决此问题)
链接错误是非常烦人的,因为它并不会给出明确的错误位置,也不会在 IntelliSense 中看到波浪线报错。所以,究竟为什么会这样呢,让我们深入来探讨这个问题。
在深入探讨之前,我们要明白当我们写完程序,按下生成按钮之后,电脑都为我们做了什么。简单来说可以简化为以下三个步骤
- 预处理,处理所有#开头的语句(除#pragma),展开#include等。
- 编译,此处包括编译和汇编,生成目标文件(Object Files)。
- 链接,将目标文件链接为一个可执行文件,进行符号重定位等。
- 通常情况.h文件仅仅是在预处理过程中#include位置展开那个文件。
- 通常情况每个.c/.cpp文件会编译出一个目标文件。
- 拆分.h和.c/.cpp能提升编译速度是因为在修改某些代码后只需要重新编译修改的代码所在的编译单元,并不需要重新编译整个项目。
- 第三方库或系统库通常只提供.h文件和链接库(分静态链接和动态链接,这点比较复杂本文就不讲了)(例如stdio.h会链接到编译好的系统库)
- 只是习惯上把.h和.c/.cpp取一样的名字罢了,实际上无所谓,也可以没有.h,如下示例也可正确编译和运行。
// main.cint fun(int);int main(){ fun(666); return 0;}
// fun.cint fun(int i){return i;}
以上内容是最近关于这个问题的思考,如有错误欢迎指正。
如果喜欢
欢迎转发
以及点
在看
。
欢迎关注公众号 Xianfei 北京邮电大学软件学院 王衔飞 xianfei@bupt.edu.cn 参考书籍:《程序员的自我修养:链接、装载与库》