学科分类
目录
C语言

#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可以有效避免重定义错误的发生。

点击此处
隐藏目录