一、虛指正(vptr)和虛表(vtbl)
我們以下圖介紹上述兩者:
1、當(dāng)類中存在虛函數(shù)就會(huì)出現(xiàn)虛指針vpt,無論虛函數(shù)有多少個(gè),有且僅有一個(gè)虛函數(shù),指向虛表(rvtbl)的地址;
2、虛表是什么呢??
我們可以將它理解為一種表格,每個(gè)表格的位置存放一個(gè)虛函數(shù)對(duì)應(yīng)內(nèi)存的地址;
例如:基類A中包含兩個(gè)虛函數(shù)vfunc1()、vfunc2(),那么類A的對(duì)象在在內(nèi)存中表現(xiàn)如上圖a(A object),其存儲(chǔ)類的兩個(gè)基本數(shù)據(jù):m_data1、2m_data2以及兩個(gè)虛函數(shù)對(duì)應(yīng)的虛指針vptr,而虛指針指向虛表的地址,虛表存放虛函數(shù)內(nèi)存的兩個(gè)地址:0x401ED0、0x401FD0;同理,A類的子類B,B類的子類C也有類似的原理;
將vptr實(shí)現(xiàn)vtbl內(nèi)容翻譯為C:
(*p->vptr)n;
(* p->vptr[n])(p);
3、動(dòng)態(tài)綁定: 虛機(jī)制
動(dòng)態(tài)綁定(dynamic binding):動(dòng)態(tài)綁定是指在執(zhí)行期間(非編譯期)判斷所引用對(duì)象的實(shí)際類型,根據(jù)其實(shí)際的類型調(diào)用其相應(yīng)的方法。
C++中,通過基類的引用或指針調(diào)用虛函數(shù)時(shí),發(fā)生動(dòng)態(tài)綁定。引用(或指針)既可以指向基類對(duì)象也可以指向派生類對(duì)象,這一事實(shí)是動(dòng)態(tài)綁定的關(guān)鍵。用引用(或指針)調(diào)用的虛函數(shù)在運(yùn)行時(shí)確定,被調(diào)用的函數(shù)是引用(或指針)所指對(duì)象的實(shí)際類型所定義的;
C++中動(dòng)態(tài)綁定條件發(fā)生需要滿足2個(gè)條件:
(1)只有指定為虛函數(shù)的成員函數(shù)才能進(jìn)行動(dòng)態(tài)綁定,成員函數(shù)默認(rèn)為非虛函數(shù),非虛函數(shù)不能進(jìn)行動(dòng)態(tài)綁定
(2)必須通過基類類型的引用或指針進(jìn)行函數(shù)調(diào)用
所謂的動(dòng)態(tài)類型,當(dāng)引用或指針調(diào)用了虛函數(shù)時(shí),它就是動(dòng)態(tài)類型,它的行為要到程序運(yùn)行時(shí)才能定義 ;
當(dāng)我們用派生類去初始化基類的引用或指針后,假如調(diào)用的是非虛函數(shù),那么這時(shí)實(shí)際調(diào)用的函數(shù)是基類的函數(shù);假如調(diào)用的是虛函數(shù),那么這是調(diào)用的是派生類自己定義的虛函數(shù) ?下面是具體的例子來說明靜態(tài)類型和動(dòng)態(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ù),并且此時(shí)使用派生類的對(duì)象去初始化基類的引用,發(fā)生了動(dòng)態(tài)綁定,調(diào)用的是實(shí)際類 型B的show()----"派生類的show"
c.get();//此時(shí)不滿足動(dòng)態(tài)綁定的條件,c是靜態(tài)類型,結(jié)果是-------基類的get()
二、this指針
1、C++this指針,一個(gè)對(duì)象的this指針并不是對(duì)象本身的一部分,不會(huì)影響sizeof(對(duì)象)的結(jié)果。this作用域是在類內(nèi)部,當(dāng)在類的非靜態(tài)成員函數(shù)中訪問類的非靜態(tài)成員的時(shí)候,編譯器會(huì)自動(dòng)將對(duì)象本身的地址作為一個(gè)隱含參數(shù)傳遞給函數(shù)。this指針是類的一個(gè)自動(dòng)生成、自動(dòng)隱藏的私有成員,它存在于類的非靜態(tài)成員函數(shù)中,指向被調(diào)用函數(shù)所在的對(duì)象。全局僅有一個(gè)this指針,當(dāng)一個(gè)對(duì)象被創(chuàng)建時(shí),this指針就存放指向?qū)ο髷?shù)據(jù)的首地址;
注:簡(jiǎn)單點(diǎn)說,通過一個(gè)對(duì)象調(diào)用函數(shù)時(shí),函數(shù)的地址就是this;
舉例:
父類CDocument
子類CMyDoc,子類對(duì)虛函數(shù)Serialise()進(jìn)行了重新定義;
通過語句myDoc.OnFileOpen()調(diào)用父類函數(shù)時(shí),子類對(duì)象myDoc的地址即為this,上述語句可表述為:CDocument::OnFileOpen(&myDoc),&myDoc就是this,this調(diào)用虛函數(shù),進(jìn)行動(dòng)態(tài)綁定,通過this->Serialize()調(diào)用了子類的虛函數(shù),而不是父類的虛函數(shù),this->Serialize()也可以表達(dá)為虛指針、虛表的形式,即:*(this->vptr)[n](this),這樣我們就可以更好的理解this以及虛機(jī)制;
三.動(dòng)態(tài)綁定
再第一章節(jié)已介紹過動(dòng)態(tài)綁定,這里就不再贅述;
四、const
課件中已做了詳細(xì)的介紹,我這里簡(jiǎn)單總結(jié)下,并做些衍生:
1、常數(shù)對(duì)象可以調(diào)用常函數(shù);
非常數(shù)對(duì)象可以調(diào)用常函數(shù);
常數(shù)對(duì)象不可以調(diào)用非常函數(shù);
非常數(shù)對(duì)象可以調(diào)用非常函數(shù);
注:當(dāng)成員函數(shù)的常數(shù)版本和非常版本同時(shí)存在時(shí)(以函數(shù)重載形式出現(xiàn)),常數(shù)對(duì)象只可以調(diào)用常函數(shù);非常數(shù)對(duì)象只可以調(diào)用非常函數(shù)。
2、注意的幾點(diǎn):
1)const一般放在成員函數(shù)后頭,不放在全局函數(shù)后頭, 例:void function() const { return data;} ;
2)在成員函數(shù)后面加const是屬于簽名, 就是當(dāng)兩個(gè)成員函數(shù)傳參相同,那么加不加const也會(huì)被區(qū)分成兩個(gè)函數(shù). ;
3、const的其他使用方法:
1)定義常量
(1)const修飾變量,以下兩種定義形式在本質(zhì)上是一樣的。
它的含義是:const修飾的類型為TYPE的變量value是不可變的。
TYPE const ValueName = value;
const TYPE ValueName = value;
(2)將const改為外部連接,作用于擴(kuò)大至全局,編譯時(shí)會(huì)分配內(nèi)存,并且可以不進(jìn)行初始化,僅僅作為聲明,編譯器認(rèn)為在程序其他地方進(jìn)行了定義.
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ū)別方法,沿著*號(hào)劃一條線: 如果const位于*的左側(cè),則const就是用來修飾指針?biāo)赶虻淖兞浚粗羔樦赶驗(yàn)槌A浚?如果const位于*的右側(cè),const就是修飾指針本身,即指針本身是常量。
五、關(guān)于New,Delete
new對(duì)象的流程不能更改,但是實(shí)現(xiàn)過程中的函數(shù)可以被更改.
operator new
operator delete
array new一定要array delete;
回憶前邊的內(nèi)容:delete 某個(gè)對(duì)象,其實(shí)質(zhì)是先調(diào)用析構(gòu)函數(shù),再釋放內(nèi)存
六、重載::operator new, ::operator new[],::operator delete ,::operator delete[]
在全局當(dāng)中:
Note: 如果你重載了全局的操作符, 所以要額外小心.
這些重載不可以被聲明在一個(gè)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;
就會(huì)調(diào)用上面重載的函數(shù).
new[] , delete[] 也如此.
七、實(shí)例
當(dāng)類中重載了new , delete , 而又想調(diào)用全局的new , delete
可以這樣寫:
::delete a;
string類內(nèi)其實(shí)是一個(gè)指針.
當(dāng)創(chuàng)建一個(gè)數(shù)組的時(shí)候, 內(nèi)存當(dāng)中就會(huì)多分配一個(gè)指針,該指針用于保存當(dāng)前數(shù)組個(gè)數(shù).
八、重載new(),delete()示例
允許重載成員函數(shù)new(….) 其中參數(shù)中,必須有第一個(gè)且第一個(gè)必須是size_t size. 其余參數(shù)以new所指定的placement argument為初值.
Foo* p = new(300,’c’)Foo; //這里是三個(gè)參數(shù)
我們也可以重載類成員函數(shù) operator delete() ,寫出多個(gè)版本. 但他們絕不會(huì)被 通常所使用的delete調(diào)用.只有當(dāng)new所調(diào)用的ctor拋出 異常,才會(huì)調(diào)用這些重載版的operator delete(). 它們只能這樣被調(diào)用,主要用來歸還未能完全創(chuàng)建成功的對(duì)象所占用的內(nèi)存.
九、Basic_String使用new(extra)擴(kuò)充申請(qǐng)量
Basic_String在重載new()過后,傳遞了一個(gè)extra參數(shù), 用于后臺(tái)自動(dòng)多申請(qǐng)extra空間。