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