學習記錄之頭文件

在一個C++程序中,只包含兩類文件——.cpp文件和.h文件。其中,.cpp文件被稱作C++源文件,裏面放的都是C++的源代碼;而.h文件則被稱作C++頭文件,裏面放的也是C++的源代碼。
C+ +語言支持“分別編譯也就是說,一個程序所有的內容,可以分成不同的部分分別放在不同的.cpp文件裏。.cpp文件裏的東西都是相對獨立的,在編 譯時不需要與其他文件互通,只需要在編譯成目標文件後再與其他的目標文件做一次鏈接(link)就行了。比如,在文件a.cpp中定義 了一個全局函數“void a() {}”,而在文件b.cpp中需要調用這個函數。即使這樣,文件a.cpp和文件b.cpp並不需要相互知道對方的存在,而是可以分別地對它們進行編譯, 編譯成目標文件之後再鏈接,整個程序就可以運行了。


從寫程序的角度來講,很簡單。在文件b.cpp中,在調用 “void a()”函數之前,先聲明一下這個函數“void a();”,就可以了。這是因爲編譯器在編譯b.cpp的時候會生成一個符號表(symbol table),像“void a()”這樣的看不到定義的符號,就會被存放在這個表中。再進行鏈接的時候,編譯器就會在別的目標文件中去尋找這個符號的定義。一旦找到了,程序也就可以 順利地生成了。

注意這裏提到了兩個概念,一個是“定義”,一個是“聲明”。簡單地說,“定義”就是把一個符號完完整整地描述出來:它是變 量還是函數,返回什麼類型,需要什麼參數等等。而“聲明”則只是聲明這個符號的存在,即告訴編譯器,這個符號是在其他文件中定義的,我這裏先用着,你鏈接 的時候再到別的地方去找找看它到底是什麼吧。定義的時候要按C++語法完整地定義一個符號(變量或者函數),而聲明的時候就只需要寫出這個符號的原型了。 需要注意的是,一個符號,在整個程序中可以被聲明多次,但卻要且僅要被定義一次。試想,如果一個符號出現了兩種不同的定義

有一個很常用的函數“void f() {}”,在整個程序中的許多.cpp文件中都會被調用,那麼,我們就只需要在一個文件中定義這個函數,而在其他的文件中聲明這個函數就可以了。一個函數還 好對付,聲明起來也就一句話。但是,如果函數多了,比如是一大堆的數學函數,有好幾百個,那怎麼辦?能保證每個程序員都可以完完全全地把所有函數的形式都 準確地記下來並寫出來嗎?

二、什麼是頭文件
這 個方法固然可行,但還是太麻煩,而且還顯得很笨拙。於是,頭文件便可以發揮它的作用了。所謂的頭文件,其實它的內容跟.cpp文件中的內容是一樣的,都是 C++的源代碼。但頭文件不用被編譯。我們把所有的函數聲明全部放進一個頭文件中,當某一個.cpp源文件需要它們時,它們就可以通過一個宏命令 “#include”包含進這個.cpp文件中,從而把它們的內容合併到.cpp文件中去。當.cpp文件被編譯時,這些被包含進去的.h文件的作用便發 揮了。
假設所有的數學函數只有兩個:f1和f2,把它們的定義放在math.cpp裏:
/* math.cpp */
double f1()
{
    //do something here....
    return;
}
double f2(double a)
{
    //do something here...
    return a * a;
}
/* end of math.cpp */
並把“這些”函數的聲明放在一個頭文件math.h中:
/* math.h */
double f1();
double f2(double);
/* end of math.h */
在另一個文件main.cpp中,我要調用這兩個函數,那麼就只需要把頭文件包含進來:
/* main.cpp */
#include "math.h"
main()
{
    int number1 = f1();
    int number2 = f2(number1);
}
/* end of main.cpp */
這 樣,便是一個完整的程序了。需要注意的是,.h文件不用寫在編譯器的命令之後,但它必須要在編譯器找得到的地方。 main.cpp和math.cpp都可以分別通過編譯,生成main.o和math.o,然後再把這兩個目標文件進行鏈接,程序就可以運行了。
#include
#include 是一個來自C語言的宏命令,它在編譯器進行編譯之前,即在預編譯的時候就會起作用。#include的作用是把它後面所寫的那個文件的內容,完完整整地、 一字不改地包含到當前的文件中來。值得一提的是,它本身是沒有其它任何作用與副功能的,它的作用就是把每一個它出現的地方,替換成它後面所寫的那個文件的 內容。簡單的文本替換,別無其他。因此,main.cpp文件中的第一句(#include "math.h"),在編譯之前就會被替換成math.h文件的內容。即在編譯過程將要開始的時候,main.cpp的內容已經發生了改變:
/* ~main.cpp */
double f1();
double f2(double);
main()
{
    int number1 = f1();
    int number2 = f2(number1);
}
/* end of ~main.cpp */
除了main.cpp以外,還有其他的很多.cpp文件也用到了f1和f2函數的話,那麼它們也通通只需要在使用這兩個函數前寫上一句#include "math.h"就行了。


頭文件的作用就是被其他的.cpp包含進去的。它們本身並不參與編譯,但實際上,它們的內容卻在多個.cpp文件中得到了 編譯。通過“定義只能有一次”的規則,我們很容易可以得出,頭文件中應該只放變量和函數的聲明,而不能放它們的定義。因爲一個頭文件的內容實際上是會被引 入到多個不同的.cpp文件中的,並且它們都會被編譯。放聲明當然沒事,如果放了定義,那麼也就相當於在多個文件中出現了對於一個符號(變量或函數)的定 義,縱然這些定義都是相同的,但對於編譯器來說,這樣做不合法。

所以,應該記住的一點就是,.h頭文件中,只能存在變量或者函數的聲明, 而不要放定義。即,只能在頭文件中寫形如:extern int a;和void f();的句子。這些纔是聲明。如果寫上int a;或者void f() {}這樣的句子,那麼一旦這個頭文件被兩個或兩個以上的.cpp文件包含的話,編譯器會立馬報錯。





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