理解C++中的this指針

在C++中,this指針隱式存在於實例化對象當中。而對對象的取址操作和指針賦值實際上賦值的正是對象的this指針,如:

class A;
class B : public A;
……

B b;
A* pa = &b; // 對象取址賦值給指針
在上面的代碼中,b對象是一個子類對象,所謂的取址&也即是將對象b的this指針的值賦值給一個它的父類指針。當然,本質上來說,對象b的this指針其實就是等於&b,兩者沒有本質區別。這行看似簡單的代碼對於我們理解對象的調用將會有很好的幫助,原因自然涉及到C++具有的一個重要特性:多態。

關於多態的用法和好處不是本文要闡述的內容,凡是涉及到C++面向對象程序設計的項目一般多少要用到多態(不然用純C寫與C++也就沒有太多的區別)。那麼這個this指針到底有什麼用呢?可以簡單看一下以下兩段代碼,比較差別:

#include <iostream>
using namespace std;

class A {
public:
	A(){cout<<"A:"<<this<<endl;}
	virtual void fun() {
		cout << "A's fun()" << endl;
		cout<<this<<endl;
		show();		// 等價於this.show(); 
		A::show();	// 強制執行父類自身的show方法
	}
	virtual void show() {
		cout << "A's show()" << endl;
		cout<<this<<endl;
	}
};

class B: public A {
public:
	B(){cout<<"B:"<<this<<endl;}
	virtual void fun() {
		cout << "B's fun()" << endl;
		cout<<this<<endl;
		A::fun();
	}
	virtual void show() {
		cout << "B's show()" << endl;
		cout<<this<<endl;
	}
};

int main() {
	B bobj;
	A *aptr = &bobj;	// 將子類bobj的this指針賦值給aptr指針。因此在執行時,所有的方法默認都是調用這個子類的this指針!
	aptr->fun();
	system("pause");
}
程序的執行結果是:


上面的程序用到了簡單的多態,只需要注意一點即可:當類存在繼承關係時,this指針指向的永遠是真正實例化的對象地址。這話不太準確也不好理解,從代碼的執行看就非常清晰了:當程序在執行

aptr->fun();
時,系統在子類的show方法中調用了父類的fun方法,並在父類內部執行了一個show方法。那麼這個show方法執行的爲什麼不是父類A中的show方法卻調用了對父類A來說似乎不可見的子類B中的show方法了呢(這話很拗口T T)?原因就在這個this指針!這裏爲了方便大家調試加了很多的輸出語句用於查看對應的this指針值:正是因爲在執行多態的過程中,真正的實例化對象是子類對象,通過this指針訪問成員函數時,由於虛函數的存在所以默認的show()方法等價於this.show(),而我們看到這個this其實是子類的地址,那麼她調用的也就正是子類的show()方法了!那麼如果要強制訪問父類本身的show方法,就需要改爲A::show()。

到了這裏或許已經可以基本理解this指針在多態中的作用。不妨再花點時間看一下下面這個程序,大體結構和前面的程序是一樣的,如果覺得程序有點繞可以將那些輸出this指針的語句刪除:

#include <iostream>
using namespace std;

class Base {
public:
	Base():i(1) { 
		cout<<i<<endl;
		cout<<this<<endl;
		f();      // 等價於this.f();但有差異
	}
	virtual void f() { 
		cout<<i<<endl;
		cout<<this<<endl;
		i *= 50; 
	}
	virtual void print() { 
		cout <<i << endl; 
		cout<<this<<endl;
		f();
	}
private:
	int i;
};

class Derived : public Base {
public:
	Derived():i(2) { 
		cout<<i<<endl; 
		cout<<this<<endl;
		f();
	}
	virtual void f() { 
		cout<<i<<endl;
		cout<<this<<endl;
		i*=30; 
	}
private:
	int i;
};
void main()
{
	Derived d;
	Base *p = &d;
	p->print();
	system("pause");
}


程序輸出:


發現this指針確實無論在對象的哪個函數裏執行都是一樣的。但親們注意到沒,在實例化這個子類對象過程中,虛函數f()被多次調用,其中包括在父類的構造函數中被調用。如果單步調試會發現,在父類Base的構造中執行的f()依然是父類自身的f()函數。而按照前面的邏輯,在實例化對象中,this指針是隱式存在的,程序的輸出也驗證了這一點。那麼既然如此f()這個虛函數在子類中又重新實現了(多態),在父類的構造函數中執行f()相當於執行this.f(),這個this其實是子類的對象指針,爲什麼會去執行父類中原本的f()函數呢?這個又涉及到C++面向對象設計中類的初始化順序問題!

在創建對象時,系統需要完成四項工作:
1 構造父類成員對象
2 構造子類成員對象
3 調用父類構造函數
4 調用子類構造函數
這四項工作的時間順序是怎樣的呢?
原則是:先成員後構造函數,先父類構造後子類構造 == 先父類成員,父類構造;再子類成員,子類構造

所以,在這裏父類構造完成前,子類對象還是不完整的!這話不太準確,但試想一下,如果編譯器允許在父類構造的時候直接調用子類重寫的函數f(),這是多麼危險的行爲:萬一你在子類重寫的f()中用到了子類新的數據成員,而根據前面的規則“ 先父類成員,父類構造;再子類成員,子類構造”,這時候子類的成員都還沒實例化出來!所以,可以這麼理解this:如果對象已經實例化完成,那麼this指向的就是你new出來的對象;如果是構造過程中,那麼this就是指當前對象。說到底,this就是一個內存地址而已,對象的實例化和函數調用不過是在這個地址基礎上的一段01代碼而已~


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