預備知識
1、代碼轉換分析技巧
#include<iostream> #include<cstdio> using namespace std; class Dog { public: Dog(unsigned en = 0) :energe(en){} void bark() const { for (size_t i = 0; i < energe; i++) cout << "wang wang!\n"; } void feed(unsigned add) { energe += add; } private: unsigned energe; }; int main() { Dog dog; dog.feed(2); dog.bark(); return 0; }
翻譯轉換
//原型轉換為C代碼后的樣子 void bark(const Dog*this); void feed(Dog*this,unsigned add); //調用時的轉換 dog.feed(2); -----> feed(&dog , 2); dog.bark(); -----> bark(&dog);
2、頂層const,底層const
頂層const:指針變量本身是常量。(頂層const不適合引用,因為引用天生就是一個常量,始終引用一個對象,直到消亡)
如 int* const p = &a;
底層const:變量指向(或者引用)的對象被視為常量。(注意這里的用詞:被視為,因為對象不一定就是常量,也可能是底層const指針、引用的一廂情願)
如 const int*p ; int const *p; 這二者寫法等價
const int& r;
一個非底層const指針,它對它指向的對象有 讀、寫的權利,因此,為了確保數據安全,它僅能指向非常量對象。
int a=1; const int b = 2; int*p1 = &a; //OK p1 = &b; //ERROR不允許
一個底層const指針,他對它指向的對象僅有讀的權利。因此,它可以指向常量和非常量。
int a=1; const int b = 2; const int*p1 = &a; //OK p1 = &b ; //OK
3、函數的參數是否為 底層const 可以作為重載的依據
舉例說,下面2個函數可以重載。
void foo(const int*p); //A void foo(int*p); //B
編譯器在重載解析時,根據傳遞的參數來判斷調用哪一個版本。
當僅存在版本A時,若傳遞的是非常量int的指針,或常量int的指針,都可以成功調用。
當僅存在版本B時,若傳遞的是非常量int的指針,可以成功調用,但不允許傳遞常量int的指針。
當A、B 都存在,重載時,若傳遞的是非常量int的指針,則優先使用版本B,因為這是最匹配的。
若傳遞的是常量int的指針, 則使用版本A。
另外,參數本身是否是const 不作為重載的依據,下面的不能重載。
void foo(const int a); void foo(int a); //C語言中,參數修飾為const和不使用const修飾 被編譯器一樣對待,C++為兼容C,也使用了這個策略。因此二者等價。
如果一個成員函數在邏輯上不會修改對象的狀態(字段),就應該定義為const函數
在上面的Dog類代碼中,如果去掉bark函數后的const修飾符,並試着用一個const 對象去調用bark函數,則發現編譯器報錯。
void bark() { for (size_t i = 0; i < energe; i++) cout << "wang wang!\n"; } ///////////////////////// const Dog dog; dog.bark(); //錯誤提示為類型不兼容
拋開代碼,從業務邏輯上來看,bark函數只是在屏幕上輸出消息,根本不會改變對象的狀態,那即便是const 對象,也必須能成功調用啊。
這里報錯顯然是bark函數的問題:應該定義為const函數,這點大家都是很清楚的。但是為什么要這樣的呢?
按照最開始介紹的分析方法,發生錯誤的代碼等價與下面的C代碼。
const Dog dog; bark(&dog) //而bark函數轉化后的原型為:void bark(Dog*this);
顯然,將常量的指針賦值給非常量指針是不允許的。
再比如,C++標准庫中的string類,它的用於獲取字符串長度的成員函數都是const修飾的。如果不是這樣的話,那么字符串常量就不能獲取他們的長度了,這簡直荒謬!
size_type size() const; size_type length() const;
const成員函數可以形成重載
成員函數同時存在 const版本和非const版本?可以重載?是的。
同樣,先按照最開始介紹的第1條分析方法,轉換為C代碼。然后根據第3條的分析就可以了。這里不再贅述。
最常用的就是,按照約定當重載索引運算符 [ ] 時,會同時編寫一個 const版本和非const版本。
例如std::vector的[ ]運算符函數
reference operator[]( size_type pos ); const_reference operator[]( size_type pos ) const;
總結
1、如果一個成員函數在邏輯上不會修改對象的狀態(字段),就應該定義為const函數
2、
如果對象是const,則它只能調用const成員函數。
如果對象是普通的非const對象:
調用的某個成員函數是非const函數,則理所當然調用它。
調用的某個成員函數是const函數,則當然也可以調用他。(底層const指針可以指向非常量對象)
調用的某個成員函數同時存在 const版本和非const版本,則優先調用非const成員函數,編譯器總是使用最匹配的版本。