1. 程式人生 > >Effective C++——》條款3:儘可能使用const .

Effective C++——》條款3:儘可能使用const .

const是常量的意思,它可以定義一個不可改變的量,主要用於以下幾個地方:

1. 修飾變數,使之不可改變

舉個例子:

const int var = 3;

此時var的值就不能改變了。也正是因為const的變數不能輕易修改儲存的值,所以在宣告的時候就要初始化,這樣就是不行的:

const int var;

編譯器就會報錯。

2. 修飾指標

指標是特殊的變數,有時我們希望對它所指向的物件操作,而有時我們又希望對指標本身進行操作。同樣,const應用於指標也有兩個含義:一個是指向常量(指向的內容不可更改),一個是常量指標(指標的指向不可更改)。看下面這兩個例子:

const int* p = &a;/* p為指向常量的指標,即p指向的物件是常量
即a是常量,不可以通過*p = 3 來修改a的值,但這時p = &b換個指向還是可以的 */
int* const p = &a; /* p為常量指標,即p的指向不可更改,即指向的地址不改變即必須是&a,不可以通過p = &b來修改p的指向,但這時*p = 3改變a的值還是可以的 */ const int* const p = &a; /* p為指向常量的常量指標即什麼也不能改變,p的指向以及指向的物件都不可以更改,無論是*p = 3,還是p = &b都是錯誤的 */

還有一種形式是int const *p,這種形式是表示常量指標,還是指向常量的指標呢?Effective C++給出的建議是看“*”的位置,當const位於星號左側時,const修飾的是值

,即表示指向常量,而當const位於星號右側時,const修飾的是指標,即表示常量指標。所以int const *p等價於const int *p,你想對了嗎?(關鍵看const修飾的是*p還是p)。

const有時還會修飾函式的形參或者函式的返回值,都是屬於1或2這兩種情況。修飾函式形參的用法:

void fun(const char a)
{
a = ‘d’; // 錯誤,因為的值不可以改變
cout << a; // OK
}

還有一個地方要注意一下,若有:

void fun1(const char* a)
{
cout << a << endl;
}
void fun2(char *a)
{
cout << a << endl;
}

當實參為const時,比如const char* msg = “hello”,此時fun1(msg)是可以的,但fun2(msg)會報編譯錯,說是無法將const char*轉成char*;而當實參為普通變數時,比如char* msg = “hello”,fun1(msg)和fun2(msg)都是OK的。這是為什麼呢?因為當const的變數傳遞給非const的變數會不安全(非const的變數可以修改原來定義為常量的東西了!),所以C++限制了這種用法(需用強制型別轉換來告訴編譯器,編譯器才會放行);而反過來,當非const的變數傳遞給const變數時,不存在安全問題,所以C++編譯器總是會放行的。因此,如果在函式體內確實不改變形參a的值,那麼採用帶const的fun1的寫法會更好,適用性更強。

3. 修飾迭代器

C++的STL中使用迭代器作為介面,它定義了普通的迭代器,如vector<T>::iterator,也定義了指向常量的迭代器,如vector<T>::const_iterator,初學者可能想當然地認為const vector<T>::iterator等價於vector<T>::const_iterator,其實不是這樣的,const vector<T>::iterator表示這個迭代器的指向不可以更改,即表示的是常量迭代器

4. 在類中修飾成員函式

const放在類中成員函式的後面,表示這個成為函式不會修改類的成員變數,比如:

class A
{
private:
int a;
double b;
public:
void fun1() const;
void fun2();
};

注意這裡的fun1()函式後面有一個const,表示這個函式不會修改類的成員變數(在它的函式體裡面出現任何改變a或b的值的操作,均不能通過編譯);另一方面fun2()函式後面沒有const,表示這個函式可能修改類的成員變數,注意這裡用的詞是“可能”,fun2()可以修改也可以不修改,但為了增強安全性,所以良好的程式設計風格一般會把不改動成員變數的成員函式修飾為const的。

有一點要切記:有無const是可以構成成員函式的過載的

在本書中還提到了一個尖銳的問題,如果假定類是這樣的:

class B
{
private:
int* p;
public:
…
};

我們看到,類的成員函式是指標,假定它在建構函式時會被初始化,而指向一段記憶體空間。那麼如果不改變p本身(即指向不變),但是改變了p指向的內容(比如*p = 3),這樣到底算不算對成員變數進行改動了呢?

讀者可以在VS環境中寫一下測試用例,可以發現VS編譯器對這種情況是放行的,*p = 3完全可以通過,但是p = &b就不可以了。

雖然編譯器是放過你了,但這也許並不是你的本意,本書中推薦的是“從邏輯上看”,就要交由寫程式碼的你去好好思量一下。如果在某個函式裡確實改動了p所指向的內容,那麼最好就不要加上const;反過來,如果加上了const就不要改變成員變數,包括它所指向的值。

在const和非const成員函式中避免重複

我覺得這是一個非常重要的內容,有沒有加const是構成函式過載的,但通常這種過載的相似度很高,就用書上的例子:

class TestBlock
{
private:
string text;
public:
...
const char& operator[](size_t position) const
{
return text[position];
}
char& operator[](size_t position)
{
return text[position];
}
};

可以看到兩個過載函式裡面的操作都是一樣的,別因此認為可以用ctrl+c,ctrl+v而省事了,如果你要改動其中一個函式體裡的內容,另一個就要同步更新,而萬一你忘記了更新,後果是非常嚴重的!

一個好的方法來實現同步——在非const的函式中呼叫const函式!這樣來修改

char& operator[] (size_t position)
{
return const_cast<char&>(
static_cast<const TestBlock&>(*this)[postion]
);
}

說白了,就進行兩次轉換,一次是把非const的物件(就是自己(*this)轉成const物件),但注意返回值要求是非const的,所以用const_cast再進行一次轉換就OK了。關於C++轉換可以參照