十六 對(duì)象模型:關(guān)于vptr和vtbl

如圖模型所示,B類繼承A類,C類繼承B類,子類有父類的成分,繼承的包括數(shù)據(jù)、非虛函數(shù)和虛函數(shù)。當(dāng)類里面有虛函數(shù),無(wú)論多少數(shù)量,會(huì)多一個(gè)指針,如圖vptr(虛指針),指向vtbl(虛函數(shù)表)。虛函數(shù)的調(diào)用是通過(guò)虛函數(shù)表實(shí)現(xiàn),無(wú)論子類是否要重寫函數(shù)。
有一個(gè)指針p指向c(new c),通過(guò)p調(diào)用vfunc1(),先調(diào)用虛函數(shù)表,在虛函數(shù)表上找到對(duì)應(yīng)編號(hào)的函數(shù),實(shí)現(xiàn)調(diào)用,這種方式叫做動(dòng)態(tài)綁定。解析成c語(yǔ)言的形式如下:
(*(p->vptr)[n])(p); //n是在虛函數(shù)表中函數(shù)的編號(hào),根據(jù)你寫的代碼的順序排
//或者
(* p->vptr[n])(p);

(1)如圖左下角實(shí)現(xiàn)不同形狀的draw(),1.將指向A(shape類)的指針?lè)诺饺萜骼锩妫?.將draw()寫成虛函數(shù);3.具體形狀類(例如rect類)繼承抽象類shape。
注:為了讓容器容納各種各樣的形狀,容器里面放的是指針,因?yàn)槿萜骼锩鏀?shù)據(jù)類型必須一致,指針必須指向父類,這樣聲明的時(shí)候指向父類,用的時(shí)候就可以指向子類。
(2)對(duì)比c語(yǔ)言的實(shí)現(xiàn)方法,c語(yǔ)言是先判斷哪一種形狀,然后調(diào)用其draw()函數(shù),這樣寫要添加某個(gè)形狀,重新寫判斷類型代碼,不方便。
總結(jié):C++編譯器看到函數(shù),有兩個(gè)考量。
一是靜態(tài)綁定,編譯成Call、XXX(地址);
二是動(dòng)態(tài)綁定(虛機(jī)制:動(dòng)態(tài)綁定的形式),要符合三個(gè)條件:1.通過(guò)指針調(diào)用 2.這個(gè)指針向上轉(zhuǎn)型3.調(diào)用虛函數(shù)。調(diào)用哪個(gè)虛函數(shù),要看p指向什么,這就是理解動(dòng)態(tài)綁定。
結(jié)合第四周作業(yè)探究?jī)?nèi)存模型:



所以根據(jù)測(cè)試代碼sizeof大小組成如圖所示
Fruit:8+(4+4)+8+(1+7)=32
Apple:8+(4+4)+8+(1+3+4)+(1+7)=40
測(cè)試代碼:
#include <iostream>
using namespace std;
class Fruit
{
public:
int no;
double weight;
char key;
void print() { }
virtual void process() { }
};
//-------------------------------------------------
class Apple : public Fruit
{
public:
int size;
char type;
void save() { }
virtual void process() { }
};
//-------------------------------------------------
int main(int argc, char const *argv[])
{
Fruit fruit;
Apple apple;
cout << "--------------Fruit----------------" << endl;
cout << "sizeof(Fruit) = " << sizeof(Fruit) << endl;
printf("Fruit.vptr = %x\n", &fruit);
printf("Fruit.vptr_process = %x\n", *(int*)&fruit);
printf("Fruit = %x\n", &fruit);
printf("Fruit.no = %x\n", &fruit.no);
printf("Fruit.weight = %x\n", &fruit.weight);
printf("Fruit.key = %x\n", &fruit.key);
cout << "--------------Apple----------------" << endl;
cout << "sizeof(Apple) = " << sizeof(Apple) << endl;
printf("Apple.vptr = %x\n", &apple);
printf("Apple.vptr_process = %x\n", *(int*)&apple);
printf("Apple = %x\n", &apple);
printf("Apple.no = %x\n", &apple.no);
printf("Apple.weight = %x\n", &apple.weight);
printf("Apple.key = %x\n", &apple.key);
printf("Apple.size = %x\n", &apple.size);
printf("Apple.type = %x\n", &apple.type);
system("pause");
return 0;
}
十七 對(duì)象模型:關(guān)于this
通過(guò)一個(gè)對(duì)象來(lái)調(diào)用一個(gè)函數(shù),那個(gè)對(duì)象的地址就是this指針

第三周筆記有對(duì)這種模式具體的敘述及優(yōu)點(diǎn),這里不再贅述。
鏈接:http://www.itdecent.cn/p/ca6613dd4c8d
十八 對(duì)象模型:關(guān)于Dynamic Binding

a是A的對(duì)象,雖然初值是b經(jīng)過(guò)強(qiáng)制類型轉(zhuǎn)換的,但因?yàn)槭峭ㄟ^(guò)對(duì)象調(diào)用,不是指針,所以這是靜態(tài)綁定。編譯的匯編碼為箭頭所指。

pa為動(dòng)態(tài)綁定,滿足三個(gè)條件(new出來(lái)B,類型是A,是向上轉(zhuǎn)型),call不是一個(gè)固定的地址。
(*(P->vptr)[n])(p)的解釋:通過(guò)指針p找到虛指針vptr,再找到虛表,取出其中第n個(gè),把它當(dāng)成函數(shù)指針去調(diào)用,由于是通過(guò)p來(lái)調(diào)用,所以p就是一個(gè)this指針,(p)就是this指針。
十九 重談const

const第一周筆記有提過(guò),鏈接:http://www.itdecent.cn/p/2fafca2021d5
(1)這里需要新提出的是:basic_string有兩個(gè)成員函數(shù),一個(gè)operator[]函數(shù)后面有const,一個(gè)沒有const,這兩者可以存在,且有const的不用寫COW,沒有const的必須寫COW。
(2)常成員函數(shù)的const和non-const版本同時(shí)存在,const object只能調(diào)用const版本,non-const object只能調(diào)用non-const版本。
二十 關(guān)于New Delete
第二周有記載,這里不再贅述。
鏈接:http://www.itdecent.cn/p/12dcce512fd4
重載Operator new,Operator delete
重載operator new和operator delete用來(lái)設(shè)計(jì)自己的內(nèi)存池,也就是內(nèi)存管理。
全局寫法:
void* myAlloy(size_t size)
{return malloc(size);}
void myFree(void* ptr)
{return free(ptr);}
//他們不可以被聲明與一個(gè)namespace內(nèi)
inline void* operator new(size_t size)
{cout<<“jjhou global new() \n"; return myAlloc(size);}
inline void* operator new[](size_t size)
{cout<<“jjhou global new[]() \n"; return myAlloc(size);}
inline void* operator delete(void* ptr)
{cout<<“jjhou global delete() \n"; myFree(ptr);}
inline void* operator delete[](void* ptr)
{cout<<“jjhou global delete[]() \n"; myFree(ptr);}
new的時(shí)候三個(gè)動(dòng)作(其中一個(gè)調(diào)用new函數(shù),此new和前頭new不同),如果寫了自己的重載new函數(shù),則調(diào)用該函數(shù),否則調(diào)用全局的new函數(shù)。
重載全局的函數(shù),影響無(wú)遠(yuǎn)弗界。
成員函數(shù)寫法:
Foo* p=new Foo;
//try{
// void* mem=operator new(sizeof(Foo));
// p=static_cast<Foo*>(mem);
// p->Foo::Foo();
//}
delete p
//p->~Foo();
//operator delete(p);
class Foo{
public:
void* operator new(size_t);
void operator delete(void*,size_t); //size_t 可有可無(wú)
//...
};
示例


在G4.9中,數(shù)組大小是數(shù)組整包大小加上一個(gè)計(jì)數(shù)器。
Foo* p=::new Foo(7);
::delete p;
Foo* pArray=::new Foo[5];
::delete[] pArray;
這樣調(diào)用(也就是寫上global scope operator::),會(huì)繞過(guò)前述overloaded functions,強(qiáng)迫使用global version。
重載new(),delete()
我們可以重載class member operator new(),寫出多個(gè)版本,前提是每一版本的聲明都必須有獨(dú)特的參數(shù)列,其中第一個(gè)參數(shù)必須是size_t,其余參數(shù)以new所指定的placement arguments為初值。出現(xiàn)于new(......)小括號(hào)內(nèi)的便是所謂placement arguments。
Foo* pf=new(300,'c')Foo;
我們也可以重載class member operator delete(),寫出多個(gè)版本。但它們絕不會(huì)被delete調(diào)用。只有當(dāng)new所調(diào)用的ctor拋出exception,才會(huì)調(diào)用這些重載版的operator delete()。他只可能這樣被調(diào)用。主要用來(lái)歸還未能完全創(chuàng)建成功的object所占用的memory。
Basic_String 使用new(extra)擴(kuò)充申請(qǐng)量
