深入分析C++中聲明與定義的區(qū)別
首先談下聲明與定義的區(qū)別。
聲明是將一個(gè)名稱引入程序。定義提供了一個(gè)實(shí)體在程序中的唯一描述。聲明和定義有時(shí)是同時(shí)存在的。
如int a; extern int b=1;
只有當(dāng)extern中不存在初始化式是才是聲明。其他情況既是定義也是聲明。
但是在下列情況下,聲明僅僅是聲明:
1:僅僅提供函數(shù)原型。如void func(int,int); 2: extern int a; 3:class A; 4:typedef聲明 5:在類中定義的靜態(tài)數(shù)據(jù)成員的聲明
如:
class A { public: static int a;//聲明。 };
下列情況下 ,定義僅僅是定義:
1:在類定義之外,定義并初始化一個(gè)靜態(tài)數(shù)據(jù)成員。如 A::a=0; 2:在類外定義非內(nèi)聯(lián)成員函數(shù)。聲明僅僅是將一個(gè)符號(hào)引入到一個(gè)作用域。而定義提供了一個(gè)實(shí)體在程序中的唯一描述。在一個(gè)給定的定義域中重復(fù)聲明一個(gè)符號(hào)是可以的,但是卻不能重復(fù)定義,否則將會(huì)引起編譯錯(cuò)誤。但是在類中的成員函數(shù)和靜態(tài)數(shù)據(jù)成員卻是例外,雖然在類內(nèi)它們都是聲明,但是也不能有多個(gè)。
如:
明白了聲明與定義的區(qū)別,還需要明白 內(nèi)部鏈接、外部鏈接。只有明白了它們你才會(huì)知道開頭提出的問(wèn)題。
在編譯時(shí),編譯器只檢測(cè)程序語(yǔ)法和函數(shù)、變量是否被聲明。如果函數(shù)未被聲明,編譯器會(huì)給出一個(gè)警告,但可以生成目標(biāo)文件。而在鏈接程序時(shí),鏈接器會(huì)在所有的目標(biāo)文件中找尋函數(shù)的實(shí)現(xiàn)。如果找不到,那到就會(huì)報(bào)鏈接錯(cuò)誤碼(Linker Error)。在VC下,這種錯(cuò)誤一般是:Link 2001錯(cuò)誤,意思說(shuō)是說(shuō),鏈接器未能找到函數(shù)的實(shí)現(xiàn)。
鏈接把不同編譯單元產(chǎn)生的符號(hào)聯(lián)系起來(lái)。有兩種鏈接方式:內(nèi)部鏈接和外部鏈接。
如果一個(gè)符號(hào)名對(duì)于它的編譯單元來(lái)說(shuō)是局部的,并且在鏈接時(shí)不可能與其他編譯單元中的同樣的名稱相沖突,那個(gè)這個(gè)符號(hào)就是內(nèi)部鏈接。內(nèi)部鏈接意味著對(duì)此符號(hào)的訪問(wèn)僅限于當(dāng)前的編譯單元中,對(duì)其他編譯單元都是不可見(jiàn)的。
static關(guān)鍵字作用在全局變量時(shí),表示靜態(tài)全局變量。但是作用域僅僅在當(dāng)前文件作用域內(nèi)。其他文件中即使使用extern聲明也是無(wú)法使用的。const也類似。
帶有static、const關(guān)鍵字和枚舉類型的連接是內(nèi)部的。
具有內(nèi)部鏈接的符號(hào)無(wú)法作用于當(dāng)前文件外部,要讓其影響程序的其他部分,可以將其放在.h文件中。此時(shí)在所有包含此.h文件的源文件都有自己的定義且互不影響。
類的定義具有內(nèi)部鏈接,由于它是定義,因此在同一編譯單元中不能重復(fù)出現(xiàn)。如果需要在其他編譯單元使用,類必須被定義在頭文件且被其他文件包含。僅僅在其他文件中使用class a;聲明是不行的,原因就是類的定義是內(nèi)部鏈接,不會(huì)在目標(biāo)文件導(dǎo)出符號(hào)。也就不會(huì)被其他單元解析它們的未定義符號(hào)。理解這一點(diǎn)很重要。
內(nèi)聯(lián)函數(shù)也具有內(nèi)部鏈接。
在一個(gè)多文件的程序中,如果一個(gè)符號(hào)在鏈接時(shí)可以和其他編譯單元交互,那么這個(gè)名稱就有外部鏈接。外部鏈接意味著該定義不僅僅局限在單個(gè)編譯單元中。它可以在.o文件中產(chǎn)生外部符號(hào)。可以被其他編譯單元訪問(wèn)用來(lái)解析它們未定義的符號(hào)。因此它們?cè)谡麄€(gè)程序中必須是唯一的,否則將會(huì)導(dǎo)致重復(fù)定義。
非內(nèi)聯(lián)成員函數(shù)、非內(nèi)聯(lián)函數(shù)、非靜態(tài)自由函數(shù)都具有外部鏈接。
內(nèi)聯(lián)函數(shù)之所有具有內(nèi)部鏈接,因?yàn)榫幾g器在可能的時(shí)候,會(huì)將所有 對(duì)函數(shù)的調(diào)用替換為函數(shù)體,不將任何符號(hào)寫入.o文件。
判斷一個(gè)符號(hào)是內(nèi)部鏈接還是外部鏈接的一個(gè)很好的方法就是看該符號(hào)是否被寫入.o文件。
前面說(shuō)的是定義對(duì)鏈接方式的影響,接下來(lái)說(shuō)下聲明對(duì)鏈接方式的影響。
由于聲明只對(duì)當(dāng)前編譯單元有用,因此聲明并不將任何東西寫入.o文件。
如extern int a;
int func();
這些聲明本身不會(huì)影響到.o文件的內(nèi)容。每一個(gè)都只是命名一個(gè)外部符號(hào),使當(dāng)前的編譯單元在需要的時(shí)候可以訪問(wèn)相應(yīng)的全局定義。
函數(shù)調(diào)用會(huì)導(dǎo)致一個(gè)未定義的符號(hào)被寫入到.o文件。如果a在該文件中沒(méi)有被使用,那么沒(méi)有被寫入到.o文件。而func函數(shù)有對(duì)此函數(shù)的調(diào)用。也就會(huì)將此符號(hào)寫入目標(biāo)文件。此后此.o文件與定義此符號(hào)的.o文件被連接在一起,前面未定義的符號(hào)被解析。
上述聲明有可能導(dǎo)致該符號(hào)被寫入目標(biāo)文件中。但是以下聲明并不會(huì)導(dǎo)致該符號(hào)寫入到目標(biāo)文件中。
如:
typedef int Int; Class A; struct s; union point;
它們的鏈接也是內(nèi)部的。
類聲明和類定義都是內(nèi)部鏈接。只是為當(dāng)前編譯單元所用。
靜態(tài)的類數(shù)據(jù)成員的定義具有外部鏈接。如
class A { static int a;//聲明。具有內(nèi)部鏈接。 };
靜態(tài)數(shù)據(jù)成員a僅僅是一個(gè)聲明,但是它的定義A::a=0;卻具有外部鏈接。
C++對(duì)類和枚舉類型的處理方式是不一樣的。比如:在不定義類時(shí)可以聲明一個(gè)類。但是不能未經(jīng)定義就聲明一個(gè)枚舉類型。
基于以上的分析,我們可以知道:將具有外部鏈接的定義放在頭文件中幾乎都是編程錯(cuò)誤。因?yàn)槿绻擃^文件中被多個(gè)源文件包含,那么就會(huì)存在多個(gè)定義,鏈接時(shí)就會(huì)出錯(cuò)。
在頭文件中放置內(nèi)部鏈接的定義卻是合法的,但不推薦使用的。因?yàn)轭^文件被包含到多個(gè)源文件中時(shí),不僅僅會(huì)污染全局命名空間,而且會(huì)在每個(gè)編譯單元中有自己的實(shí)體存在。大量消耗內(nèi)存空間,還會(huì)影響機(jī)器性能。
const和static修飾的全局變量?jī)H僅在當(dāng)前文件作用域內(nèi)有效。它們具有內(nèi)部鏈接屬性。
下面列出一些應(yīng)該或是不應(yīng)該寫入頭文件的定義:
//test.h #ifndef TEST_H #define TEST_H int a; //a有外部鏈接,不能在頭文件中定義。 extern int b=10;//同上。 const int c=2;//c具有內(nèi)部鏈接,可以定在頭文件中但應(yīng)該避免。 static int d=3;//同上。 static void func(){} //同上。 void func2(){} //同a。 void func3();//可以。僅僅是聲明。并不會(huì)導(dǎo)致符號(hào)名被寫入目標(biāo)文件。 class A { public: static int e;//可以,具有內(nèi)部鏈接。 int f;//可以,同上。 void func4();//聲明,內(nèi)部鏈接。同上。 }; A::e=10;//不可以在頭文件中包含具有外部鏈接的定義。符號(hào)名別寫入目標(biāo)文件。 void A:func4()//不可以,類成員函數(shù)。外部連接。 { //,...... } #endif
相信大家現(xiàn)在明白為什么只在類型聲明成員函數(shù),而不實(shí)現(xiàn)它是合法的了。也可以回答為什么類的定義可以放在.h文件中。而類的實(shí)現(xiàn)可以放在同名的cpp文件中。老師以前的介紹是說(shuō)編譯器會(huì)自動(dòng)尋找同名的cpp文件。其實(shí)是因?yàn)橛捎赾pp文件中存儲(chǔ)的是成員函數(shù)的實(shí)現(xiàn),而成員函數(shù)具有外部鏈接特性,會(huì)在目標(biāo)文件產(chǎn)生符號(hào)。在此文件中此符號(hào)是定義過(guò)的。其他調(diào)用此成員函數(shù)的目標(biāo)文件也會(huì)產(chǎn)生一個(gè)未定的符號(hào)。兩目標(biāo)文件連接后此符號(hào)就被解析。注意static數(shù)據(jù)成員應(yīng)該放在cpp文件中。而不能放在.h文件。
有內(nèi)部鏈接的定義可以定義在cpp文件中,并不會(huì)影響全局的符號(hào)空間 。但是在cpp文件作用域中要避免定義(并不禁止)沒(méi)有聲明為靜態(tài)的數(shù)據(jù)和函數(shù),因?yàn)樗鼈兙哂型獠挎溄印?/p>
如
int a; void func() { ...... }
上述定義具有外部鏈接可能會(huì)與全局命名空間的其他符號(hào)名稱存在潛在沖突。如果確實(shí)需要使用全局的變量或函數(shù)??梢詾樗鼈兗由蟬tatic關(guān)鍵字。使其作用域局限在當(dāng)前文件內(nèi),具有內(nèi)部鏈接也就不會(huì)對(duì)全局命名空間產(chǎn)生影響。因?yàn)閮?nèi)聯(lián)函數(shù)和靜態(tài)自由函數(shù)、枚舉以及const類型的數(shù)據(jù)都具有內(nèi)部鏈接,所以它們可以定義在cpp文件中,而不會(huì)影響全局命名空間。
typedef和宏定義不會(huì)將符號(hào)引入.o文件,它們也可以出現(xiàn)在cpp文件中,不會(huì)影響全局命名空間。
typedef 為一個(gè)已存在的類型創(chuàng)建一個(gè)別名。而不是創(chuàng)建一個(gè)新的類型。它不提供類型安全。如
typedef int IntA; typedef int InB;
在需要IntA的地方使用IntB是不會(huì)報(bào)錯(cuò)的。它們可以互相替換。因?yàn)榇宋覀兎Q它不提供類型安全。但是在定義函數(shù)類型時(shí)typedef經(jīng)常使用,可以使定義更清晰。
標(biāo)準(zhǔn)c庫(kù)提供一個(gè)assert宏,用以保證給定的表達(dá)式值非零。否則便會(huì)輸出錯(cuò)誤信息并終止程序執(zhí)行。只有在程序中沒(méi)有定義NDEBUG時(shí),assert才會(huì)工作。一旦定義NDEBUG ,assert語(yǔ)句將會(huì)被忽略 。注意與VC中的ASSERT相區(qū)別。ASSERT是vc提供的。當(dāng)_DEBUG被定義時(shí)才會(huì)起作用。
在vc的DEBUG模式下_DEBUG會(huì)被定義。而在RELEASE模式下NDEBUG會(huì)被定義。
好了,相信大家都會(huì)明白開頭提出的問(wèn)題了。如果有不明白的,請(qǐng)務(wù)必留言哦。如有錯(cuò)誤,也請(qǐng)不吝指正?。?/p>
以上內(nèi)容參考自《Large Scale C++ software design》。
相關(guān)文章
C++ Invalidaterect()函數(shù)作用案例詳解
這篇文章主要介紹了C++ Invalidaterect()函數(shù)作用案例詳解,本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-08-08Qt串口通信開發(fā)之QSerialPort模塊Qt串口通信接收數(shù)據(jù)不完整的解決方法
這篇文章主要介紹了Qt串口通信開發(fā)之QSerialPort模塊Qt串口通信接收數(shù)據(jù)不完整的解決方法,需要的朋友可以參考下2020-03-03C++?LeetCode1769移動(dòng)所有球到每個(gè)盒子最小操作數(shù)示例
這篇文章主要為大家介紹了C++?LeetCode1769移動(dòng)所有球到每個(gè)盒子所需最小操作數(shù)示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-12-12Java?C++題解leetcode1598文件夾操作日志搜集器
這篇文章主要為大家介紹了Java?C++題解leetcode1598文件夾操作日志搜集器示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-09-09C語(yǔ)言學(xué)習(xí)之柔性數(shù)組詳解
結(jié)構(gòu)體的最后一個(gè)元素允許是未知大小的數(shù)組,這就叫柔性數(shù)組。這篇文中主要為大家詳細(xì)介紹了C語(yǔ)言中柔性數(shù)組的相關(guān)知識(shí),需要的可以了解一下2023-03-03