extern宣告變數詳解
extern宣告變數無外乎如下兩種:
1、宣告全域性變數
2、宣告函式
今天我們只談extern,什麼const、static之類等等與之相關或不相關的一律忽略,下面就分別對以上兩種情況一一講解
宣告和定義
既然提到extern宣告變數,那我們就必須搞清楚宣告和定義的區別。
這裡我們將普通資料變數和函式統稱變數。從記憶體分配角度來說,宣告和定義的區別在於宣告一個變數不會分配記憶體,而定義一個變數會分配記憶體。一個變數可以被宣告多次,但是隻能被定義一次。
基於以上前提,我們可以把宣告和定義類比為指標和記憶體的關係。我們知道,指標其實就是指向記憶體的一個符號,變數的定義就好比一塊記憶體區域,而宣告就好比它的指標,可以有多個指標指向同一個記憶體區域,而一個指標只能指向一個記憶體區域,這樣就很好理解為什麼變數只能被定義一次,如果被定義多次,那就會分配多個記憶體,這樣你通過變數的宣告到底去找哪塊記憶體區域呢,這會是個問題。
對於資料來說,宣告和定義往往是同時存在的,比如下面的一行語句
int data;
這樣既聲明瞭data
同時也定義了data
,怎樣做到只宣告而不定義呢,用extern就可以了
extern int data;
對於函式來說,宣告和定義就很容易區分了,一般我們會將宣告放在標頭檔案而將定義放在原始檔裡
void hello();
這是一個函式的宣告,而
void hello()
{
printf("hello world!\n");
}
這是一個函式的定義。當然,函式的宣告和定義也可以同時發生,如果我們沒有標頭檔案而只有原始檔,並且在原始檔裡並沒有void hello();
hello()
,你呼叫的程式碼必須在函式定義之後。
其實上面的要點只在於一句話:使用變數之前必須宣告,宣告可以有多次,而定義只能有一次。記住這句話,後面的就都很容易理解了。
extern宣告全域性變數
我們先來看如下例子,現有三個檔案:test.h, test.cpp, main.cpp,其中main.cpp和test.cpp需要共享一個變數g_name,三個檔案的內容如下
/* test.h */ #ifndef _TEST_H_ #define _TEST_H_ #include <string> std::string g_name; void hello(); #endif /* test.cpp */ #include <stdio.h> #include "test.h" void hello() { printf("hello %s!\n", g_name.c_str()); } /* main.cpp */ #include "test.h" std::string g_name; int main() { g_name = "Handy"; hello(); return 0; }
三者關係為,test.cpp包含了test.h,main.cpp也包含了test.h,這裡的包含其實就是include。我們執行編譯命令
g++ main.cpp test.cpp
編譯報錯redefinition of 'g_name'
,說的是g_name
被重定義了
我們看一下g_name
出現的地方,一個是在test.h裡,一個是在main.cpp裡,兩條語句都是std::string g_name
,前面我們已經說過,這樣的方式既宣告也定義了變數,那g_name是如何被重定義的呢,首先我們需要理解include的含義,我們可以將include一個頭檔案理解為在該行展開標頭檔案裡的所有程式碼,由於main.cpp包含了test.h,我們在那一行將test.h的內容展開,就會發現main.cpp裡有兩句std::string
g_name;
所以在main.cpp裡,g_name被定義了兩次。
由於我們可以將include標頭檔案理解為展開程式碼,所以編譯的時候其實不需要指定標頭檔案,只需要原始檔就夠了。需要注意的是,重定義並不是指在同一個原檔案裡定義多次,而是指在整個程式碼空間裡,比如上面的例子是就是指在test.cpp和main.cpp裡,其實上面的例子裡g_name是被重定義了三次,其中test.cpp裡一次,main.cpp裡兩次。
那上面重定義的問題怎麼解決呢,很簡答,將test.h裡的std::string g_name;
改為extern std::string g_name;
就可以了,由於extern語句只宣告變數而不定義變數,因此test.cpp和main.cpp展開標頭檔案後,也只是將g_name聲明瞭兩次,而真正的定義還是在main.cpp裡
extern宣告函式
還是上面的例子,我們怎麼在main.cpp裡不包含標頭檔案就可以呼叫hello函式呢,既然今天的主題是extern,不用提醒也知道,使用extern就可以了,程式碼如下
/* test.cpp */
#include <string>
#include <stdio.h>
// 宣告g_name
extern std::string g_name;
// 宣告和定義void hello()
void hello()
{
printf("hello %s!\n", g_name.c_str());
}
/* main.cpp */
#include <string>
// 宣告和定義g_name
std::string g_name;
// 宣告void hello()
extern void hello();
int main()
{
g_name = "Handy"
hello();
return 0;
}
注意這裡用到extern宣告變數和函式兩種場景,我分別在語句後面做了註釋。編譯命令如下
g++ main.cpp test.cpp
這裡我們並沒有用到標頭檔案,但是依然可以在不同檔案間共享變數和函式,這一切都是extern的功勞!
總結
要了解extern主要搞清以下幾個概念:
1、宣告和定義的區別。全域性程式碼空間裡,變數可以有多個宣告,但只能有一個定義
2、include標頭檔案等同於展開標頭檔案裡的程式碼
瞭解了以上兩點,再來分析extern的用法,是不是就會清晰很多了
本文為作者原創,轉載請註明出處,多謝!