C语言为什么一般不在.h中定义函数或者变量?(精华)
1、日常聊一聊(听说文章与音乐更配)
今天文章开头插入了一首韩剧《请回答1988》的插曲,每次听到这段曲子都会有所感怀,大家可以听一听,同时把剧也推荐给没看过的小伙伴。好了,今天的主题是一个大家可能不太关注的主题,不过也是非常有趣的一个主题,大部分C语言模块设计爱好者早就形成了习惯可能就不会关注这一块的内容,不过我们今天想拿出来探个究竟.
2、头文件的作用
大部分C编程爱好者都知道,在我们的.h文件里面经常看到的是函数的声明、变量的声明、以及各种各样的宏等等,而且在我前面的文章中我也提到过C语言的模块化设计中常常说到对应的.h和.c文件认为是一个对象,那么.h文件主要是对外的一些接口,一些私有的数据等等实现都会封装在我们的.c中,如果更加形象一点的说明我们的.h,就相当于文章下面的推荐文章链接组成的框,点进去就到了对应的具体文章,具体的文章也就是我们的.c文件了。
这里再回到我们的程序中来,如下代码所示:
#include <stdio.h>
#include <stdlib.h>
int sAdd(int a,int b);
int sSub(int a,int b);
//...
int main(int argc, char *argv[]) {
printf("Add: %d\n",sAdd(1 , 1));
printf("Sub: %d\n",sSub(1 , 1));
return 0;
}
/***************************************
* Fuction:sAdd
***************************************/
int sAdd(int a,int b)
{
return (a + b);
}
/***************************************
* Fuction:sSub
***************************************/
int sSub(int a,int b)
{
return (a - b);
}
//...
解析一下: 上面是大部分小伙伴最开始学习编程的时候做法,那么我们把main函数前面的声明放到一个对应的.h文件,加法与减法的具体实现放到对应的.c文件,然后再main函数前面用#include "xxx.h",便可以了。简单点说.h完全可以直接在#include处进行展开以便于我们理解分析。
3、头文件中定义函数或者变量
代码阅读量比较多的小伙伴应该见过直接在头文件里面定义函数或者变量的做法,那么这里我们直接对上面进行改造,得到如下代码:
//Filename: main.c
#include <stdio.h>
#include <stdlib.h>
#include "Add.h"
int main(int argc, char *argv[]) {
printf("Add: %d\n",sAdd(global_Var1 , global_Var2));
printf("Sub: %d\n",sSub(global_Var1 , global_Var2));
return 0;
}
//Filename : Add.h
/***************************************
* Fuction:sAdd
***************************************/
int global_Var1 = 2;
int global_Var2 = 2;
/***************************************
* Fuction:sAdd
***************************************/
int sAdd(int a,int b)
{
return (a + b);
}
/***************************************
* Fuction:sSub
***************************************/
int sSub(int a,int b)
{
return (a - b);
}
解析一下:上面的程序也能够成功编译并运行,并且能够获得正确的结果,这也进一步证明直接展开#include "xxx.h"是可以解释得通的。
3、为什么不选择在头文件定义变量或者函数
1)多个源文件声明.h会重复定义
我们如果按照上面的展开法进行解释,其实很好理解多个源文件声明#include "xxx.h"会导致多个相同的全局变量和函数,这样必然导致重复定义故障,如果小伙伴们不信我直接敲代码看结果:
#include <stdio.h>
#include <stdlib.h>
#include "APP1.h"
#include "APP2.h"
/*********************************************
* Fuction:main
********************************************/
int main(int argc, char *argv[]) {
printf("%d\n",add_APP1(1,1));
printf("%d\n",add_APP2(1,1));
return 0;
}
//Filename:Add.h
/*********************************************
* Fuction:add
********************************************/
int add(int a,int b)
{
return (a + b);
}
//Filename:App1.h
int add_APP1(int a,int b);
//Filename:App1.c
#include "Add.h"
/*********************************************
* Fuction:add_APP1
********************************************/
int add_APP1(int a,int b)
{
return add(a,b);
}
解析:APP2对应的.c和.h我就不再贴上去了,毕竟代码比较简单。最后一张图为编译结果,第二行显示重复定义add这个函数。同样作者也进行了对变量一样的做法,同样也是显示的是重复定义相关变量,所以也验证了我们的前面的理论。
2)添加条件编译#ifndef
我们大部分小伙伴可能会想我们直接在头文件的函数和变量定义加上条件编译#ifndef来避免重复包含不就可以了吗?好了,作者也进行了这个实验大家简单看看代码和结果,后面我来解释下:
//Filename:Add.h
/*********************************************
* Fuction:add
********************************************/
#ifndef __ADD_H__
#define __ADD_H__
int add(int a,int b)
{
return (a + b);
}
#endif
解析一下:加了该预编译以后其结果与上一节的结果一致,还是提示重复定义该函数。那是为什么呢?我在前面的《嵌入式编程有道-C语言(1)》中有描述C程序到机器码的一个生成过程,App1.c和App2.c分别编译,然后分别展开#include,在最后的链接过程中发现了同名的函数和变量,所以还是会编译不通过。
3)static能够帮我们搞定这种窘境
我们都知道static表示静态的的意思,也可以说是对变量和函数进行文本上的限制,多个文件中是可以有同名的static函数和变量的,不过他们却代表着不同的函数和变量,这一点要尤为注意,如果该头文件在不同文件中大量使用其中的变量和函数也会导致程序的增加和内存的变大,下面作者就为这几点用程序来证明下:
//Filename:Add.h
#ifndef __ADD_H__
#define __ADD_H__
static int add(int a,int b)
{
return (a + b);
}
#endif
//filename:App1.c
#include <stdio.h>
#include "Add.h"
/*********************************************
* Fuction:add_APP1
********************************************/
int add_APP1(int a,int b)
{
printf("add_APP1_addFuc:0x%x\n",(int*)add);
return add(a,b);
}
4、小节
最后小节一下,其实为什么大家一般不在.h文件里面定义变量或者函数其实原因还有很多,最主要的是不符合相关设计要求,比如进行代码上的改动话,编译会造成连锁反应导致编译时间再次编译时间较长等问题,同时代码上的分析比较麻烦,所以大家对于这类问题其实也是可以平时闲暇时候好好研究一下,同时你会在其中收获很多意外的知识点,要做到"知其然而知其所以然",其实在头文件中定义函数或者变量并不是没有这样的设计需求,大家可以考虑一下static在头文件中定义的这种使用有缺点,是否会有什么优点呢?哈哈,这里我就不揭秘了,请听下回分解!
☞
☞
☞
☞
☞
☞
☞
☞