Learning C++ 之1.9a 頭文件

頭文件以及目的:

隨着程序變得越來越大,在不同的文件引入其他文件中的函數變得異常複雜。那麼爲什麼不把這些聲明提前放到一個統一的地方呢?

C++中不只有.cpp的代碼文件,另一種類型文件叫做頭文件。頭文件的後綴名一般是.h,有時候是.hpp。頭文件的目的是包含其他文件使用的聲明。

使用標準的庫函數頭文件:

看下面的程序

#include <iostream>
int main()
{
    std::cout << "Hello, world!" << std::endl;
    return 0;
}

這個程序輸出Hello World到屏幕上。然而你可能會問,cout這個函數並沒有定義過,爲什麼可以使用呢?答案就是#incude <iostream>  ,cout函數在iostream中,這裏我們引用了該文件就表示把iostream中的函數都copy過來了,所以cout就可以使用了。頭文件中的聲明這樣就可以通過include,在我們的代碼中使用了。

切記,頭文件只包含函數的聲明,並不包含函數的實現。那麼cout只是聲明瞭,在哪裏具體實現的呢。他是在C++運行支持庫中實現的,當link的時候會自動鏈接到你的程序上。


設想如果沒有頭文件的話,我們需要使用std::cout就需要把相關的聲明手動的copy到你所使用的文件中,而且你需要知道那些是相關的,哪些不是。相比較而言,直接include進來更加容易。

寫你自己的頭文件:

現在讓我們回到之前的例子中,我們有兩個文件如下:

add.cpp

int add(int x, int y)
{
    return x + y;
}

main.cpp

#include <iostream>
 
int add(int x, int y); // forward declaration using function prototype
 
int main()
{
    std::cout << "The sum of 3 and 4 is " << add(3, 4) << std::endl;
    return 0;
}
我們用了提前聲明的方法來保證main.cpp可以使用add.cpp中的函數,爲你的代碼中寫所有的函數的聲明很快會讓你感到乏味的。

頭文件幫忙我們解決了這個負擔問題,函數在頭文件中只需要聲明一次,然後就可以通過include的方式隨處引用。這同樣減少了很多工作量,當你的一個函數需要改動的時候,不會去更改每一處引用的函數的地方,只需要改頭文件就行了。

寫我們自己的頭文件非常簡單,一般頭文件包含兩部分:

第一部分稱作heaher Guard,這一部分可以有效地防止一個程序多次引用同一個頭文件。

第二部分是真正的內容部分,這裏麪包含了所有需要被其他代碼調用的函數聲明,因爲頭文件以.h爲文件尾,所以我們可以成爲add.h。

add.h

// This is start of the header guard.  ADD_H can be any unique name.  By convention, we use the name of the header file.
#ifndef ADD_H
#define ADD_H
 
// This is the content of the .h file, which is where the declarations go
int add(int x, int y); // function prototype for add.h -- don't forget the semicolon!
 
// This is the end of the header guard
#endif

main.cpp

#include <iostream>
#include "add.h"
 
int main()
{
    std::cout << "The sum of 3 and 4 is " << add(3, 4) << std::endl;
    return 0;
}

當編譯器編譯到#include “add.h”的時候,編譯器會把add.h中的內容copy到相應的文件裏。因爲add.h中年有add()函數。所以這個地方就作爲了add函數的一個提前聲明。

整個流程如下圖所示:


<>和“”的區別:

你可能會有疑問,爲什麼同樣是include文件,一個是<>而另外一個是""呢。是這樣的,<>是C++標準的庫,而""是你自己的一些頭文件。程序裏找<>文件的時候會直接去C++系統中的頭文件庫查找,而.h文件會從當前目錄直接查找,如果找不到,再去其他目錄查找,直到檢查系統的目錄。

頭文件可能會重複引用,這些你不用擔心,只需要確認你所需要的文件都被include進來了就行了。

爲什麼iostream沒有一個.h擴展呢?

這個問題也常常被提及,答案是iostream和iostream.h是兩個不同的頭文件,這個有個歷史淵源。

最初創建C++的時候,所有的C++的頭文件都是以.h結尾的。直到C++加入了ANSI標準委員會,他們想把運行庫的頭文件全部加載到std的命名空間裏面。然而,如果這樣的話,納悶之前的C++版本寫的code都不能正常運行了。

爲了避免重寫所有的代碼,一系列內容相同,但是沒有後綴的頭文件創建了,放到了std命名空間裏面。這些新的頭文件都沒有後綴。這樣一來確保了之前的老的程序還可以正常工作。

當你從標準庫裏引用文件的時候,確保使用的是std裏面的沒有後綴的文件,而不是帶有.h的文件,這樣可能會引起版本不支持的問題。

另外一些C的標準庫函數都統一增加了c前綴,如stdlib.h替換成cstdlib。這些頭文件同樣移到了std命名空間裏面。

然而當你自己寫頭文件的時候,還是要求帶有.h文件的。因爲你的頭文件不在std命名空間裏。

原則:只要庫裏有非.h文件的頭文件,那麼就用沒有.H的文件。不然就用.h的頭文件。

包含來自其他目錄的頭文件

另一個普遍的問題就是怎麼包含來自其他目錄的頭文件。

一個比較糟糕的做法是使用相對路徑來包含,如下面的例子:

#include "headers/myHeader.h"
#include "../moreHeaders/myOtherHeader.h"

這種做法的缺點是你的路徑必須相對固定,如果更新一下排版,可能就會有問題了。

最好的版本是告訴你的編譯器你有專門放置頭文件的一個地方,這樣在當前目錄找不到的時候,就回去你指定的地方找。這個在你的IDE裏可以實現。

VS:Soulution Project 右鍵,Properties,VC++ Directory tab,Include Directories

Code::Block:  BUild Options,Search Directories

g++:使用-I參數

g++ -o main -I /source/includes main.cpp

這種方式的好處是,不管你怎麼改變代碼路徑,你只需要在編譯文件或者IDE裏改一下相關路徑就OK了。

是否可以往頭文件裏放置函數?

不行,會導致鏈接錯誤。在之後的課程裏面會講到原因-header guards。只是聲明就可以了。

頭文件最好建議:

下面是幾點好的寫頭文件的建議:

  • 必須包含head guards
  • 不能在頭文件裏定義變量,除非他們是常數。頭文件只能用來聲明。
  • 不要在頭文件裏定義函數
  • 每一個頭文件都有獨立性,做一類特殊的服務。比如你需要把A函數相關的函數放到A.h裏面,把B函數相關的函數放到B.h裏面。這樣不會造成混淆。
  • 將你的頭文件和源文件命名相同
  • 儘可能少地include文件,和代碼無關的頭文件不要include
  • 不要include C++文件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章