十六 對(duì)象模型:關(guān)于vptr和vtbl
如圖模型所示幢痘,B類繼承A類,C類繼承B類家破,子類有父類的成分颜说,繼承的包括數(shù)據(jù)购岗、非虛函數(shù)和虛函數(shù)。當(dāng)類里面有虛函數(shù)门粪,無論多少數(shù)量喊积,會(huì)多一個(gè)指針,如圖vptr(虛指針)玄妈,指向vtbl(虛函數(shù)表)乾吻。虛函數(shù)的調(diào)用是通過虛函數(shù)表實(shí)現(xiàn),無論子類是否要重寫函數(shù)拟蜻。
有一個(gè)指針p指向c(new c)绎签,通過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類)的指針放到容器里面;2.將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.通過指針調(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
通過一個(gè)對(duì)象來調(diào)用一個(gè)函數(shù)贫贝,那個(gè)對(duì)象的地址就是this指針
第三周筆記有對(duì)這種模式具體的敘述及優(yōu)點(diǎn)秉犹,這里不再贅述。
鏈接:http://www.reibang.com/p/ca6613dd4c8d
十八 對(duì)象模型:關(guān)于Dynamic Binding
a是A的對(duì)象稚晚,雖然初值是b經(jīng)過強(qiáng)制類型轉(zhuǎn)換的崇堵,但因?yàn)槭峭ㄟ^對(duì)象調(diào)用,不是指針客燕,所以這是靜態(tài)綁定鸳劳。編譯的匯編碼為箭頭所指。
pa為動(dòng)態(tài)綁定也搓,滿足三個(gè)條件(new出來B赏廓,類型是A,是向上轉(zhuǎn)型)傍妒,call不是一個(gè)固定的地址幔摸。
(*(P->vptr)[n])(p)的解釋:通過指針p找到虛指針vptr,再找到虛表颤练,取出其中第n個(gè)既忆,把它當(dāng)成函數(shù)指針去調(diào)用,由于是通過p來調(diào)用嗦玖,所以p就是一個(gè)this指針患雇,(p)就是this指針。
十九 重談const
const第一周筆記有提過宇挫,鏈接:http://www.reibang.com/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.reibang.com/p/12dcce512fd4
重載Operator new,Operator delete
重載operator new和operator delete用來設(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ù)睹簇,影響無遠(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 可有可無
//...
};
示例
在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ì)繞過前述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)用。主要用來歸還未能完全創(chuàng)建成功的object所占用的memory裁奇。