在 C++ 项目中,经常会发生头文件重复引入导致冲突问题。事实上,一个完整的 C++ 项目由多个代码文件组成,根据后缀的不同,大致可以分成两个部分:
(1).h 文件:又称“头文件”,用于存放常量、函数的声明部分、类的声明部分;
(2).cpp 文件:又称“源文件”,用于存放变量、函数的定义部分,类的实现部分;
但是,常常会重复导入.h文件,导致重定义的错误,本文主要是为了解决.h文件重复导入的问题。
C++ 多文件编程中,多次 #include 导致重复引入的演示代码如下:
A.h
class A
{
public:
A() {}
void say();
};
A.cpp
#include "A.h"
#include <iostream>
using namespace std;
void A::say()
{
cout << "AAAAAAAAAAA" << endl;
}
B.h
#include "A.h";
class B
{
private:
A a;
public:
void setA(A a);
A getA();
void say();
};
B.cpp
#include "B.h"
#include <iostream>
using namespace std;
void B::setA(A a)
{
this->a = a;
}
A B::getA()
{
return a;
}
void B::say()
{
cout << "BBBBBBBBB" << endl;
}
main.cpp
#include <iostream>
#include"A.h"
#include"B.h"
using namespace std;
int main()
{
A* a = new A();
B* b = new B();
b->setA(*a);
b->getA().say();
return 0;
}
以上代码运行之后会报如下错误:
如上错误所示,以上代码 A 类重新定义了,原因是在 main.cpp 文件中引入了 A.h 和 B.h,而在 B.h 中有引入了 A.h,相当于 A.h 被引入了两次,所以会报错。
问题的解决方案有三种,分别是:使用宏定义避免重复引入、使用#pragma once避免重复引入、使用_Pragma操作符。
(1)使用宏定义避免重复引入
在实际多文件开发中,我们往往使用如下的宏定义来避免发生重复引入:
#ifndef _NAME_H
#define _NAME_H
//头文件内容
#endif
其中,_NAME_H 是宏的名称。需要注意的是,这里设置的宏名必须是独一无二的,不要和项目中其他宏的名称相同。
之前演示的代码是 A.h 文件被重复引入,只要修改下 A.h,就可以解决重复引入的问题,修改后的 A.h 代码如下:
#ifndef _A
#define _A
class A
{
public:
A() {}
void say();
};
#endif
(2)使用#pragma once避免重复引入
我们还可以使用 #pragma one 指令,将其附加到指定文件的最开头位置,则该文件就只会被 #include 一次。
#ifndef 是通过定义独一无二的宏来避免重复引入的,这意味着每次引入头文件都要进行识别,所以效率不高。但考虑到 C 和 C++ 都支持宏定义,所以项目中使用 #ifndef 规避可能出现的“头文件重复引入”问题,不会影响项目的可移植性。
和 ifndef 相比,#pragma once 不涉及宏定义,当编译器遇到它时就会立刻知道当前文件只引入一次,所以效率很高。但值得一提的是,并不是每个版本的编译器都能识别 #pragma once 指令,一些较老版本的编译器就不支持该指令(执行时会发出警告,但编译会继续进行),即 #pragma once 指令的兼容性不是很好。
目前,几乎所有常见的编译器都支持 #pragma once 指令,甚至于 Visual Studio 2017 新建头文件时就会自带该指令。可以这么说,在 C/C++ 中,#pragma once 是一个非标准但却逐渐被很多编译器支持的指令。
如何使用? 只要在被重复文件的顶部添加 #pragma once 即可。
#pragma once
class A
{
public:
A() {}
void say();
};
(3)用_Pragma操作符
C99 标准中新增加了一个和 #pragma 指令类似的 _Pragma 操作符,其可以看做是 #pragma 的增强版,不仅可以实现 #pragma 所有的功能,更重要的是,_Pragma 还能和宏搭配使用。
修改后的代码如下:
_Pragma("once")
class A
{
public:
A() {}
void say();
};
总结:
(1) #pragma once 和 _Pragma("once") 可算作一类,其特点是编译效率高,但可移植性差(比如:编译器不支持,会发出警告,但不会中断程序的执行)
(2)而 #ifndef 的特点是可移植性高,编译效率差。
需要知道的是:在某些场景中,考虑到编译效率和可移植性,#pragma once 和 #ifndef 经常被结合使用来避免头文件被重复引入。比如说:
#pragma once
#ifndef _A
#define _A
class A
{
public:
A() {}
void say();
};
#endif
[本章完...]