宣告是將乙個名稱引入程式。定義提供了乙個實體在程式中的唯一描述。宣告和定義有時是同時存在的。
如inta;
externintb=1;
只有當extern
中不存在初始化式是才是宣告。其他情況既是定義也是宣告。
但是在下列情況下,宣告僅僅是宣告:
1:僅僅提供函式原型。如
voidfunc(int,int);
2:externinta; 3:
classa;4:
typedef
宣告5:在類中定義的靜態資料成員的宣告如:
class a
;
下列情況下,定義僅僅是定義:
1:在類定義之外,定義並初始化乙個靜態資料成員。如
a::a=0;
2:在類外定義非內聯成員函式。
宣告僅僅是將乙個符號引入到乙個作用域。而定義提供了乙個實體在程式中的唯一描述。在乙個給定的定義域中重複宣告乙個符號是可以的,
但是卻不能重複定義
,否則將會引起編譯錯誤。但是在類中的成員函式和靜態資料成員卻是例外,雖然在類內它們都是宣告,但是也不能有多個。如:
class a
;
明白了宣告與定義的區別,還需要明白 內部鏈結、外部鏈結。只有明白了它們你才會知道開頭提出的問題。
在編譯時,編譯器只檢測程式語法和函式、變數是否被宣告。如果函式未被宣告,編譯器會給出乙個警告,但可以生成目標檔案。而在鏈結程式時,鏈結器會在所有的目標檔案中找尋函式的實現。如果找不到,那到就會報鏈結錯誤碼(linkererror
)。在vc
下,這種錯誤一般是:
link2001
錯誤,意思說是說,鏈結器未能找到函式的實現。
如果乙個符號名對於它的編譯單元來說是區域性的,並且在鏈結時不可能與其他編譯單元中的同樣的名稱相衝突,那個這個符號就是內部鏈結。內部鏈結意味著對此符號的訪問僅限於當前的編譯單元中,對其他編譯單元都是不可見的。
static關鍵字作用在全域性變數時,表示靜態全域性變數。但是作用域僅僅在當前檔案作用域內。其他檔案中即使使用
extern
宣告也是無法使用的。
const
也類似。
帶有static
、const
關鍵字和列舉型別的連線是內部的。
具有內部鏈結的符號無法作用於當前檔案外部,要讓其影響程式的其他部分,可以將其放在.h
檔案中。此時在所有包含此
.h檔案的原始檔都有自己的定義且互不影響。
類的定義具有內部鏈結,由於它是定義,因此在同一編譯單元中不能重複出現。如果需要在其他編譯單元使用,類必須被定義在標頭檔案且被其他檔案包含。僅僅在其他檔案中使用classa;
宣告是不行的,原因就是類的定義是內部鏈結,不會在目標檔案匯出符號。也就不會被其他單元解析它們的未定義符號。理解這一點很重要。
內聯函式也具有內部鏈結。
在乙個多檔案的程式中,如果乙個符號在鏈結時可以和其他編譯單元互動,那麼這個名稱就有外部鏈結。外部鏈結意味著該定義不僅僅侷限在單個編譯單元中。它可以在.o
檔案中產生外部符號。可以被其他編譯單元訪問用來解析它們未定義的符號。因此它們在整個程式中必須是唯一的,否則將會導致重複定義。
非內聯成員函式、非內聯函式、非靜態自由函式都具有外部鏈結。
內聯函式之所有具有內部鏈結,因為編譯器在可能的時候,會將所有對函式的呼叫替換為函式體,不將任何符號寫入.o
檔案。判斷乙個符號是內部鏈結還是外部鏈結的乙個很好的方法就是看該符號是否被寫入.o
檔案。前面說的是定義對鏈結方式的影響,接下來說下宣告對鏈結方式的影響。
由於宣告只對當前編譯單元有用,因此宣告並不將任何東西寫入.o
檔案。如externinta;
intfunc();
這些宣告本身不會影響到.o
檔案的內容。每乙個都只是命名乙個外部符號,使當前的編譯單元在需要的時候可以訪問相應的全域性定義。
函式呼叫會導致乙個未定義的符號被寫入到.o
檔案。如果
a在該檔案中沒有被使用,那麼沒有被寫入到
.o檔案。而
func
函式有對此函式的呼叫。也就會將此符號寫入目標檔案。此後此
.o檔案與定義此符號的
.o檔案被連線在一起,前面未定義的符號被解析。
上述宣告有可能導致該符號被寫入目標檔案中。但是以下宣告並不會導致該符號寫入到目標檔案中。 如:
typedef int int;class a;
struct s;
union point;
它們的鏈結也是內部的。
類宣告和類定義都是內部鏈結。只是為當前編譯單元所用。
靜態的類資料成員的定義具有外部鏈結。如
class a
;
靜態資料成員a
僅僅是乙個宣告,但是它的定義
a::a=0;
卻具有外部鏈結。
c++對類和列舉型別的處理方式是不一樣的。比如:在不定義類時可以宣告乙個類。但是不能未經定義就宣告乙個列舉型別。
基於以上的分析,我們可以知道:將具有外部鏈結的定義放在標頭檔案中幾乎都是程式設計錯誤。因為如果該標頭檔案中被多個原始檔包含,那麼就會存在多個定義,鏈結時就會出錯。
在標頭檔案中放置內部鏈結的定義卻是合法的,但不推薦使用的。因為標頭檔案被包含到多個原始檔中時,不僅僅會汙染全域性命名空間,而且會在每個編譯單元中有自己的實體存在。大量消耗記憶體空間,還會影響機器效能。
const和
static
修飾的全域性變數僅僅在當前檔案作用域內有效。它們具有內部鏈結屬性。
下面列出一些應該或是不應該寫入標頭檔案的定義:
#ifndef test_h
#define test_h
int a; //a有外部鏈結,不能在標頭檔案中定義。
extern int b=10;//同上。
const int c=2;//c具有內部鏈結,可以定在標頭檔案中但應該避免。
static int d=3;//同上。
static void func(){} //同上。
void func2(){} //同a。
void func3();//可以。僅僅是宣告。並不會導致符號名被寫入目標檔案。
class a
;a::e=10;//不可以在標頭檔案中包含具有外部鏈結的定義。符號名別寫入目標檔案。
void a:func4()//不可以,類成員函式。外部連線。
#endif
相信大家現在明白為什麼只在型別宣告成員函式,而不實現它是合法的了。也可以回答為什麼類的定義可以放在.h
檔案中。而類的實現可以放在同名的
cpp檔案中。老師以前的介紹是說編譯器會自動尋找同名的
cpp檔案。其實是因為由於
cpp檔案中儲存的是成員函式的實現,而成員函式具有外部鏈結特性,會在目標檔案產生符號。在此檔案中此符號是定義過的。其他呼叫此成員函式的目標檔案也會產生乙個未定的符號。兩目標檔案連線後此符號就被解析。注意
static
資料成員應該放在
cpp檔案中。而不能放在
.h檔案。
有內部鏈結的定義可以定義在cpp
檔案中,並不會影響全域性的符號空間。但是在
cpp檔案作用域中要避免定義(並不禁止)沒有宣告為靜態的資料和函式,因為它們具有外部鏈結。 如
int a;
void func()
上述定義具有外部鏈結可能會與全域性命名空間的其他符號名稱存在潛在衝突。如果確實需要使用全域性的變數或函式。可以為它們加上static關鍵字。使其作用域侷限在當前檔案內,具有內部鏈結也就不會對全域性命名空間產生影響。因為內聯函式和靜態自由函式、列舉以及const
型別的資料都具有內部鏈結,所以它們可以定義在
cpp檔案中,而不會影響全域性命名空間。
typedef和巨集定義不會將符號引入
.o檔案,它們也可以出現在
cpp檔案中,不會影響全域性命名空間。
typedef為乙個已存在的型別建立乙個別名。而不是建立乙個新的型別。它不提供型別安全。如
typedef int inta;
typedef int inb;
在需要inta
的地方使用
intb
是不會報錯的。它們可以互相替換。因為此我們稱它不提供型別安全。但是在定義函式型別時
typedef
經常使用,可以使定義更清晰。
標準c庫提供乙個
assert
巨集,用以保證給定的表示式值非零。否則便會輸出錯誤資訊並終止程式執行。只有在程式中沒有定義
ndebug
時,assert
才會工作。一旦定義
ndebug
,assert
語句將會被忽略。注意與
vc中的
assert
相區別。
assert是vc
提供的。當
_debug
被定義時才會起作用。
在vc的
debug
模式下_debug
會被定義。而在
release
模式下ndebug
會被定義。
以上內容參考自《large scale c++ software design》。
C 學了這麼多年,你仍不知道的事!!!
宣告是將乙個名稱引入程式。定義提供了乙個實體在程式中的唯一描述。宣告和定義有時是同時存在的。如inta externintb 1 只有當extern 中不存在初始化式是才是宣告。其他情況既是定義也是宣告。但是在下列情況下,宣告僅僅是宣告 1 僅僅提供函式原型。如 voidfunc int,int 2...
C語言你不知道的事(2)
記錄學習的第八天 今天還是分享一下c語言的一些比較愉快的知識點 1.switch語句中break的重要性 首先呢我們看一下 int x scanf d x switch x 這是一段再正常不過的swich語句 如果我們把所有的break刪掉,會出現什麼情況呢 是編譯報錯?還是什麼?x 嗯哼哼 答案是...
關於提單,你不知道的事!
提單bill of lading b l 就代表貨物,一定要對提單有足夠的了解。基本知識和注意點 提單通常是3正3副,也有2正3副的。假如信用證有要求的話,要和貨代特別說明。t t付款方式時,理論上只需要一張正本就可以了 提貨後其他正本自動失效,副本不能提貨 t t收到全部貨款後,給客人寄正本時可以...