有關這兩者的區別和聯絡,之前其實一直都非常的模糊,特別是extern關鍵字。這次讀C++ Primer,在第二章正好讀到,於是好好理解了一次,而且做了一些代碼測試。結論是這樣的:
1. definition只能用於變數,也就是定義一個變數,此時,變數的記憶體空間會被分配。諸如int i, int i = 10這樣的都是definition,因為i變數會被分配記憶體。
2. declaration可以用於變數或類型(比如聲明一個struct,但是不定義變數),如果用於變數,該變數不會被分配記憶體,而且前面必須加上 extern(表示這個變數的definition不是在這裡,而且在其他地方,所以是extern);如果是類型,那就沒什麼好說的了,本來類型就不需 要分配記憶體,只有變數才需要記憶體,如果在類型的聲明的前面加上extern,也是可以的,只不過這樣做沒有任何意義。注意:如果這樣寫:extern int i = 10; ,那麼,這也是definition,因為給i賦值了,extern就和沒加一樣。
3. 無論整個Program有多少個源檔案,代碼量有多大,一個變數只能被definition一次,但是declaration不限次數。
註:說到這裡,有關function方法的定義和聲明,也是類似的。一般來說,如果沒有寫出方法中的代碼,那麼,這就是declaration, 如果寫出了代碼,那麼就是對方法的definition了。所以,一般在標頭檔中寫一個方法的declaration,比如void print_sth();,然後在源檔案中寫出方法的實現即可。只不過在這裡,void print_sth();和extern void print_sth();效果是一樣的,加不加extern都一樣。
4. 綜合以上三點,如果我們在一個a.cc中定義了一個全域變數int i,那麼在b.cc中如果想使用這個i,那麼,必須聲明成extern int i才可以,如果也寫int i,那就是重複definition。說 到這裡,我在學習的時候就有一個疑問了:如果說,我們把int i這句代碼寫在一個名為common.h的標頭檔中,然後前面加上條件編譯,最後這個標頭檔被a.cc和b.cc都include,這樣a.cc和 b.cc中不就不需要extern int i這樣的代碼不就可以使用i了嗎?事實上,經過實驗,這樣的想法是極端錯誤的。
測試代碼可以這樣構建:common.h
-
Code: Select all
-
#ifndef _COMMON_H
#define _COMMON_Hint i = 10;
void print_sth();
#endif
a.cc這樣:
-
Code: Select all
-
#include <iostream>
#include "common.h"
using namespace std;
int main()
{
cout << "i is: " << i << endl;
print_sth();
return 0;
}
b.cc這樣:
-
Code: Select all
-
#include <iostream>
#include "common.h"
using namespace std;
void print_sth()
{
cout << "i in b.cc is: " << i << endl;
}
然後編譯: g++ -o test a.cc b.cc,出現錯誤:
/tmp/ccDYjlJE.o(.data+0x0): multiple definition of `i'
/tmp/ccNz3Dvy.o(.data+0x0): first defined here
collect2: ld returned 1 exit status
為什麼會出現這樣的錯誤呢?這裡面有一個概念沒有搞清楚:
A. 以為使用條件編譯可以讓 int i = 10; 這句代碼只執行一次。事實上,我們通過這個命令列:g++ -E a.cc b.cc >& output ,這個命令列是讓g++在做完預先處理之後就停止,然後將預先處理的結果列印到螢幕,然後我們開啟output檔案,在裡面,我們會發現int i = 10;這句代碼出現了兩次。所以,結論就是:條件編譯只在一個源檔案中生效,換句話說,通過使用條件編譯,我們可以保證在一個源檔案中,不會產生多餘的 include,但是,在多個源檔案中,條件編譯是無效的。注意:一個小知識點,g++的-E option不要和-o option連用,否則會導致輸出的預先處理後的代碼不完整。
所以,上面的代碼是錯誤的。根據上面的例子,我們又可以總結一些東西了,接著上面的第四點:
5. 在標頭檔中(.h檔案中),一般我們唯寫declaration,所以,在標頭檔中,我們一般做的是:定義類型(各種struct,typedef等), 定義函數(前面說過了,沒有代碼的函式宣告是declaration)。如果要在標頭檔中定義變數,那麼,必須保證以下兩點中的一點:
a)這個標頭檔只會被一個源檔案include
b)將這個變數定義變成聲明,也就是前面加上extern,然後在一個且只能有一個源檔案中對該變數做definition
如果不是這樣,那就必然出現multiple definition。任何源檔案想要引用其他源檔案中或標頭檔中定義的變數,必須要使用extern,表示該變數不是在當前源檔案中definition的。當然,前提條件是這個變數是全域變數(廢話 )。
所以,前面給出的那個測試例子,可以這樣修改就OK了。在common.h中:
-
Code: Select all
-
#ifndef _COMMON_H
#define _COMMON_Hvoid print_sth();
#endif
a.cc:
-
Code: Select all
-
#include <iostream>
#include "common.h"
using namespace std;int i = 10;
int main()
{
cout << "i is: " << i << endl;
print_sth();
return 0;
}
b.cc:
-
Code: Select all
-
#include <iostream>
#include "common.h"
using namespace std;extern int i;
void print_sth()
{
i = 20;
cout << "i in b.cc is: " << i << endl;
}
這樣就OK了,程式編譯通過,輸出是:
i is: 10
i in b.cc is: 20
或者直接在common.h中,將 int i = 10; 改成extern int i;然後在a.cc中做definition:int i = 10; ,這樣也是可以的。