1. 程式人生 > >【C++】儘可能使用const

【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 constnesslogical 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,因為textLengthlengthIsValid都可能被修改。但是編譯器堅持認為是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 版本可避免程式碼重複。