头文件是c语言中重要的文件类型,用于保存程序的声明。函数原型一般都放在头文件中,函数定义则放在源文件中,当源文件或头文件通过#include指令包含另一个头文件的时候,编译预处理器用头文件的内容取代#include伪指令。这就是说,头文件的所有内容最终都会被合并到某一个或某几个源文件中。
头文件的作用:
(1)通过头文件来调用库功能。在很多场合,源代码不便(或不准)向用户公布,只要向用户提供头文件和二进制的库即可。用户只需按照头文件中的接口声明来调用库函数,而不必关心接口是怎么实现的。连接器会从库中提取相应的代码,并和用户的程序连接生成可执行文件或者动态连接库文件;
(2)头文件能加强类型安全检查。如果某个接口被实现或被使用时的方式与头文件中的声明不一致,编译器就会指出错误,这一简单的规则能大大减轻程序员调试、改错的负担;
(3)头文件可以提高程序的可读性(清晰性)。
那么怎么写好头文件呢,哪些东西应该放在头文件中呢,让我们依次来看看。
头文件中的元素比较多,其顺序(结构)一般应安排如下:
(1)头文件注释(包括文件说明、功能描述、版权声明等)(必须有);
/*********************************************************************
; Project : xxx
; File Name : xxx
; Vesion number : 1.00
; File History : Revision 1.00 2010/07/30 frist creation
; Auther : xxx
; Brief : xxx
**********************************************************************/
(2)内部包含卫哨开始(#ifndef XXX/#define XXX)(必须有);
#ifndef _COMMON_H_
#define _COMMON_H_
... ...
(3)#include其他头文件(如果需要);
#include "..//xxx//xxx.h"
(4)外部变量和全局函数声明(如果需要);
extern ... ...
Note:在.h文件中声明的函数,如果在其对应的.c文件中有定义,那么我们在声明这个函数时,不使用extern修饰符, 如果反之,则必须显示使用extern修饰符.
(5)常量和宏定义(如果需要);
(6)类型前置声明和定义(如果需要);
(7)全局函数原型和内联函数的定义(如果需要);
Note:如果程序中需要内联函数,那么内联函数的定义应当放在头文件中,因为内联函数调用语句最终被扩展开来而不是采用真正的函数调用机制。
(8)内部包含卫哨结束:#endif // XXX(必须有);
... ...
#endif
(9)文件版本及修订说明。
上述排列顺序并非绝对,也不存在对错之分,可根据具体情况灵活安排。
基于头文件的作用,需要先来谈谈C语言的存储类说明符: auto, extern, static.
单一源文件中的 auto,extern,static
auto: 变量的默认存储类别就是auto,例如 int num; 等同于 auto int num; 不用显示指定。
我们知道,在一对{ }中的代码段叫做一个块(block), 程序运行到一个auto包含变量的时候,
会自动给变量分配存储空间,在离开块的时候会自动释放空间, 例如调用一个函数之后,函数块
内的局部变量空间将会释放。 auto 强调存储空间的自动分配与释放。
extern: 在函数体外定义的变量默认为extern类型。extern变量必须定义在所有函数外面。在程序开始之前被分配空间,在执行时不被释放。可以在定义时初始化,也可以由系统在分配存储空间时初始化为0. 其只被初始化一次。在其后面的所有函数,都可以访问该变量。
static: 静态变量. 必须显示指定其类行为static.程序执行前分配空间,执行时不会释放,如果不初始化,则系统在分配空间时初识化为0,同extern类似,只被初始化一次,这意味着,如下函数的count变量在多次调用函数时会递增。
void cnt()
{
static int count=0;// 无论调用cnt()多少次,count只被初始化一次。
count++;
}
静态变量只在包含它的程序块中可见。
如果在某个块内定义的auto变量或者static变量与一个extern变量同名,则在块内只有auto或者static可见,同名extern被屏蔽。
多文件中的存储类别
主要是针对extern关键字.
关于定义和声明的区别.对于auto,static和register变量来说,定义和声明相一致,所以统称定义/声明.
使用extern 关键字可以使变量被其他文件访问。
在文件A中定义:
int a=10; 或者 extern int a=10;
注意:如果不使用extern 关键字定义,则是否初始化都可以,如果使用了extern,则必须初始化,
extern int a; 这样的定义是错误的.
如何引用? 在文件B中,或者在定义前面的函数中, 变量a是不可见的,如果在要使用的块内使用extern声明
extern int a; 之后就可以使用了。
比如
文件A: int a=10; //定义
文件B: extern int a; //声明
printf("%d",a);//使用
STATIC关键字: 它即可以在函数内定义,也可以在函数外定义. 但无论如何,都不会被其他文件访问,
所以static 可以用来保护数据不被外部文件访问。
头文件中重要的东西举例:
1。 struct 数据结构定义
struct swp_ctrl_err{
U8 atout;
U8 gdtout;
U8 rsttout;
};
struct swp_stru{
U8 swp_state;
U8 pwr_mode;
U8 ep_cap;
U8 ACT_last_frame;
U8 SHDLC_linked;
U8 rxrdy;
U8 activated_ever;
U8 exp_clf_ns;
struct swp_ctrl_err ctrl_err;
};
extern volatile struct swp_stru swp_info;
数据结构的定义必须放在.h的头文件中,变量声明则在c文件中:volatile struct swp_stru swp_info;
2。内联函数亦应该放在.h文件中,这取决于内联函数的机制:内联函数调用语句最终被扩展开来而不是采用真正的函数调用机制
__inline void enter_idle(void)
{
int tmp;
tmp=0; //Enter the idle mode, CPU clock stop
__asm
{
MCR p15, 0, tmp, c7, c0, 4
NOP
NOP
NOP
NOP
NOP
NOP
}
}
最后介绍几个常用且很有用的宏定义。
//类型相关
typedef unsigned char uchar;
typedef unsigned short ushort;
typedef unsigned int uint;
typedef unsigned long ulong;
typedef unsigned char U8;
typedef unsigned short U16;
typedef unsigned long U32;
typedef signed char S8;
typedef signed short S16;
typedef signed long S32;
//字节操作相关
#define HiByte(x) *((uchar *)&x)
#define LoByte(x) *(((uchar *)&x)+1)
#define BYTE(x) *((uchar *)&x)
#define WORD(x) *((ushort *)&x)
#define LO_NIBBLE 0x0F
#define HI_NIBBLE 0xF0
//位操作相关
#define BIT_SET(x,n) (x=x | (0x01<<n))
#define BIT_TEST(x,n) ((x & (0x01<<n))!=0)
#define BIT_CLEAR(x,n) (x=x & ~(0x01<<n))