用類去定義對象時,系統會為每一個對象分配存儲空間。如果一個類包括了數據和函數,要分別為數據和函數的代碼分配存儲空間。按理說,如果用同一個類定義了10個對象,那么就需要分別為10個對象的數據和函數代碼分配存儲單元。
能否只用一段空間來存放這個共同的函數代碼段,在調用各對象的函數時,都去調用這個公用的函數代碼。
這里舉一個例子來說明:
#include <iostream> using namespace std; class Time { public: int hour; int minute; int sec; void setHour( ) { hour=10; } }; int main( ) { cout<<sizeof(Time)<<endl; return 0; }
程序運行的結果是12
這就說明Time類對象中僅僅包含數據成員部分,而卻沒有包含成員函數部分。
雖然調用不同對象的成員函數時都是執行同一段函數代碼,但是執行結果一般是不相同的。這樣,我們很自然地要問,類的成員函數如何區分不同的實例對象的數據成員呢?不同的對象使用的是同一個函數代碼段,它怎么能夠分別對不同對象中的數據進行操作呢?
原來C++為此專門設立了一個名為this的指針,用來指向不同的對象。一個對象的this指針並不是對象本身的一部分,不會影響sizeof(對象)的結果。
this指針是一個隱含於每一個成員函數中的特殊指針。它是一個指向正在被該成員函數操作的對象,也就是要操作該成員函數的對象。this作用域是在類內部,當對一個對象調用成員函數時,編譯程序先將對象的地址賦給this指針,編譯器會自動將對象本身的地址作為一個隱含參數傳遞給函數。也就是說,即使你沒有寫this指針,編譯器在編譯的時候也是加上this的,它作為非靜態成員函數的隱含形參。被調用的成員函數函數體內所有對類成員的訪問,都會被轉化為“this->類成員”的方式。
形象地描述就是:一個學生可以有多本書一樣,而這些書都是屬於這個同學的;同理,如果有很多個同學在一起,那么為了確定他們的書不要拿混淆了,最好的辦法我想應該就是每個同學都在自己的書上寫上名字,這樣肯定就不會拿錯了。
同理,一個對象的多個成員就可看作是這個對象所擁有的書;而在很多個對象中間,我們為了證明某個成員是自己的成員,而不是其他對象的成員,我們同樣需要給這些成員取上名字。在C++中,我們利用this指針幫助對象做到這一點,this指針記錄每個對象的內存地址,然后通過運算符->訪問該對象的成員。
關於this指針的一個精典回答:
你可以看見桌子、椅子、地板等,
但是房子你是看不到全貌了。
你可以看到它的成員函數、成員變量,
但是實例本身呢?
this是一個指針,它時時刻刻指向你這個實例本身。
例如:
class A { int x; public: A():x(0) {} void add(int i) { x += i; } };
其中,非靜態成員函數add在編譯器看來應該是:
void add(int i, A* const this) { this->x += i; }
this在成員函數的開始前構造,在成員的結束后清除。當調用一個類的成員函數時,編譯器將類的指針作為函數的this參數傳遞進去。如:
A a; a.add(5);
此處,編譯器將會編譯成:
a.add(5, &a);
下面,對代碼進行反匯編分析下this指針。
a.add(5); 012613E6 push 5 //參數入棧 012613E8 lea ecx, [a] //對象a地址存儲到ecx中 012613EB call A::add (1261113h) //調用函數 void add(int i) { … 012614BF pop ecx 012614C0 mov dword ptr [ebp-8], ecx //將對象地址從ecx中讀出,寫入到add的ebp-8處 x += i; 012614C3 mov eax, dword ptr [this] 012614C6 mov ecx, dword ptr [eax] 012614C8 add ecx, dword ptr [i] 012614CB mov edx, dword ptr [this] 012614CE mov dword ptr [edx], ecx }
反匯編的代碼說明了編譯器將對象本身的地址作為參數進行運算處理了。
再舉個例子
class A { public: int x; A():x(0){} void add(A * p) { __asm { push eax mov eax,dword ptr[p] mov dword ptr[ebp-8], eax //this = p pop eax } x += 1; } }; int main(void) { A a1, a2; a1.add(&a2); cout<<"a1.x = "<<a1.x<<endl; cout<<"a2.x = "<<a2.x<<endl; return 0; }
程序執行結果為:
a1.x = 0
a2.x = 1
為什么程序運行結果是這樣呢?
A的成員函數add對數據成員x進行加1操作,a1對象調用add后,本應使a1的x加1,可結果是對a2的x進行了加1。
函數add(A * p)中加了一段匯編代碼:
mov eax,dword ptr[p] mov dword ptr[ebp-8], eax
這2行代碼,將參數的值寫入到ptr[ebp-8]中,即:this = p。在執行a1.add(&a2)時,this指針指向了a2,所以a1的x並沒有變,而a2的x進行了加1。
this不是常規意義上的變量,它是一個系統變量,所以不能求其地址或對其賦值。this指針僅用於非靜態函數中。
例如:
#include <iostream> using namespace std; class CNullPointCall { public: static void Test1( ); void Test2( ); void Test3(int iTest); void Test4( ); private: static int m_iStatic; int m_iTest; }; int CNullPointCall::m_iStatic = 0; void CNullPointCall::Test1( ) { cout << m_iStatic << endl; } void CNullPointCall::Test2( ) { cout << "Very Cool!" << endl; } void CNullPointCall::Test3(int iTest) { cout << iTest << endl; } void CNullPointCall::Test4( ) { cout << m_iTest << endl; } CNullPointCall *pNull = NULL; // 給指針賦值為空 void main( ) { pNull->Test1( ); // call 1,OK pNull->Test2( ); // call 2,OK pNull->Test3(13); // call 3,OK pNull->Test4( ); // call 4,error }
該程序能夠成功編譯,但在運行是會出錯。
這是因為CNullPointCall的非靜態成員函數Test4中隱含有this指針,但卻沒有CNullPointCall對象地址傳遞給this指針。
void CNullPointCall::Test4( CNullPointCall*const this ) { cout << this->m_iTest << endl; }
除了Test4之外,其余3個類成員函數的調用是成功的。
對於Test4來說,this的值也就是pNull的值,也就是說this的值為NULL,這就造成了程序的崩潰。Test1()是靜態函數,編譯器不會給它傳遞this指針,所以能夠正確調用,這里就相當於CNullPointCall::Test1( )。對於Test2()和Test3()兩個成員函數,雖然編譯器會給這兩個函數傳遞this指針,但是它們並沒有通過this指針來訪問類的成員變量,因此Test2和Test3都可以正確調用。
在以下場景中,經常需要顯式引用this指針:
(1)在類的非靜態成員函數中返回類對象本身的時候,直接使用 return *this,例如:實現對象的鏈式引用。
(2)當參數與成員變量名相同時,如this->x = x,不能寫成x = x。
(3)避免對同一對象進行賦值操作。
例1:實現對象的鏈式引用
#include <iostream> using namespace std; class Person { public: Person(string n, int a) { name = n; //這里的 name 等價於this->name age = a; //這里的 age 等價於this->age } int get_age(void) const { return age; } Person& add_age(int i) { age += i; return *this; // 返回本對象的引用 } private: string name; int age; }; int main(void) { Person Li("Li", 20); cout<<"Li age = "<< Li.get_age()<<endl; cout<<"Li add age = "<< Li.add_age(1).get_age()<<endl; //增加1歲的同時,可以對新的年齡直接輸出; return 0; }
程序執行結果為:
Li age = 20
Li add age = 21
例2:參數與成員變量名相同的處理
#include <iostream> using namespace std; class Point { public: int x; Point ():x(0) {} Point (int a) { x=a; } void print() { cout<<"x = "<<x<<endl; } void set_x(int x) { x = x; } }; int main() { Point pt(5); pt.set_x(10); pt.print(); return 0; }
程序執行結果為:
x = 5
若將set_x函數改為:
void set_x(int x) { this->x = x; }
程序執行結果為:
x = 10
例3:避免對同一對象進行賦值操作
#include <iostream> using namespace std; class Location { int X,Y; //默認為私有的 public: void init(int x,int y) { X =x; Y =y; }; void assign(Location& pointer); int GetX() { return X; } int GetY() { return Y; } }; void Location::assign(Location& pointer) { if(&pointer!=this) //同一對象之間的賦值沒有意義,所以要保證pointer不等於this { X=pointer.X; Y=pointer.Y; } } int main() { Location x; x.init(5,4);//初始化賦值 Location y; y.assign(x); cout<<"x.X = "<< x.GetX()<<" x.Y = "<<x.GetY(); cout<<"y.X = "<< y.GetX()<<" y.Y = "<<y.GetY(); return 0; }
問題:在一個類中,為什么靜態成員函數(static member function)中不能使用this指針?
靜態成員函數並不是針對某個類的實例對象,而是屬於整個類的,為所有的對象實例所共有。他在作用域的范圍內是全局的,獨立於類的對象之外的。他只對類內部的靜態成員變量做操作。當實例化一個類的對象時候,里面不存在靜態成員的。this指針是相當於一個類的實例的指針,this是用來操作對象實例的內容的,既然靜態成員函數和變量都是獨立於類的實例對象之外的,他就不能用this指針。也不能操作非靜態成員。