一斋泄、虛指正(vptr)和虛表(vtbl)
我們以下圖介紹上述兩者:
1岔留、當(dāng)類中存在虛函數(shù)就會出現(xiàn)虛指針vpt吨拗,無論虛函數(shù)有多少個庵佣,有且僅有一個虛函數(shù)歉胶,指向虛表(rvtbl)的地址;
2巴粪、虛表是什么呢??
我們可以將它理解為一種表格通今,每個表格的位置存放一個虛函數(shù)對應(yīng)內(nèi)存的地址;
例如:基類A中包含兩個虛函數(shù)vfunc1()肛根、vfunc2()辫塌,那么類A的對象在在內(nèi)存中表現(xiàn)如上圖a(A object),其存儲類的兩個基本數(shù)據(jù):m_data1派哲、2m_data2以及兩個虛函數(shù)對應(yīng)的虛指針vptr臼氨,而虛指針指向虛表的地址,虛表存放虛函數(shù)內(nèi)存的兩個地址:0x401ED0芭届、0x401FD0储矩;同理,A類的子類B褂乍,B類的子類C也有類似的原理持隧;
將vptr實現(xiàn)vtbl內(nèi)容翻譯為C:
(*p->vptr)n;
(* p->vptr[n])(p);
3、動態(tài)綁定: 虛機制
動態(tài)綁定(dynamic binding):動態(tài)綁定是指在執(zhí)行期間(非編譯期)判斷所引用對象的實際類型逃片,根據(jù)其實際的類型調(diào)用其相應(yīng)的方法屡拨。
C++中,通過基類的引用或指針調(diào)用虛函數(shù)時褥实,發(fā)生動態(tài)綁定呀狼。引用(或指針)既可以指向基類對象也可以指向派生類對象,這一事實是動態(tài)綁定的關(guān)鍵损离。用引用(或指針)調(diào)用的虛函數(shù)在運行時確定哥艇,被調(diào)用的函數(shù)是引用(或指針)所指對象的實際類型所定義的;
C++中動態(tài)綁定條件發(fā)生需要滿足2個條件:
(1)只有指定為虛函數(shù)的成員函數(shù)才能進行動態(tài)綁定草冈,成員函數(shù)默認為非虛函數(shù)她奥,非虛函數(shù)不能進行動態(tài)綁定
(2)必須通過基類類型的引用或指針進行函數(shù)調(diào)用
所謂的動態(tài)類型,當(dāng)引用或指針調(diào)用了虛函數(shù)時怎棱,它就是動態(tài)類型,它的行為要到程序運行時才能定義 绷跑;
當(dāng)我們用派生類去初始化基類的引用或指針后拳恋,假如調(diào)用的是非虛函數(shù),那么這時實際調(diào)用的函數(shù)是基類的函數(shù)砸捏;假如調(diào)用的是虛函數(shù)谬运,那么這是調(diào)用的是派生類自己定義的虛函數(shù) ?下面是具體的例子來說明靜態(tài)類型和動態(tài)類型
class A{
public:
virtual void show(){cout<<"j基類的show()"<
void get(){cout<<"基類的get()"<
};
class B:public A{
public:
virtual void show(){cout<<“派生類的show()”<
void get(){cout<<"派生類的get()"<
};
main:
A a;
B b;
A &c=b;
c.show();//show函數(shù)是虛函數(shù)隙赁,并且此時使用派生類的對象去初始化基類的引用,發(fā)生了動態(tài)綁定梆暖,調(diào)用的是實際類 型B的show()----"派生類的show"
c.get();//此時不滿足動態(tài)綁定的條件伞访,c是靜態(tài)類型,結(jié)果是-------基類的get()
二轰驳、this指針
1厚掷、C++this指針,一個對象的this指針并不是對象本身的一部分级解,不會影響sizeof(對象)的結(jié)果冒黑。this作用域是在類內(nèi)部,當(dāng)在類的非靜態(tài)成員函數(shù)中訪問類的非靜態(tài)成員的時候勤哗,編譯器會自動將對象本身的地址作為一個隱含參數(shù)傳遞給函數(shù)抡爹。this指針是類的一個自動生成、自動隱藏的私有成員冬竟,它存在于類的非靜態(tài)成員函數(shù)中,指向被調(diào)用函數(shù)所在的對象民逼。全局僅有一個this指針,當(dāng)一個對象被創(chuàng)建時袋狞,this指針就存放指向?qū)ο髷?shù)據(jù)的首地址;
注:簡單點說早处,通過一個對象調(diào)用函數(shù)時,函數(shù)的地址就是this贬循;
舉例:
父類CDocument
子類CMyDoc,子類對虛函數(shù)Serialise()進行了重新定義咸包;
通過語句myDoc.OnFileOpen()調(diào)用父類函數(shù)時,子類對象myDoc的地址即為this杖虾,上述語句可表述為:CDocument::OnFileOpen(&myDoc)烂瘫,&myDoc就是this,this調(diào)用虛函數(shù)奇适,進行動態(tài)綁定坟比,通過this->Serialize()調(diào)用了子類的虛函數(shù)芦鳍,而不是父類的虛函數(shù),this->Serialize()也可以表達為虛指針葛账、虛表的形式柠衅,即:*(this->vptr)[n](this),這樣我們就可以更好的理解this以及虛機制籍琳;
三.動態(tài)綁定
再第一章節(jié)已介紹過動態(tài)綁定菲宴,這里就不再贅述;
四巩割、const
課件中已做了詳細的介紹裙顽,我這里簡單總結(jié)下,并做些衍生:
1宣谈、常數(shù)對象可以調(diào)用常函數(shù)愈犹;
非常數(shù)對象可以調(diào)用常函數(shù);
常數(shù)對象不可以調(diào)用非常函數(shù)闻丑;
非常數(shù)對象可以調(diào)用非常函數(shù)漩怎;
注:當(dāng)成員函數(shù)的常數(shù)版本和非常版本同時存在時(以函數(shù)重載形式出現(xiàn)),常數(shù)對象只可以調(diào)用常函數(shù)嗦嗡;非常數(shù)對象只可以調(diào)用非常函數(shù)勋锤。
2、注意的幾點:
1)const一般放在成員函數(shù)后頭,不放在全局函數(shù)后頭侥祭, 例:void function() const { return data;} 叁执;
2)在成員函數(shù)后面加const是屬于簽名, 就是當(dāng)兩個成員函數(shù)傳參相同,那么加不加const也會被區(qū)分成兩個函數(shù). ;
3矮冬、const的其他使用方法:
1)定義常量
(1)const修飾變量谈宛,以下兩種定義形式在本質(zhì)上是一樣的。
它的含義是:const修飾的類型為TYPE的變量value是不可變的胎署。
TYPE const ValueName = value;
const TYPE ValueName = value;
(2)將const改為外部連接,作用于擴大至全局,編譯時會分配內(nèi)存,并且可以不進行初始化,僅僅作為聲明,編譯器認為在程序其他地方進行了定義.
extend const int ValueName = value;
2)指針使用CONST
(1)指針本身是常量不可變
char* const pContent;
(2)指針?biāo)赶虻膬?nèi)容是常量不可變
const char *pContent;
(3)兩者都不可變
const char* const pContent;
(4)還有其中區(qū)別方法吆录,沿著*號劃一條線: 如果const位于*的左側(cè),則const就是用來修飾指針?biāo)赶虻淖兞壳砟粒粗羔樦赶驗槌A浚?如果const位于*的右側(cè)恢筝,const就是修飾指針本身,即指針本身是常量巨坊。
五撬槽、關(guān)于New,Delete
new對象的流程不能更改,但是實現(xiàn)過程中的函數(shù)可以被更改.
operator new
operator delete
array new一定要array delete;
回憶前邊的內(nèi)容:delete 某個對象抱究,其實質(zhì)是先調(diào)用析構(gòu)函數(shù)恢氯,再釋放內(nèi)存
六、重載::operator new, ::operator new[],::operator delete ,::operator delete[]
在全局當(dāng)中:
Note: 如果你重載了全局的操作符, 所以要額外小心.
這些重載不可以被聲明在一個namespace中.
//這里的函數(shù)是編譯器去調(diào)用, 所以size是編譯器給出.
void* operator new( size_t size )
{ return malloc(size);}
void* operator new[]( size_t size )
{ return malloc(size);}
void* operator delete(void* ptr )
{ free(ptr);}
void* operator delete[](void* ptr )
{ free(ptr);}
重載 member new , delete
在class里面重載new, delete
class foo{
public:
void* operator new(size_t size);
void operator delete(void *, size_t size); //size為可選
…….
};
那么你在:
foo *a = new foo;
delete a;
就會調(diào)用上面重載的函數(shù).
new[] , delete[] 也如此.
七鼓寺、實例
當(dāng)類中重載了new , delete , 而又想調(diào)用全局的new , delete
可以這樣寫:
::delete a;
string類內(nèi)其實是一個指針.
當(dāng)創(chuàng)建一個數(shù)組的時候, 內(nèi)存當(dāng)中就會多分配一個指針,該指針用于保存當(dāng)前數(shù)組個數(shù).
八勋拟、重載new(),delete()示例
允許重載成員函數(shù)new(….) 其中參數(shù)中,必須有第一個且第一個必須是size_t size. 其余參數(shù)以new所指定的placement argument為初值.
Foo* p = new(300,’c’)Foo; //這里是三個參數(shù)
我們也可以重載類成員函數(shù) operator delete() ,寫出多個版本. 但他們絕不會被 通常所使用的delete調(diào)用.只有當(dāng)new所調(diào)用的ctor拋出 異常,才會調(diào)用這些重載版的operator delete(). 它們只能這樣被調(diào)用,主要用來歸還未能完全創(chuàng)建成功的對象所占用的內(nèi)存.
九、Basic_String使用new(extra)擴充申請量
Basic_String在重載new()過后妈候,傳遞了一個extra參數(shù)敢靡, 用于后臺自動多申請extra空間。