一、头文件的定义
C语言中常常使用预处理指令#include,把另一个文件的内容复制到源文件当中,被复制的文件就是头文件,其后缀名为.h。
二、头文件的作用
1、在多文件的工程当中,头文件用于全局变量、外部函数的声明,其本身不包含程序的实现代码
例如:
extern int var;//var在某个c文件中定义
extern void myFunction(void);//一般情况下,都默认该函数为外部函数,因此可以把extern省略掉
2、用typedef给现有的类型起个新名字
例如:
typedef int ElementType;
typedef struct LinkedList
{
ElementType value;
struct LinkedList *next;
}*LinkedList;
3、宏定义
例如:
#define TRUE 1
#define FALSE 0
三、头文件的书写方式
如上所述,#include的本质是把头文件的内容拷贝一份到当下的文件当中,因此我们有必要考虑一个问题——如何避免头文件重复包含。
相信很少有人会重复写两次#include "demo.h",但是,更常见的一种重复包含的情况是:有两个头文件first.h和second.h,其中second.h中有#include "first.h"。那么当main.c同时包含first.h和second.h的时候,实际上包含了两次first.h
因此,我们可以通过条件编译来
避免重复包含。例如:编写一个头文件demo.h
#ifndef _DEMO_H
#define _DEMO_H
//宏定义
//typedef
//全局变量声明
//外部函数声明
#endif
四、为什么不可以在头文件中定义变量?
1、首先搞清楚编译的过程,流程图如下:
C源程序-->预编译处理(.c)-->编译、优化程序(.s、.asm)-->汇编程序(.obj、.o、.a、.ko)-->链接程序(.exe、.elf、.axf等)
对一个工程编译的时候,每个c文件都是单独编译的并得到对应的.o文件的,紧接着通过连接器把各个.o文件连接起来,最终生成统一的可执行文件。
2、假如一个工程当中,a.c和b.c都包含了同一个头文件demo.h,而demo.h里定义了变量——int var,即使我在demo.h里加了#ifndef和#endif来避免重复包含,但是编译还是会出错,编译器会说,a.o和b.o重定义了变量var。因为编译器编译的时候是分别对a.c和b.c编译的,这里并没有重复包含,但是却会分别在两个c文件定义完全相同的变量,连接器工作的时候就会发现错误并报错。
五、为什么要把函数声明写在头文件?
用一个例子说明:现在有三个文件main.c、test.c、test.h
/*****************main.c*****************/
#include <stdio.h>
#include "test.h"
int main(int argc, char *argv[])
{
Function1();
return 0;
}
/*****************end of main.c*****************/
/*****************test.c*****************/
#include "test.h"
void Function1(void)
{
Function2();
}
void Function2(void)
{
printf("Hello world!\n");
}
/*****************end of test.c*****************/
/*****************test.h*****************/
#ifndef _TEST_H
#define _TEST_H
void Function1(void);
void Function2(void);
#endif
/*****************end of test.h*****************/
如上面所述,编译器会分别对main.c和test.c编译
在编译main.c的时候,#include "test.h"把Function1()的声明拷贝到当前文件的对应位置,
则Function1()的作用域从当前的声明位置一直到c文件末尾。
如果去掉该#include语句,则Function1()在调用的时候还不在作用域内,因此编译器会报错。
在编译test.c的时候,如果该c文件中只有一个函数Function2()且main.c只调用Function2()的时候,不写#include "test.h"也是可以的,因为分别编译后得到的main.o和test.o经过连接器的连接,main.o中调用Function2()的时候可以跳转到test.o的Function2()的函数体当中,这个连接过程是连接器自动搜索并实现的。(当然,这是一个不好的编程习惯)
但是,如上面例程所示,当有两个函数Function1()和Function2()的时候,定义的先后不同,如果没有函数声明,则编译器则无法识别Function1()当中的Function2()。所以一般都要养成习惯,函数调用前都声明。
另外,对于Function2(),如果它仅供test.c内定义的函数调用的话,应该定义为static函数,并把test.h中的对应声明去掉,这样就对test.c以外的文件隐藏了它的实现细节,而且避免了不同c文件编写过程中出现的函数同名问题。
六、#include "头文件"一般是放在c文件中还是放在h文件中?
除非h文件当中必须用到某些声明,否则尽量把#include放在c文件中,例如野火STM32开发板资料当中的摄像头例程。
bsp_ov7725.c当中除了包含bsp_ov7725.h外,根据需要还包含了bsp_sccb.h和bsp_ili9341_lcd.h。
而stm32f10x.h却被包含在bsp_ov7725.h中,因为在STM32例程当中,这个库常常用到。更重要的是,在bsp_ov7725.h的内容中包含了下图所示的GPIOA这些标识符,而这些标识符是在stm32f10x.h中声明的,因此stm32f10x.h必须被包含在bsp_ov7725.h中。