【C++】儘可能使用const
指標、迭代器與const的關係
1. 如果關鍵字出現在星號左邊,表示被指物是常量;如果出現在星號右邊,表示指標自身是常量;如果出現在星號兩邊,表示被指物和指標兩者都是常量。如果被指物是常量,把 const 寫在型別之前或者型別之後、星號之前是一樣的。
2. 迭代器的作用就像 T* 指標,const 在迭代器前,迭代器不能改變,即宣告一個 T* const 指標;const 在迭代器變數前,迭代器所指變數不能變,即宣告一個 const T* 指標,需要用 const_iterator。
vector<int> vec;
const vector<int >::iterator iter = vec.begin();
//iter就像個T* const
*iter = 10; //right
++iter; //iter不能變
vector<int>::const_iterator cIter = vec.begin();
//cIter就像個const T*
*cIter = 20; //*cIter 不能變
cIter++; //right
面對函式宣告時的const
const與函式返回值
令函式返回一個常量值,往往可以降低因客戶錯誤而造成的意外,而又不至於放棄安全性和高效性。
const與函式引數
除非需要改動引數或者 local 物件,否則將引數宣告為 const
const成員函式
目的:確認該成員函式可以作用於const物件
身上。
為什麼?
1. 使class介面容易被理解,可以得知哪個函式可以改動物件內容而哪個函式不行
2. 可以操作const物件,const成員函式可用來處理取得的const物件,也可以操作non-const物件
PS:兩個成員函式如果只是常量性不同,那麼也可以被過載。
class TextBlock
{
public:
TextBlock(string str)
{
text = str;
}
const char& operator[] (size_t position) const
{
cout << "call const" << endl;
return text[position];
}
char& operator[] (size_t position)
{
cout << "call non-const" << endl;
return text[position];
}
private:
string text;
};
int main()
{
TextBlock tb("Hello");
cout << tb[0] << endl;//呼叫non-const
tb[0] = 'X'; //right
const TextBlock ctb("World");
cout << ctb[0] << endl;//呼叫const
//ctb[0] = 'X'; //error 寫一個const物件
return 0;
程式輸出:
還要注意,`non-const operator[]` 返回型別是 `char&` **如果返回型別是個內建型別,那麼改動函式返回值從來就不合法**。即使合法,那麼按照pass by value返回,改變的也只是物件的一個副本,而沒有改變物件本身。
成員函式是const意味著什麼?
兩個流行概念:bitwise constness
和logical constness
bitwise constness
是C++對常量性的定義,const成員函式不可以更改物件內任何non-static成員變數。
一個更改了“指標所指物”的成員函式雖然不能算是const,但如果只有指標隸屬於物件,而指標所指物件不屬於物件,那麼稱此函式為bitwise constness
編譯器不會有異議。
using namespace std;
class CTextBlock
{
public:
CTextBlock(const char text[])
{
pText = (char*)malloc(sizeof(char)*strlen(text));
strcpy(pText,text);
}
char& operator[] (size_t position)const
{
return pText[position];
}
private:
char *pText;
};
int main()
{
const CTextBlock cctb("Hello");
char* pc = &cctb[0];
*pc = 'J';
cout << cctb[0] << endl;
return 0;
}
operator實現程式碼並不更改pText,編譯器認定它是bitwise constness。建立了一個常量物件,並設初值,而且只對它呼叫const成員函式,但是cctb的成員內容變成了”Jello”,終究還是改變了它的值。
logical constness
一個const成員函式可以修改它所處理的物件內的某些bits,但只有在客戶端偵測不出的情況下才得如此。
class CTextBlock
{
public:
CTextBlock(const char text[])
{
pText = (char*)malloc(sizeof(char)*strlen(text));
strcpy(pText,text);
}
size_t length() const;
private:
char *pText;
size_t textLenght; //最近一次計算的文字區塊長度
bool lengthIsValid; //目前的長度是否有效
};
size_t CTextBlock::length() const
{
if(!lengthIsValid)
{
textLenght = strlen(pText); //error
lengthIsValid = true; //在const成員函式內不能給物件成員賦值
}
return textLenght;
}
成員函式
length()
當然不是bitwise const,因為textLength
和lengthIsValid
都可能被修改。但是編譯器堅持認為是bitwise const
,因此出現 error。解決方法:利用C++的一個與 const 相關的擺動場:mutable(可變的)。mutable 釋放掉 non-static 成員變數的 bitwise constness 約束。
比如上例中改為:
mutable size_t textLength; mutable bool lengthIsValid;
這樣在const成員函式內,這些變數也可被更改。
在const和non-const成員函式中避免程式碼重複
比如const完全和non-const版本做的工作一樣,只是其返回型別多了一個const資格修飾。這時候我們想的是實現這個功能一次並使用兩次,也就是說必須令其中一個呼叫另一個。
那麼誰呼叫誰呢?
const 成員函式呼叫 non-const 成員函式是一種錯誤行為,因為物件有可能因此被改動;而反向呼叫是安全的,non-const成員函式本來就可以對其物件做任何動作,所以在其中呼叫一個const成員函式並不會帶來風險。
還是以 class TextBlock
為例:
/* const 和以前一樣*/
const char& operator[] (size_t position) const
{
...
...
...
return text[position];
}
/* non-const 呼叫 const operator[]*/
char& operator[] (size_t position)
{
return
const_cast<char&>(
static_cast<const TextBlock&>(*this)
[position]
);
return text[position];
}
兩次轉型:
1. 將自身*this
從原始型別TextBlock&
轉型為const TextBlock&
,使用static_cast
為*this
加上const
;
2. 從const operator[]
的返回值中移除const
,用const_cast
。
請記住:
1. 將某些東西宣告為const可幫助編譯器偵測出錯誤用法。const 可被施加於任何作用域內的物件、函式引數、函式返回型別、成員函式本體。
2. 編譯器強制實施bitwise constness,但編寫程式時應該使用“概念上的常量性”。
3. 當 const 和 non-const 成員函式有著實質等價的實現時,令non-const 版本呼叫 const 版本可避免程式碼重複。