C语言的艺术之——头文件

好记性不如烂笔头o(^▽^)o

系列的文章:
《C语言的艺术之——头文件》
《C语言的艺术之——函数》
《C语言的艺术之——标识符命令与定义》
《C语言的艺术之——变量》
C语言的艺术之——注释
C语言的艺术之——排版与格式
C语言的艺术之——安全性

编码原则:

清晰:易于维护、易于重构
简洁:易于理解并且易于实现
适合的风格:尽量与原有代码保持风格一致

C语言的艺术之——头文件

1、头文件中适合放置接口的声明,不适合放置实现

头文件中应放置对外部的声明,如对外提供的函数声明、宏定义、类型定义等。

内部使用的函数(相当于类的私有方法)声明不应放在头文件中。
内部使用的宏、枚举、结构定义不应放入头文件中。

变量定义不应放在头文件中,应放在.c文件中。

  变量的声明尽量不要放在头文件中,亦即尽量不要使用全局变量作为接口。变量是模块或单元的内部实现细节,不应通过在头文件中声明的方式直接暴露给外部,应通过函数接口的方式进行对外暴露。 **即使必须使用全局变量,也只应当在.c中定义全局变量,在.h中仅声明变量为全局的

2、头文件应当职责单一

头文件过于复杂,依赖过于复杂是导致编译时间过长的主要原因。

3、头文件应向稳定的方向包含

头文件的包含关系是一种依赖,一般来说,应当让不稳定的模块依赖稳定的模块,从而当不稳定的模块发生变化时,不会影响(编译)稳定的模块。

4、每一个.c文件应有一个同名.h文件,用于声明需要对外公开的接口

如果一个.c文件不需要对外公布任何接口,则其就不应当存在,除非它是程序的入口,如main函数所在的文件。

  一旦把私有定义、声明放到独立的头文件中,就无法从技术上避免别人include之,难以保证这些定义最后真的只是私有的。

示例:对于如下场景,如在一个.c中存在函数调用关系:

void foo()
{
    bar();
}

void bar()
{
    Do something;
}

必须在foo之前声明bar,否则会导致编译错误。
这一类的函数声明,应当在.c的头部声明,并声明为static的,如下:

static void bar();

void foo()
{
    bar();
}

void bar()
{
    Do something;
}

5、禁止头文件循环依赖

头文件循环依赖,指a.h包含b.h,b.h包含c.h,c.h包含a.h之类导致任何一个头文件修改,都导致所有包含了a.h/b.h/c.h的代码全部重新编译一遍。而如果是单向依赖,如a.h包含b.h,b.h包含c.h,而c.h不包含任何头文件,则修改a.h不会导致包含了b.h/c.h的源代码重新编译。

6、.c/.h文件禁止包含用不到的头文件

很多系统中头文件包含关系复杂,开发人员为了省事起见,可能不会去一一钻研,直接包含一切想到的头文件,甚至有些产品干脆发布了一个god.h,其中包含了所有头文件,然后发布给各个项目组使用,这种只图一时省事的做法,导致整个系统的编译时间进一步恶化,并对后来人的维护造成了巨大的麻烦。

7、头文件应当自包含

简单的说,自包含就是任意一个头文件均可独立编译。如果一个文件包含某个头文件,还要包含另外一个头文件才能工作的话,就会增加交流障碍,给这个头文件的用户增添不必要的负担。

示例:
  如果a.h不是自包含的,需要包含b.h才能编译,会带来的危害:
  每个使用a.h头文件的.c文件,为了让引入的a.h的内容编译通过,都要包含额外的头文件b.h。
  额外的头文件b.h必须在a.h之前进行包含,这在包含顺序上产生了依赖。
注意:
  该规则需要与“.c/.h文件禁止包含用不到的头文件”规则一起使用,不能为了让a.h自包含,而在a.h中包含不必要的头文件。a.h要刚刚可以自包含,不能在a.h中多包含任何满足自包含之外的其他头文件。

8、总是编写内部#include保护符(#define 保护)

多次包含一个头文件可以通过认真的设计来避免。如果不能做到这一点,就需要采取阻止头文件内容被包含多于一次的机制。

  通常的手段是为每个文件配置一个宏,当头文件第一次被包含时就定义这个宏,并在头文件被再次包含时使用它以排除文件内容。
  所有头文件都应当使用#define 防止头文件被多重包含,命名格式为

FILENAME_H

为了保证唯一性,更好的命名是

PROJECTNAME_PATH_FILENAME_H

注:没有在宏最前面加上单下划线”“,是因为一般以单下划线”“和双下划线”_”开头的标识符为ANSI C等使用,在有些静态检查工具中,若全局可见的标识符以”“开头会给出告警。
  定义包含保护符时,应该遵守如下规则:
1)保护符使用唯一名称;
2)不要在受保护部分的前后放置代码或者注释。

示例:假定VOS工程的timer模块的timer.h,其目录为VOS/include/timer/timer.h,应按如下方式保护:

#ifndef VOS_INCLUDE_TIMER_TIMER_H 
#define VOS_INCLUDE_TIMER_TIMER_H 
... 
#endif

也可以使用如下简单方式保护:

#ifndef TIMER_H 
#define TIMER_H
 .. 
#endif

例外情况:头文件的版权声明部分以及头文件的整体注释部分(如阐述此头文件的开发背景、使用注意事项等)可以放在保护符(#ifndef XX_H)前面。

9、禁止在头文件中定义变量

在头文件中定义变量,将会由于头文件被其他.c文件包含而导致变量重复定义。

10、只能通过包含头文件的方式使用其他.c提供的接口,禁止在.c中通过extern的方式使用外部函数接口、变量

若a.c使用了b.c定义的foo()函数,则应当在b.h中声明extern int foo(int input);并在a.c中通过#include

11、禁止在extern “C”中包含头文件

在extern “C”中包含头文件,会导致extern “C”嵌套,Visual Studio对extern “C”嵌套层次有限制,嵌套层次太多会编译错误。

  在extern “C”中包含头文件,可能会导致被包含头文件的原有意图遭到破坏。例如,存在a.h和b.h两个头文件:

#ifndef A_H__ 
#define A_H__ 

#ifdef __cplusplus
void foo(int); 
#define a(value) foo(value)
#else
void a(int)
#endif 

#endif /* A_H__ *
#ifndef B_H__ 
#define B_H__ 

#ifdef __cplusplus 
extern "C" { 
#endif 

#include "a.h" 
    void b(); 

#ifdef __cplusplus 
} 
#endif 

#endif /* B_H__ *

使用C++预处理器展开b.h,将会得到

extern "C" { 
    void foo(int); 
    void b();
}

  按照a.h作者的本意,函数foo是一个C++自由函数,其链接规范为”C++”。但在b.h中,由于#include “a.h”被放到了extern “C” { }的内部,函数foo的链接规范被不正确地更改了。

示例:错误的使用方式:

extern “C”
{ 
#include “xxx.h”
... 
}

正确的使用方式:

#include “xxx.h”
extern “C”
{ 
... 
}
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值