C语言头文件定义技巧以及注意事项

<KEIL编译器预处理器和预定义宏(include,ifdef,def等)>一篇中我们详细的介绍了C语言开发过程中经常使用到的一些宏名称及其意义,并且简单展示了其作用。
在一个大的项目开发过程中,我们的源文件,头文件一般都会按照模块划分,形成多个文件。同时,各种宏定义,变量定义,函数定义等就会相当的多,这样一来,在我们引用一个定义的时候,就要去包含非常多的头文件进来。在一个C文件前面列出来一大堆引用的头文件,除了带来不必要的工作,而且有时候经常把先后顺序搞错,带来一些编译错误,给排查和开发带来了一些困扰,那么有没有什么办法可以解决这个问题呢?答案是肯定的,下面我们就示例一下如何来解决这个问题,并且对一些关键问题提出警示。
假定我们有如下几个文件:
key.h的内容如下:

#ifndef	__KEY_H__
#define __KEY_H__

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#define KEY_DOWN	0x0001	//按键按下
#define KEY_UP	    	0x0002	//按键弹起

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif

led.h的内容如下:

#ifndef	__LED_H__
#define __LED_H__

#ifdef __cplusplus
extern "C" {
#endif /* __cplusplus */

#define LED_GREEN 	0x0001	//绿色led
#define LED_RED	    	0x0002	//红色led

#ifdef __cplusplus
}
#endif /* __cplusplus */

#endif

在main.c文件里面,我需要引用两个变量,KEY_DOWN和 LED_RED,那么我们就要如下方式使用:

#include "config.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include "stm32f10x.h"
#include “led.h”	//引用宏LED_RED
#include “key.h”	//引用宏KEY_DOWN

int main(void)
{
	int key;
	while(1{
		key = GetKey();
		if(key == KEY_DOWN)
		  LedOn(LED_RED);
		delay(10);
	}
}

单独从这几个文件来看,这是正确的处理方式,也没有任何毛病。但是我们延伸一下,假如我们要引用的头文件更多呢?比如很多全局的宏定义,变量定义等等,那么我们就要在c文件前面罗列一大堆文件来表明我们的引用来源。
更好的解决办法是我们定义另外一个头文件,把项目全部的头文件(有时候可能极个别例外)都按照一定顺序放到这个文件里面,在其他的c文件前面只需要放一个这个头文件就可以了。
比如定义一个header。h

   ```c
#ifndef	__LED_H__
#define __LED_H__

#include "config.h"
#include <stdarg.h>
#include <stdio.h>
#include <stdint.h>
#include "stm32f10x.h"

#include “led.h”
#include “key.h”
#endif

在C文件里面就修改为如下,这样是不是看起来清爽多了:

#include "header.h"

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__
#define __LED_H__	//如果没有定义,就定义一个宏
//#ifndef和#endif是配对使用的,在这之间的代码只有在__LED_H__宏没有被定义的时候才会被编译进去,否则会忽略掉。
#endif

在每一个头文件的最开始,声明了一个和文件名称一样的标识宏(建议这样定义宏名称,以保持整个项目里面宏名字的唯一性),起什么作用呢?
我们知道,如前一章所描述,预处理器在处理预处理命令的时候,会按照文件顺序展开文件,并且将内容集中到一个预编译文件里面(我们可以想象,编译器最后会将每一个C文件引用到的外部文件按照预编译命令放置的地方(命令在哪里就在哪里展开引用的文件)展开为一个长长的C文件)。我们在main.c里面包含了header.h,假如先编译main.c文件,那么在led.h和key.h里面定义的宏就会被定义一次(因为两个宏__LED_H__和__KEY_H__还没有被定义)。main.c的内容大致就是如下

//此处省略了其他头文件的变量
//由于led.h被先引用,所以该文件的内容放到前面
#define LED_GREEN 	0x0001	//绿色led
#define LED_RED	    	0x0002	//红色led
//由于key.h被后引用,所以该文件的内容放到后面
#define KEY_DOWN	0x0001	//按键按下
#define KEY_UP	    	0x0002	//按键弹起
//根据这样的文件放置顺序原则,我们在key.h里面就不能去引用led.h里面定义的宏。
int main(void)
{
	int key;
	while(1{
		key = GetKey();
		if(key == KEY_DOWN)
		  LedOn(LED_RED);
		delay(10);
	}
}

接下来我们在编译其他c文件或者再次包含这两个头文件的时候,由于宏已经被定义过,里面的内容就不会被展开,避免了重复定义的错误。
技巧二:

#ifdef __cplusplus
 extern "C" {	//告诉C++编译器,下面所有代码安照C的规范来使用
#endif /* __cplusplus */

#endif

我们添加了一个extern “C” 的声明,起什么作用呢?
因为我们在开发一个大型工程的时候,可能是C和C++语言混合开发的,那么由于C语言和C++语言编译器在把代码编译为汇编语言的时候,各自会把函数名称按照自己的规则命名为另外的名字,或者两个编译器对默认返回值,参数传递顺序可能不一致,从而导致链接的时候找不到符合或者传递参数错误等等情况发生。该语法extern “C” 的声明就告诉了C++编译器,下面的函数定义要按照c的规则来编译和链接,保持两者的一致性。

发布了13 篇原创文章 · 获赞 7 · 访问量 5361

猜你喜欢

转载自blog.csdn.net/huangbinvip/article/details/104785470
今日推荐