談談基類與子類的this指針(C++)

引入

  定義一個類的對象,首先系統已經給這個對象分配了空間,然後會調用構造函數(說明:假設存在構造函數)。一個類有多個對象,當程序中調用對象的某個函數時,有可能要訪問到這個對象的成員變量。而對於同一個類的每一個對象,都是共享同一份類函數對象有單獨的變量,但是沒有單獨的函數,所以當調用函數時,系統必須讓函數知道這是哪個對象的操作,從而確定成員變量是哪個對象的。這種用於對成員變量歸屬對像進行區分的東西就叫做this指針。事實上它就是對象的地址.

          記得孫鑫VC++視頻教程裏有一段剖析MFC的代碼,大意就是 CTESTAPP類是CWINAPP的子類,而CTESTAPP創建一個全局對象時,在CWINAPP的構造函數裏面用了this指針,但是這裏this指針指向的的是CTESTAPP的對象,而不是所在類的對象,也許讀者這時候對這個this指針有點糊塗了! 不要怕!馬上讓你明明白白! 請看下面一段代碼:

[html] view plaincopy在CODE上查看代碼片派生到我的代碼片
  1. #include <iostream.h>    
  2. class PARENT{//基類       
  3.              int d; public:   
  4.              PARENT()  
  5.                 { d=1;   
  6.                   cout<<"PARENT this ="<<this<<endl;                
  7.                   cout<<"d="<<this->d<<endl;       
  8.                  }   
  9.   
  10.              };    
  11. class CHILD : public PARENT//子類    
  12.           {  int b;      
  13.   
  14.       public:   
  15.             CHILD():PARENT()  
  16.                    {  b=2;    
  17.                       cout<<"CHILD this = "<<this<<endl;    
  18.                       cout<<"b="<<this->b<<endl;       
  19.                     }   
  20.            };    
  21. int main(int argc, char* argv[])   
  22. {   CHILD cb;//CHILD對象    
  23.     cout<<"CHILD object cb's addr is="<<&cb<<endl;    
  24.     return 0;   
  25. }   

        這段代碼和上面提到的MFC的代碼原理一樣!此代碼運行的結果你會發現this的值都一樣!而且this都是指向cb對象的! 
因爲this指針式在創建一個對象時,隱含的將對象的地址賦予一個指針,那就是this指針。在創建對象cb時,先講cb的首地址賦給this,根據繼承性,首先調用基類的構造函數,雖然此時cb對象還未完全創建(必須調用完子類的構造函數時,此時對象纔會創建完畢),但是此時cb對象的基類部分已經構造完畢,所以這時候的this指針可以看成是cb的this指針,但是隻能調用基類PARENT的數據成員。如果此時你在PARENT()里加上一句cout<<"b="<<this->b<<endl;讓其調用子類的數據成員,則會報錯!因爲this指向的對象沒有構造子類的部分!在執行完基類的構造函數進入子類的構造函數後,這時候this指向的對象構造完成,這時候this指針也就是一個真正的的指向cb的常指針了。

        這時候你也不難理解了MFC當中CWINAPP傳遞的this指針是指向子類CTESTAPP的對象,而不是所在類CWINAPP了!這裏在基類中使用this的意思有點像,由於子類對象沒構造好,但是指向對象的指針已近指向那個對象了,早晚對象都會構造好的,那不如先拿this指針代替子類對象用!

補充:構造函數爲什麼不能是虛函數

       1. 從存儲空間角度,虛函數對應一個指向vtable虛函數表的指針,這大家都知道,可是這個指向vtable的指針其實是存儲在對象的內存空間的。問題出來了,如果構造函數是虛的,就需要通過 vtable來調用,可是對象還沒有實例化,也就是內存空間還沒有,怎麼找vtable呢?所以構造函數不能是虛函數。
2. 從使用角度,虛函數主要用於在信息不全的情況下,能使重載的函數得到對應的調用。構造函數本身就是要初始化實例,那使用虛函數也沒有實際意義呀。所以構造函數沒有必要是虛函數。虛函數的作用在於通過父類的指針或者引用來調用它的時候能夠變成調用子類的那個成員函數。而構造函數是在創建對象時自動調用的,不可能通過父類的指針或者引用去調用,因此也就規定構造函數不能是虛函數。
3. 構造函數不需要是虛函數,也不允許是虛函數,因爲創建一個對象時我們總是要明確指定對象的類型,儘管我們可能通過實驗室的基類的指針或引用去訪問它但析構卻不一定,我們往往通過基類的指針來銷燬對象。這時候如果析構函數不是虛函數,就不能正確識別對象類型從而不能正確調用析構函數。
4. 從實現上看,vbtl在構造函數調用後才建立,因而構造函數不可能成爲虛函數從實際含義上看,在調用構造函數時還不能確定對象的真實類型(因爲子類會調父類的構造函數);而且構造函數的作用是提供初始化,在對象生命期只執行一次,不是對象的動態行爲,也沒有必要成爲虛函數。
5. 當一個構造函數被調用時,它做的首要的事情之一是初始化它的VPTR。因此,它只能知道它是“當前”類的,而完全忽視這個對象後面是否還有繼承者。當編譯器爲這個構造函數產生代碼時,它是爲這個類的構造函數產生代碼——既不是爲基類,也不是爲它的派生類(因爲類不知道誰繼承它)。所以它使用的VPTR必須是對於這個類的VTABLE。而且,只要它是最後的構造函數調用,那麼在這個對象的生命期內,VPTR將保持被初始化爲指向這個VTABLE, 但如果接着還有一個更晚派生的構造函數被調用,這個構造函數又將設置VPTR指向它的 VTABLE,等.直到最後的構造函數結束。VPTR的狀態是由被最後調用的構造函數確定的。這就是爲什麼構造函數調用是從基類到更加派生類順序的另一個理由。但是,當這一系列構造函數調用正發生時,每個構造函數都已經設置VPTR指向它自己的VTABLE。如果函數調用使用虛機制,它將只產生通過它自己的VTABLE的調用,而不是最後的VTABLE(所有構造函數被調用後纔會有最後的VTABLE)。
Note:
1. 如果我們定義了一個構造函數,編譯器就不會再爲我們生成默認構造函數了。
2. 編譯器生成的析構函數是非虛的,除非是一個子類,其父類有個虛析構,此時的函數虛特性來自父類。
3. 有虛函數的類,幾乎可以確定要有個虛析構函數。
4. 如果一個類不可能是基類就不要申明析構函數爲虛函數,虛函數是要耗費空間的。
5. 析構函數的異常退出會導致析構不完全,從而有內存泄露。最好是提供一個管理類,在管理類中提供一個方法來析構,調用者再根據這個方法的結果決定下一步的操作。
6. 在構造函數不要調用虛函數。在基類構造的時候,虛函數是非虛,不會走到派生類中,既是採用的靜態綁定。顯然的是:當我們構造一個子類的對象時,先調用基類的構造函數,構造子類中基類部分,子類還沒有構造,還沒有初始化,如果在基類的構造中調用虛函數,如果可以的話就是調用一個還沒有被初始化的對象,那是很危險的,所以C++中是不可以在構造父類對象部分的時候調用子類的虛函數實現。但是不是說你不可以那麼寫程序,你這麼寫,編譯器也不會報錯。只是你如果這麼寫的話編譯器不會給你調用子類的實現,而是還是調用基類的實現。
7.
析構函數中也不要調用虛函數。在析構的時候會首先調用子類的析構函數,析構掉對象中的子類部分,然後在調用基類的析構函數析構基類部分,如果在基類的析構函數裏面調用虛函數,會導致其調用已經析構了的子類對象裏面的函數,這是非常危險的。
8. 記得在寫派生類的拷貝函數時,調用基類的拷貝函數拷貝基類的部分,不能忘記了。

 http://www.cnblogs.com/cswuyg/archive/2010/08/21/1805153.html


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章