標籤:
const是C語言的關鍵字,經C++進行擴充,變得功能強大,用法複雜。const用於定義一個常變數(唯讀變數),當const與指標,引用,函數等結合起來使用時,情況會變得複雜的多。下面將從五個方面總結const的用法。
1.const位置
const位置較為靈活,一般來說,除了修飾一個類的成員函數外,const不會出現先一條語句的最後。樣本如下:
#include <iostream>using namespace std;int main(int argc,char* argv[]){ int i=5; const int v1=1; int const v2=2; const int* p1; int const * p2; //以下三條語句報編譯錯誤,為什嗎? //const * int p3; //int* const p3=&v1; //int * const p3; int * const p3=&i; const int* const p4=&v1; int const * const p5=&v2; const int & r1=v1; int const & r2=v2; //以下語句報編譯錯誤,為什嗎? //const & int r3; //以下語句警示告,並忽略const int& const r4=i; cout<<*p4<<endl; return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
閱讀以上程式,得出如下結論:
(1)程式輸出結果是1,以上程式示範const的位置與它的語義之間的關係,看似複雜,
實際有規律可循。
(2)const和資料類型結合在一起時形成所謂的“常類型”,利用常類型可申明或定義
常變數。const用來修飾類型時,既可以放在類型前面,也可以放在類型後面,如const int i和int const i是合法且等價的。用常類型申明或定義變數時,const只會出現在變數前面。
(3)const和被修飾的類型之間不能有其他標識符。
(4)int const p和int const p是不同的申明語句,原因是前者const修飾的是int,後者const修飾的是int*。前者表示指標p指向整型常變數(指標所指單元的內容不允許修改),而指標本身可以指向其他的常變數,即p為指向常量的指標——常量指標。後者表示指標p本身的值不可修改,一旦p指向某個整型變數之後就不能指向其他的變數,即p是個指標常量。
(5)引用本身可以理解為指標常量,在引用前使用const沒有意義。上例中int & const r4=I;中const是多餘的。即沒有引用常量的說法,只有常引用。常引用指被引用對象是一個常量,不允許通過引用和修改被引用對象的值。
在很多情況下,為表達同一種語義,可將const放在不同的位置。但在某些情況下,const只能放在特定的位置,考查const配合二重指標的例子,代碼如下:
int main(int argc,char* argv[]){ //const配合二重指標 int const **p1; int* const * p2; int i=5; int j=6; const int * ptr1=&i; int * const ptr2=&j; p1=&ptr1; p2=&ptr2; cout<<**p1<< " "<<**p2<<endl; return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
閱讀以上代碼得出如下結論:
(1)程式的運行結果是:5 6
(2)int const *p1和int const * p2申明的二重指標p1和p2的含義完全不同。P1不
是指標常量,它所指向的變數的類型是int const (指向整型常量的指標)。P2也不是指標常量,其指向的變數類型是int const(整型指標常量)。若按p1=&ptr1和p2=&ptr2賦值,均產生編譯錯誤。
2.const對象和對象的const成員
const定義一個基本類型的變數是不允許修改該變數的值。const修飾類的對象稱為常對象,const修飾的類成員函數稱為常函數。考查如下代碼:
#include <iostream>using namespace std;class A{ int num;public: A(){num =5;}; void disp(); void disp() const; void set(int n){num=n;};};void A::disp(){ cout<<"Another version of disp()"<<endl;}void A::disp() const{ cout<<num<<endl;}int main(int argc,char* argv[]){ A a1; a1.set(3); a1.disp(); A const a2; a2.disp(); return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
程式執行結果是:
Another version of disp()
5
閱讀以上程式,得出如下結論:
(1)常函數的申明與定義分開進行時,兩邊都要使用const關鍵字,否則發生編譯錯誤。
(2)只有類的非靜態成員函數可以被申明為常函數,原因是靜態成員函數不含this指標,屬於類層級的函數。其它類型的函數(如外部函數等)不能被申明為常函數。
(3)一個類的兩個成員函數,如果函數的傳回值類型、函數名、函數的參數列表完全相同,一個是常函數,一個是普通函數,那麼它們構成重載關係。如上例中的void disp()和void disp() const。這是因為void disp()和void disp() const具有不同的簽名。C++把this指標也作為參數評估的一部分,由於上面的函數被定義成class A的成員函數,那麼它們最終會被看作void disp(A*)和void disp(const A*),從而構成重載。
(4)非唯讀對象(如a1)調用某個函數時,先尋找它的非const函數版本,如果沒有找到,再調用它的const函數版本。而常對象(a2),只能調用類中定義的常函數,否則出現編譯錯誤。
(5)存在const和非const版本的成員函數時,普通對象若想調用const函數,應該通過建立該對象的常引用或指向該對象的常指標。如上面的程式,要用對象a1調用常函數disp(),可以使用如下語句:
((const A&)a1).disp();
或者:
((const A*)&a1)->disp();
(6)非唯讀對象中,也可以將部分資料成員定義為常量,稱為類對象的常量成員。類對象的非靜態常量成員必須在建構函式中初始化,且只能藉助於初始化列表,因為初始化列表才是初始化,建構函式中通過賦值運算子進行的是賦值,並非初始化。
3.const修飾函數的參數和函數的傳回值
在定義函數時常用到const,主要用來修飾參數和傳回值。其目的是讓編譯器為程式員做變數的唯讀性檢查,以使程式更加健壯。考查如下代碼:
void disp1(const int &ri){ cout<<ri<<endl;}void disp2(const int i){ cout<<i<<endl;}const int disp3(const int& ri){ cout<<ri<<endl; return ri;}int& disp4(int& ri){ cout<<ri<<endl; return ri;}const int& disp5(int& ri){ cout<<ri<<endl; return ri;}int main(int argc,char* argv[]){ int n=5; disp1(n); disp2(n); disp3(n); disp4(n)=6; disp5(n);//disp5(n)=6;是錯誤的 getchar(); return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
程式運行結果是:
5
5
5
5
6
閱讀以上代碼得出如下結論:
(1)const修飾傳遞調用的形參申明為常量,沒有實用價值。如上例中的void disp2(cons tint i)這樣的申明沒有意義,因為形參i的改變並不影響實參的值。
(2)函數的傳回值是實值型別時,被const修飾沒有意義,因為此時傳回值是一個非左
值,本身就不能改變,上例中const int disp3(cons tint& ri)對傳回值的const限定是多餘的。
(3)const修飾實值型別的形參時,不構成函數重載,如void disp(const int i)與void disp(int i)。但當const修飾非實值型別(引用、指標)的形參時構成函數重載,如void disp(const int& i)與void disp(int& i)。
4.常見的對const的誤解
(1)誤解一:用const修改的變數值一定是不能改變的。當const修飾的局部變數儲存在非唯讀記憶體中,通過指標可間接修改。
(2)誤解二:常引用或常指標,只能指向常變數,這是一個極大的誤解。常引用或者常指標只能說明不能通過該引用(或者該指標)去修改被引用的對象,至於被引用對象原來是什麼性質是無法由常引用(常指標)決定的。
5. 將const類型轉化為非const類型
使用C++中cons_cast運算子可去除複合類型中的const或volatile屬性。當大量使用const_cast是不明智的,只能說程式存在設計缺陷。使用方法見下例:
void constTest(){ int i; cout<<"please input a integer:"; cin>>i; const int a=i; int& r=const_cast<int&>(a);//若寫成int& r=a;則發生編譯錯誤 ++r; cout<<a<<endl;}int main(int argc,char* argv[]){ constTest(); return 0;}
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
程式輸入5,輸出6。
閱讀以上程式,得出如下結論:
(1)const_cast運算子的文法形式是const_cast< type_id> (expression)。
(2)const_cast只能去除目標的const或者volatile屬性,不能進行不同類型的轉換。如下轉換就是錯誤的:
cons tint A={1,2,3};
char* p=const_cast< char*>(A);//不能由const int[]轉換為char*
(3)一個變數被定義為唯讀變數(常變數),那麼它永遠是常變數。cosnt_cast取消的是間接引用時的改寫入權限,而不能改變變數本身的const屬性。
(4)利用傳統的C語言中的強制類型轉換也可以將const type*類型轉換為type*類型,或者將const type&轉換為type&類型。但是使用const_cast會更好一些,因為const_cast轉換能力較弱,目的明確,不易出錯,而C風格的強制類型轉換能力太強,風險較大,故建議不要採用C風格的強制類型轉換。
參考文獻
[1]陳剛.C++進階進階教程[M].武漢:武漢大學出版社,2008.
C/C++中const的用法