C语言头文件定义技巧以及注意事项
“ 原创文章,欢迎分享,转载请注明来源,并给出原始链接,未经书面允许,请勿用于商业用途。”
在一篇中我们详细的介绍了C语言开发过程中经常使用到的一些宏名称及其意义,并且简单展示了其作用。
在一个大的项目开发过程中,我们的源文件,头文件一般都会按照模块划分,形成多个文件。同时,各种宏定义,变量定义,函数定义等就会相当的多,这样一来,在我们引用一个定义的时候,就要去包含非常多的头文件进来。在一个C文件前面列出来一大堆引用的头文件,除了带来不必要的工作,而且有时候经常把先后顺序搞错,带来一些编译错误,给排查和开发带来了一些困扰,那么有没有什么办法可以解决这个问题呢?答案是肯定的,下面我们就示例一下如何来解决这个问题,并且对一些关键问题提出警示。
假定我们有如下几个文件:
key.h的内容如下:
#ifndef__KEY_H__
extern "C" {
}
led.h的内容如下:
#ifndef__LED_H__
extern "C" {
}
在main.c文件里面,我需要引用两个变量,KEY_DOWN和 LED_RED,那么我们就要如下方式使用:
int main(void)
{
int key;
while(1)
{
key = GetKey();
if(key == KEY_DOWN)
LedOn(LED_RED);
delay(10);
}
}
单独从这几个文件来看,这是正确的处理方式,也没有任何毛病。但是我们延伸一下,假如我们要引用的头文件更多呢?比如很多全局的宏定义,变量定义等等,那么我们就要在c文件前面罗列一大堆文件来表明我们的引用来源。
更好的解决办法是我们定义另外一个头文件,把项目全部的头文件(有时候可能极个别例外)都按照一定顺序放到这个文件里面,在其他的c文件前面只需要放一个这个头文件就可以了。
比如定义一个header。h
#ifndef__LED_H__
在C文件里面就修改为如下,这样是不是看起来清爽多了:
int main(void)
{
int key;
while(1)
{
key = GetKey();
if(key == KEY_DOWN)
LedOn(LED_RED);
delay(10);
}
}
当然,在这样处理的过程中我们要注意几个地方:
1.header.h里面的头文件要安照引用的先后顺序排列,只能后面文件里面的引用前面的文件里面的定义,不可以颠倒过来引用。
2.每一个头文件前面都要有防止重复引用的标识符号定义,如#define __KEY_H__
在头文件定义的过程中,我们使用了两个技巧:
技巧一:
#ifndef__LED_H__ //检查是否定义宏__LED_H__
//#ifndef和#endif是配对使用的,在这之间的代码只有在__LED_H__宏没有被定义的时候才会被编译进去,否则会忽略掉。
在每一个头文件的最开始,声明了一个和文件名称一样的标识宏(**建议这样定义宏名称,以保持整个项目里面宏名字的唯一性**),起什么作用呢?
我们知道,如前一章所描述,预处理器在处理预处理命令的时候,会按照文件顺序展开文件,并且将内容集中到一个预编译文件里面(我们可以想象,编译器最后会将每一个C文件引用到的外部文件按照预编译命令放置的地方(命令在哪里就在哪里展开引用的文件)展开为一个长长的C文件)。我们在main.c里面包含了header.h,假如先编译main.c文件,那么在led.h和key.h里面定义的宏就会被定义一次(因为两个宏__LED_H__和__KEY_H__还没有被定义)。main.c的内容大致就是如下
//此处省略了其他头文件的变量
//由于led.h被先引用,所以该文件的内容放到前面
//由于key.h被后引用,所以该文件的内容放到后面
//根据这样的文件放置顺序原则,我们在key.h里面就不能去引用led.h里面定义的宏。
int main(void)
{
int key;
while(1)
{
key = GetKey();
if(key == KEY_DOWN)
LedOn(LED_RED);
delay(10);
}
}
接下来我们在编译其他c文件或者再次包含这两个头文件的时候,由于宏已经被定义过,里面的内容就不会被展开,避免了重复定义的错误。
技巧二:
extern "C" { //告诉C++编译器,下面所有代码安照C的规范来使用
我们添加了一个extern "C" 的声明,起什么作用呢?
因为我们在开发一个大型工程的时候,可能是C和C++语言混合开发的,那么由于C语言和C++语言编译器在把代码编译为汇编语言的时候,各自会把函数名称按照自己的规则命名为另外的名字,或者两个编译器对默认返回值,参数传递顺序可能不一致,从而导致链接的时候找不到符合或者传递参数错误等等情况发生。该语法extern "C" 的声明就告诉了C++编译器,下面的函数定义要按照c的规则来编译和链接,保持两者的一致性。