1. 程式人生 > >const物件為什麼可以在標頭檔案中定義

const物件為什麼可以在標頭檔案中定義

對於標頭檔案中為什麼可以定義const變數(或物件),以及推薦用const代替#define巨集定義,之前一直概念不清晰,今天就總結一下。

之前在網上查過,解釋的都不太到位,或者角度不一樣(從編譯原理、強弱定義?),總之不能清晰理解,發現《C++ Primer》上基本上涵蓋了所有平常遇到的C/C++問題,而且《C++ Primer》地位正宗,上面的解釋可信度自然沒得說。so,有問題,先找《C++ Primer》。

1.const物件預設為檔案的區域性變數。《C++ Primer 4》p86,

2.標頭檔案用於宣告而不是用於定義。《C++ Primer 4》p100,原因是:

1.定義和宣告的區別:定義只可以出現一次,而宣告則可以出現多次。下面這些是定義(怎樣判斷的?),都不應該出現在標頭檔案中:

extern int ival = 10;

double fica_rate;

同一個程式中有兩個以上檔案含有上述任一個定義都會導致多重定義連結錯誤。編譯時可能不會報錯,因為編譯時每個原始檔都是單獨編譯生成目標檔案,彼此是相互獨立不可見的,只有在連結的時候整個工程才作為一個整體,連結主要解決模組間的相互引用問題,而在編譯階段生成目標檔案是,會暫時擱置那些外部引用,外部引用是在連結時進行確定的。

因為標頭檔案包含在多個原始檔中,所以不應該含有變數或函式的定義。但是……

3.對於標頭檔案不應該含有定義這一規則,有三個例外。標頭檔案中可以定義類、值在編譯時就已知道的const物件(陣列的大小可以用const int型變數定義,這在C中是不行的),和inline函式。《C++ Primer 4》p100,在補充一個,

模板"必須"定義在標頭檔案中,包括模板類和模板函式。

下面來解釋原因:

1.這些實體可以在多個原始檔中定義,只要每個原始檔中的定義是相同的。

2.在標頭檔案中定義這些實體,是因為編譯器需要他們的定義(不只是宣告)來產生程式碼(這句話對模板同樣適用)。例如:為了產生能定義或使用類的物件的程式碼,編譯器需要知道組成該型別的資料成員。同樣還需要知道能夠在這些物件上執行的操作。類定義提供所需要的資訊。當然(但是)類定義內部的成員可以是宣告,類定義和類內部成員的定義不是一回事哦,可以不同步進行,也”不建議“類定義時定義類內部函式。在標頭檔案中定義const物件則需要更多解釋。

3.下面是本節的重點了(引自《C++ Primer 4》,滿滿的都是乾貨):

const 變數預設時是定義該變數的檔案的區域性變數。正如我們現在所看到的,這樣設定預設情況的原因在於允許 const 變數定義在標頭檔案中。

在 C++ 中,有些地方需要放置常量表達式。例如,列舉成員的初始化式必須是常量表達式。在以後的章節中將會看到其他需要常量表達式的
例子。

一般來說,常量表達式是編譯器在編譯時就能夠計算出結果的表示式。當 const 整型變數通過常量表達式自我初始化時,這個 const 整型變數就可能是常量表達式。而 const 變數要成為常量表達式,初始化式必須為編譯器可見。 為了能夠讓多個檔案使用相同的常量值,const 變數和它的初始化式必須是每個檔案都可見的。而要使初始化式可見,一般都把這樣的 const 變數定義在標頭檔案中。那樣的話,無論該 const 變數何時使用,編譯器都能夠看見其初始化式。

但是,C++ 中的任何變數都只能定義一次(第 2.3.5 節)。定義會分配儲存空間,而所有對該變數的使用都關聯到同一儲存空間。因為 const 物件預設為定義它的檔案的區域性變數,所以把它們的定義放在標頭檔案中是合法的。 

這種行為有一個很重要的含義:當我們在標頭檔案中定義了 const 變數後,每個包含該標頭檔案的原始檔都有了自己的 const 變數,其名稱和值都一樣。 

當該 const 變數是用常量表達式初始化時,可以保證所有的變數都有相同的值。但是在實踐中,大部分的編譯器在編譯時都會用相應的常量表達式替換這些 const 變數的任何使用。所以,在實踐中不會有任何儲存空間用於儲存用常量表達式初始化的 const 變數。 
如果 const 變數不是用常量表達式初始化,那麼它就不應該在標頭檔案中定義(若不初始化const變數,這個const變數有什麼用呢?)。相反,和其他的變數一樣,該 const 變數應該在一個原始檔中定義並初始化。應在標頭檔案中為它新增 extern 宣告,以使其能被多個檔案共享。

下面再補充點關於編譯和連結方面的知識:

編譯:將預處理生成的檔案,經過詞法、語法、語義分析及優化後編譯成若個目標模組(linux中的 .o 或win32中的 .obj)。

連結:由連結程式將編譯後形成的一組目標模組以及他們所需要的庫函式連結在一起,形成一個完整的載入模型。(win32中的可執行程式 .exe)

編譯可以理解為,將高階語言翻譯成計算機可以理解的二進位制程式碼,即機器語言。編譯器需要的是語法正確,函式與變數的宣告正確。

連結時主要連結函式和全域性變數,目標檔案之間相互連結自己所需要的函式和全域性變數,而函式可能來自其他目標檔案或庫檔案。

連結可分為:

  1. 靜態連結
  2. 載入時動態連結
  3. 執行時動態連結