1 const和指針的關係
對於我這個c++初學者來說,指針搭配常量讓人有點小頭疼。在描述一些概念比如“指向const的const指針”更讓人大腦一時轉不過來。
看書中的例子:
char greeting[] = "HELLO"
char* p = greeting; //non-const pointer,non-const data
char* const p = greeting; //const pointer,non-const data
const char* p = greeting; //non-const pointer,const data
const char* const p = greeting; //const pointer,const data
記住書中總結的三句話可以理清const和指針的關係。
const 出現在*左邊,表示被指物是常量 const char* p 等價於 char const *p
const 出現在*右邊,表示指針自身是常量
const出現在*兩邊,被指物和指針都是常量
下面通過迭代器的例子來說明const和指針的關係:
std::vector<int> vec;
const std::vector<int>::iterator iter = vec.begin(); //等價於聲明一個T* const指針 指針自身不可改變,被指物可以改動。
*iter = 10; //OK 改變被指物
++iter; //error iter是const
爲了改變指針,可以採用const_iterator,如下:
const std::vector<int>::const_iterator cIter = vec.begin(); //等價於const T*
*cIter = 10 ;//error! 被指物是const
++cIter; //OK 改變指針自身
2 聲明爲const可以幫助編譯器偵測出錯誤用法。const 可以被加到任何作用於內的對象,函數參數,返回值,成員函數本體。
看下面的例子:
const Rational operator*(const Rational& l,const Rational& r) //重載了operator* 的操作
調用時:
Rational a,b,c
if((a*b)= c) //爲什麼會有這種寫法?其實本意是(a*b)== c)好不好! 使用const可以避免(a*b)被重新複製。
3 兩個成員函數如果是常量性不同,可以被重載。
例:
class TextBlock{
public:
const char& operator[](std::size_t pos) const //①const object
{
return text[pos];
}
char& operator[](std::size_t pos) //②non-const object
{
return text[pos];
}
}
使用時:
TextBlock tb("hello"); // 匹配②non-const函數
cout << tb[0];
void print(const TextBlock& ctb) // 匹配①const函數
{
cout << ctb[0];
}
4 編譯器強制實施bitwise constness,但是在編碼時應使用概念上的常量性(conceptual constness)。
bitwise constness陣營的人相信:成員函數只有在不更改對象的任何成員變量(static除外)時纔可以說是const。
也就是說const成員函數不可以更改任何non-const成員變量。
不幸的是:如果一個更改了“指向物”的成員函數不能算是bitwise,但是如果其指針屬於對象,那麼此函數爲bitwise可以被編譯器編譯。
看下面的例子:
class CTextBlock{
public:
char& operator[](std:size_t position) const
{return pText[position];}
private:
char* pText; //指針屬於對象的成員變量
};
該函數不適當地聲明瞭一個const函數,而函數返回一個引用指向內部值。 由於operator[]實現代碼並不更改pText,因此編譯器認爲是bitwise const。
但是以下操作終究還是可以改變它的值:
const cTextBlock cctb("hello");
char* pc = &cctb[0];
*pc ='J';
以上的情況編譯器不會報錯,但是終究還是改變了值。於是導出了logical constness,他們認爲一個const成員函數可以修改它所處理的對象內的某些bits,但只在客戶端偵測不出的情況下才得如此。
下面這個例子CTextBlock class實現高速緩存文本區塊的長度以便應付詢問:
class CTextBlock{
public:
std::size_t lenth() const;
private:
char* pText;
std::size_t textLenth; //最後一次計算的文本區塊長度。non-const都可能被修改
bool lenthIsValid; //目前的長度是否有效。non-const都可能被修改
};
std::size_t CtextBlock::lenth() const;
{
if(!lenthIsValid){
textLenth = std::strlen(pText); //error! 在const成員函數中不能賦值給textLenth和lengthIsValid,解決方法:mutable
lenthIsValid = true;//error!
}
return textLenth;
}
上面的解決方法可以使用mutable(可變的),它能放掉non-static成員變量的bitwise constness約束。
class CTextBlock{
public:
std::size_t lenth() const;
private:
char* pText;
mutabe std::size_t textLenth; //最後一次計算的文本區塊長度。non-const都可能被修改
mutabe bool lenthIsValid; //目前的長度是否有效。non-const都可能被修改
};
5 當const和non-const成員函數有着等值等價的實現時,令non-const版本調用const版本可以避免代碼重複。
令non-const operator[]調用其const兄弟可以避免代碼重複的安全做法如下:
class TextBlock{
public:
const char& operator[](std::size_t pos) const
{
。。。//各種等價check
return text[pos];
}
char& operator[](std::size_t pos)
{
return //調用const將op[],將返回值的const轉除????
const_cast<char&>(
static_case<const TextBlock&>(*this)[pos]
);
}
}
說明:兩次轉型
(1)<const TextBlock&>(*this) 爲了避免調用自己無窮遞歸,將*this從原始類型TextBlock& 轉爲const TextBlock& 。使用static_case進行強迫安全轉型。
(2)從const operator[]的返回值中移除const
注:可以在non-const中調用const,但是反之從const中調用non-const存在不安全性,不提倡。