深入理解和探讨C++头文件包含问题

1.头文件包含

1.1 系统头文件与自定义头文件

使用预处理指令 #include 可以引用用户和系统头文件。它的形式有以下两种:

 

#include <stdio.h>

这种形式用于引用系统头文件,编译器自带的头文件就是系统头文件,编译器编译时直接去系统目录找此头文件。

 

#include "header.h"

这种形式用于引用用户自定义头文件,它优先在包含当前文件的目录中搜索名为header.h 的文件, 如果没找到再到系统目录下查找。

1.2 #pagma once与#ifndef

为了避免同一个头文件被包含(include)多次,C/C++中有两种宏实现方式:一种是#ifndef方式;另一种是#pragma once方式。

1.2.1 用法说明

 

#ifndef用法

xxxx.h中

 #ifndef  __SOMEFILE_H__

#define   __SOMEFILE_H__

 ... ... // 声明、定义语句

#endif

 

 

#pragma once用法

xxxx.h中

#pragma once

 ... ... // 声明、定义语句

 

1.2.2 区别

(1)

#ifndef的方式受C/C++语言标准支持。它不仅可以保证同一个文件不会被包含多次,也能保证内容完全相同的两个文件(或者代码片段)不会被不小心同时包含。

     当然,缺点就是如果不同头文件中的宏名不小心“撞车”,可能就会导致你看到头文件明明存在,但编译器却硬说找不到声明的状况——这种情况有时非常让人郁闷。

     由于编译器每次都需要打开头文件才能判定是否有重复定义,因此在编译大型项目时,ifndef会使得编译时间相对较长,因此一些编译器逐渐开始支持#pragma once的方式。

(2)

#pragma once 一般由编译器提供保证:同一个文件不会被包含多次。注意这里所说的“同一个文件”是指物理上的一个文件,而不是指内容相同的两个文件。你无法对一个头文件中的一段代码作pragma once声明,而只能针对文件。

其好处是,你不必再担心宏名冲突了,当然也就不会出现宏名冲突引发的奇怪问题。大型项目的编译速度也因此提高了一些。

对应的缺点就是如果某个头文件有多份拷贝,本方法不能保证他们不被重复包含。当然,相比宏名冲突引发的“找不到声明”的问题,这种重复包含很容易被发现并修正。

另外,这种方式不支持跨平台!

(3)

#pragma once 方式产生于#ifndef之后,因此很多人可能甚至没有听说过。目前看来#ifndef更受到推崇。因为#ifndef受C/C++语言标准的支持,不受编译器的任何限制;而#pragma once方式却不受一些较老版本的编译器支持,一些支持了的编译器又打算去掉它,所以它的兼容性可能不够好。

 

1.3 解决头文件互相包含的问题

我们知道,当一个类(设类A)中包含另一个类(设类B)的对象时,必须在该文件中包含另一个类的头文件,如果两个类都互用到了对方的对象,理论上就要互相包含头文件,但是这样是无法通过编译的,其原因是它们的头文件互相包含了,你包含我,我又包含你,没完没了,如果没有使用代码防止头文件相互包含,那么编译器会报错如 :

fatal error C1014: 包含文件太多: 深度 = 1024

如果使用了如#pragma once这种手段防止头文件相互包含,那么编译器会报错如:

error C4430 : 缺少类型说明符!

 

1.3.1错误示范

#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()

{

}

 

 

1.3.2 为什么会报错

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.3.3 解决方法

1、如果在A类的定义中使用了B类的对象,那么就必须包含B类的头文件,只声明是不行的,因为要根据类的成员来给对象分配存储空间,如果不知道对象有哪些成员构成,就无法分配了。而B类中就不能使用A类的对象了,一旦使用就必须包含A类头文件,又造成相互包含的问题,这时可以用A类的指针,然后在B类头文件中加上A类的声明,指针占的字节数是固定的,不用担心程序不知道如何分配多大的内存。

 

2、两个类中互相使用了对方的指针,这样的情况很简单,分别在各自的头文件中声明一下使用的类,而在各自的源文件中包含对方的头文件即可。

 

3、如果A类继承了B类,那么在A类的头文件中必须要包含B类的头文件,只声明是不行的,道理同1.

 

1.3.4 正确示例

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()

{

}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章