以上代碼可以正常運行!
我們可能會趕到奇怪,為什么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。
另外,模板類函數應該也是編譯器特殊處理過