先問一個問題,在C++裏,成員函數裏的this指針和調用此函數的對象地址總是一樣的嗎?如果你的回答是:不一定。那麼至少你是個老手吧,下面的內容你就不用看了;如果你的回答是:是啊,那麼強烈建議你看看下面的內容。
非靜態成員函數,無論是不是虛函數,都隱藏了一個this指針參數。這個參數的目的就是給函數提供一個基地址,以便於函數體內能找到對象的成員變量。那非靜態成員函數是如何根據this指針找到成員變量的呢?直接看例子吧
1沒有虛表的情況
- #include <iostream>
- #include <stdio.h>
- using namespace std;
- class A
- {
- public:
- int x;
- int y;
- public:
- void F1()
- {
- this->x = 1;
- this->y = 2;
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- int main(int argc, char** argv)
- {
- A a;
- cout<<"a對象的地址是:"<<&a<<endl;
- cout<<"a對象的大小是:"<<sizeof(A)<<endl;
- cout<<"成員a.x的地址是: "<<&a.x<<endl;
- cout<<"成員a.x的偏移是:"<<&A::x<<endl;
- a.F1();
- cin>>argc;
- return 0;
- }
那麼函數F1的實現僞代碼爲:
*(this+&A::x-1) = 1;
*(this+&A::y-1) = 2;
其中&A::x是成員x的偏移+1,&A::y是成員y的偏移+1,這可是C++基本語法的知識,希望你知道,呵呵。可這些偏移量是相對應那裏的偏移呢,是對象地址嗎,答案是NO。是相對於第一個成員變量的偏移,這對於有些對象也許沒有差別,但是對於有虛表的類的對象,就有差別了。這些偏移在編譯期間就是確定了的。對於本例在VC++2010下&A::x,值爲1, &A::y,值爲5。爲什麼不是0,4,請看《Inside The C++ Object Model》。
所以,對於找到成員變量,需要進一步確定的只有this的值。程序運行結果如下:
可見此例中,對象的地址與this指針的地址相同,內存圖如下所示。
2有一個虛表的情況
- #include <iostream>
- #include <stdio.h>
- using namespace std;
- class A
- {
- public:
- int x;
- int y;
- public:
- virtual void F1()
- {
- this->x = 1;
- this->y = 2;
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- int main(int argc, char** argv)
- {
- A* p = new A();
- cout<<"a對象的地址是:"<<p<<endl;
- cout<<"a對象的大小是:"<<sizeof(A)<<endl;
- cout<<"成員a.x的地址是: "<<&p->x<<endl;
- cout<<"成員a.x的偏移是:"<<&A::x<<endl;
- p->F1();
- cin>>argc;
- return 0;
- }
此時函數F1的實現僞代碼爲:
*(this+4+&A::x-1) = 1; //+4是因爲存在虛表指針
*(this+4+&A::y-1) = 2; //+4是因爲存在虛表指針
程序運行結果如下:
內存佈局如下:
結論:this的值和對象地址相同,成員變量偏移量不因虛表指針存在而改變。帶虛表的類的成員函數對成員變量的尋址方式不同,增加一個+4。
3單繼承的情況
- #include <iostream>
- #include <stdio.h>
- using namespace std;
- class A
- {
- public:
- int x;
- int y;
- public:
- void F1()
- {
- this->x = 1;
- this->y = 2;
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- class B : public A
- {
- public:
- int z;
- public:
- virtual void F2()
- {
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- int main(int argc, char** argv)
- {
- B* pb = new B();
- cout<<"對象的地址爲:"<<std::hex<<std::showbase<<pb<<endl;
- pb->F1();
- pb->F2();
- cin>>argc;
- return 0;
- }
運行結果:
內存佈局:
結論:this指針的值受兩個因素確定,一是對象的地址,二是定義成員函數的類。This指向的是對象內,定義該方法的類得subobject。
4 多繼承的情況
先看A沒有虛函數,B有虛函數的情況
- #include <iostream>
- #include <stdio.h>
- using namespace std;
- class A
- {
- public:
- int x;
- int y;
- public:
- void F1()
- {
- this->x = 1;
- this->y = 2;
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- class B
- {
- public:
- int z;
- public:
- virtual void F2()
- {
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- class C : public A, public B
- {
- public:
- int a;
- public:
- virtual void F2()
- {
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- void F3()
- {
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- int main(int argc, char** argv)
- {
- C* pc = new C();
- cout<<"對象的大小爲:"<<sizeof(C)<<endl;
- cout<<"對象的地址爲:"<<std::hex<<std::showbase<<pc<<endl;
- pc->F1();
- pc->F2();
- pc->F3();
- cin>>argc;
- return 0;
- }
結果:
內存佈局:
再看,如果A,B都有虛函數的情況。
代碼:
- #include <iostream>
- #include <stdio.h>
- using namespace std;
- class A
- {
- public:
- int x;
- int y;
- public:
- virtual void F1()
- {
- this->x = 1;
- this->y = 2;
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- class B
- {
- public:
- int z;
- public:
- virtual void F2()
- {
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- class C : public A, public B
- {
- public:
- int a;
- public:
- virtual void F2()
- {
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- void F3()
- {
- cout<<"this指針得值是:"<<std::hex<<std::showbase<<this<<endl;
- }
- };
- int main(int argc, char** argv)
- {
- C* pc = new C();
- cout<<"對象的大小爲:"<<sizeof(C)<<endl;
- cout<<"對象的地址爲:"<<std::hex<<std::showbase<<pc<<endl;
- cout<<"x的地址"<<&pc->x<<endl;
- cout<<"z的地址"<<&pc->z<<endl;
- pc->F1();
- pc->F2();
- pc->B::F2();
- pc->F3();
- cin>>argc;
- return 0;
- }
結果:
內存佈局:
結論:再一次驗證了this指針的值的確定方法,this始終要保證指向定義了該成員函數的類得subobject。因爲C++保證base class subobject與base class object完全對應,從而保證了成員函數能根據成員變量在定義了該變量的類中的偏移尋址。