const與指標結合
const與指標結合有兩種情況:一個常指標:指向不能變;指向常對象的指標:可以指向其他的變數,但這些變數必須是const類型。怎麼區別它們呢?const在*左邊,則是一個指向常對象的指標,而const在*的右邊,則是指標的指向不能變。舉個例子:
int a = 10;int b = 5;const int *pa = &a;//(*pa) = 10;指標指向的對象是常量,不能通過指標修改對象int * const pb = &b;//pb = &a;常指標,不能修改指向
const與迭代器結合
以此類推,由指標實現的迭代器也有兩種類型:
vector<int> ivec;for(int i = 0; i < 3;++i)ivec.push_back(i);vector<int>::const_iterator citer = ivec.begin();//*citer = 10;不能通過const_iterator修改指向的對象++citer;//可以修改指向const vector<int>::iterator iter = ivec.begin();*iter = 10;//++iter;不能改變指向
const與函數的傳回值,參數,函數自身結合與傳回值結合:
為什麼要讓一個函數返回一個const值呢?舉一個例子可以說明,假如我們定義了一個類,並重載了乘法操作符*,和賦值操作符=。那麼對於這個類的3個對象a,b,c,下面的操作就變得合法了:(a*b) = c;但是如果乘法操作的傳回值是const類型的,那麼(a*b) = c就不合法了。
很多人都會對這個小的修改不屑一顧,因為正常人都不會這麼寫程式,但是很多時候,我們都會將if((a*b) == c)寫成(a*b) = c!這時候,這個操作就派上了用場,編譯器會直接檢查出這個操作的錯誤。
const與類的成員函數結合,構成const成員函數。const成員函數不能改變對象的值。如果的確是這樣,那麼盡量將函數寫為const函數,因為:
1.它使類的介面更加容易理解,讓人們一看就知道哪個函數是改變對象內容的,哪個不是:
class Test{private:int val;public:Test(int i = 10):val(i){}int getVal()const;void setVal(int);};
值得注意的是,如果聲明為const函數,那麼在定義時,也要加上const:
#include "item2.h"int Test::getVal()const{return val;}void Test::setVal(int i){val = i;}
2.它們是得“操作const對象成為可能”。為什麼要操作const對象呢?說來話長。我們先看一個例子:
class Person{public:Person(string nm = "mao",string add = "china"):name(nm),address(add){}private:string name;string address;};class Student:public Person{public:Student(int i = 0):schoolNumber(i){}bool is_same(Student);private:int schoolNumber;};
其中,is_same函數完成比較兩個學生是否是同一個人的操作:
bool Student::is_same(Student s){return schoolNumber == s.schoolNumber;}
但是,這個函數的效率很低,原因在於:這個函數是按值傳遞的。這意味著會有一個Student類型的實參複製給s。這就會調用s的建構函式,s的建構函式會調用Person的建構函式,而Person的建構函式會調用那兩個string對象的建構函式。有沒有辦法提高他的效率呢?有的,就是pass by refrence to const:bool is_same(Student &)const;具體來說,因為原函數傳遞的是值,並不會修改對象的資料成員,所以可以把它替換為使用const函數;其次,傳遞引用時,並不會發生對象的複製,也就沒有建構函式,解構函式的調用了。
扯遠了一點,我們再回到問題的開始:因為pass by refrence to const是更有效作法,所以我們會經常這樣做,但是這樣做的前提,就是需要把函數定義為const函數。
const函數還有一點需要注意的是,他可以與非const函數發生重載:
class TextBook{public:TextBook(string bookNumber = "newbook",string content = "this is a book"):isbn(bookNumber),test(content){}char &operator[](size_t pos){return test[pos];}const char &operator[](size_t pos)const{return test[pos];}private:string isbn;string test;};
那麼
TextBook book("effective c++","item3");cout<<book[1]<<endl;//調用非const操作符const TextBook book_read_only;cout<<book_read_only[1]<<endl;//調用const操作符[]//book_read_only[1] = "aaa";//錯誤,無法修改
關於const函數,還有一個值得思辨的地方,就是假如這個傳遞給函數的參數是一個指標,那麼const函數的確不會修改這個指標,但是我們可以輕易地通過指標來修改這個對象的其他內容。此時,最好不要把它聲明為const函數。假如我們的類需要跟C API通訊,那麼就得使用char*來儲存書籍內容了:
class TextBook{public:TextBook(char* content = "aaa"){int i = 0;while(content[i] != NULL){++i;}ptest = (char*)malloc(sizeof(char) * i);int j = 0;for( j = 0 ; j < i;++j)ptest[j] = content[j];ptest[j] = NULL;}char& operator[](size_t pos)const{return ptest[pos];}private:char* ptest;};
此時雖然將下標操作符聲明為const函數,但是編譯器依然允許我們改動的內容,反正我們並沒有改變指標的指向:
TextBook book("item3");cout<<book[0]<<endl;book[0] = 'I';cout<<book[0]<<endl;
這樣的const函式宣告就會遭人誤解,還是不要聲明為好。
但有的時候,我們又希望某些成員變數在const函數中也能被改動,怎麼辦呢?就是使用mutable關鍵字來聲明這個變數:
int getRemainingNumber()const{return total--;}private:char* ptest;mutable int total;
最後,我們還要說一個問題。就是如果一個函數的const版本和非const版本的功能類似,那麼能不能通過一個調用另外一個呢?答案是可以的:我們可以在非const版本中調用const版本,而不要(而不是不能,無語ing)在const版本中調用非const版本。後者是顯然的,const函數既然承諾不修改資料成員,那麼調用可以修改資料成員的非const版本就顯得名不副實。前者需要仔細的推敲:
const char& operator[](size_t pos)const{return content[pos];}char& operator[](size_t pos){return const_cast<char&>(static_cast<const TextBook&>(*this)[pos]);//首先,將*this由原來的TextBook&轉化為const TextBook&//然後,調用const char& operator[](size_t pos)const,如果沒有第一部,則會自己遞迴調用自己!//最後,使用const_cast<char&>去掉const類型}
這一小節的內容差不多就是這麼多了,總的來說:
1.能用const的地方盡量用,這樣編譯器會幫你檢查很多錯誤。
2.如果如果一個函數有const版本和非const版本,那麼我們可以使用非const版本來調用const版本,來減少代碼的工作量。