2个办法解决头文件重复包含的问题

文件包含

  • 前言
  • 条件编译
  • 头文件的嵌套包含与2种解决办法
  • 头文件包含的两种方式及区别
  • 结语

前言

在一些大型项目中,各种头文件会相互包含,关系就显得错综复杂,同时由于文件的包含就是复制粘贴,那些重复包含的头文件就会使代码长度大大增加,那有没有什么办法解决这个问题呢?答案肯定是有的,这就涉及到条件编译了。

条件编译

所谓条件编译就是在编译阶段让编译器选择编译或不编译某些代码,需要注意的是条件编译是在编译阶段进行的,而不是等到程序运行时,利用这点我们就可以让编译器选择是否包含头文件,从而避免头文件被重复包含。
下面看看一些常见的条件编译指令:
1.

#if  ...(常量表达式)
...(内容)
#endif

例:

#define MAX 100

#if MAX//如果MAX为真,则定义常量MIN
#define MIN 1
#endif

2.多个分支的条件编译

#if ...(常量表达式1)
...(内容1)
#elif ...(常量表达式2)
...(内容2)
#else
...(内容3)
#endif

例:

#define MAX 100
#define MIN 0

#if MIN
	#define RET 0//MAX为真,编译这句代码
#elif MAX
	#define RET 1
#else
	#define RET 2
#endif

3.判断是否被定义

#if defined ...(某个标识符)
...(内容)
#endif

也可以写成以下形式

#ifdef ...(某个标识符)
...(内容)
#endif

---------------------------------------------------------------------

#if !defined ...(某个标识符)
...(内容)
#endif

也可以写成以下形式

#ifndef  ...(某个标识符)
...(内容)
#endif

#define MIN 0

#ifdef MIN
	#define RET 1//MIN已经在前面定义了,故编译这句代码
#endif

4.嵌套指令

类似于以下这种

#ifndef TAP1
  #ifdef SIGN1
	  ...
  #endif
  #ifdef SIGN2
  	  ...
  #endif
#elseif defined TAP2
  ...
#endif

其中第3条编译指令–判断是否被定义是我们这篇文章的重点,希望读者先理解第3条编译指令的用法再往下读。

头文件的嵌套包含与2种解决办法

假设现在有两个文件test1.h和test2.h,由两个程序员编写,最后再合并,我们避免不了会写出以下这样的头文件

test1.h

#include

test2.h

#include
#include"test1.h"

在test2.h中就重复包含了stdio.h这个头文件(test2.h自己包含了一次stdio.h,在包含test1.h时又间接包含了stdio.h),那如何解决这个问题呢?下面提供两种解决办法:
1.

在每个头文件中写这样的代码:

#ifndef FILENAME(任意某个未定义过的标识符,最好用该头文件的名字)
#define FILENAME
...//头文件内容
#endif

如在test1.h这样写

#ifndef _TEST1_H_
#define _TEST1_H_
#include
#endif

2.
也可以在每个头文件中加上下面这句代码:

#pragma once

有些编译器会在头文件开头自动加上这句代码,但一些比较古老的编译器是不支持这种写法的。

以上就是解决头文件重复包含的两种办法,需要注意的是,头文件里面不要定义全局变量或函数(可以声明),否则就会出错,例如某个程序有一个头文件test1.h和2个源文件test2.c和test3.c,其内容如下:
test1.h

#ifndef _TEST1_H_
#define _TEST1_H_

#include
int a=0;//该头文件定义了一个全局变量

#endif

test2.c

#include"test1.h"
#include"test1.h"

test3.c

#include"test1.h"
#include"test1.h"

int main()
{
	printf("%d",a);
	return 0;
}

编译是以C文件为单位进行编译的,且头文件的包含就是将头文件的内容复制粘贴到当前文件中,所以在单独编译test2.c时没有什么问题(尽管包含了两次头文件,看似a被定义了两次,但由于#ifndef在起作用,所以在test2.c中头文件只被复制粘贴了一次,即a只被定义了一次),接着在单独编译test3.c时也没有什么问题,但在链接阶段就出问题了,链接器在将这两个C文件进行链接时,发现里面有2个a,且都是有效的,于是就报错了。所以在头文件中千万不要定义全局变量和函数,否则你就是公司的神仙队友。(关于编译链接如果有不懂的可以去看看我的《程序的翻译环境和运行环境》 亦星编程)

头文件包含的两种方式及区别

相信有细心的读者已经发现了在前面的例子中,我有时会用尖括号包含头文件,有时又会用双引号包含头文件,这里再讲讲头文件的两种包含方式及区别

1. #include"filename"

像这种用双引号括起来的头文件,编译时首先在源文件所在目录下面查找头文件,如果没有找到,编译器就会像查找库函数头文件那样在标准位置查找头文件,如果也没有找到,就会提示编译错误。

2. #include
这种用尖括号括起来的头文件,编译器就会直接去标准路径下去查找,如果找不到,就提示编译错误。

在知道这两种头文件包含方式的区别后,或许有的人就会想:那我以后可不可以都用双引号来包含头文件呢?这当然是可以的,但这样头文件的查找效率就会低一些(库文件明明是去标准路径下去查找,但由于是双引号包含的头文件,还得到源文件当前目录下去白跑一趟),同时也不容易区分该文件是库文件还是用户自己定义的头文件,所以建议库文件用尖括号,用户自己定义的文件用双引号。

结语

相信通过本片文章,你对头文件的知识有了一个更深的理解,如果本文有什么讲得不对的地方,恳请指正,如果你对本文有什么好的建议,也十分欢迎您的反馈,您的每一次反馈将会铸就下一次更好的体验,当然了,如果你觉得本文对你帮助很大,动动小手,给博主一个点赞收藏加关注吧。

你可能感兴趣的:(c++,windows,microsoft)