1. 程式人生 > >extern宣告變數詳解

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的用法,是不是就會清晰很多了

本文為作者原創,轉載請註明出處,多謝!