許多人在學習 C++ 時,似乎對宣告和定義的區別及其目的感到困惑。特別是關於函式或類。為了理解所有這些看似迂腐和重複的東西的必要性,讓我們回顧一下構建過程。
首先,每個 .cpp 檔案都會被獨立編譯。在此過程中,任何 #include 指令都會首先將包含的檔案插入到 .cpp 中。然後,編譯器會從頭到尾處理 .cpp 檔案,生成機器語言程式碼,並將其輸出到 .obj 檔案。為了本次討論的目的,需要記住的是,這是一個從頭到尾的單次遍歷過程,並且在任何時候,它都完全不知道其他 .cpp 檔案中的任何內容。編譯器甚至在處理當前 .cpp 檔案的任何一點之上,都不知道檔案中的任何內容。編譯器生成的程式碼並不能完全解析記憶體地址和函式呼叫。那是連結器的任務。當編譯器在 .cpp 檔案中遇到函式呼叫時,它通常不知道該函式做什麼,也不知道它是如何做的。編譯器唯一需要知道的是傳遞了哪些引數以及返回值的型別。函式呼叫的 .obj 程式碼可以生成,然後連結器將在稍後確定確切的跳轉位置。
現在,記住所有這些,我們可以討論真正的主題。
宣告: 宣告將一個名稱引入一個作用域。總的來說,作用域可以是整個 .cpp 檔案,也可以是程式碼中由 {} 界定的任何內容,無論是函式、函式內的迴圈,甚至是在函式內任意放置的 {} 塊。引入的名稱在其宣告點到該作用域的結束點內都是可見的。宣告只是告訴編譯器如何使用某個實體,它實際上並沒有建立任何東西。
1 2
|
extern int y; // declares y, but does not define it. y is defined elsewhere,
// but the program can now use it since it knows what it is (an integer)
|
原型: 原型只是函式宣告的另一種說法。
|
double someFunction( double, int );
|
定義: 定義完全指定一個實體。定義是實體在記憶體中實際建立的地方。所有定義也是宣告,但並非所有宣告都是定義。
|
Int x; // declares and defines x. it is a definition because it creates the variable allocating memory
|
實現: 實現是函式定義的另一種說法。也就是說,實現是函式本身的實際程式碼。
1 2 3 4
|
double someFunction( double x, int y )
{
return x * y;
}
|
任何實體都可以被多次宣告,但只能被定義一次。
為什麼這一切對您很重要
1 2 3 4 5 6 7 8 9 10 11 12 13
|
int main()
{
double a = 3.5;
int b = 2;
double c;
c = someFunction( a, b );
}
double someFunction( double x, int y )
{
return x * y;
}
|
回想一下,編譯是一個單次的自上而下的過程。因為這個原因,上面的程式碼無法工作,因為編譯器在到達 "c = someFunction( a, b );" 行時,並不知道如何處理 someFunction()。解決方案是在此之前宣告 someFunction()。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
double someFunction( double, int );
int main()
{
double a = 3.5;
int b = 2;
double c;
c = someFunction( a, b );
}
double someFunction( double x, int y )
{
return x * y;
}
|
1 2 3 4 5 6 7 8 9 10 11 12 13
|
double someFunction( double x, int y )
{
return x * y;
}
int main()
{
double a = 3.5;
int b = 2;
double c;
c = someFunction( a, b );
}
|
由於定義也是宣告,所以以上兩種方法都可以。第一種使用了原型,而第二種只是將 someFunction() 的定義移到了 main() 中呼叫它的前面。作為一般規則,使用原型是首選方法,因為它允許更好地組織程式碼(您不必從底部開始向上閱讀),並且可以防止在程式碼重新組織時引入錯誤。此外,請考慮一組遞迴函式,其中每個函式都呼叫其他函式。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
|
funcA()
{
if( condition )
funcB();
return;
}
funcB()
{
if( condition )
funcC();
return;
}
funcC()
{
if( condition )
funcA();
return;
}
|
這對於新手來說可能有點奇怪,但在某些型別的演算法中,如排序和語法解析,這是相當常見的。需要注意的是,沒有任何方法可以在沒有至少一個原型的情況下組織這些函式。所以,請給自己一個方便,養成使用原型的習慣。
關於 .h 和 .cpp 組織的一個說明
以上所有內容都直接與您應如何將程式碼組織到標頭檔案和原始檔相關。大多數學習 C++ 的學生在學習的第一個月左右可能不會編寫足夠大的程式來需要多個原始檔。但當他們需要時,我看到很多人對哪些內容應該放在哪個檔案中感到困惑。經驗法則是這樣的:標頭檔案應該包含
宣告,原始檔應該包含
定義。原因現在應該很清楚了。一個實體可以被多次宣告,但只能被定義一次。如果標頭檔案包含定義,您可能會導致同一個實體被多次定義。這很可能會在連結過程中導致問題,因為連結器試圖解析外部地址時會發現多個同名的實體。
有關標頭檔案組織的更深入討論,請參閱
Disch 的這篇文章。