上接:C/C++要點全掌握(二)。
上接:C/C++要點全掌握(二)
.11、const辨析
const 是一個“左結合”的修飾符,一般與左側類型標識符結合聲明只讀變量(常量);指針修飾符(*)是一個“右結合”修飾符,一般與右側變量名結合聲明指針(在定義指針時可將”*p”看作一個整體,前面類型即為*p內容的數據類型),其優先級高於const與類型修飾符。如果const與類型修飾符(如int)二者直接相連,中間沒有指針修飾符(*),那麼const和類型修飾符(如int)的前後順序可以任意。
(一)常見常量的兩種定義方法及分析
(1)a為整型常量,a不能為左值(即a不能出現在賦值運算符“=”左邊),故a的值不會改變:
int const a=1;
const int a=1;
a=2;//錯誤
(2) *p為整型常量,*p不能為左值。p為指向i的指針,不能通過*p修改i的值,但直接給i賦值會改變*p的值,可以改變p的值使其不指向i。
int i=1,j;
int const *p=&i;
const int *p=&i;
*p=2;//錯誤
i=2;//此時*p為2
p=&j;//p指向j
(3)p為整型指針常量,p不能為左值。p為指向i的指針常量,不能修改p的指向,可通過*p修改i的值。
int i=1, j;
int* const p=&i;
const pInt p=&i;//typedef int* pInt;
p=&j;//錯誤
*p=2;//此時i為2
(4)*p為整型常量,p為整型指針常量,*p和p都不能為左值。不能修改p的指向,也不能通過*p修改i的值。
int i=1, j;
int const* const p=&i;
const int* const p=&i;
p=&j;//錯誤*p=2;//錯誤
(5)常量數組。Arr[i]不能為左值。
const int arr[5]={1,2,3,4,5};
int const arr[5]={1,2,3,4,5};
arr[0]=11;//錯誤
*(arr+1)=22;//錯誤
(二)const使用注意
(1)const必須初始化
const int a=5;
const int j;//編譯警告“使用了未初始化的局部變量”
int k=j;//運行出錯
(2) const指針可以接受const和非const的地址,但是非const指針只能接受非const的地址。
const int a=1;
int* p=&a;// 不能用一個非常量指針(p)指向常量(a)
(3)在前面介紹堆棧時已提到給字符指針初始化的字符串是存在於常量區,故不能利用字符指針對其修改。
char *p="tht";
p[0]='2';//編譯時沒錯,運行時訪問沖突
(4)在另一連接文件中聲明引用const常量時不能賦值。
extern const int i; //合法
extern const int j=10;//非法,常量不可以被再次賦值
(三)const與函數
(1) const修飾形參
void func1(const int *p);//指針(C/C++)
void func2(const int &i);//引用(C++)
void func3(const A &a);//A為自定義類型
如果該參數作傳出參數用(相對與高級語言的out關鍵字,顯然該參數不能“按值傳遞”),不論它是什麼數據類型,也不論它采用“指針傳遞”還是“引用傳遞”,都不能加const修飾,否則該參數將不能作為左值,進而不能通過它將所需結果傳出。
const只能修飾傳入參數。
如果傳入參數采用“指針傳遞”(函數func1),那麼加const 修飾可以防止意外地改動該指針所指的內容,起到保護作用。如果傳入參數采用“引用傳遞”(函數func2),則const修飾可以保護該參數。如果傳入參數采用“值傳遞”,由於函數將自動產生臨時變量用於復制該參數,該參數本來就無需保護,所以不用加const 修飾。
對於非基本數據類型的參數而言,像“void Func(A a)”這樣聲明的函數注定效率比較底。因為函數體內將產生A 類型的臨時對象用於復制參數a,而臨時對象的構造、復制、析構過程都將消耗時間。為了提高效率,可以將函數聲明改為“void Func(A &a)”,因為“引用傳遞”僅借用一下參數的別名而已,不需要產生臨時對象。但是函數“void Func(A &a)”存在一個缺點:“引用傳遞”有可能改變實參,這是我們不期望的。解決這個問題很容易,加const修飾即可(函數func3)。
對於內部數據類型的參數,不存在構造、析構的過程,而復制也非常快,故不要將“值傳遞”的方式改為“const 引用傳遞”。否則既達不到提高效率的目的,又降低了函數的可理解性。
(2) const修飾返回值
如果給以返回指針方式的函數的返回值加const 修飾,那麼函數返回值(即指針)的內容不能被修改,該返回值只能被賦給加const 修飾的同類型指針。
const char* getStr();//聲明
char* str=getStr();//錯誤
const char * str=getStr();//正確
如果函數返回值采用普通值傳遞方式,由於函數會把返回值復制到外部臨時的存儲單元中,加const 修飾沒有任何價值。
如果返回值不是基本數據類型,將函數“A GetA()”改寫為“const A & GetA()”(引用方式)的確能提高效率。但此時千萬千萬要小心,一定要搞清楚函數究竟是想返回一個對象的“拷貝”還是僅返回“別名”就可以了,否則程序會出錯。函數返回值采用“引用傳遞”的場合並不多,這種方式一般只出現在類的賦值函數中,目的是為了實現鏈式表達。
class A
{
const A & operate = (const A &other); // 賦值函數
};
A a, b, c; // a, b, c 為A 的對象
c=new A();
a = b = c; //正確(正常的鏈式賦值)
(a = b) = c; //錯誤(不正常的鏈式賦值)
如果將賦值函數的返回值去掉const 修飾,上例中,正常的鏈式賦值語句“a = b = c”仍然正確,不正常的鏈式賦值語句“(a = b) = c”也合法。
(3)const修飾成員函數
任何不會修改數據成員的函數都應該聲明為const 類型,表明這個函數不會對這個類對象的數據成員(准確地說是非靜態數據成員)作任何改變。如果在編寫const 成員函數時,不慎修改了數據成員,或者調用了其它非const 成員函數,編譯器將指出錯誤,這無疑會提高程序的健壯性。聲明const 成員函數,const關鍵字只能放在函數聲明的尾部。在設計類的時候,一個原則就是對於不改變數據成員的成員函數都要在後面加const。
class A
{
private:
int m_data;
public:
int getData() const;// const成員函數,只讀不修改數據成員
void setData(int data);/普通函數
};
const成員函數使用需注意:
A. const對象只能訪問const成員函數,而非const對象可以訪問任意的成員函數,包括const成員函數。
A a1;
const A a2=a1;
a1.setData(1);//正確
a1.getData();//正確
a2.setData(2);//錯誤,const對象不能訪問非const成員函數
a2.getData();//正確
B. const成員函數不可以修改對象的數據,不管對象是否具有const性質。它在編譯時,以是否修改成員數據為依據,進行檢查。
C. 然而加上mutable修飾符的數據成員,對於任何情況下通過任何手段都可修改,自然此時的const成員函數是可以修改它的。
(四)const與類的數據成員
上面介紹了const修飾類的成員函數,這裡順便提下const修飾類的數據成員。const數據成員只在某個對象生存期內是常量,而對於整個類而言卻是可變的。因為類可以創建多個對象,不同的對象其const數據成員的值可以不同。所以不能在類聲明中初始化const數據成員,因為類的對象未被創建時,編譯器不知道const 數據成員的值是什麼。
class A
{
public:
int m_data;
const int m_data=1; //編譯錯誤,只有靜態常量整型數據成員才可以在類中初始化
...
};
const數據成員的初始化只能在類的構造函數的初始化表中進行。
class A
{
public:
int m_data;
const int m_const;
A(int data=1,int initConst=0);
};
//構造函數中初始化表中初始化const數據成員
A::A(int data,int initConst):m_const(initConst)
{
m_data=data;
}
(五)const與函數重載
const(或volatile)修飾參數類型時,函數重載的辨析。
(1)當參數的類型以const或volatile修飾時,在識別函數聲明是否相同時,並不考慮const或volatile修飾符,只看參數變量的類型。
//不構成重載
void fun(int a);//整型變量
void fun(const int a);//整型變量,只是該變量在函數體內是只讀的
(2)以下兩個函數聲明構成重載,因為參數是不同的類型,前者指向整型的指針,後者是指向常整型的指針,是不同類型的指針。
void fun(int *a);// 整型指針
void fun(const int *a); //常整型指針
(3)以下兩個函數聲明不構成重載,兩者均是指向整型的指針,只是後者用const修飾了指針,所以不構成重載。
void fun(int *a); // 整型指針
void fun(int *const a); 整型常指針
(4)這裡順便提下,對於成員函數和加了const修飾的成員函數,顯然從語法上無法區分調用哪個,但可以編譯運行,測試結果顯示程序調用了沒加const修飾的成員函數。
#include<iostream>
using namespace std;
class A
{
public:
void fun() const;
void fun();
};
void A::fun() const
{
cout<<"const";
}
void A::fun()
{
cout<<"no const";
}
void main()
{
A a;
a.fun();//輸出:no const
}
摘自 tht的專欄