1 原理
1.1 首先嫌吠,關(guān)于聲明和定義的區(qū)別匾竿。
這種寫法(函數(shù)原型后加;號表示結(jié)束的寫法)只能叫函數(shù)聲明而不能叫函數(shù)定義嘱函,只有帶函數(shù)體的聲明才叫定義,比如下面
只有分配存儲空間的變量聲明才叫變量定義列另,其實(shí)函數(shù)也是一樣芽腾,編譯器只有見到函數(shù)定義才會生成指令,而指令在程序運(yùn)行時(shí)當(dāng)然也要占存儲空間页衙。那么沒有函數(shù)體的函數(shù)聲明有什么用呢摊滔?它為編譯器提供了有用的信息,編譯器在翻譯代碼的過程中店乐,只有見到函數(shù)原型(不管帶不帶函數(shù)體)之后才知道這個(gè)函數(shù)的名字艰躺、參數(shù)類型和返回值,這樣碰到函數(shù)調(diào)用時(shí)才知道怎么生成相應(yīng)的指令眨八,所以函數(shù)原型必須出現(xiàn)在函數(shù)調(diào)用之前腺兴,這也是遵循“先聲明后使用”的原則。
1.2 標(biāo)識符的鏈接屬性(Linkage)有三種
外部鏈接(ExternalLinkage)
如果最終的可執(zhí)行文件由多個(gè)程序文件鏈接而成廉侧,一個(gè)標(biāo)識符在任意程序文件中即使聲明多次也都代表同一個(gè)變量或函數(shù)页响,則這個(gè)標(biāo)識符具有External Linkage。具有External Linkage的標(biāo)識符編譯后在符號表中是GLOBAL的符號段誊。
內(nèi)部鏈接(InternalLinkage)
如果一個(gè)標(biāo)識符在某個(gè)程序文件中即使聲明多次也都代表同一個(gè)變量或函數(shù)闰蚕,則這個(gè)標(biāo)識符具有Internal Linkage。具有Internal Linkage的標(biāo)識符編譯后在符號表中是LOCAL的符號连舍。
無鏈接(NoLinkage)
除以上情況之外的標(biāo)識符都屬于No Linkage的没陡,例如函數(shù)的局部變量,以及不表示變量和函數(shù)的其它標(biāo)識符索赏。
1.3 聲明和定義的次數(shù)限制
凡是被多次聲明的變量或函數(shù)盼玄,必須有且只有一個(gè)聲明是定義,如果有多個(gè)定義潜腻,或者一個(gè)定義都沒有埃儿,鏈接器就無法完成鏈接。顯然砾赔,聲明可以有很多次蝌箍。
2 用extern和static修飾函數(shù)
測試案例2.1
測試案例2.1結(jié)果
編譯不能通過青灼,error C2129: 靜態(tài)函數(shù)“void print_03(void)”已聲明但未定義因?yàn)閜rint_03在extern_static_test1.h中被聲明為static暴心。當(dāng)static修飾函數(shù)的時(shí)候妓盲,說明此函數(shù)只能被自己內(nèi)部的文件使用,即具有internal linkage.因此main不能調(diào)用extern_static_test1.h中被static修飾的函數(shù)专普。如有有這樣幾個(gè)文件a.h, a.cpp, main.cpp悯衬。其中a.h被a.cpp和main.cpp所包含,那么a.h中用static修飾的函數(shù)只能被a.cpp中的函數(shù)調(diào)用檀夹,不能被main.cpp中的函數(shù)調(diào)用.這個(gè)internal linkage屬性就被確定了筋粗。即使你故意在main.cpp中進(jìn)行一次external的聲明(如extern void print_03(void);)也不能改變此internal linkage鏈接屬性。
測試案例2.2
為了修正測試案例2.1的錯(cuò)誤炸渡,在main.cpp中刪除print_03();其它文件保持不變
則編譯沒有錯(cuò)誤了娜亿,運(yùn)行結(jié)果是,
測試案例2.2結(jié)果
1. ?print_02()說明,函數(shù)被聲明為external后蚌堵,才能做到在多個(gè)文件中多次聲明的情況下买决,依然指示同一個(gè)定義。
2. ?print_01()說明吼畏,函數(shù)聲明如果不加external督赤,也不加static,則默認(rèn)為external.
3.
print_04()說明泻蚊,如果函數(shù)聲明使用了static修飾符躲舌,則這個(gè)函數(shù)具有internallinkage,只能被聲明所在的文件內(nèi)部調(diào)用性雄。所以這里的print_04()調(diào)用了聲明為static的print_03()没卸。print_04()不具有static屬性,所以在main.cpp中能夠被調(diào)用秒旋。
測試案例2.3
在main.cpp中強(qiáng)制用extern修飾符聲明print_03();其它文件保持不變會發(fā)生什么办悟?
測試案例2.3結(jié)果
依然出現(xiàn)編譯錯(cuò)誤。說明滩褥,在a.h中已經(jīng)被聲明為static的文件病蛉,被main.cpp包含之后,main.cpp中不能修改它的屬性為external瑰煎。
3 用extern和static修飾變量
測試案例3.1
由于是變量定義铺然,所以不寫在extern_static_test1.h中,extern_static_test1.h和上文保持基本一致酒甸,為了簡化魄健,只保留了一個(gè)函數(shù)。
變量定義寫在了extern_static_test1.cpp中插勤,如下
現(xiàn)在來看看main.cpp文件
測試案例3.1結(jié)果
編譯發(fā)現(xiàn)沽瘦,var1和var3都出現(xiàn)了編譯錯(cuò)誤革骨。
這說明,雖然已經(jīng)在這里喪心病狂地對3個(gè)變量的生命都寫明了extern.
extern int var1;
extern int var2;
extern int var3
但是析恋,因?yàn)樵趀xtern_static_test1.cpp的文件中良哲,var3已經(jīng)被定義為了static,所以它具有internallinkage了助隧,只能在extern_static_test1.cpp中被使用了筑凫,不能在main.cpp中被使用了;因?yàn)樵趀xtern_static_test1.cpp的文件中并村,var1沒有修飾符巍实,變量如果沒有鏈接屬性的修飾符,默認(rèn)是static哩牍。這和函數(shù)正好相反棚潦,函數(shù)如果沒有鏈接屬性的修飾符,默認(rèn)是external膝昆。想想丸边,這樣設(shè)定是符合實(shí)際需求的,函數(shù)就是用來進(jìn)行操作外潜,被調(diào)用的原环,默認(rèn)external更方便,變量不應(yīng)該被隨意使用处窥,而應(yīng)該被函數(shù)操作嘱吗,這樣才安全。
main.cpp修改之后滔驾,正確
測試案例3.2
如果把main.cpp中external聲明的關(guān)鍵字external去掉谒麦,extern_static_test1.h和extern_static_test1.cpp保持不變,這樣是否正確呢?
測試案例3.2結(jié)果
出現(xiàn)編譯錯(cuò)誤哆致,說明绕德,如果要使得一個(gè)變量具有external linkage,必須在定義時(shí)和聲明時(shí)都得加上external修飾符摊阀,比如這里需要在extern_static_test1.cpp中var2的定義和main.cpp中的var2的聲明中都寫上external修飾符耻蛇。因?yàn)樽兞磕J(rèn)是static,你要不特別說明它是external胞此,那就默認(rèn)是static了臣咖。
二者的一些定性說明:
static:
一、在C中漱牵,static主要定義全局靜態(tài)變量夺蛇、定義局部靜態(tài)變量、定義靜態(tài)函數(shù)酣胀。
1刁赦、定義全局靜態(tài)變量:在全局變量前面加上關(guān)鍵字static娶聘,該全局變量變成了全局靜態(tài)變量。全局靜態(tài)變量有以下特點(diǎn)甚脉。
???????????? a.在全局區(qū)分配內(nèi)存丸升。
?????????? ? b.如果沒有初始化,其默認(rèn)值為0.
????????? ? c.該變量在本文件內(nèi)從定義開始到文件結(jié)束可見宦焦。
2发钝、定義局部靜態(tài)變量:在局部變量前面加上關(guān)鍵字static顿涣,其特點(diǎn)如下:
???????????? a.該變量在全局?jǐn)?shù)據(jù)區(qū)分配內(nèi)存波闹。
??????????? b.它始終駐留在全局?jǐn)?shù)據(jù)區(qū),直到程序運(yùn)行結(jié)束涛碑。
??????????? c. 其作用域?yàn)榫植孔饔糜蚓椋?dāng)定義它的函數(shù)或語句塊結(jié)束時(shí),其作用域隨之結(jié)束蒲障。
3歹篓、定義靜態(tài)函數(shù):在函數(shù)返回類型前加上static關(guān)鍵字,函數(shù)即被定義為靜態(tài)函數(shù)揉阎,其特點(diǎn)如下:
???????????? a.靜態(tài)函數(shù)只能在本源文件中使用
????????????? b.在文件作用域中聲明的inline函數(shù)默認(rèn)為static類型
二庄撮、在C++中新增了兩種作用:定義靜態(tài)數(shù)據(jù)成員或靜態(tài)函數(shù)成員。
定義靜態(tài)數(shù)據(jù)成員毙籽。
????? ? ? ? ? a.內(nèi)存分配:靜態(tài)數(shù)據(jù)成員在程序的全局?jǐn)?shù)據(jù)區(qū)分配洞斯。
????? ? ? ?? ? b.初始化和定義:靜態(tài)數(shù)據(jù)成員定義時(shí)要分配空間,所以不能在類聲明中定義坑赡。
????????? 靜態(tài)數(shù)據(jù)成員因?yàn)槌绦蛞蚤_始運(yùn)行就必須存在烙如,所以其初始化的最佳位置在類的內(nèi)部,public毅否、protected亚铁、private關(guān)鍵字對它的限定和普通數(shù)據(jù)成員一樣,因?yàn)槠淇臻g在全局?jǐn)?shù)據(jù)分配螟加,屬于所有本類的對象共享徘溢。它不屬于特定的類對象,在沒產(chǎn)生類對象時(shí)捆探,其作用域可見然爆,即沒有產(chǎn)生類的實(shí)例時(shí),就可以操作它了徐许。
靜態(tài)成員函數(shù)施蜜。靜態(tài)成員函數(shù)與類相聯(lián)系,不與類的對象相聯(lián)系雌隅。靜態(tài)成員函數(shù)不能訪問非靜態(tài)數(shù)據(jù)成員翻默。
extern:
extern可以置于變量或函數(shù)前缸沃,以在別的文件中標(biāo)識變量或函數(shù)的定義,并提示編譯器遇到此變量或函數(shù)時(shí)在其他模塊中尋找其定義修械。extern是C趾牧、C++語言中表明函數(shù)和全局變量作用范圍(可見性)的關(guān)鍵字。對于extern變量來說肯污,僅僅是一個(gè)變量的聲明翘单,其并不是定義,不會分配內(nèi)存空間蹦渣。extern表示將變量或函數(shù)聲明為外部鏈接哄芜,變量默認(rèn)是內(nèi)部鏈接,函數(shù)默認(rèn)是外部鏈接柬唯。因此用來外部鏈接的函數(shù)认臊,聲明時(shí)有無extern都可以連接通過。而全局變量則不行锄奢。通常失晴,在模塊的頭文件中,對本模塊提供給其他模塊引用的函數(shù)和全局變量以關(guān)鍵字extern聲明拘央。