c++普通函數在頭文件定義報重復定義的錯誤。而class定義不會


 

 

 

 

 

 

 

以上代碼可以正常運行!

我們可能會趕到奇怪,為什么class C在header中定義了,並且在其他兩個cpp中都include了,結果,鏈接時不會報重復定義的錯誤?

 

原因:

編譯單元:一個.cc或.cpp作為一個編譯單元.生成.o,簡單來說一個cpp文件就是一個編譯單元。

類的聲明:

class A;  //類的聲明

類的聲明和普通變量聲明一樣,不產生目標代碼,可以在同一、以及多個編譯單元重復聲明。

類的定義:

class A {

……

};  //類的定義

類的定義不產生目標代碼,只是告訴編譯器,類的數據格式是如何的,實例化后對象該占多大空間,它和普通變量的聲明唯一的區別是不能在同一編譯單元內出現多次。同一編譯單元內,類重復定義,會編譯時報錯,因為編譯器不知道在該編譯單元中,實例化對象的時候該調用哪一個定義的類?但是在不同編譯單元內,類可以重復定義,因為類的定義未產生實際代碼。因為它具有內部鏈接,(所謂內部鏈接指的是該名稱對於所在編譯單元是局部的,在鏈接時不會與其他編譯單元中同樣 的名稱產生命名沖突),所以類定義在頭文件中在鏈接時候不會沖突。

 

函數頭文件中定義多次include就報重復定義問題

先來做一個實驗,你在一個頭文件中定義一個類,然后把內中的一個函數的實現寫在這個頭文件當中。

//A_test.h

#ifndefine _A_TEST_

#define _A_TEST_

class A

{

void test();

};

void A::test()

{

}

#endif

//A_test.cpp

#include A_test.h

//B_test.cpp

#include A_test.h

//C_test.cpp

#include A_test.h

然后在兩個B.cpp,C.cpp包含這個頭文件,請問能否編譯通過。答案是不行的,會報錯,所你重復定義函數test()

但是如果你把這個函數定義到class A里面,然后編譯就不報錯了。

另外如果你把這個函數定義在這個頭文件類的外面,但是前面加上inline,也可以通過編譯。

除此之外,如果你把這個函數的實現寫在另外一個包含這個頭文件的cpp文件中,也可以通過編譯,這也是最規范的寫法。

 

與此對應,如果你在這個頭文件中聲明了一個函數,如果直接就在這個頭文件中實現,那么除非你把它定義為inline 函數,不然會發生二次定義的錯誤,當然把一個實現放到一個對應的cpp中,自然不會報錯。

在常規理解中,.h只能寫聲明,cpp寫實現。這是很規范 。但是為什么有些庫的頭文件也把一些類的實現寫出來了,有些函數也直接定義在那個頭文件中,在很多.cpp中也不斷的被包含呢,結果並不報錯。舉個例子來時,complex.h這個頭文件在很多數值.cpp中間都要包含,如果這個頭文件中有些函數寫了實現,就會報錯。

那么究竟如何來理解這種現象呢:

有3點:

1是編譯器的唯一命名規則,就是inline函數,class和模板類函數被多次包含的情況下,在編譯的時候,編譯器會自動把他們認為是同一個函數,不會發生二次定義的問題。前提是他們一模一樣。

2是編譯器會把class里面定義的函數當做inline函數,所以直接在類里面實現函數的定義沒有關系。由上面的說明,他不會發生二次定義的問題。

3一般函數的聲明和實現分開,在編譯的時候,聲明可以無數次,但是定義只能一份,只會生成一份函數的.obj,所以有函數調用的地方,編譯器必須在調用的地方先保持現場,然后在花點時間去調用函數,然后回來,恢復現場。所以函數在頭文件中實現,如果被包含二次。函數的實現就被編譯了2次,如果單獨寫在一個.cpp中間,自然就編譯成為一份.obj,不會產生二義性的問題。

3.inline函數在編譯的時候直接復制在有該函數的地方,在空間上有消耗,但是在省去了時間上的消耗,是一個模板函數。也就是說在有這些函數的地方都不需要去調用函數,也就不涉及有2種函數可以調用產生的二義性問題。

 

因此,complex.h這個頭文件要被反復包含,要么把所有函數都放到類里面定義,要么全面寫在外面,前面加上inline。

另外,模板類函數應該也是編譯器特殊處理過


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM