程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C >> C語言基礎知識 >> C++基礎之this指針與另一種“多態”

C++基礎之this指針與另一種“多態”

編輯:C語言基礎知識

一、引入
定義一個類的對象,首先系統已經給這個對象分配了空間,然後會調用構造函數。

一個類有多個對象,當程序中調用對象的某個函數時,有可能要訪問到這個對象的成員變量。
而對於同一個類的每一個對象,都是共享同一份類函數。對象有單獨的變量,但是沒有單獨的函數,所以當調用函數時,系統必須讓函數知道這是哪個對象的操作,從而確定成員變量是哪個對象的。
這種用於對成員變量歸屬對像進行區分的東西,就叫做this指針。事實上它就是對象的地址,這一點從反匯編出來的代碼可以看到。

二、分析
1、測試代碼:
代碼如下:

/////////////////////////////////////////////////////////////////////////////////////
#include<iostream>
using   namespace   std;
/////////////////////////////////////////////////////
class A
{
public:
    A(char *szname)
    {
        cout<<"construct"<<endl;
        name
 = new char[20];
        strcpy(name,
 szname);
    }
    ~A()
    {
        cout<<"destruct"<<endl;
        delete name;
    }
    void    show();
private:
    char    *name;
};
/////////////////////////////////////////////////////
void    A::show()
{
    cout<<"name
 = "<<name<<endl;
}
/////////////////////////////////////////////////////
int main()
{
    A
 a("zhangsan");
    a.show();
    system("pause");
    return 0;
}

程序在VC++6.0 32位操作系統上編譯、運行。
對編譯後的EXE文件,進行反匯編。反匯編工具為OllyDbg。

2、反匯編分析
關鍵點截圖如下:
(1)從圖1可以發現this指針通過ECX寄存器,傳遞給了成員函數。this指針就是對象的地址。

圖 1 Main函數

(2)從圖 2可以發現訪問對象的成員變量用的就是之前通過ECX傳入的this指針。

 

圖 2 show()函數

三、深入理解

通過截圖及相關的資料,可以很清晰的知道在調用構造函數、show()函數之前的那個ECX就是this指針,也就是說這是一個驗證性的實驗,答案已經很清楚了,所要做的就是去動手體驗一下。但是,假如我不懂C++、我不懂什麼this指針,我一樣可以發現這個叫做“this指針”的東西。通過OD的動態調試,當顯示出了name時,逐步回溯可以發現name的源頭是ECX。OD重新載入,查看在進入show()函數之前ECX是哪裡來的,最終可以一步步的發現,ECX就是一個地址,這個地址裡邊的第一個值也是一個地址,指向一串字符串。再往上分析,進入show()上邊的構造函數,可以發現裡邊有new操作,strcpy操作,這裡就發現了字符串空間、內容的來源。至此,基本就分析完了。

通過這個過程可以發現很多C++的知識。如:對象的空間是在調用構造函數之前就分配好了的對象裡邊沒有函數;this指針通過寄存器ECX傳遞;通過聲明定義的對象它的空間分配在棧中;等等這些跟系統或者C++有關聯的知識。

但是,對於一個不懂C++的人看來,上面一段的體會都是沒有的。從匯編指令看不出C++的思想,this指針不過是一個地址;對象不過是一些空間;構造函數、析構函數以及其它的函數,也不過是一堆指令的集合。

C++的同一個類定義出來的多個對象,從匯編指令看來是這樣的:有很多塊地址空間,它們有相同的大小。當不同的對象調用成員函數時,在匯編指令看來是:它們都call同一個地址,這個call指令其實裡邊是一個jmp指令,用於跳向某個位置,在call指令之前一般都會把一個地址放到ECX中,當然有時候會用堆棧或者其它寄存器。

C++的繼承、多態、封裝,對匯編程序員來說是看不出有什麼神奇的,對於C++程序員來說那可就不同了,可以省去很多的工作,把很多事情都交給了編譯器,讓編譯器自動給你搞定。

C++程序員所討論的對象及其眾多的特點、優點,最終還是變成了“低級”的指令,而且可能是效率低下的指令,即便如此,它的優點仍遠大於缺點,它讓編程變得容易、高效。

四、延伸

忽然想到了C++的多態,一句話“將子類類型的指針賦值給父類類型的指針”,多態是通過虛函數實現。對虛函數及其相關內容的原理、詳細理解就不細說了。

說下我的簡單理解,有一個基類A和子類B、C,有一個函數以基類A的指針為參數,然後在函數裡頭通過指針調用基類的成員函數。假如這個被調用的基類成員函數不是虛函數,那麼是不可能實現多態的,因為翻譯成匯編指令的時候,調用成員函數的這個地方是一個call指令,然後這個call指令跳到某個地方去執行,這是一個固定了的地址。通過定義為虛函數,調用成員函數的這個地方是通過虛函數表指針來確定調用哪個函數的,而虛函數表指針就放在對象的地址空間中,如果對象變了,那麼虛函數表指針也變了,調用的函數也就不同了。對於那個以基類A的指針為參數的函數,指針即是對象的地址,如果傳遞的地址是子類B或者C的對象的地址,那麼虛函數表指針也就不同了,調用的成員函數也就不同了。

這就是多態,這種多態使得調用同一個函數,因為傳遞參數的不同而顯示出差異,參數可以是基類對象或者眾多不同的子類對象。它們的差異是類與類之間的。

有虛函數的對象的內存布局,比沒有虛函數的對象多了一個指向虛函數表的指針。因為虛函數的調用是通過虛函數表指針來實現的,所以有了多態。

再考慮一下C++的this指針,一個類中的成員函數,依據this指針來區分不同的對象,也就是說根據this指針實現了訪問不同的對象的成員變量。

這是否也是多態的一種表現?這裡所說的多態已經不是那個“父類指針指向子類對象”的教條了,而是體現在同一個類的不同對象之間,調用同一個成員函數,依據參數“this指針”來實現訪問不同的對象的成員變量。成員函數訪問成員變量,在編譯期無法確定它訪問的成員變量在哪一個地址的,只有到了運行期依據this指針才能確定訪問的地址。這一點很類似於類的多態:以基類指針為參數的函數裡調用了某個基類的虛成員函數,在編譯期無法確定程序運行時調用的會是哪個類的對象,只有到了運行期才確定會調用哪個類的對象。

this指針識別了同一個類的不同的對象,換句話說,this指針使得成員函數可以訪問同一個類的不同對象。再深入一點,this指針使得成員函數會因為this指針的不同而訪問到了不同的成員變量。這也是多態吧,只是它是必然存在的多態,這種多態跟基類與派生類之間的多態是不同級別的多態,它不像一般的多態可以通過對使用虛函數的選擇來取捨,它是一個類對應多個對象、多個對象共享一份成員函數代碼帶來的必然結果。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved