深入理解和探討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()

{

}

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