版權(quán)說明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請進行舉報或認領(lǐng)
文檔簡介
第7章const和inline7.1const變量 137.2const成員變量與成員函數(shù)7.3內(nèi)聯(lián)函數(shù)本章小結(jié)習(xí)題
const問題一直是C++語言設(shè)計中的一個難題。很多從C轉(zhuǎn)成C++?的程序員可能還是會經(jīng)常使用#define這種宏定義來聲明常量,但是對于程序來說,用const來聲明常量會更加靈活。比如用const聲明常量時可以有數(shù)據(jù)類型,而#define這種常量的聲明就沒有數(shù)據(jù)類型,這樣便于編譯器對其進行數(shù)據(jù)類型的安全性檢查等。7.1const變量通俗地講,常量就是常數(shù),或代表固定不變值的名字。在程序中,如果想讓某個變量的內(nèi)容從初始化后就一直保持不變,就可以定義一個常量。被const修飾的對象都受到保護,可以防止在程序中其他的地方被意外地改動,從而提高程序的健壯性。7.1.1const與值替代
1.內(nèi)部常量在C++中,平常使用的內(nèi)部數(shù)據(jù)類型的值都可以稱作是值常量,下面介紹幾種內(nèi)部數(shù)據(jù)類型的常量。(1)實數(shù)型常量:在C++中,有兩種實數(shù)(即浮點數(shù)):一種是帶有小數(shù)點的,還有一種就是指數(shù)型的:小數(shù)點型:1.23 指數(shù)型:1.23e2或1.23E2(2)整型常量:十進制:10 八進制:012 十六進制:0X12(3)字符常量:普通字符:'a' 特殊字符:'\n'(4)字符串常量:
"Helloworld“(5)枚舉常量:
enumday{Mon,Tue,Wends,Thu,F(xiàn)ri,Sat,Sun};在這里,Mon的值默認為0,后面依次加1。2.const聲明常量下面來看看在程序中定義的常量。如果在整個程序的編寫中,在很多地方都需要用到一個常數(shù)時,那么,在這些地方的一個或多個地方寫錯了這個值的話,就會導(dǎo)致計算錯誤。這時,可以給這個常數(shù)取一個名字,在每處常數(shù)被使用的地方都用這個名字代替,那么編譯器只需要檢查這個名字的拼寫錯誤,這樣能更容易地避免數(shù)值的不一致性導(dǎo)致的計算錯誤。舉個常見的例子,在編寫很多關(guān)于圓的周長或面積的計算公式的程序時,經(jīng)常需要使用到圓周率p,此時,可以通過命名一個容易理解并且便于記憶的名字來改進程序的可讀性,并且在定義的時候為其加上關(guān)鍵字const來進行修飾,為其加上常量的性質(zhì),來幫助預(yù)防程序出現(xiàn)數(shù)值一致性的錯誤。由于圓周率p不屬于C++語言的字符描述集的范圍,因此不能直接當(dāng)作C++程序中的變量名,所以可以用另外的名字來表示:
constfloatpi=3.141592653;
在這里,細心的讀者可能會提出疑問:float型的變量能存儲上面所有小數(shù)點后的數(shù)值嗎?不錯,的確不行!因為在C++中利用const關(guān)鍵字來聲明常量的時候是需要指定數(shù)據(jù)類型的,而且C++的編譯器會進行類型的檢查。由于float型只能存儲7位有效精度的實數(shù),因此在上面的聲明中,pi的實際值是3.141593,最后的3位是不起作用的,而且最后一位會四舍五入。如果將上面定義中的float型改為double型,則能夠完全存儲上面小數(shù)點后的數(shù)值。
在將某變量利用const關(guān)鍵字修飾之后,程序中的任何語句對它都只能讀取值而不能修改,這樣可以防止該變量的值在程序中被無意中修改。也正是由于常量不能被修改,因此,常量必須在定義時就初始化,而不能像下面的代碼:
constfloatpi;pi=3.1415926;
這樣做是錯誤的,常量的名稱永遠都不能放在賦值語句的左邊。由于常量在程序運行之前就已經(jīng)知道了它的值,即在編譯時就能求值了,因此在定義常量時,對其進行初始化的值可以是一個常量的表達式。但是這個常量表達式中不能含有某個函數(shù),因為一般的函數(shù)都是在程序運行時才能返回具體的值,在編譯時是無法獲取其返回值的。不過有一點比較容易混淆,即類似于sizeof()這種看起來像函數(shù)的表達式,它其實是C++中的基本操作符,它在編譯時就可以確定返回值了,所以這種表達式可以出現(xiàn)在常量的賦值語句中。其實在C語言中就已出現(xiàn)了const,用來聲明常量,但是C語言中的const對數(shù)據(jù)的修飾僅僅是表明這是一個不能再被更改的普通變量,而且對該變量的數(shù)據(jù)類型沒有說明。
3.const聲明變量在C語言中,const關(guān)鍵字的主要作用是定義常量、修飾函數(shù)參數(shù)和修飾函數(shù)返回值這三個;在C++中,const關(guān)鍵字不僅有這三個作用,而且還能修飾函數(shù)的定義體和定義類中某個函數(shù)為恒態(tài)函數(shù),即無法改變類中的數(shù)據(jù)成員。
此外,在C語言中,const修飾變量時是需要占用系統(tǒng)內(nèi)存的,并且它的名字是全局符。這也使得C語言的編譯器不能把const修飾的對象當(dāng)作是一個編譯期間的常量。例如:
constsize=100;
chara[size];
看起來這兩個語句是正確的,是合理的聲明,但是這段C程序卻會得到一個編譯的錯誤。其原因是,在C語言中,用const修飾的變量size占用了內(nèi)存的某個地址,故C語言的編譯器得不到它在編譯時的值,因此會出現(xiàn)編譯的錯誤。在C語言當(dāng)中,直接用const加上變量名即可聲明一個常量,和#define一樣,無需指定該變量的數(shù)據(jù)類型。但是在C++中不行,在C++中使用const修飾變量時必須同時聲明該變量的數(shù)據(jù)類型,這也是面向?qū)ο蟪绦蛟O(shè)計的一個特性的體現(xiàn)。
在C語言中還可以這樣使用const來修飾變量:
constsize;
C語言的編譯器把這看做是一個合理的聲明,這個聲明表示在別的地方有內(nèi)存分配,在這里進行引用。但是這樣在C++中是不允許的,因為在C語言中默認const是外部連接的,而在C++中默認const是內(nèi)部連接的,所以在C++中僅僅這樣聲明是不正確的。如果想用C++達到上面同樣的目的,必須顯式地用extern將內(nèi)部連接改為外部連接:
externconstsize;這樣,C++的編譯器也能知道這是一個聲明,表示在別的地方有內(nèi)存分配。當(dāng)然,這樣顯式地在C語言中聲明也是可以的,但是由于在C語言中默認的就是外部連接,所以這樣使用沒有太大的意義。4.const與?#define的區(qū)別其實,從某種意義上來說,在C語言當(dāng)中使用const的意義也不大。在前面曾提到,用const修飾的變量在編譯期間必須知道它的值,所以即使在常數(shù)表達式里想使用一個已命名的值,用const的效率并不如在預(yù)處理器中使用#define的高。因此在C語言里,建議還是使用#define來聲明常量。
const常量和?#define聲明的宏常量的區(qū)別如下:
(1)在C++中聲明的const常量是有數(shù)據(jù)類型的,而用#define聲明的常量是沒有數(shù)據(jù)類型的。因此,C++的編譯器可以對常量進行數(shù)據(jù)類型的安全性檢查,而C語言的編譯器只是對?#define聲明的常量進行字符的替代,這樣對數(shù)據(jù)類型不僅沒有安全性檢查,而且在字符替代的過程中,可能會產(chǎn)生意料不到的錯誤,比如邊際效應(yīng)等。
(2)很多集成化的編譯工具在對C++程序進行編譯時,可以對const常量進行調(diào)試,但是對于C語言中的宏常量是無法進行調(diào)試的,所以在C++中使用const常量對程序的調(diào)試也是有很大幫助的。
(3)使用const常量比?#define形式更節(jié)省空間,避免不必要的內(nèi)存分配。例如:
#defineA10 //宏定義
constinta=10; //const常量定義,此時并沒有將a放入ROM中
intb=A; //編譯期間會進行宏替換,需要分配內(nèi)存intc=a; //此時會為a分配內(nèi)存,以后不再分配
intd=A; //編譯期間會進行宏替換,需要再次分配內(nèi)存
inte=a; //不需要分配內(nèi)存7.1.2常量指針與指針常量
1.常量指針當(dāng)一個指針?biāo)赶虻膬?nèi)容是不能被修改的內(nèi)存空間時,稱這個指針為常量指針。例如:
constinta=100;constint*p=&a;
此時,就稱p為常量指針,因為指針p指向的是一個常量,它占據(jù)的內(nèi)存空間是不能被外界所修改的。因為常量是不能修改的,所以下面通過指針的賦值操作是錯誤的:*p=200;常量指針的含義是指針指向了一個不能再被修改的變量,而這個指針本身是可以修改的,例如可以將上面的指針p再指向另一個常量:
constintb=200;p=&b;
這時指針p還是一個常量指針,只是它指向的是另一個常量而已,當(dāng)然在這里還是不能為*p去賦值。
常量指針有一個很特殊的用法,還是以上面已經(jīng)定義的常量指針p為例:
intc=300;p=&c;*p=400; //錯誤
c=400; //正確變量c并不是常量,所以它可以被修改,但是為什么還是不能對*p進行賦值呢?這是因為定義了一個指向某個常量的指針,只是限制了這個指針的間接訪問操作,而不能規(guī)定這個指針?biāo)赶虻闹灯浔旧淼牟僮鞯囊?guī)定性。就像上面將常量指針指向一個普通的變量,這樣就可以保護被指向的變量在指針的操作中無法被修改,但是可以直接操作這個非常量的值。這種用法在函數(shù)傳遞中會被經(jīng)常使用到。
下面來看看函數(shù)傳遞時const的用法:
#include<iostream>usingnamespacestd;voidmyStrcpy(char*destStr,constchar*sourceStr){ while(*destStr++=*sourceStr++) ;}intmain(){ chara[15]="Helloworld!"; charb[15]; myStrcpy(b,a); cout<<b<<endl; return0;}
運行結(jié)果如下:char型的數(shù)組a本來是一個變量,但是在傳遞給函數(shù)strcpy作為參數(shù)時,被常量指針修飾之后就成為不能被修改的常量了,這樣就不允許對源字符串進行任何修改。因為在將char型數(shù)組a傳遞給strcpy函數(shù)時,相當(dāng)于執(zhí)行了以下操作:constint*sourceStr=&a;所以在函數(shù)體里,*sourceStr是個常量,不能將*sourceStr作為左值進行任何操作,從而達到保護源字符串的目的。當(dāng)然,在主函數(shù)中,數(shù)組a還是一個普通的變量,不受任何約束,可以隨時被修改。2.指針常量從字面意思就可以粗略地看出,指針常量是指這個指針本身就是一個常量。和常量指針相反,指針常量所指向的內(nèi)存空間的值是可以修改的,但是這個指針本身是不能被修改的。例如:
#include<iostream>usingnamespacestd;intmain(){ chara='B'; char*constp=&a; *p='A'; cout<<*p<<endl; return0;}
運行結(jié)果如下:
因為p是指針常量,它本身是不能被修改的,所以它不能出現(xiàn)在賦值語句的左側(cè),否則將會引起編譯錯誤。指針常量在初始化時和普通常量一樣,既然不能被修改,那么在定義指針常量時就必須初始化。
由于*p并不是常量,因此可以對其進行修改,例如還可以這樣做:*(p+1)
此時,可以對指針p所指向的內(nèi)存區(qū)域的下一個字節(jié)進行操作。既然有指向常量的指針,也有指針常量,那么當(dāng)然可以定義一個指向常量的指針常量:
constinta=10;constint*constp=&a;
這時,指針p指向的是一個常量,所以*p值是不能被修改的,而指針p本身也是一個指針常量,因此指針p也是不能被修改的,即p所指向的地址是不能被修改的。所以此時p和*p都不能作為賦值語句的左值而被修改。7.1.3常量引用在很多時候,函數(shù)需要將非內(nèi)部數(shù)據(jù)類型(如程序員自己定義的類)的對象作為參數(shù),而這樣的函數(shù)的運行效率是非常低的。因為在運行期間,在函數(shù)體的內(nèi)部將會產(chǎn)生非內(nèi)部數(shù)據(jù)類型的臨時對象用于復(fù)制其作為參數(shù)的對象,而臨時對象的構(gòu)造、析構(gòu)以及賦值的操作都將消耗系統(tǒng)資源。例如:
classA{ …};voidfunction(Aa){ …}
這時,為了提高效率,可以將函數(shù)參數(shù)中的類對象改為類對象的引用,這僅僅是借用了一下別名而已,不會產(chǎn)生非內(nèi)部數(shù)據(jù)類型的對象,這樣可以大大提高程序的效率。但是,這樣做也有一個不足的地方,就是引用在傳遞的過程中有可能改變原對象的值,這不是希望看到的。為了解決這個問題,只需要為這個引用加上const關(guān)鍵字來修飾即可:
voidfunction(constA&a)
這樣,不但不會對原對象的值有任何影響,而且比用指針更簡單些,體現(xiàn)了使用const所帶來的安全性。
在C++中的引用不像指針那樣,分常量指針和指針常量,C++是不會區(qū)分某個變量的const引用和const變量的引用的。因為你永遠都不可能給引用本身去重新賦值,使它去指向另外一個變量,而且引用總是const的,所以無所謂什么常量引用和引用常量了。因此,像前面那樣對引用應(yīng)用const關(guān)鍵字來修飾,其作用只是使引用的目標(biāo)成為const變量,所以你不可能看到這樣的情況:
constintconst&a=1;7.1.4傳遞const值在很多時候,在調(diào)用某個需要參數(shù)的函數(shù)時,并不希望在它的函數(shù)體內(nèi)將這個參數(shù)進行任何修改,而僅僅是將值“借給”這個函數(shù)當(dāng)作參數(shù)來使用,通常是在將值傳遞給該函數(shù)時加上關(guān)鍵字const進行修飾以達到這個目的。下面簡單看一下這種傳遞const值的方法:
voidfunction(constinta){ …}
這樣,在主函數(shù)中調(diào)用函數(shù)function時,用相應(yīng)的變量初始化const修飾的常量,在函數(shù)體中就可以按照被const修飾的內(nèi)容進行常量化,而為該函數(shù)傳遞的參數(shù)值在該函數(shù)體內(nèi)是無法被修改的。前面兩個小節(jié)介紹的用const修飾指針或引用作為函數(shù)的參數(shù)來傳遞值,就屬于傳遞const值的特殊情況。因為當(dāng)參數(shù)采用指針或引用來傳遞時,可以防止指針或引用所指向的內(nèi)容被意外修改。在一般的程序中,使用const指針或const引用來傳遞函數(shù)的參數(shù)的情況較為普遍,而一般在僅僅是內(nèi)部數(shù)據(jù)類型的值傳遞時,建議盡量不要用const關(guān)鍵字來進行修飾。因為對于內(nèi)部數(shù)數(shù)據(jù)類型的參數(shù),函數(shù)本身會自動產(chǎn)生一個臨時變量來復(fù)制該參數(shù)的內(nèi)容,而且作為內(nèi)部數(shù)據(jù)類型,不存在構(gòu)造和析構(gòu)的問題,復(fù)制的過程也很快,不會消耗多少系統(tǒng)資源。這樣,這個內(nèi)部數(shù)據(jù)類型的參數(shù)本來就無需保護,所以這個時候更不需要利用const關(guān)鍵字來進行修飾,以此達到對該參數(shù)的保護目的。當(dāng)然,顯式地使用const來修飾也不會產(chǎn)生錯誤,對于一些C++的初學(xué)者,可能還會有加深理解的作用。其實,還有一種方法可以確保在函數(shù)內(nèi)部的執(zhí)行不會影響到函數(shù)外部傳遞給函數(shù)的參數(shù),就是在函數(shù)體的內(nèi)部定義另外一個變量來復(fù)制這個參數(shù)的值。例如:voidfunction(inta){ constint&b=a; …}
這樣,在函數(shù)體內(nèi)部只需要對變量b進行操作即可。關(guān)于傳遞const值,有一個需要特別注意的地方,就是const只能修飾輸入函數(shù)。如果函數(shù)的某個參數(shù)是作為輸出使用的,那么這個時候,不論采用的是const指針傳遞還是const引用傳遞,都不能用const關(guān)鍵字來進行修飾,否則這個函數(shù)將失去輸出的功能??傊?,通過使用常量參數(shù),函數(shù)的調(diào)用者可以保證他們所傳遞給函數(shù)的對象不會在函數(shù)執(zhí)行過程中被改變,也不會對函數(shù)的執(zhí)行帶來任何其他影響。7.1.5返回const值當(dāng)希望函數(shù)的返回值在使用時不被外界改變時,可以為其加上const關(guān)鍵字來進行修飾。當(dāng)然,如果一個函數(shù)的返回值是void,那么就完全不用const來對返回值進行修飾了。其實,不僅僅是對于void型的函數(shù)返回值,如果函數(shù)返回的是內(nèi)部數(shù)據(jù)類型,也不必用const對返回值進行修飾。例如:
constintfunction(inta){ …}
該函數(shù)返回的是int型的值,即返回的本來就是一個數(shù)值,當(dāng)然不可能再通過賦值語句對其進行重新賦值。所以,一般來說,當(dāng)函數(shù)的返回類型為內(nèi)部數(shù)據(jù)類型時,建議最好將const關(guān)鍵字去掉。當(dāng)函數(shù)返回非內(nèi)部數(shù)據(jù)類型(即程序員自己定義的類)的對象或這個對象的引用時,也最好不使用const關(guān)鍵字進行修飾,因為此時返回值如果具有const常量屬性,則返回的實例只能訪問這個自定義類的public成員、protected成員和const成員函數(shù)(在下一小節(jié)將介紹),而且不允許對這些成員進行賦值操作,這在一般的軟件設(shè)計中出現(xiàn)的幾率比較小。所以,不建議為返回值為某個自定義類的對象或?qū)ο蟮囊玫暮瘮?shù)加上const來修飾返回值。
當(dāng)然,并不是當(dāng)所有的函數(shù)返回類對象時都不適用const來修飾返回值,只是一般情況下,此時,用const修飾返回值的情況多用于二元操作符的重載并會產(chǎn)生新對象的情況。當(dāng)函數(shù)的返回值為指針時,應(yīng)為函數(shù)的返回值加上const關(guān)鍵字,那么該函數(shù)返回的指針不能被用在賦值語句的左側(cè)而被修改,它只能賦值給被const關(guān)鍵字修飾的同類型的指針。例如:
constchar*function();constchar*p=function();
而下面的語句將會出現(xiàn)編譯錯誤:
char*p=function();7.2const成員變量與成員函數(shù)1.const成員變量和普通變量一樣,在構(gòu)造一個類的時候,也可以為這個類的某些成員加上const關(guān)鍵字來修飾,使其成為const數(shù)據(jù)成員。而對于const成員變量,需要注意的是,由于它必須被初始化而且又不能被更新,那么在構(gòu)造函數(shù)中,只能使用初始化成員列表來對其進行初始化,如果試圖在構(gòu)造函數(shù)的函數(shù)體內(nèi)或者該類的其他地方對const成員變量進行初始化或修改,都會引起編譯錯誤。例如:
#include<iostream>usingnamespacestd;classA{ constinta;public: A(intb):a(b){}
intgetA() { returna; }};intmain(){ A*a=newA(10); cout<<a->getA()<<endl; return0;}
運行結(jié)果如下:
因為C++是一種面向?qū)ο蟮某绦蛟O(shè)計語言,所以,const成員變量也只是在該類的某個對象的生存期間內(nèi)是常量,對于整個類來說則是可以改變的。因為這個類可以創(chuàng)建很多個它自己的對象,而在這些不同的類對象中,其const成員變量的值都可以互不相同。比如在上面的例子中,在創(chuàng)建類A的對象時,可以為其構(gòu)造函數(shù)傳入不同的參數(shù)提供給成員初始化列表,這樣,在不同的類對象中,常量a就具有不同的值。所以,在類的聲明中,不能對const成員變量進行初始化。因為在類的對象沒有被具體創(chuàng)建時,編譯器無法知道某個具體對象的const成員變量的值是多少。
因此,類中的const成員變量并不能使其在每一個類對象中都具有固定的值,這樣在大型的項目開發(fā)中可能會帶來一些不便。如果想要使用在整個類中都是常量的成員,那么可以使用枚舉來實現(xiàn)。例如:
classA{ enum{a=10,b=20,…}; …};
此外,這種枚舉型的常量不會占用類對象的存儲空間,編譯器在編譯期間就知道這些成員變量的值。不過這種使用方法的不足之處是,枚舉的默認類型為整型數(shù)據(jù),所以最大值是有限制的,而且不能表示浮點型的數(shù)據(jù)。2.const成員函數(shù)使用了const關(guān)鍵字進行修飾的成員函數(shù),稱之為const成員函數(shù)。例如:
voidfunction()const{ …}
當(dāng)某個類中有const成員變量時,這些const成員變量不像普通變量一樣能被該類中的所有成員函數(shù)所訪問或進行操作,只有const成員函數(shù)才能訪問或者操作類中的常量成員或?qū)ο?,沒有加const修飾的成員函數(shù)試圖訪問或操作const成員時,編譯器為了保證這些成員的const特性,會引發(fā)編譯錯誤。當(dāng)然,在const成員函數(shù)的內(nèi)部,也不能試圖去改變const成員變量的值。
從上面的const成員函數(shù)的聲明格式可以看出,const關(guān)鍵字是加在函數(shù)說明的后面的,它是整個函數(shù)類型說明的一個組成部分,所以,是否為成員函數(shù)加上const關(guān)鍵字進行修飾,是可以進行函數(shù)重載的。而且,既然const成員函數(shù)要操作類的const成員,那么在函數(shù)體的內(nèi)部,肯定還需要出現(xiàn)const關(guān)鍵字。前面講過,const成員變量的初始化必須放在初始化成員列表中來進行,構(gòu)造函數(shù)是不能對const成員變量進行操作的,所以,構(gòu)造函數(shù)是不能聲明為const的。同理,析構(gòu)函數(shù)也不可以聲明為const的。
const成員函數(shù)能訪問或操作const成員變量,當(dāng)然也能訪問、操作不是const的成員變量,那么這樣做又有什么意義呢?
有些時候,雖然類的某些數(shù)據(jù)成員沒有被const關(guān)鍵字加以修飾,但是在某個函數(shù)的函數(shù)體內(nèi),還是不希望這些數(shù)據(jù)成員被修改,那么,這時為函數(shù)加上const修飾就能達到不會修改任何數(shù)據(jù)成員的目的。如果在函數(shù)體內(nèi),不小心執(zhí)行了修改這些數(shù)據(jù)成員的操作,或者調(diào)用了其他的非const成員函數(shù)而引發(fā)某些操作時,編譯器都將提示錯誤,這樣能大大提高程序的健壯性。下面,通過具體的程序來理解為什么要給訪問普通數(shù)據(jù)成員的函數(shù)加上const修飾。
classA{public: intadd(){number++} intgetValue()const;private: intnumber;};intA::getValue()const{ //++number; //編譯錯誤,因為試圖修改數(shù)據(jù)成員
//add();//編譯錯誤,因為試圖調(diào)用非const成員函數(shù)
returnnumber;}
這樣,在getValue()函數(shù)的執(zhí)行過程中,就能確保number的值不會被任何其他操作所修改。3.使用const的注意事項
(1)在能夠理解并且知道為什么要使用的情況下,要盡可能放心地去使用const關(guān)鍵字,這樣經(jīng)常可以提高程序的可讀性、可維護性以及健壯性。但是并不是說const常量可以過多地使用。如果在某個頭文件中定義了一個常量,那么在程序鏈接時,所有使用這個頭文件的代碼都將復(fù)制這個常量,如果用到該頭文件的代碼比較多,則會增加程序的可執(zhí)行文件的大小,在一個大的工程項目中,這是很影響效率的。所以,不要隨便將一個對象修飾為常量,要真正需要時才使用const關(guān)鍵字。
(2)永遠不要將const修飾的成員放在賦值語句的左側(cè),試圖對其進行修改,這是使用const關(guān)鍵字最基本的原則。(3)在傳遞參數(shù)時,const關(guān)鍵字一般修飾指針或者引用,而不是內(nèi)部數(shù)據(jù)類型或者是自定義類的實例,因此這時的const關(guān)鍵字顯得沒有太大的意義。
(4)區(qū)分清楚并理解const和成員函數(shù)之間的關(guān)系:傳遞const值、返回const值和const成員函數(shù),const關(guān)鍵字要放在需要它的地方,并了解const放在不同地方的不同意義,不要輕易地將函數(shù)的返回值修飾為const。而且,除非是操作符重載時,否則一般不要將函數(shù)的返回值的類型聲明為對某個類對象的const引用。
(5)應(yīng)該盡量將常量局部化,只在需要常量的模塊中定義就可以了,因為并不是所有的常量都需要全局來訪問,除非是整個工程項目都需要知道的關(guān)鍵常量。7.3內(nèi)聯(lián)函數(shù)在C++?程序中,一些局部的變量都是存放在棧里面,而系統(tǒng)的??臻g是有限的,在程序中,如果頻繁地去使用一些相同的函數(shù),就會大量地消耗??臻g,如果棧空間用盡了,那么程序的執(zhí)行就會出現(xiàn)錯誤。不僅僅是函數(shù)的局部變量會帶來問題,在某個函數(shù)被調(diào)用時,其實是將程序執(zhí)行的順序轉(zhuǎn)移到該函數(shù)在內(nèi)存中存放的地址,在調(diào)用完該函數(shù)之后,程序的執(zhí)行將跳轉(zhuǎn)回原來的地址,繼續(xù)執(zhí)行原來的內(nèi)容。這就要求在調(diào)用該函數(shù)之前,需要記錄調(diào)用者原來的執(zhí)行地址,調(diào)用完畢后需要返回所記錄的地址并繼續(xù)往下執(zhí)行,這在時間和空間上都會有一定的消耗,影響整個程序的執(zhí)行效率。
為了解決這個問題,在C++中特別地引入了內(nèi)聯(lián)函數(shù),用inline關(guān)鍵字來修飾。
7.3.1inline和編譯器通過以下程序來看看內(nèi)聯(lián)函數(shù)是如何解決上述問題的:
#include<iostream>#include<string>usingnamespacestd;inlinestringfunction(inta){ return(a%2==0)?"偶數(shù)":"奇數(shù)";}voidmain(){ for(inti=0;i<10;i++) { cout<<i<<":"<<function(i)<<endl;}}
如果沒有用inline來修飾函數(shù),那么在主函數(shù)中的循環(huán)內(nèi)部將調(diào)用10次function()函數(shù),這樣,就會反復(fù)在棧中為function()的局部變量開辟內(nèi)存,這將消耗大量的??臻g。
為函數(shù)function()加上inline關(guān)鍵字進行修飾后,它就成了一個內(nèi)聯(lián)函數(shù),在主函數(shù)的執(zhí)行過程之前,編譯器已經(jīng)將每次的function()函數(shù)調(diào)用都替換成:
return(a%2==0)?"偶數(shù)":"奇數(shù)";
這樣,就不會因為反復(fù)調(diào)用函數(shù)并為其局部變量反復(fù)開辟??臻g了。因為在程序編譯時,編譯器就已經(jīng)將程序中出現(xiàn)的調(diào)用內(nèi)聯(lián)函數(shù)的表達式都用內(nèi)聯(lián)函數(shù)的函數(shù)體中的語句進行了替換。這樣,既不用反復(fù)地為函數(shù)的局部變量在棧中開辟內(nèi)存空間,也不存在執(zhí)行程序在函數(shù)的調(diào)用者和函數(shù)之間來回跳轉(zhuǎn)而引起的時間和空間的消耗。
通過上面的程序片段可以很清楚地看到,內(nèi)聯(lián)函數(shù)的定義只需要在函數(shù)聲明或定義的時候,在前面為其加上inline關(guān)鍵字即可,即
inline返回類型函數(shù)名(參數(shù)列表){ …}
在定義內(nèi)聯(lián)函數(shù)時要注意,在聲明函數(shù)為內(nèi)聯(lián)函數(shù)時,必須同時定義該函數(shù)的函數(shù)體,如果將聲明和函數(shù)體的定義分開,那么即使在聲明時加上了inline關(guān)鍵字,編譯器也只是將該函數(shù)當(dāng)作普通函數(shù)一樣對待。例如,如下的函數(shù)聲明中,inline關(guān)鍵字是不會起任何作用的:
inlinestringfunction(inta);
當(dāng)某個函數(shù)加上inline關(guān)鍵字聲明為內(nèi)聯(lián)函數(shù)以后,在程序的其他地方對該函數(shù)的調(diào)用就不像一般的函數(shù)是在函數(shù)運行時調(diào)用,而是通過編譯器在編譯期間就已經(jīng)對該函數(shù)的調(diào)用處運用了代碼的替換。不過要注意的是,內(nèi)聯(lián)函數(shù)必須在其第一次被調(diào)用前就定義或聲明。另外,inline關(guān)鍵字的使用并不是沒有限制的,由于編譯器對內(nèi)聯(lián)函數(shù)的處理是對其在調(diào)用時實行代碼的替換,那么就要求內(nèi)聯(lián)函數(shù)的函數(shù)體內(nèi)的代碼是簡單結(jié)構(gòu)的,不能有復(fù)雜的結(jié)構(gòu)控制語句或循環(huán)語句,如switch、for等。比如在前面所舉的例子,主函數(shù)在for循環(huán)中調(diào)用內(nèi)聯(lián)函數(shù),如果內(nèi)聯(lián)函數(shù)中有更加復(fù)雜的循環(huán)語句,那么在執(zhí)行代碼的替換后,很可能就會造成循環(huán)結(jié)構(gòu)的混亂,從而導(dǎo)致程序的錯誤。而且內(nèi)聯(lián)函數(shù)本身不能是直接遞歸函數(shù),這樣,也會由于函數(shù)體內(nèi)的遞歸關(guān)系導(dǎo)致程序結(jié)構(gòu)的混亂。既然內(nèi)聯(lián)函數(shù)是由編譯器在編譯期間處理,那么在下一章將要介紹的虛函數(shù)就沒有辦法被編譯器當(dāng)作內(nèi)聯(lián)函數(shù)了,由于多態(tài)中虛函數(shù)的特性,要等到程序運行時才知道到底執(zhí)行哪一個函數(shù)。實際上,內(nèi)聯(lián)函數(shù)所帶來的并不僅僅是節(jié)省函數(shù)調(diào)用對棧空間等的開銷,而且會對編譯器有一定的優(yōu)化作用。因為當(dāng)編譯器在編譯一段連續(xù)的且沒有對其他函數(shù)調(diào)用的代碼時,會大大地提高效率,所以當(dāng)使用inline關(guān)鍵字修飾一個函數(shù)使其成為內(nèi)聯(lián)函數(shù)時,就可以使得編譯器在對這些連續(xù)的無函數(shù)調(diào)用的代碼進行編譯時帶來特殊的優(yōu)化。
如果某個被inline修飾的函數(shù)的函數(shù)體很小,那么為這個內(nèi)聯(lián)函數(shù)的函數(shù)體生成的目標(biāo)代碼可能比在調(diào)用一個函數(shù)時產(chǎn)生的目標(biāo)代碼要小很多。此時,使用內(nèi)聯(lián)函數(shù)多所帶來的結(jié)果就是使得目標(biāo)代碼更小,而且對指令的緩存有更高的使用率。
inline關(guān)鍵字對于編譯器來說,其實只能算是一種建議,而不是對編譯器的命令。這個建議可以用inline關(guān)鍵字顯式或隱式地向編譯器提出。這里所說的“隱式”,其實是對于類的成員函數(shù)而言的,因為類的成員函數(shù)都被編譯器默認為內(nèi)聯(lián)函數(shù)。當(dāng)然,和前面所提到的關(guān)于inline關(guān)鍵字的使用限制一樣,只有當(dāng)成員函數(shù)的函數(shù)體沒有復(fù)雜的結(jié)構(gòu)時,成員函數(shù)才能被編譯器當(dāng)作內(nèi)聯(lián)函數(shù)來對待,否則,編譯器將放棄對成員函數(shù)的內(nèi)聯(lián)方式的使用。
對于普通的函數(shù)也一樣,當(dāng)函數(shù)的結(jié)構(gòu)體很復(fù)雜時,即使使用inline關(guān)鍵字進行了修飾,在編譯時編譯器還是會放棄內(nèi)聯(lián)的方式。在這里,對前面的一個知識點進行一些補充。編譯器是否接受inline關(guān)鍵字的請求,并不僅僅取決于函數(shù)體內(nèi)的結(jié)構(gòu)是否復(fù)雜。因為內(nèi)聯(lián)函數(shù)并不是完美無瑕的,它的使用也是有一定的代價的。由于內(nèi)聯(lián)函數(shù)實現(xiàn)的本質(zhì)就是在調(diào)用時實行代碼的替換,那么這肯定會增加整個工程的目標(biāo)代碼的數(shù)量,因此在程序中過多地使用內(nèi)聯(lián)函數(shù)就會導(dǎo)致程序的目標(biāo)代碼的龐大,從而導(dǎo)致資源有限的計算機的執(zhí)行效率的下降,這樣內(nèi)聯(lián)函數(shù)本來想體現(xiàn)的優(yōu)勢將不復(fù)存在,甚至還不如普通函數(shù)的調(diào)用。所以,當(dāng)函數(shù)用inline關(guān)鍵字向編譯器提出請求時,編譯器會根據(jù)函數(shù)的具體情況來決定會不會對該函數(shù)真正使用內(nèi)聯(lián)的方式在編譯時對其進行處理。因此,從這個角度來說,inline關(guān)鍵字對于編譯器僅僅是個請求。既然編譯器對內(nèi)聯(lián)函數(shù)的操作僅僅是代碼的替換,而#define這種宏定義的方式也是替換,那么它們之間又有什么區(qū)別呢?其實,在很多時候使用宏來進行代碼的替換,也是為了節(jié)省一些系統(tǒng)資源的開銷。例如,可以通過下面的宏定義來獲得a和b中較大的一個,而不需要再去定義一個函數(shù):
#defineMAX(a,b)((a)>(b))?(a):(b)
但是宏的使用的局限性太大,比如在前面也提到了,對于常量建議用const關(guān)鍵字而不使用#define的形式,其原因就是宏的使用沒有對數(shù)據(jù)類型的檢查。你很難想象,對于一個函數(shù)來說,內(nèi)部的數(shù)據(jù)如果沒有數(shù)據(jù)類型會是什么樣子。內(nèi)聯(lián)函數(shù)和宏定義最根本的區(qū)別是:內(nèi)聯(lián)函數(shù)的調(diào)用是由編譯器在編譯期間進行函數(shù)體代碼的替換,而宏則是由預(yù)處理器在編譯之前進行宏體對宏名的替換。下面總結(jié)一下宏定義和內(nèi)聯(lián)函數(shù)在代碼替換方面的區(qū)別:
(1)宏的替換方式是在編譯之前由預(yù)處理器進行的,即先用宏體替換宏名,然后再進行編譯,而內(nèi)聯(lián)函數(shù)的替換方式是在編譯時由編譯器將函數(shù)體替換為調(diào)用表達式。(2)由于在宏的定義中是沒有數(shù)據(jù)類型的,因此宏所做的替換其實只是簡單的字符串替換,而在對內(nèi)聯(lián)函數(shù)的調(diào)用中,對于在代碼替換過程中的參數(shù)是有數(shù)據(jù)類型限制的。
(3)在宏的定義中所使用的參數(shù)不占系統(tǒng)的內(nèi)存空間,只是做簡單的字符串替換,而在對內(nèi)聯(lián)函數(shù)的調(diào)用中,參數(shù)的傳遞則是具體變量之間的內(nèi)容傳遞,而且形參會作為代碼替代后函數(shù)的局部變量,顯然是要占系統(tǒng)的內(nèi)存空間的。因此,內(nèi)聯(lián)函數(shù)不僅能起到宏定義一樣的替換作用,節(jié)省函數(shù)在調(diào)用期間對系統(tǒng)資源的開銷,而且解決了在宏的定義中沒有數(shù)據(jù)類型的不足,比宏的使用更加靈活。7.3.2inline函數(shù)與程序效率在前一節(jié)已經(jīng)介紹過,C++引入內(nèi)聯(lián)函數(shù)的目的就是希望解決程序中由于函數(shù)調(diào)用而引起的效率問題,所以內(nèi)聯(lián)函數(shù)的使用能夠提高函數(shù)的運行效率。內(nèi)聯(lián)函數(shù)的調(diào)用方法和普通函數(shù)一樣,但是由于在編譯期間,編譯器已將內(nèi)聯(lián)函數(shù)的函數(shù)體完全替代了程序中對內(nèi)聯(lián)函數(shù)的調(diào)用,因此內(nèi)聯(lián)函數(shù)的執(zhí)行效率比一般的函數(shù)要高。在前面已經(jīng)介紹,被inline關(guān)鍵字修飾的內(nèi)聯(lián)函數(shù)不需要再像普通的函數(shù)被調(diào)用一樣,使程序的執(zhí)行權(quán)在函數(shù)的內(nèi)部和函數(shù)被調(diào)用的地方來回跳轉(zhuǎn),在時間和空間上都大大提高了效率,而且不需要再為函數(shù)的局部變量在棧中反復(fù)開辟內(nèi)存空間了。
一般來說,類中的成員函數(shù)都會被編譯器當(dāng)作內(nèi)聯(lián)函數(shù)來對待,而最常用的情況莫過于在類中將關(guān)于類成員存取的函數(shù)作為內(nèi)聯(lián)函數(shù)來使用。因為在程序員自定義的類中,經(jīng)常會有很多私有的或者是受保護的數(shù)據(jù)成員,以防止外界訪
溫馨提示
- 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
- 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
- 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內(nèi)容里面會有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
- 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
- 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內(nèi)容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對任何下載內(nèi)容負責(zé)。
- 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請與我們聯(lián)系,我們立即糾正。
- 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時也不承擔(dān)用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。
最新文檔
- 2025園林綠化合同
- 2025建設(shè)工程施工合同(VIII)
- 2025企業(yè)代培訓(xùn)合同范文
- 2025合同模板健身俱樂部會員入會協(xié)議 范本
- 沙盤模型制作合同
- 醫(yī)療科技在小兒發(fā)熱治療中的應(yīng)用
- 課題申報參考:馬克思隱喻敘事的唯物史觀原理研究
- 課題申報參考:禮俗互動視域下明清江南婚嫁刺繡裝飾研究
- 課題申報參考:科學(xué)教育教學(xué)體系研究
- 綠色能源在校園電力供應(yīng)中的應(yīng)用與展望
- 2024年蘇州工業(yè)園區(qū)服務(wù)外包職業(yè)學(xué)院高職單招職業(yè)適應(yīng)性測試歷年參考題庫含答案解析
- 人教版初中語文2022-2024年三年中考真題匯編-學(xué)生版-專題08 古詩詞名篇名句默寫
- 2024-2025學(xué)年人教版(2024)七年級(上)數(shù)學(xué)寒假作業(yè)(十二)
- 山西粵電能源有限公司招聘筆試沖刺題2025
- ESG表現(xiàn)對企業(yè)財務(wù)績效的影響研究
- 旅游活動碳排放管理評價指標(biāo)體系構(gòu)建及實證研究
- 2022年全國職業(yè)院校技能大賽-電氣安裝與維修賽項規(guī)程
- 小學(xué)德育養(yǎng)成教育工作分層實施方案
- 2024年湖南高速鐵路職業(yè)技術(shù)學(xué)院單招職業(yè)技能測試題庫附答案
- 2024年4月浙江省00015英語二試題及答案含評分參考
- 黑枸杞生物原液應(yīng)用及產(chǎn)業(yè)化項目可行性研究報告
評論
0/150
提交評論