ifndef
一个预处理指令,作用是判断一个符号是否存在,或者是不存在,与之对应的还有一个ifdef、ifndef一般用于解决头文件重复包含的问题。
符号
符号一般指的是,函数定义的名称,类名,宏名称,这些都可以称为符号。
#ifndef Y
#define Y //如果Y不存在,则重新定义Y的符号
#else
//如果Y存在
#endif
ifndef解决include的重复包含关系
头文件A.h的内容
#pragma once
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
创建一个A.h头文件,然后进行引用这个头文件,其实引用的这个步骤在编译器看来就是代码替换,比如在一个cpp中,引入一个A.h头文件,其实就是吧A.h的代码拷贝一份到cpp文件,代码例子(因为编译器的编译步骤为:预编译-->等价的头文件替换成代码-->代码编译)
1 引用A.h文件
#include "A.h"
//以下是别的代码
int main(void)
{
Api* pOne = Factory::createApi(1);
Api* pTwo = Factory::createApi(2);
pOne->test("哈哈哈");
pTwo->test("嘻嘻嘻");
system("pause");
return 0;
}
2 经过编译器预处理之后,把include "A.h”进行替换(与文本宏替换等价)
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
//以下是别的代码
int main(void)
{
Api* pOne = Factory::createApi(1);
Api* pTwo = Factory::createApi(2);
pOne->test("哈哈哈");
pTwo->test("嘻嘻嘻");
system("pause");
return 0;
}
3 递归循环头文件等价代码的替换,一直到iostream头文件的代码替换,最终编译。
考虑问题
1 如果一个cpp文件中,代码出现重复包含头文件的操作,为什么会出现这种情况,因为写代码的时候可能一个头文件下面写了很多代码,可能有时候忘记自己添加过一个相同的头文件了,后面又重复包含了一个头文件,这样可以编译过去,但是重复替换给编译器带来了编译速度慢的问题
#include "A.h"
//代码....
#include "A.h"
//代码....
#include "A.h"
2 头文件代码被编译器重复替换之后,出现了三段相同的代码,因为刚刚已经引用了3次include "A.h"头文件,最终导致编译速度慢
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
//代码....
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
//代码....
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
3 解决问题,在include "A.h"头文件添加#ifndef预处理指令,此时就可以解决重复包含的问题
#ifndef AAA
#define AAA
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
#endif
最终会被替换成如下形式,如果AAA符号没有被定义,则重新定义AAA符号,那么下面两段代码还会继续判断AAA是否被定义,这时候AAA已经被前面一段进行定义了,所以这一段代码就会无效,编译器将不会进行等价代码替换,这时候就解决了头文件重复包含了
//第一段代码
#ifndef AAA//如果AAA不存在
#define AAA//如果AAA不存在,则重新定义AAA
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
#endif
//代码....
//以下两段代码是无效的
//第二段代码
#ifndef AAA//以下代码将变灰,不再有效,因为AAA已经被重新定义了
#define AAA
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
#endif
//代码....
//第三段代码
#ifndef AAA//以下代码将变灰,不再有效,因为AAA已经被重新定义了
#define AAA
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
#endif
ifndef重命名解决
假设定义了两个头文件,include "A.h"与include "B.h",里面定义相同的预处理指令ifndef AAA,此时A.h是ifndef AAA,B.h也是ifndef AAA
include "A.h"的代码
#ifndef AAA
#define AAA
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
#endif
include "B.h"的代码
#ifndef AAA
#define AAA
#include <iostream>
void B()
{
std::cout << "B" << std::endl;
}
#endif
此时经过编译器把头文件进行等待替换之后,第二段代码变成无效的代码,第二段是B其实是有效的代码,此时因为符号重名了,所以就变的无效了
//第一段代码
#ifndef AAA
#define AAA
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
#endif
//第二段代码
//因为符号AAA已经定义了,所以这一段代码无效,编译器不会进行编译
#ifndef AAA
#define AAA
#include <iostream>
void B()
{
std::cout << "B" << std::endl;
}
#endif
所以,ifndef后面跟着的符号是有特殊要求规则的,因为头文件的名称是固定的,一个头文件对应一个名称,所以可以让头文件的名称作为ifndef的符号名称,此时符号就不会重名了,一般的命名方法是__头文件名称_H,如 ifndef __A_H,其中A是头文件的文件名,H是头文件的后缀名
include "A.h"的内容
#ifndef __A_H
#define __A_H
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
#endif
include "B.h"的内容
#ifndef __B_H
#define __B_H
#include <iostream>
void B()
{
std::cout << "B" << std::endl;
}
#endif
所以经过处理之后,代码的写法应该是如下形式
//第一段代码有效
#ifndef __A_H
#define __A_H
#include <iostream>
void A()
{
std::cout << "A" << std::endl;
}
#endif
//第二段代码也有效,也会进行编译
#ifndef __B_H
#define __B_H
#include <iostream>
void B()
{
std::cout << "B" << std::endl;
}
#endif
此时就可以利用ifndef完美解决头文件重复包含的问题了
总结
1 ifndef可以有效解决头文件重复包含问题
2 include头文件其实就是等价的代码拷贝
3 ifndef的符号名必须是独立的,没有定义过的,否则会造成有效代码没有进行编译
4 ifndef的一般用法为 __头文件名称_H