一文看懂const extern static如何定義?究竟放在源文件還是頭文件?

編譯單元(模塊)
在VC或VS上編寫完代碼,點擊編譯按鈕準備生成exe文件時,編譯器做了兩步工作:
第一步,將每個.cpp(.c)和相應的.h文件編譯成obj文件;
第二步,將工程中所有的obj文件進行LINK,生成最終.exe文件。

那麼,錯誤可能在兩個地方產生:

一個是編譯時的錯誤,這個主要是語法錯誤;
一個是鏈接時的錯誤,主要是重複定義變量等。

編譯單元指在編譯階段生成的每個obj文件。
一個obj文件就是一個編譯單元。
一個.cpp(.c)和它相應的.h文件共同組成了一個編譯單元。
一個工程由很多編譯單元組成,每個obj文件裏包含了變量存儲的相對地址等。

聲明與定義
函數或變量在聲明時,並沒有給它實際的物理內存空間,它有時候可保證你的程序編譯通過;
函數或變量在定義時,它就在內存中有了實際的物理空間。

如果想要聲明一個變量而非定義它,就在變量名前邊添加關鍵字extern,而且不要用=顯示初始化變量。同時也就告訴編譯器該變量是在其他文件中定義的,可以從其他文件中找到這個變量。

1 extern

extern關鍵字用來聲明變量或者函數是一個外部變量或者外部函數,也就是說告訴編譯器該變量是在其他文件中定義的,編譯的時候不要報錯,在鏈接的時候按照字符串尋址可以找到這個變量或者函數。

在其他文件中使用某個文件中定義的變量。

如果A.h中定義了全局變量比如int a;,那麼在其他文件中的函數調用變量a的時候需要在對應頭文件或者定義文件中(保證在使用這個變量前)使用extern int a;來聲明這個變量,但是這樣做有一個弊端,首先如果A.h中集中定義了大量的全局變量供其他文件使用,那麼其他的調用文件中會重複的出現大量的extern ***語句,第二,如果其他文件直接引用A.h,那麼會造成全局變量的重複定義,編譯不過,等等。爲了避免上面的種種問題,總結了下extern的使用規範,內容如下:

1、在定義文件中定義全局變量, 比如A.cpp中定義全局變量 int aa;
2、在對應的頭文件A.h中聲明外部變量 extern int aa;
3、在使用aa變量的文件B.cpp中包含A.h;

在編譯階段,B編譯單元雖然找不到該函數或變量,但它不會報錯,它會在鏈接時從A編譯單元生成的目標代碼中找到此函數。

2 const

2.1 默認狀態下,const對象僅在文件內有效。

參考(C++ primer P54頁)
舉例: const int bb = 22;
因爲編譯器將在編譯過程中把用到該變量的地方都替換爲初始值。也就是說,編譯器會找到所有的bb替換爲22。假如多個文件中都有bb,爲了執行上述替換,必須訪問到其初始值,那麼每個文件中必須得有該變量定義,就會造成對同一變量重複定義。因此const對象默認在文件內有效。當多個文件中出現同名const時,其實等同於在不同文件中分別定義了獨立的變量。

《C++primer》中,講到頭文件中不可以包含定義,有三個例外:
類,常量表達式初始化的const對象,inline。

因此在C++的頭文件定義const變量,多個源文件包含該頭文件,這種是可以編譯通過的。但是導致也導致了重複定義變量的問題。

2.2 const變量定義和聲明都加extern關鍵字

參考《C++ primer》 P54頁

源文件用extern const int cc = 33定義,其他用到該變量cc的文件用extern const int cc聲明。這種方法可以在多個文件間共享const對象。

注意:
如果源文件定義時不加extern,由於2.1中已經說明了const對象僅在文件內有效,因此只有其他文件加extern聲明是不起作用的。所以在源文件定義時也必須加extern。

這種用法在Qt中能夠運行,但是會提示

warning: no previous extern declaration for non-static variable ‘cc’

該警告意爲:在此之前沒有對於非靜態變量‘cc’的外部聲明。可能Qt認爲該寫法不夠規範。

綜上所述,最佳的使用方法如下。如果是const對象時,在定義和聲明時相應加上const。
1、源文件A.cpp 中定義,比如:A.cpp中定義 int aa;
2、頭文件A.h文件聲明, 比如:extern int aa;
3、在使用aa變量的文件中包含A.h;

//variable.h如下:
#include <iostream>

void showVariable();
int getValue();

extern const int aa;
const int bb = 22;
//const int bb = getValue();
#include "variable.h"
#include <random>

const int aa = 11;
extern const int cc = 33;

//const int aa = getValue();
//extern const int cc = getValue();

void showVariable()
{
    std::cout << ".cpp:  aa=" << aa <<"  " << &aa << std::endl;
    std::cout << ".cpp:  bb=" << bb <<"  " << &bb << std::endl;
    std::cout << ".cpp:  cc=" << cc <<"  " << &cc << std::endl;
}

int getValue()
{
    return rand();
}

#include <iostream>
#include "variable.h"

void main()
{
    extern const int cc;
    std::cout << "main:  aa=" << aa <<"  " << &aa << std::endl;
    std::cout << "main:  bb=" << bb <<"  " << &bb << std::endl;
    std::cout << "main:  cc=" << cc <<"  " << &cc << std::endl << endl;
    showVariable();
    return 0;
}

上述代碼執行結果如下:
打印結果
上述可以看到對於aa和cc地址相同,爲同一對象。bb地址不同,表明重複創建了對象。

如果把代碼中的//去掉,即使用getValue()爲aa、bb、cc進行定義。結果如下。
《C++ primer》第54頁中寫到多個文件共享const變量,並且不希望多個文件生成獨立的變量,並且該const變量初始值不是一個常量表達式時,需要使用頭文件和源文件都加extern的寫法。但是下面例子中,明顯各變量值不是常量表達式,但是也能正常運行。
在這裏插入圖片描述

3 靜態全局變量(static)

注意使用static修飾變量,就不能使用extern來修飾,即static和extern不可同時出現。
static修飾的全局變量的聲明與定義同時進行,即當你在頭文件中使用static聲明瞭全局變量,同時它也被定義了。

static修飾的全局變量的作用域只能是本身的編譯單元。如果有多個文件包含了定義static變量的頭文件,在其他編譯單元使用它時,只是簡單的把其值複製給了其他編譯單元,其他編譯單元會另外開個內存保存它,在其他編譯單元對它的修改並不影響本身在定義時的值。(與頭文件中定義const類似)

即在其他編譯單元A使用它時,它所在的物理地址,和其他編譯單元B使用它時,它所在的物理地址不一樣,A和B對它所做的修改都不能傳遞給對方。

多個地方引用靜態全局變量所在的頭文件,不會出現重定義錯誤,因爲在每個編譯單元都對它開闢了額外的空間進行存儲。
一般定義static 全局變量時,都把它放在.cpp文件中而不是.h文件中,這樣就不會給其他包含此頭文件的編譯單元裏邊重複生成變量。

在標準C++中引入命名空間之前,程序必須將名字聲明爲static,使他們用於當前編譯單元。C++文件中靜態聲明的使用從C語言繼承而來,在C語言中,聲明爲static 的局部實體在聲明它的文件之外不可見。

C++不贊成文件靜態聲明。C++標準取消了在文件中聲明靜態聲明的做法。應該避免static靜態聲明,而在源文件中使用未命名的命名空間,在未命名的命名空間中定義變量。詳情請見《C++ primer》 605頁。

未命名的命名空間僅在文件內部有效,其作用範圍不會橫跨多個不同的文件。

4 類的靜態變量

4.1 定義類的靜態變量

a. 關鍵字static用於說明一個類的成員變量時,該成員爲靜態成員。靜態成員提供了一個同類對象的共享機制;
b. 把一個類的成員說明爲static時,該類無論創建多少個對象,這些對象都共享這個static成員;
c. 靜態成員變量屬於類,不屬於對象;
d. 定義靜態成員變量的時候,是在類的外部(cpp文件中)。
e. 訪問靜態成員變量的兩種方法:
對象使用.靜態變量
類名::靜態變量

4.2 靜態成員變量一定要定義(分配內存)

靜態成員變量在類中僅僅是聲明,沒有定義,所以要在類的外面定義(h和cpp分開始要定義在類的cpp中),實際上是給靜態成員變量分配內存。

錯誤:undefined reference to `Name::n’
//以下是staticmember.h
class Name
{
public:
    Name(int i);
    static int n;
    static constexpr double num = 100.5;
};

在main文件使用Name::n時會出現如上述錯誤。

類中的靜態變量是不分配存儲空間的,只是向編譯器作出聲明。
對於類中的普通成員變量,他是在實例化時分配內存的。
對於類中的靜態成員變量,他在是在定義時就分配空間。在類中僅僅是聲明。
之所以會報錯就是我們還沒有定義,沒有爲他分配空間。
應該在類的cpp文件中進行如下定義:

#include "staticmember.h"

int Name::n = 5;

錯誤:mutiple definition of `Name::n’

如果把int Name::n = 5寫在頭文件中,由於重複包含了該頭文件,因此出現上述錯誤。

4.3 靜態變量的類內初始化

上述講了類的靜態成員的定義。實際上,也可以進行靜態變量的類內初始化。若一定要在類內初始化靜態成員,那麼就必須滿足如下條件纔行:
靜態成員必須爲字面值常量類型的constexpr。

所謂的constexpr,就是常量表達式,指值不會改變且在編譯過程中就能得到計算結果的表達式。比如字面值,或者用常量表達式初始化的const對象也是常量表達式。爲了幫助用戶檢查自己聲明/定義的變量的值是否爲一個常量表達式,C++11新規定,允許將變量聲明爲constexpr類型,以便由編譯器來進行驗證變量是否爲常量表達式。參考《C++ primer》P58、P270。

如上述的static constexpr double num = 100.5就是進行了類內初始化。
《C++ primer》第270頁裏邊,寫到給靜態成員提供const 整數類型的類內初始值。從上述例子可以看出來,不必非得是整數值。

參考鏈接:
https://blog.csdn.net/candyliuxj/article/details/7853938
https://blog.csdn.net/QTVLC/article/details/82016715

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