C++ this指標(直戳本質)

2020-07-16 10:04:20
為了能讓大家看清 this 指標的本質,我們會先講一點 C++ 的歷史——C++ 程式到C程式的翻譯過程。

C++ 程式到C程式的翻譯

C++ 是在C語言的基礎上發展而來的,第一個 C++ 的編譯器實際上是將 C++ 程式翻譯成C語言程式,然後再用C語言編譯器進行編譯。

C語言沒有類的概念,只有結構,函數都是全域性函數,沒有成員函數。翻譯時,將 class 翻譯成 struct、物件翻譯成結構變數是顯而易見的,但是對類的成員函數應該如何翻譯?對myCar.Modify();這樣通過一個物件呼叫成員函數的語句,又該如何翻譯呢?

C語言中只有全域性函數,因此成員函數只能被翻譯成全域性函數;myCar.Modify();這樣的語句也只能被翻譯成普通的呼叫全域性函數的語句。那如何讓翻譯後的 Modify 全域性函數還能作用在 myCar 這個結構變數上呢?答案就是引入“this 指標”。下面來看一段 C++ 程式到C 程式的翻譯。

C++程式:
class CCar
{
public:
    int price;
    void SetPrice(int p);
};

void CCar::SetPrice(int p)
{
    price=  p;
}
int main()
{
    CCar car;
    car.SetPrice(20000);
    return 0;
}
翻譯後的C程式(此程式應儲存為擴充套件名為 .c 的檔案後再編譯):
struct CCar
{
    int price;
};
void SetPrice(struct CCar* this, int p)
{
    this->price = p;
}
int main()
{
    struct CCar car;
    SetPrice(&car, 20000);
    return 0;
}
可以看出,類被翻譯成結構體,物件被翻譯成結構變數,成員函數被翻譯成全域性函數。但是C程式的全域性函數 SetPrice 比 C++ 的成員函數 SelPrice 多了一個引數,就是struct CCar *thiscar.SetPrice(20000);被翻譯成SetPrice(&car, 20000);,後者在執行時,this 形參指向的正是 car 這個變數,因而達到了 SetPrice 函數作用在 car 變數上的效果。

思考題:以上翻譯還不完整,因為建構函式的作用沒有體現出來。思考建構函式應該如何翻譯。另外,靜態成員函數和靜態成員變數應如何翻譯?

this 指標的作用

實際上,現在的C編譯器從本質上來說也是按上面的方法來處理成員函數和對成員函數的呼叫的,即非靜態成員函數實際上的形參個數比程式設計師寫的多一個。多出來的引數就是所謂的“this指標”。這個“this指標”指向了成員函數作用的物件,在成員函數執行的過程中,正是通過“Ihis指標”才能找到物件所在的地址,因而也就能找到物件的所有非靜態成員變數的地址。

下面程式的執行結果能夠證明這一點:
#include <iostream>
using namespace std;
class A
{
    int i;
public:
    void Hello(){ cout << "hello" << endl; }
};
int main()
{
    A* p = NULL;
    p -> Hello();
}
程式的輸出結果是:
hello

在上面的程式中,p 明明是一個空指標,為何通過它還能正確呼叫 A 的成員函數 Hello 呢?因為,參考上面 C++ 到C程式的翻譯,P->Hello()實質上應該是Hello(p),在翻譯後的 Hello 函數中,cout 語句沒有用到 this 指標,因此依然可以輸出結果。如果 Hello 函數中有對成員變數的存取,則程式就會出錯。

C++ 規定,在非靜態成員函數內部可以直接使用 this 關鍵字,this 就代表指向該函數所作用的物件的指標。看下面的例子:
#include <iostream>
using namespace std;
class Complex {
public:
    double real, imag;
    Complex(double r, double i) : real(r), imag(i) {}
    Complex AddOne()
    {
        this->real++;
        return *this;
    }
};
int main()
{
    Complex cl(1, 1), c2(0, 0);
    c2 = cl.AddOne();
    cout << c2.real << "," << c2.imag << endl; //輸出 2,1
    return 0;
}
第 9 行,this 指標的型別是 Complex*。因為 this 指標就指向函數所作用的物件,所以 this->rear 和 real 是完全等價的。*this代表函數所作用的物件,因此執行第 16 行,進入 AddOne 函數後,*this實際上就是 c1。因此的 c2 值會變得和 c1 相同。

因為靜態成員函數並不作用於某個物件,所以在其內部不能使用 this 指標;否則,這個 this 指標該指向哪個物件呢?