当前位置 博文首页 > Allen Roson:深入理解和探讨C++头文件包含问题
使用预处理指令 #include 可以引用用户和系统头文件。它的形式有以下两种:
?
#include <stdio.h>
这种形式用于引用系统头文件,编译器自带的头文件就是系统头文件,编译器编译时直接去系统目录找此头文件。
?
#include "header.h"
这种形式用于引用用户自定义头文件,它优先在包含当前文件的目录中搜索名为header.h 的文件, 如果没找到再到系统目录下查找。
为了避免同一个头文件被包含(include)多次,C/C++中有两种宏实现方式:一种是#ifndef方式;另一种是#pragma once方式。
?
#ifndef用法:
xxxx.h中
?#ifndef??__SOMEFILE_H__
#define?? __SOMEFILE_H__
?... ... // 声明、定义语句
#endif
?
?
#pragma once用法:
xxxx.h中
#pragma once
?... ... // 声明、定义语句
?
(1)
#ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。
? ?? 当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,但编译器却硬说找不到声明的状况——这种情况有时非常让人郁闷。
? ?? 由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。
(2)
#pragma once 一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。
其好处是,你不必再担心宏名冲突了,当然也就不会出现宏名冲突引发的奇怪问题。大型项目的编译速度也因此提高了一些。
对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名冲突引发的“找不到声明”的问题,这种重复包含很容易被发现并修正。
另外,这种方式不支持跨平台!
(3)
#pragma once 方式产生于#ifndef之后,因此很多人可能甚至没有听说过。目前看来#ifndef更受到推崇。因为#ifndef受C/C++语言标准的支持,不受编译器的任何限制;而#pragma once方式却不受一些较老版本的编译器支持,一些支持了的编译器又打算去掉它,所以它的兼容性可能不够好。
?
我们知道,当一个类(设类A)中包含另一个类(设类B)的对象时,必须在该文件中包含另一个类的头文件,如果两个类都互用到了对方的对象,理论上就要互相包含头文件,但是这样是无法通过编译的,其原因是它们的头文件互相包含了,你包含我,我又包含你,没完没了,如果没有使用代码防止头文件相互包含,那么编译器会报错如 :
fatal error C1014: 包含文件太多: 深度 = 1024
如果使用了如#pragma once这种手段防止头文件相互包含,那么编译器会报错如:
error C4430 : 缺少类型说明符!
?
#ClassA.h
#pragma once
#include "ClassB.h"
class ClassA
{
public:
??? ClassA();
??? ~ClassA();
??? ClassB m_objB;
};
?
#ClassA.cpp
#include "stdafx.h"
#include "ClassA.h"
ClassA::ClassA()
{
}
ClassA::~ClassA()
{
}
?
?
#ClassB.h
#pragma once
#include "ClassA.h"
?
class ClassB
{
public:
??? ClassB();
??? ~ClassB();
?
??? ClassA m_objA;
};
#ClassB.cpp
#include "stdafx.h"
#include "ClassB.h"
ClassB::ClassB()
{
}
ClassB::~ClassB()
{
}
?
?
fatal error C1014: 包含文件太多: 深度 = 1024
假设现在有2个类ClassA和ClassB是存在头文件相互包含的问题。
//#include作用:在预处理阶段,编译器将源文件包含的头文件内容复制到包含语句(#include)处。
?
/*假设程序先编译ClassA.cpp,在ClassA.cpp中包含了ClassA.h,此时把ClassA.h内容复制到到ClassA.cpp中替换掉
#include "ClassA.h"这一行,ClassA.cpp 变成下面这个样子:*/
?
/*这是#include "ClassA.h"被替换后的代码,注意由于#pragma once的作用,ClassA.h已经被包含了一次到ClassA.cpp中,
不会再次被包含进去*/
#include "ClassB.h"
class ClassA
{
public:
ClassA();
~ClassA();
?
ClassB m_objB;
};
?
//后面的是ClassA.cpp原来的代码
ClassA::ClassA()
{
}
ClassA::~ClassA()
{
}
?
/*同理可知此时ClassA.cpp中还有个#include "ClassB.h"也要被替换成ClassB.h的内容,替换之后,ClassA.cpp 变成下面这个样子:*/
?
#include "ClassA.h"
class ClassB
{
public:
??? ClassB();
??? ~ClassB();
??? ClassA m_objA;
};
?
class ClassA
{
public:
ClassA();
~ClassA();
?
ClassB m_objB;
};
?
//后面的是ClassA.cpp原来的代码
ClassA::ClassA()
{
}
ClassA::~ClassA()
{
}
?
/*此时此刻,ClassA.cpp中的#include "ClassA.h"已经被替换了一次,ClassA.h的#include "ClassB.h"也被
替换了一次到ClassA.cpp中,由于#pragma once的作用,ClassB.h和ClassA.h不会再被重复包含到ClassA.cpp
中了,所以抛开编译器一些自动优化的处理不说,上面这个代码就可以算作是ClassA.cpp将要被编译的最终代码,
那么就在就可以明确地看出来到底有什么错了:
1、在ClassB的类声明中,出现了ClassA m_objA,由于ClassB的声明之前没有出现ClassA的声明,所以编译器无法
识别这个类型,会报错:error C4430 : 缺少类型说明符 - 假定为 int。注意 : C++ 不支持默认 int
2、在ClassA.cpp中,编译器能识别出来m_objB的类型是ClassB,但是在ClassB.cpp中,ClassA的声明在ClassB之前,会造成编译器不认识在ClassB.cpp中声明的m_objB这个成员变量的类型,那么编译器会认为ClassB.cpp中的m_objB的类型可能和ClassA.cpp中的m_objB的类型不一致,同一个类中同名的成员变量的类型如果不相同,那么编译器会报错:
error C3646 : “m_objB”: 未知重写说明符*/
?
1、如果在A类的定义中使用了B类的对象,那么就必须包含B类的头文件,只声明是不行的,因为要根据类的成员来给对象分配存储空间,如果不知道对象有哪些成员构成,就无法分配了。而B类中就不能使用A类的对象了,一旦使用就必须包含A类头文件,又造成相互包含的问题,这时可以用A类的指针,然后在B类头文件中加上A类的声明,指针占的字节数是固定的,不用担心程序不知道如何分配多大的内存。
?
2、两个类中互相使用了对方的指针,这样的情况很简单,分别在各自的头文件中声明一下使用的类,而在各自的源文件中包含对方的头文件即可。
?
3、如果A类继承了B类,那么在A类的头文件中必须要包含B类的头文件,只声明是不行的,道理同1.
?
A类不用修改,B类改为如下代码即可:
#ClassB.h
#pragma once
class ClassA;
class ClassB
{
public:
??? ClassB();
??? ~ClassB();
??? ClassA *m_ptrA;
};
#ClassB.cpp
#include "stdafx.h"
#include "ClassB.h"
#include "ClassA.h"
?
ClassB::ClassB()
{
}
ClassB::~ClassB()
{
}
cs