關鍵字 const 非常多才多藝。在類的外部,你可以將它用于全局常量或命名空間常量,就像那些在文件、函數或模塊范圍內被聲明為 static 的對象。在類的內部,你可以將它用于 static 和 non-static 數據成員上。對于指針,你可以指定這個指針本身是 const,或者它所指向的數據是 const,或者兩者都是,或者都不是。
char greeting[] = "Hello";
char *p = greeting; // non-const pointer,
// non-const data
const char *p = greeting; // non-const pointer,
// const data
char * const p = greeting; // const pointer,
// non-const data
const char * const p = greeting; // const pointer,
// const data
這樣的語法本身其實并不像表面上那樣反復無常。如果 const 出現在 * 左邊,則指針指向的內容為常量;如果 const 出現在 * 右邊,則指針自身為常量;如果 const 出現在 * 兩邊,則兩者都為常量。
當指針指向的內容為常量時,一些人將 const 放在類型之前,另一些人將 const 放在類型之后 * 之前。兩者在意義上并沒有區別,所以,如下兩個函數具有相同的參數類型:
void f1(const Widget *pw); // f1 takes a pointer to a
// constant Widget object
void f2(Widget const *pw); // so does f2
因為它們都存在于實際的代碼中,你應該習慣于這兩種形式。
STL iterators 以指針為原型,所以一個 iterator 在行為上非常類似于一個 T* 指針。聲明一個 iterator 為 const 就類似于聲明一個指針為 const(也就是說聲明一個 T* const 指針):不能將 iterator 指向另外一件不同的東西,但是它所指向的東西本身可以變化。如果你要一個 iterator 指向一個不能變化的東西(也就是 const T* 的 STL 對等物),你應該用 const_iterator:
std::vector vec;
...
const std::vector::iterator iter = // iter acts like a T* const
vec.begin();
*iter = 10; // OK, changes what iter points to
++iter; // error! iter is const
std::vector::const_iterator cIter = //cIter acts like a const T*
vec.begin();
*cIter = 10; // error! *cIter is const
++cIter; // fine, changes cIter
對 const 最強有力的用法來自于它在函數聲明中的應用。在一個函數聲明中,const 既可以用在函數返回值上,也可以用在個別的參數上,對于成員函數,還可以用于整個函數。
一個函數返回一個常量,常常可以在不放棄安全和效率的前提下盡可能減少客戶的錯誤造成的影響。例如,考慮在 Item 24 中考察的 rational 成員 operator* 的聲明:
class Rational { ... };
const Rational operator*(const Rational& lhs, const Rational& rhs);
很多第一次看到這些的人會不以為然。為什么 operator* 的結果應該是一個 const 對象?因為如果它不是,客戶就可以犯下如此暴行:
Rational a, b, c;
...
(a * b) = c; // invoke operator= on the
// result of a*b!
我不知道為什么一些程序員要為兩個數的乘積賦值,但是我知道很多程序員這樣做也并非不稱職。所有這些可能來自一個簡單的輸入錯誤(要求這個類型能夠隱式轉型到 bool):
if (a * b = c) ... // oops, meant to do a comparison!
如果 a 和 b 是內建類型,這樣的代碼顯而易見是非法的。一個好的用戶自定義類型的特點就是要避免與內建類型毫無理由的不和諧,而且對我來說允許給兩個數的乘積賦值看上去正是毫無理由的。將 operator* 的返回值聲明為 const 就可以避免這一點,這就是我們要這樣做的理由。
關于 const 參數沒什么特別新鮮之處——它們的行為就像局部的 const 對象,而且無論何時,只要你能,你就應該這樣使用。除非你需要改變一個參數或本地對象的能力,否則,確認將它聲明為 const。它只需要你鍵入六個字符,就能將你從我們剛剛看到的這個惱人的錯誤中拯救出來:“我想鍵入‘==’,但我意外地鍵入了‘=’”。
const 成員函數
成員函數被聲明為 const 的目的是確信這個函數可能會被 const 對象調用。因為兩個原因,這樣的成員函數非常重要。首先,它使一個類的接口更容易被理解。知道哪個函數可以改變對象而哪個不可以是很重要的。第二,它們可以和 const 對象一起工作。書寫高效代碼有一個很重要的方面,就像 Item 20 所解釋的,提升一個 C++ 程序的性能的基本方法就是就是傳遞一個對象的引用給一個 const 參數。這個技術只有在 const 候選對象有 const 成員函數可操作時才是可用的。
很多人沒有注意到這樣的事實,即成員函數只有常量性不同時是可以被重載的,這是 C++ 的一個重要特性。考慮一個代表文字塊的類:
class TextBlock {
public:
...
const char& operator[](std::size_t position) const // operator[] for
{ return text[position]; } // const objects
char& operator[](std::size_t position) // operator[] for
{ return text[position]; } // non-const objects
private:
std::string text;
};
TextBlock 的 operator[]s 可能會這樣使用:
TextBlock tb("Hello");
std::cout << tb[0]; // calls non-const
// TextBlock::operator[]
const TextBlock ctb("World");
std::cout << ctb[0]; // calls const TextBlock::operator[]
|