成員函數後面加const,表示在該函數中不能對類的數據成員進行改變,好比下面的代碼:
1 #include <stdio.h> 2 3 class A 4 { 5 private: 6 mutable int aa; 7 public: 8 A(){} 9 int x() 10 { 11 printf("no const\n"); 12 return aa++; 13 } 14 int x() const 15 { 16 printf("const\n"); 17 return aa++; 18 } 19 }; 20 21 int main() 22 { 23 A a1; 24 a1.x(); 25 26 const A a2; 27 a2.x(); 28 29 return 0; 30 }
代碼的運行結果爲:函數
首先說明成員函數後面加const這種機制,這是軟件工程上的一種機制,在先前的軟件開發中,類中各個函數的實現是由不一樣人完成了,因此你們共同操做同一組的數據成員,這樣就會產生一種麻煩,會有些菜鳥或者不遵照規則的傢伙胡亂的修改數據成員。當時只能用文檔的形式來約束你們,在開發文檔裏清楚的寫上你的這個函數實現不容許任何的修改數據成員,可是這樣的文檔的形式仍是不能很好的約束那些傢伙,因而C++語言的設計者Stroustrup想出來一個好的辦法:
若是成員函數的後面加上了const,就表示這個成員函數不能對數據成員進行任何的修改動做。我把每一個函數的聲明都寫好,不能修改數據成員的函數我在後面加上const。首先你的函數的實現也必須加上相應的const,不然函數的聲明和實現不一樣,跑步起來;其次只要你在函數後面加上了const,就不能修改數據成員了。這樣從編譯器的水平攔截修改數據成員的行爲,而不是從文檔的角度限制這種行爲。this
固然有特殊狀況,就是用mutable關鍵字修飾過的成員變量能夠在聲明爲const 函數中被改變。
關於mutable:
關鍵字mutable是C++中一個不經常使用的關鍵字,他只能用於類的非靜態和很是量數據成員。
咱們知道一個對象的狀態由該對象的非靜態數據成員決定,因此隨着數據成員的改變,對像的狀態也會隨之發生變化!
若是一個類的成員函數被聲明爲const類型,表示該函數不會改變對象的狀態,也就是該函數不會修改類的非靜態數據成員。
可是有些時候須要在該類函數中對類的數據成員進行賦值。這個時候就須要用到mutable關鍵字了。
可是上面的程序還有一個問題,在程序中實現了函數的重載,可是這個重載比較古怪,函數的參數列表是徹底同樣的,不同的是函數後面有沒有const修飾。程序的實現是這樣的,你用const的對象去調用這個函數,就走加const的成員函數,用非const的對象去調用這個函數就走沒有加const的成員函數。那麼具體的實現怎麼樣呢?下面繼續看:
這裏先給出重載的結論:
咱們知道,若是函數名相同,在相同的做用域內,其參數類型、參數個數,參數順序不一樣等能構成函數重載。有趣的是若是同時在類中,對於函數名相同參數列表也相同的成員函數的const函數和非const函數可以構成重載。
它們被調用的時機爲:若是定義的對象是常對象,則調用的是const成員函數,若是定義的對象是很是對象,則調用重載的非const成員函數。
上面的這句話是必須的,也就是若是你定義的對象是const的,那麼你的這個對象只能調用帶有const的成員函數,若是你調用非const的成員函數,編譯器就會報錯。
這個是理所固然的,試想你定義的對象是const的,意思就是你的對象的數據成員是不能夠被改變的。若是容許你調用非const的成員函數,萬一你在該函數中修改了對象的數據成員怎麼辦。與其說在你試圖修改數據成員的時候再攔截你還不如在編譯的時候就直接攔截你。看下面的代碼:
1 #include <stdio.h> 2 3 class A 4 { 5 private: 6 int aa; 7 public: 8 A(){} 9 int x() 10 { 11 printf("no const\n"); 12 return aa++; 13 } 14 }; 15 16 int main() 17 { 18 const A a2; 19 a2.x(); 20 21 return 0; 22 }
程序會報錯:error C2662: 'x' : cannot convert 'this' pointer from 'const class A' to 'class A &'。spa
這讓我想起來了C++老師說的另外的兩個問題(太經典了,再一次感受到了C++的體系結構,楊老師萬歲):設計
(一)看代碼:指針
1 const int x = 5; 2 int *p; 3 p = &x;
這裏是絕對會報錯的:error C2440: '=' : cannot convert from 'const int *' to 'int *'
你當編譯器是傻子啊,你先聲明瞭一個const的變量,表示這個變量不能被修改,而後你又弄一個int類型指針指向它,表示你能夠經過指針修改數據,自相矛盾。編譯器是不會給你這樣的漏洞的。code
提示:const int x = 5;const修飾的變量在定義的時候必定要初始化(除非是extern的)。對象
在看另個一代碼:blog
1 int x; 2 const int *p; 3 p = &x; 4 5 x++; 6 (*p)++;
這裏也會報錯:error C2166: l-value specifies const object。內存
錯誤的語句是(*p)++;固然定義的x是能夠被修改的,可是const int *p這個指針的意思是:指針能夠隨便的擺(就是p能夠指向x,也能夠指向y),可是有一條:我指向誰,誰不能動。因此const int *p並非說p的值(p的值是對象的地址)不能改變,而是說p指向的對象的值不能被修改。ci
(二)重頭戲來了
老師說過,C++的編譯器會在你的每一個非靜態的成員函數的參數列表中增長一個this指針,由於全部對象的代碼在內存中就一份,this指針的功能是誰調用這個成員函數,該成員函數的這個this指針就指向誰,從而處理誰的數據。(這個理論是這篇文章的重點,重中之重)
那麼咱們的帶const的成員函數和不帶const的成員函數的重載是怎麼識別的呢?我上面只給了個結論,下面我就來揭開這個神祕的面紗:
其實帶const的成員函數和不帶const的成員函數的this指針是不一樣的:
const的成員函數的this指針是:const A*類型的;不帶const的成員函數的this指針是:A*類型的;(這裏的A就是代碼中定義的類,借用一下上面的定義)。
這就恍然大悟了吧:
const成員函數不能修改數據成員的內部實現是這樣的,一個對象調用了某個帶const的成員函數,那麼這個帶const的成員函數的this指針就指向了這個對象,這就是上面(一)中的一種狀況,const A*類型的指針指向誰,誰不能動(不能被修改)。因此帶const的成員函數你休想修改數據成員。只要有調用,你的this指針就得指向這個對象,因爲你的this指針是const A*類型的,只要指向了你就不能修改。哈哈~~讓你得瑟。
還有一種狀況:就是若是一個對象是const A a;這種的,也就是說常量對象。那麼這個對象只能調用帶const的成員函數,由於若是你調用了非const的成員函數,你就是(一)中的另外一種狀況,你事先定義了一個const變量,而後在用一個非const的指針指向他,你當編譯器傻子啊。由於你的非const的成員函數的this指針是A*類型的,沒有const修飾。
總結:const A a對象只能調用const修飾的成員函數,由於a是const修飾的,只能用帶cosnt的this指針才能夠指向他。
A a能夠調用非const修飾的成員函數和const修飾的成員函數。可是編譯器會首先進行檢查,看看const修飾的成員函數有沒有修改數據成員,而後調用非const修飾的成員函數(const修飾的和非const修飾的兩個成員函數構成了重載)。雖然在不修改數據成員的狀況下調用const修飾的成員函數也是能夠的,可是有優先權(這裏是我猜的,有時間問老師)。
洗了個澡,好像大概想通了,既然是函數重載,那麼就根據重載的基礎,參數列表的不一樣。爲何會優先調用非const修飾的成員函數呢?由於非const修飾的成員函數的參數列表是這樣的(A * a,......) 帶const修飾的成員函數的列表是這樣的(const A* a,......),這裏只寫出他們的this指針,固然了,重載的區分也是靠的this指針,防止函數調用時發生二義性。A a這樣的對象調用函數的時候要把本身的地址傳給被調成員函數的this指針,a的地址就是至關於A*這樣類型的指針,並無const修飾啊,因此首先選擇最相近的那個。固然了,若是隻有一個帶const修飾的成員函數,直接調用它也沒不會報錯的。由於具體的實現就好像這樣:const int* p = &x;可是我沒有對x修改,只是簡單的動了copy了一下它的地址。這固然是容許的。
就像下面的代碼:
1 #include <stdio.h> 2 3 class A 4 { 5 private: 6 int aa; 7 public: 8 A(){} 9 10 int x()const 11 { 12 printf("const\n"); 13 14 return 0; 15 } 16 }; 17 18 int main() 19 { 20 A a2; 21 a2.x(); 22 23 return 0; 24 }
這個是能夠正常跑的,不會報錯。
1 #include <stdio.h> 2 3 class A 4 { 5 private: 6 int aa; 7 public: 8 A(){} 9 10 int x() 11 { 12 printf("no const\n"); 13 14 return 0; 15 } 16 17 int x()const 18 { 19 printf("const\n"); 20 21 return 0; 22 } 23 }; 24 25 int main() 26 { 27 A a2; 28 a2.x(); 29 30 return 0; 31 }
這個是會優先選擇非const修飾的成員函數的。
因此,有const修飾的成員函數和沒有const修飾的成員函數之間構成的重載,仍是靠的參數列表區分的調用關係,兩個函數的參數列表的不一樣點是編譯器自動給你的每一個成員函數(靜態的成員函數除外,由於靜態的成員函數沒有this指針,具體分析在另外一篇博文上)添加的this指針是不一樣的,一個是普通的,一個是帶有const修飾的。
下面的這個例子是C++的運算符的重載,這裏你就必須考慮到const修飾的對象的函數調用,也就是說必須作兩套成員函數,一套給有const修飾的對象(好比常量字符串)用,一套給沒有const修飾的對象用。
時間關係,今天先到這裏,還有矩陣分析的做業沒有作,下面的代碼是別人的,有時間再回來弄
--------------------------------------------------------------------------------------------------------------------
char& operator[](int posion) // function_1 { return data[posion]; }; 注意,這裏該函數的返回值爲一個引用,不然str[0] = 'c'這樣的語句就會不合法,由於str[0]將是一個左值。
那麼,是否提供這樣一個function_1就足夠了呢?看下面這段代碼: const String str= "She"; char c = str[0]; // 錯誤!編譯提示:error C2678: 二進制「[」 : 沒有找到接受「const String」類型的左操做數的運算符(或沒有可接受的轉換)
很顯然,咱們必須還要爲const String提供一個const版本的opeartor[]。以下: char& operator[](int posion) const { return data[posion]; } 這樣,當使用const的String對象使用[]操做符時,便會調用該const的重載版本。 可是,這樣就OK了嘛?雖然上面的那段代碼沒有問題了,可是其中卻隱藏了一個陷阱,看以下代碼: const String str = "She"; str[0] = 'T'; 上面這段代碼能夠編譯,運行經過,str變爲了"The"!而str聲明爲const的!!
如今,你應該知道了,對於const的該操做符重載函數其返回值也應該是const的,不然就會出現能夠經過其修改const對象的漏洞。修改以下: const char& operator[](int posion) const { return data[posion]; } 好了,如今沒有問題了!
咱們再回過頭來看一下,爲了給String提供一個[]操做符來讀寫指定位置的字符,須要提供以下兩個函數,以分別對非const String對象和const String對象提供支持:
char& operator[](int posion)
{ return data[posion]; };
const char& operator[](int posion) const { return data[posion]; }