前言
在一些大型项目中,各种头文件会相互包含,关系就显得错综复杂,同时由于文件的包含就是复制粘贴,那些重复包含的头文件就会使代码长度大大增加,那有没有什么办法解决这个问题呢?答案肯定是有的,这就涉及到条件编译了。
条件编译
所谓条件编译就是在编译阶段让编译器选择编译或不编译某些代码,需要注意的是条件编译是在编译阶段进行的,而不是等到程序运行时,利用这点我们就可以让编译器选择是否包含头文件,从而避免头文件被重复包含。
下面看看一些常见的条件编译指令:
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<stdio.h>
test2.h
#include<stdio.h>
#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<stdio.h>
#endif
2.
也可以在每个头文件中加上下面这句代码:
#pragma once
有些编译器会在头文件开头自动加上这句代码,但一些比较古老的编译器是不支持这种写法的。
以上就是解决头文件重复包含的两种办法,需要注意的是,头文件里面不要定义全局变量或函数(可以声明),否则就会出错,例如某个程序有一个头文件test1.h和2个源文件test2.c和test3.c,其内容如下:
test1.h
#ifndef _TEST1_H_
#define _TEST1_H_
#include<stdio.h>
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<filename>
这种用尖括号括起来的头文件,编译器就会直接去标准路径下去查找,如果找不到,就提示编译错误。
在知道这两种头文件包含方式的区别后,或许有的人就会想:那我以后可不可以都用双引号来包含头文件呢?这当然是可以的,但这样头文件的查找效率就会低一些(库文件明明是去标准路径下去查找,但由于是双引号包含的头文件,还得到源文件当前目录下去白跑一趟),同时也不容易区分该文件是库文件还是用户自己定义的头文件,所以建议库文件用尖括号,用户自己定义的文件用双引号。
结语
相信通过本片文章,你对头文件的知识有了一个更深的理解,如果本文有什么讲得不对的地方,恳请指正,如果你对本文有什么好的建议,也十分欢迎您的反馈,您的每一次反馈将会铸就下一次更好的体验,当然了,如果你觉得本文对你帮助很大,动动小手,给博主一个点赞收藏加关注吧。