#ifndef
#ifndef指令用来确定某一个宏是否未被定义,它的含义与#ifdef指令相反,如果宏没有被定义,则编译#ifndef指令下的内容,否则就跳过。#ifndef通常与#else、#endif结合使用,其语法格式如下:
#ifndef 宏名
程序段1
#else
程序段2
#endif
使用#ifndef指令判断宏是否未被定义的示例代码如下:
#define DEBUG
#ifndef DEBUG
printf("输出调试信息\n");
#else
printf("不输出调试信息\n");
#endif
上述代码中,首先定义了宏DEBUG,然后使用#ifndef指令判断宏DEBUG是否未被定义,如果未被定义,则输出调试信息。但由于宏DEBUG被定义了,#ifndef指令下的语句会被跳过,不进行编译,而#else指令下的语句会被编译,输出“不输出调试信息”。如果将宏DEBUG的定义语句去掉,则#ifndef指令下的语句会被编译,从而打印“输出调试信息”。
#ifndef指令常用于多文件包含中,如果一个项目有多个文件,有的文件会包含其他文件,如果文件重复包含,编译器会报错。例如,在一个项目中编写的头文件bar1.h、bar2.h中都包含头文件foo.h,主函数文件同时包含了bar1.h和bar2.h,那么,foo.h就被重复包含,此时编译器会报错。文件重复包含可以通过#ifndef指令解决。下面介绍使用#ifndef指令解决文件重复包含的问题,具体步骤如下:
1、定义foo.h文件
首先定义一个头文件foo.h,该文件内容具体如下:
struct Foo
{
int i;
};
上述foo.h头文件中定义了struct Foo结构体类型,它包含一个整型成员变量i。
2、定义bar1.h与bar1.c文件
定义头文件bar1.h,具体如下:
#include "foo.h"
void bar1(struct Foo f);
头文件bar1.h中声明了一个函数bar1(),它的参数是一个struct Foo结构体类型的变量,因此需要在bar1.h中包含头文件foo.h。
bar1()函数的实现在源文件bar1.c中,为了简便,将bar1()函数定义为空函数,则bar1.c文件具体实现如下:
#include "bar1.h"
void bar1(struct Foo f)
{
}
3、定义bar2.h和bar2.c文件
类似地,在bar2.h文件中也声明了一个函数bar2(),它的参数也是一个struct Foo结构体类型的变量,则bar2.h文件具体如下:
#include "foo.h"
void bar2(struct Foo f);
bar2()函数的实现在源文件bar2.c中,它的实现同样是一个空函数,bar2.c文件具体实现如下:
#include "bar2.h"
void bar2(struct Foo f)
{
}
4、定义main.c文件
在main.c文件中定义main()函数,在main()函数中定义一个struct Foo结构体类型的变量f,并调用bar1()函数和bar2()函数,main.c文件的具体实现如下:
1 #include "foo.h"
2 #include "bar1.h"
3 #include "bar2.h"
4 int main()
5 {
6 struct Foo f = { 1 };
7 bar1(f);
8 bar2(f);
9 return 0;
10 }
编译程序,Visual Studio2019会报错,如图1所示。
图1 程序报错
从图1中可以看出,程序提示struct Foo类型重定义错误。这是因为在main.c文件中,第1行代码使用#include指令引用了一次foo.h,该文件中定义了struct Foo结构体类型;第2~3行代码引入的bar1.h和bar2.h虽然没有定义struct Foo结构体类型,但是这两个头文件都分别引用了foo.h,因此,经过预处理之后,main.c文件中的代码如下:
struct Foo
{
int i;
};
struct Foo
{
int i;
};
void bar1(struct Foo f);
struct Foo
{
int i;
};
void bar2(struct Foo f);
int main()
{
struct Foo f = { 1 };
bar1(f);
bar2(f);
return 0;
}
这样的main.c文件显然不能通过编译。头文件的嵌套导致了foo.h最终被多次引用,从而导致main.c中多次出现struct Foo结构体类型的定义。
5、使用#ifndef指令解决重复包含
为了解决上述问题,可以使用#ifndef指令和#define指令组合,对foo.h文件进行修改,修改后的代码如下:
#ifndef _FOO_H_
#define _FOO_H_
struct Foo
{
int i;
};
#endif
上述代码包含了#ifndef条件编译指令,该指令内部有一条#define指令,初次编译时由于宏“_FOO_H_”尚未定义,#ifndef条件成立,调用#define指令定义__FOO_H_宏,编译struct Foo结构体类型。当foo.h中的内容被再次编译后,宏_FOO_H_已经被定义了,#ifndef的条件不成立,内容将被跳过,如此便保证了struct Foo结构体类型的定义仅可以被编译一次。利用#ifndef指令经过预处理后的main.c中的代码相当于下列代码:
#ifndef _FOO_H_
#define _FOO_H_
struct Foo
{
int i;
};
#endif
#ifndef _FOO_H_
#define _FOO_H_
struct Foo
{
int i;
};
#endif
void bar1(struct Foo f);
#ifndef _FOO_H_
#define _FOO_H_
struct Foo
{
int i;
};
#endif
void bar2(struct Foo f);
int main()
{
struct Foo f = { 1 };
bar1(f);
bar2(f);
return 0;
}
此时再编译程序就不会报错了,尽管struct Foo结构体类型的定义出现了3次,但是因为#ifndef条件编译指令,struct Foo结构体类型只会被编译一次。由此可见,当在头文件中嵌套自定义头文件时,使用#ifndef可以有效避免重定义错误的发生。