中文亚洲精品无码_熟女乱子伦免费_人人超碰人人爱国产_亚洲熟妇女综合网

當(dāng)前位置: 首頁 > news >正文

臺州品牌網(wǎng)站建設(shè)seo關(guān)鍵詞優(yōu)化推廣價格

臺州品牌網(wǎng)站建設(shè),seo關(guān)鍵詞優(yōu)化推廣價格,做整個網(wǎng)站靜態(tài)頁面多少錢,做網(wǎng)站設(shè)置時間前言 想必大家都知道面向?qū)ο蟮娜筇卣?amp;#xff1a;封裝,繼承,多態(tài)。封裝的本質(zhì)是:對外暴露必要的接口,但內(nèi)部的具體實現(xiàn)細(xì)節(jié)和部分的核心接口對外是不可見的,僅對外開放必要功能性接口。繼承的本質(zhì)是為了復(fù)用&#x…

前言

? ? ? ? 想必大家都知道面向?qū)ο蟮娜筇卣?#xff1a;封裝,繼承,多態(tài)。封裝的本質(zhì)是:對外暴露必要的接口,但內(nèi)部的具體實現(xiàn)細(xì)節(jié)和部分的核心接口對外是不可見的,僅對外開放必要功能性接口。繼承的本質(zhì)是為了復(fù)用,復(fù)用基類的數(shù)據(jù)成員和方法。對于多態(tài)而言,多態(tài)的實現(xiàn)要求必須是公有繼承作為前提,這也是我們的學(xué)習(xí)順序。那么這篇文章就帶領(lǐng)大家一起學(xué)習(xí)多態(tài)!

目錄

前言

Ⅰ.多態(tài)的概念

Ⅱ.多態(tài)的定義及實現(xiàn)

Ⅲ.抽象類

Ⅳ.多態(tài)的原理

Ⅴ.單繼承和多繼承關(guān)系的虛函數(shù)表

Ⅵ.繼承和多態(tài)常見的面試問題


Ⅰ.多態(tài)的概念

多態(tài)的概念通俗來說:就是多種形態(tài),具體點就是去完成某個行為,當(dāng)不同的對象去完成時會產(chǎn)生出不同的狀態(tài)。

下面我們通過一個例子進(jìn)行理解,同為動物的小貓咪和小狗發(fā)出的不同聲音。

☆有一個基類(Animal),它有兩個派生類(Cat,Dog),在Animal中有個方法(Say),Cat和Dog都是繼承于Animal。當(dāng)Cat調(diào)用Say時會發(fā)出“喵~喵~喵~喵~”的聲音,當(dāng)Dog調(diào)用Say時會發(fā)出“汪~汪~汪~汪~”的聲音,這就是多態(tài)的實現(xiàn)。

☆再簡單的舉一個例子:張三和李四都是學(xué)生,他們都想報考法律專業(yè)的學(xué)校,張三成績比較優(yōu)異就報了一個知名法律大學(xué),李四的成績就不是非常的拔尖,就隨便報了一個學(xué)校。張三和李四去完成報考的行為,但是他們完成時就就產(chǎn)生不同的狀態(tài)。

Ⅱ.多態(tài)的定義及實現(xiàn)

多態(tài)的構(gòu)成條件?

多態(tài)是在不同繼承關(guān)系的類對象,去調(diào)用同一函數(shù),產(chǎn)生了不同的行為。比如Cat和Dog繼承了Animal。Animal對象Say-動物語言,Cat對象Say-喵,Dog對象Say-汪。

那么在繼承中構(gòu)成多態(tài)的兩個條件:

????????☆必須通過基類的指針或者引用調(diào)用虛函數(shù)

????????☆被調(diào)用的函數(shù)必須是虛函數(shù),且派生類必須對基類的虛函數(shù)進(jìn)行重寫

虛函數(shù)?

虛函數(shù):即被virtual修飾的類成員函數(shù)稱為虛函數(shù)。

class Animal {
public:virtual void Say(){cout << " 動物語言 " << endl; }
};

虛函數(shù)的重寫

虛函數(shù)的重寫(覆蓋):派生類中有一個跟基類完全相同的虛函數(shù)(即派生類虛函數(shù)與基類虛函數(shù)的 返回值類型、函數(shù)名字、參數(shù)列表完全相同),稱子類的虛函數(shù)重寫了基類的虛函數(shù)。

class Cat : public Animal {
public:virtual void Say() {cout << "喵~喵~喵~喵~" << endl; }
};class Dog : public Animal {
public:virtual void Say() {cout << "汪~汪~汪~汪~" << endl; }
};

注意:在重寫基類虛函數(shù)時,派生類的虛函數(shù)在不加 virtual關(guān)鍵字時,雖然也可以構(gòu)成重寫(因為繼承后基類的虛函數(shù)被繼承下來了在派生類依舊保持虛函數(shù)屬性),但是該種寫法不是很規(guī)范,不建議這樣使用。

/*virtual*/ void Say() {cout << "汪~汪~汪~汪~" << endl; }

當(dāng)任何一個條件破壞,就會變成隱藏。

指針調(diào)用虛函數(shù)

注意:普通調(diào)用跟調(diào)用對象有關(guān),多態(tài)調(diào)用是跟(指針/引用)指向的對象有關(guān)。

協(xié)變(基類與派生類虛函數(shù)返回值類型不同) ?

子類重寫基父類函數(shù)時,子類中有一個跟父類完全相同的虛函數(shù),與父類虛函數(shù)返回值類型不同,但是要求返回值必須是一個父子類關(guān)系的指針或引用,稱為協(xié)變。

class A{
};class B : public A {
};class Person {
public:virtual A* f() { return new A; }
};class Student : public Person {
public:virtual B* f() { return new B; }
};

析構(gòu)函數(shù)的重寫(基類與派生類析構(gòu)函數(shù)的名字不同)

如果基類的析構(gòu)函數(shù)為虛函數(shù),此時派生類析構(gòu)函數(shù)只要定義,無論是否加virtual關(guān)鍵字, 都與基類的析構(gòu)函數(shù)構(gòu)成重寫,雖然基類與派生類析構(gòu)函數(shù)名字不同。雖然函數(shù)名不相同, 看起來違背了重寫的規(guī)則,其實不然,這里可以理解為編譯器對析構(gòu)函數(shù)的名稱做了特殊處理,編譯后析構(gòu)函數(shù)的名稱統(tǒng)一處理成destructor。

class Person {
public:virtual ~Person() {cout << "~Person()" << endl; }
};class Student : public Person {
public:/virtual ~Student() {cout << "~Student()" << endl; }
};void Test()
{Person* p1 = new Person;Person* p2 = new Student;delete p1;delete p2;
}

只有派生類Student的析構(gòu)函數(shù)重寫了Person的析構(gòu)函數(shù),下面的delete對象調(diào)用析構(gòu)函數(shù),才能構(gòu)成多態(tài),才能保證p1和p2指向的對象正確的調(diào)用析構(gòu)函數(shù)。加 virtual關(guān)鍵字。

如果未加virtual關(guān)鍵字。通過觀察發(fā)現(xiàn),發(fā)生了內(nèi)存泄漏。原本還需要釋放Student類,但是這里沒有。因為delete是使用指針調(diào)用析構(gòu)-operator delete(ptr),這里未加virtual關(guān)鍵字就是普通調(diào)用,普通調(diào)用和對象類型有關(guān),普通調(diào)用會發(fā)生隱藏關(guān)系,是什么類型就調(diào)用什么析構(gòu)函數(shù),所以就會調(diào)用兩次Person。

C++11 override 和 ?nal

從上面可以看出,C++對函數(shù)重寫的要求比較嚴(yán)格,但是有些情況下由于疏忽,可能會導(dǎo)致函數(shù) 名字母次序?qū)懛炊鵁o法構(gòu)成重載,而這種錯誤在編譯期間是不會報出的,只有在程序運行時沒有 得到預(yù)期結(jié)果才來debug會得不償失,因此:C++11提供了override和?nal兩個關(guān)鍵字,可以幫助用戶檢測是否重寫。

☆?nal:修飾虛函數(shù),表示該虛函數(shù)不能再被重寫

class Car
{
public:virtual void Drive() final {}
};class Benz :public Car
{
public:virtual void Drive() { cout << "Benz-舒適" << endl; }
};

實現(xiàn)一個不被繼承的類

?構(gòu)造私有,c++98抽象類

class A
{
private:A(){}
};class Benz :public A
{};

??定義時加 final,c++11

class A final
{
private:A(){}
};class Benz :public A
{};

☆override: 檢查派生類虛函數(shù)是否重寫了基類某個虛函數(shù),如果沒有重寫編譯報錯。

class Car{
public:virtual void Drive(){}
};class Benz :public Car {
public:virtual void Drive() override {cout << "Benz-舒適" << endl;}
};

重載、覆蓋(重寫)、隱藏(重定義)的對比

Ⅲ.抽象類

在虛函數(shù)的后面寫上 =0 ,則這個函數(shù)為純虛函數(shù)。包含純虛函數(shù)的類叫做抽象類(也叫接口 類),抽象類不能實例化出對象。派生類繼承后也不能實例化出對象,只有重寫純虛函數(shù),派生 類才能實例化出對象。純虛函數(shù)規(guī)范了派生類必須重寫,另外純虛函數(shù)更體現(xiàn)出了接口繼承。

class Car
{
public://純虛函數(shù)virtual void Drive() = 0;
};class Benz :public Car
{
public://對純虛函數(shù)進(jìn)行重寫virtual void Drive(){cout << "Benz-舒適" << endl;}
};class BMW :public Car
{
public:virtual void Drive(){cout << "BMW-操控" << endl;}
};void Test()
{//Car p;//出錯Car* pBenz = new Benz;pBenz->Drive();Car* pBMW = new BMW;pBMW->Drive();
}

接口繼承和實現(xiàn)繼承

普通函數(shù)的繼承是一種實現(xiàn)繼承,派生類繼承了基類函數(shù),可以使用函數(shù),繼承的是函數(shù)的實現(xiàn)。虛函數(shù)的繼承是一種接口繼承,派生類繼承的是基類虛函數(shù)的接口,目的是為了重寫,達(dá)成多態(tài),繼承的是接口。所以如果不實現(xiàn)多態(tài),不要把函數(shù)定義成虛函數(shù)。?

為了更好的理解接口繼承,下面我們通過試題進(jìn)行深究。以下程序輸出結(jié)果是什么()?

class A
{
public:virtual void func(int val = 1){ std::cout << "A->" << val << std::endl; }virtual void test(){ func(); }
};class B : public A
{
public:void func(int val = 0){ std::cout << "B->" << val << std::endl; }
};int main(int argc, char* argv[])
{B*p = new B;p->test();return 0;
}

A: A->0? ?B: B->1? ?C: A->1? ?D: B->0? ?E: 編譯出錯? ?F: 以上都不正確

參考答案:B: B->1,過程如圖下

Ⅳ.多態(tài)的原理

虛函數(shù)表

這里常考一道筆試題:sizeof(Base)是多少? ?

class Base
{
public:virtual void Func1(){cout << "Func1()" << endl;}
private:char _c = 'a';int _b = 1;
};int main()
{cout << sizeof(Base) << endl;return 0;
}

參考答案:12,過程如下圖

通過觀察測試我們發(fā)現(xiàn)p對象是12bytes,除了_b,_c成員,還多一個__vfptr放在對象的前面(注意有些平臺可能會放到對象的最后面,這個跟平臺有關(guān)),對象中的這個指針我們叫做虛函數(shù)表指針(v代 表virtual,f代表function)。一個含有虛函數(shù)的類中都至少都有一個虛函數(shù)表指針,因為虛函數(shù) 的地址要被放到虛函數(shù)表中,虛函數(shù)表也簡稱虛表。那么派生類中這個表放了些什么呢?我們接著往下分析。

針對上面的代碼我們做出以下改造,我們增加一個派生類Derive去繼承Base ,Derive中重寫Func1 ,Base再增加一個虛函數(shù)Func2和一個普通函數(shù)Func3。

class Base
{
public:virtual void Func1(){cout << "Base::Func1()" << endl;}virtual void Func2(){cout << "Base::Func2()" << endl;}void Func3(){cout << "Base::Func3()" << endl;}private:int _b = 1;
};class Derive : public Base
{
public:virtual void Func1(){cout << "Derive::Func1()" << endl;}private:int _d = 2;
};int main()
{Base b;Derive d;return 0;
}

通過觀察和測試,我們發(fā)現(xiàn)了以下幾點問題:

☆ 派生類對象d中也有一個虛表指針,d對象由兩部分構(gòu)成,一部分是父類繼承下來的成員,虛表指針也就是存在部分的另一部分是自己的成員。

☆基類b對象和派生類d對象虛表是不一樣的,這里我們發(fā)現(xiàn)Func1完成了重寫,所以d的虛表中存的是重寫的Derive::Func1,所以虛函數(shù)的重寫也叫作覆蓋,覆蓋就是指虛表中虛函數(shù)的覆蓋。重寫是語法的叫法,覆蓋是原理層的叫法。

☆另外Func2繼承下來后是虛函數(shù),所以放進(jìn)了虛表,Func3也繼承下來了,因為不是虛函數(shù),所以不會放進(jìn)虛表。

虛函數(shù)表本質(zhì)是一個存虛函數(shù)指針的指針數(shù)組,一般情況這個數(shù)組最后面放了一個nullptr。

☆總結(jié)一下派生類的虛表生成:a.先將基類中的虛表內(nèi)容拷貝一份到派生類虛表中 b.如果派生類重寫基類中某個虛函數(shù),用派生類自己的虛函數(shù)覆蓋虛表中基類的虛函數(shù) c.派生類自己新增加的虛函數(shù)按其在派生類中的聲明次序增加到派生類虛表的最后。

☆這里還有一個很容易混淆的問題:虛函數(shù)存在哪的?虛表存在哪的? 答:虛函數(shù)存在虛表,虛表存在對象中。注意上面的回答的錯的。但是很多時候都是這樣深以為然的。注意虛表存的是虛函數(shù)指針,不是虛函數(shù),虛函數(shù)和普通函數(shù)一樣的,都是存在代碼段的,只是他的指針又存到了虛表中。另外對象中存的不是虛表,存的是虛表指針。那么虛表存在哪的呢?實際我們?nèi)ヲ炞C一下會發(fā)現(xiàn)vs下是存在代碼段的。

多態(tài)的原理

相信大家經(jīng)過上面的理解,對于多態(tài)原理的面紗幾乎已經(jīng)解開了,對于多態(tài)的理解最重要是理解虛指針(_vfptr),虛函數(shù)(父子類被virtual關(guān)鍵字聲明的函數(shù)),虛表(虛函數(shù)的類都有一個一維的虛函數(shù)表)。父子類通過繼承關(guān)系的虛函數(shù),通過虛指針指向虛表,當(dāng)發(fā)生切片后,當(dāng)子類虛函數(shù)重寫了父類的虛函數(shù),這時就會發(fā)生覆蓋。

下面主要是對覆蓋在進(jìn)行深度理解,最后再通過匯編代碼的角度來理解虛表,虛函數(shù),虛函數(shù)指針。

class Person {
public:virtual void BuyTicket() { cout << "買票-全價" << endl; }
};class Student : public Person {
public:virtual void BuyTicket() { cout << "買票-半價" << endl; }
};void Func(Person& p)
{p.BuyTicket();
}int main()
{Person Jack;Func(Jack);Student	Tom;Func(Tom);return 0;
}

通過匯編的角度理解虛表,我們發(fā)現(xiàn)在父類中調(diào)用自己函數(shù),因為是普通調(diào)用,不管是通過虛函數(shù)指針調(diào)用虛表,還是直接調(diào)用虛表,都是在編譯時已經(jīng)從符號表確認(rèn)了函數(shù)的地址,直接call。

void Func(Person* p)
{p->BuyTicket();
}int main()
{Person Jack;Func(&Jack);Jack.BuyTicket();return 0;
}

在匯編代碼中,聲明成員Jack,Jack調(diào)用Func函數(shù),相當(dāng)于把Jack對象頭4個字節(jié)(虛表指針)移動到了edx,Jack直接調(diào)用類中的BuyTicket函數(shù),相當(dāng)于把虛表中的頭4字節(jié)存的虛函數(shù)指針移動到了eax 。? ? ??

void Func(Person* p)

{

...

????p->BuyTicket();

// p中存的是Jack對象的指針,將p移動到eax中

001940DE ?mov ? ? ? ? eax,dword ptr [p]

// [eax]就是取eax值指向的內(nèi)容,這里相當(dāng)于把Jack對象頭4個字節(jié)(虛表指針)移動到了edx

001940E1 ?mov ? ? ? ? edx,dword ptr [eax]

// [edx]就是取edx值指向的內(nèi)容,這里相當(dāng)于把虛表中的頭4字節(jié)存的虛函數(shù)指針移動到了eax

00B823EE ?mov ? ? ? ? eax,dword ptr [edx]

// call eax中存虛函數(shù)的指針。這里可以看出滿足多態(tài)的調(diào)用,不是在編譯時確定的,是運行起來以后到對象的中取找的。

001940EA ?call ? ? ? ?eax ?

001940EC ?cmp ? ? ? ? esi,esp ?

}

int main()

{

...

// 首先BuyTicket雖然是虛函數(shù),但是Jack是對象,不滿足多態(tài)的條件,所以這里是地址普通函數(shù)的調(diào)用轉(zhuǎn)換成地址時,是在編譯時已經(jīng)從符號表確認(rèn)了函數(shù)的地址,直接call

????????mike.BuyTicket();

00195182 ?lea ? ? ? ? ecx,[mike]

00195185 ?call ? ? ? ?Person::BuyTicket (01914F6h) ?

...

}

當(dāng)滿足多態(tài)調(diào)用在編譯時確定的,是運行起來以后到對象的中取找的。

void Func(Person* p)
{p->BuyTicket();
}int main()
{Student Tom;Func(&Tom);Tom.BuyTicket();return 0;
}

void Func(Person* p)
00DA458E ?mov ? ? ? ? eax,dword ptr [p] ?
void Func(Person* p)
00DA4591 ?mov ? ? ? ? edx,dword ptr [eax] ?
00DA4593 ?mov ? ? ? ? esi,esp ?
00DA4595 ?mov ? ? ? ? ecx,dword ptr [p] ?
00DA4598 ?mov ? ? ? ? eax,dword ptr [edx] ?
00DA459A ?call ? ? ? ?eax ?
00DA459C ?cmp ? ? ? ? esi,esp ?

動態(tài)綁定與靜態(tài)綁定

靜態(tài)綁定又稱為前期綁定(早綁定),在程序編譯期間確定了程序的行為,也稱為靜態(tài)多態(tài), 比如:函數(shù)重載

動態(tài)綁定又稱后期綁定(晚綁定),是在程序運行期間,根據(jù)具體拿到的類型確定程序的具體 行為,調(diào)用具體的函數(shù),也稱為動態(tài)多態(tài)。

不管虛函數(shù)的虛指針是指向父類虛表,還是子類的虛表其實都是一樣的,該有虛表該有成員都是一樣的,不一樣的是子函數(shù)的虛表中的虛函數(shù)(可能發(fā)生了重寫/覆蓋)。

int main()
{//普通調(diào)用--編譯時/靜態(tài) 綁定Student Tom;Func(&Tom);//多態(tài)調(diào)用--運行時/動態(tài) 綁定Person Jack;Func(&Jack);return 0;
}

總結(jié):多態(tài)的原理實質(zhì)就是,虛表是提前寫好的,對象指向誰就調(diào)用誰的虛表,多態(tài)調(diào)用就是依靠虛表一系列的動作。指向父類調(diào)用父類的虛函數(shù),指向子類調(diào)用子類的虛函數(shù)(可能覆蓋),運行起來后去虛表中找。

虛表的存放區(qū)域?

class Person {
public:virtual void BuyTicket() { cout << "買票-全價" << endl; }
};class Student : public Person {
public:virtual void BuyTicket() { cout << "買票-半價" << endl; }
};
void Test()
{int a = 0;cout << "棧" << &a << endl;int* p1 = new int;cout << "堆" << p1 << endl;const char* str = "hello world";cout << "代碼段/常量區(qū)" << (void*)str << endl;static int b = 0;cout << "靜態(tài)區(qū)/數(shù)據(jù)段" << &b << endl;Student s;cout << "虛表:" << (void*)*((int*)&s1) << endl;
}

通過觀察,我們發(fā)現(xiàn)代碼段與虛表的地址是挨的最近,所以我們得出結(jié)論虛表是存放在代碼段。?

?同一個類下,虛表是用一個。

void Test()
{Student s1;cout << "虛表1:" << (void*)*((int*)&s1) << endl;Student s2;cout << "虛表2:" << (void*)*((int*)&s2) << endl;
}

理解: (void*)*((int*)&s1)

Ⅴ.單繼承和多繼承關(guān)系的虛函數(shù)表

需要注意的是在單繼承和多繼承關(guān)系中,下面我們?nèi)リP(guān)注的是派生類對象的虛表模型,因為基類 的虛表模型前面我們已經(jīng)看過了,沒什么需要特別研究的

單繼承中的虛函數(shù)表

class Base {public:virtual void func1() { cout << "Base::func1" << endl; }virtual void func2() { cout << "Base::func2" << endl; }private:int a;
};class Derive :public Base {public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }virtual void func4() { cout << "Derive::func4" << endl; }private:int b;
};

觀察下圖中的監(jiān)視窗口中我們發(fā)現(xiàn)看不見func3和func4。這里是編譯器的監(jiān)視窗口故意隱藏了這 兩個函數(shù),也可以認(rèn)為是他的一個小bug。那么我們?nèi)绾尾榭磀的虛表呢?下面我們使用代碼打印 出虛表中的函數(shù)。

思路:取出b、d對象的頭4bytes,就是虛表的指針,前面我們說了虛函數(shù)表本質(zhì)是一個存虛函數(shù)
指針的指針數(shù)組,這個數(shù)組最后面放了一個nullptr

????????●先取b的地址,強(qiáng)轉(zhuǎn)成一個int*的指針

????????●再解引用取值,就取到了b對象頭4bytes的值,這個值就是指向虛表的指針

????????●再強(qiáng)轉(zhuǎn)成VFPTR*,因為虛表就是一個存VFPTR類型(虛函數(shù)指針類型)的數(shù)組。

????????●虛表指針傳遞給PrintVTable進(jìn)行打印虛表

????????●需要說明的是這個打印虛表的代碼經(jīng)常會崩潰,因為編譯器有時對虛表的處理不干凈,虛表最后面沒有放nullptr,導(dǎo)致越界,這是編譯器的問題。我們只需要點目錄欄的 - 生成 - 清理解決方案,再編譯就好了。

?注意:

????????平時使用 typedef定義一個別名是直接加在類型后面,例如:typedef ?long double REAL;這里 typedef void(*) ()VFPTR是錯誤的,函數(shù)指針的語法規(guī)定是將VFPTR別名放入函數(shù)指針類型()中

?????????☆在傳參時,PrintVTable中參數(shù)是函數(shù)指針的數(shù)組,就不能只傳對象4byte的地址,那么就應(yīng)該傳函數(shù)指針(VFPTR*)(*(int*)&b)。

//函數(shù)指針:通過地址調(diào)用函數(shù)
typedef void(*VFPTR) ();//函數(shù)指針的數(shù)組:虛表地址是連續(xù)的,通首地址連續(xù)查看虛函數(shù)地址
void PrintVTable(VFPTR vTable[])
{cout << " 虛表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d個虛函數(shù)地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}int main()
{Base b;Derive d;VFPTR* vTableb = (VFPTR*)(*(int*)&b);PrintVTable(vTableb);VFPTR* vTabled = (VFPTR*)(*(int*)&d);PrintVTable(vTabled);return 0;
}

通過打印發(fā)現(xiàn)func3和func4是在對象d的虛表中,d對象的func1函數(shù)也在虛表中覆蓋了父類的funcl函數(shù)。下圖也通過不同顏色區(qū)分出不同對象對應(yīng)的虛擬地址空間。

為了能夠同時測試32位和64位下的虛表地址,32位下是4byte,64為下是8byte,又該如何呢?

這里就通過傳參時,傳入二級指針(void**),當(dāng)32位下傳int*時解引用是4byte,但是在64下傳int*時解引用是8byte,顯然這時候是行不通的,但是傳二級指針不管是32位還是64位下解引用都是地址,地址不管怎么樣都是4byte,這里也可以寫成(int**),(double**)等等都是可以的,但通常情況下是寫成(void**)。

//32位和64位同時可以打印虛表地址
VFPTR* vTableb = (VFPTR*)(*(void**)&b);PrintVTable(vTableb);

多繼承中的虛函數(shù)表

在多繼承下,子類會繼承兩個父類的虛函數(shù)表。

子類繼承了兩個父類的虛函數(shù)表,而且都會與兩個父類的虛函數(shù)進(jìn)行重寫。

?注意:為了打印出第二個父類虛函數(shù)表的地址,需要取第一個父類地址的偏移量例如:(char*)&d + sizeof(Base1)或者?Base2* ptr2 = &d;

class Base1 {
public:virtual void func1() { cout << "Base1::func1" << endl; }virtual void func2() { cout << "Base1::func2" << endl; }private:int b1;
};class Base2 {
public:virtual void func1() { cout << "Base2::func1" << endl; }virtual void func2() { cout << "Base2::func2" << endl; }private:int b2;
};class Derive : public Base1, public Base2 {public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }private:int d1;
};typedef void(*VFPTR) ();void PrintVTable(VFPTR vTable[])
{cout << " 虛表地址>" << vTable << endl;for (int i = 0; vTable[i] != nullptr; ++i){printf(" 第%d個虛函數(shù)地址 :0X%x,->", i, vTable[i]);VFPTR f = vTable[i];f();}cout << endl;
}int main()
{Base1 b1;Base2 b2;PrintVTable((VFPTR*)(*(int*)&b1));PrintVTable((VFPTR*)(*(int*)&b2));Derive d;VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);PrintVTable(vTableb1);//VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));//PrintVTable(vTableb2);Base2* ptr2 = &d;PrintVTable((VFPTR*)(*(int*)&ptr2));return 0;
}

最后通打印我們發(fā)現(xiàn),子類會與兩個父類發(fā)生重寫,但是子類未重寫的虛函數(shù)放在第一個繼承父類部分的虛函數(shù)表中。

菱形繼承、菱形虛擬繼承

實際中我們不建議設(shè)計出菱形繼承及菱形虛擬繼承,一方面太復(fù)雜容易出問題,另一方面這樣的 模型,訪問基類成員有一定得性能損耗,在實際中也很少用。對于學(xué)習(xí)知識來說是可以見見的,過于深究會頭昏腦脹。

????????☆菱形繼承

菱形繼承就是重復(fù)繼承,在繼承的時候不加virtual關(guān)鍵字。

class Base {
public:virtual void func() { cout << "Base1::func" << endl; }
private:int b;
};class Base1 : public Base {
public:virtual void func1() { cout << "Base1::func1" << endl; }private:int b1;
};class Base2 : public Base{
public:virtual void func2() { cout << "Base2::func2" << endl; }private:int b2;
};class Derive : public Base1, public Base2 {
public:virtual void func1() { cout << "Derive::func1" << endl; }virtual void func2() { cout << "Derive::func3" << endl; }virtual void func3() { cout << "Derive::func3" << endl; }private:int d1;
};

?????????☆菱形虛擬繼承

為了解決菱形繼承的二義性和數(shù)據(jù)冗余問題,用過加virtual關(guān)鍵字形成菱形繼承。由于前面的“菱形繼承”中的類的內(nèi)部數(shù)據(jù)和接口都是完全一樣的,為了解決冗余,只是我們采用了虛擬繼承:其省略后的源碼如下所示:

class Base {……};
class Base1 : virtual public B{……};
class Base2: virtual public B{……};
class ?Derive : public B1, public B2{ …… };

菱形虛擬繼承其實結(jié)構(gòu)與菱形繼承是一樣的,不一樣是的加了虛擬后,會單獨形成一個虛基表進(jìn)行記錄變量的偏移量,這里只需要明白的是虛函數(shù)表和虛基表是不同的,要討論的結(jié)構(gòu)如下:

Ⅵ.繼承和多態(tài)常見的面試問題

概念查考

1. 下面哪種面向?qū)ο蟮姆椒梢宰屇阕兊酶挥? )

A: 繼承 B: 封裝 C: 多態(tài) D: 抽象

2. ( )是面向?qū)ο蟪绦蛟O(shè)計語言中的一種機(jī)制。這種機(jī)制實現(xiàn)了方法的定義與具體的對象無關(guān), 而對方法的調(diào)用則可以關(guān)聯(lián)于具體的對象。

A: 繼承 B: 模板 C: 對象的自身引用 D: 動態(tài)綁定

3. 面向?qū)ο笤O(shè)計中的繼承和組合,下面說法錯誤的是?()

A:繼承允許我們覆蓋重寫父類的實現(xiàn)細(xì)節(jié),父類的實現(xiàn)對于子類是可見的,是一種靜態(tài)復(fù)用,也稱為白盒復(fù)用

B:組合的對象不需要關(guān)心各自的實現(xiàn)細(xì)節(jié),之間的關(guān)系是在運行時候才確定的,是一種動態(tài)復(fù)用,也稱為黑盒復(fù)用

C:優(yōu)先使用繼承,而不是組合,是面向?qū)ο笤O(shè)計的第二原則

D:繼承可以使子類能自動繼承父類的接口,但在設(shè)計模式中認(rèn)為這是一種破壞了父類的封 裝性的表現(xiàn)

4. 以下關(guān)于純虛函數(shù)的說法,正確的是( )

A:聲明純虛函數(shù)的類不能實例化對象??B:聲明純虛函數(shù)的類是虛基類

C:子類必須實現(xiàn)基類的純虛函數(shù) D:純虛函數(shù)必須是空函數(shù)

5. 關(guān)于虛函數(shù)的描述正確的是( )

A:派生類的虛函數(shù)與基類的虛函數(shù)具有不同的參數(shù)個數(shù)和類型? B:內(nèi)聯(lián)函數(shù)不能是虛函數(shù)

C:派生類必須重新定義基類的虛函數(shù)? D:虛函數(shù)可以是一個static型的函數(shù)

6. 關(guān)于虛表說法正確的是( )

A:一個類只能有一張?zhí)摫?/p>

B:基類中有虛函數(shù),如果子類中沒有重寫基類的虛函數(shù),此時子類與基類共用同一張?zhí)摫?/p>

C:虛表是在運行期間動態(tài)生成的

D:一個類的不同對象共享該類的虛表

7. 假設(shè)A類中有虛函數(shù),B繼承自A,B重寫A中的虛函數(shù),也沒有定義任何虛函數(shù),則( )

A:A類對象的前4個字節(jié)存儲虛表地址,B類對象前4個字節(jié)不是虛表地址

B:A類對象和B類對象前4個字節(jié)存儲的都是虛基表的地址

C:A類對象和B類對象前4個字節(jié)存儲的虛表地址相同

D:A類和B類虛表中虛函數(shù)個數(shù)相同,但A類和B類使用的不是同一張?zhí)摫?/u>

8. 下面程序輸出結(jié)果是什么? ()?

#include<iostream>
using namespace std;class A{
public:A(char *s) { cout<<s<<endl; }~A(){}
};class B:virtual public A
{
public:B(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};class C:virtual public A
{
public:C(char *s1,char*s2):A(s1) { cout<<s2<<endl; }
};class D:public B,public C
{
public:D(char *s1,char *s2,char *s3,char *s4):B(s1,s2),C(s1,s3),A(s1){ cout<<s4<<endl;}
};int main() {D *p=new D("class A","class B","class C","class D");delete p;return 0;
}

A:class A class B class C class D ????????????????B:class D class B class C class A

C:class D class C class B class A ????????????????D:class A class C class B class D

9. 多繼承中指針偏移問題?下面說法正確的是( )

class Base1 { ?public: ?int _b1; };class Base2 { ?public: ?int _b2; };class Derive : public Base1, public Base2 { public: int _d; };int main(){Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

?A:p1 == p2 == p3 ????????B:p1 < p2 < p3 ????????C:p1 == p3 != p2 ????????D:p1 != p2 != p3

10. 多繼承中指針偏移問題?下面說法正確的是( ) ?

class Base1 { ?public: ?int _b1; };class Base2 { ?public: ?int _b2; };class Derive : public Base2, public Base1 { public: int _d; };int main(){Derive d;Base1* p1 = &d;Base2* p2 = &d;Derive* p3 = &d;return 0;
}

?A:p1 == p2 == p3 ????????B:p1 > (p2 =?p3) ????????C:p1 == p3 != p2 ????????D:p1 != p2 != p3

問答題?

1. 什么是多態(tài)?

答:多態(tài)分為兩種,一種是靜態(tài)的多態(tài)是靜態(tài)綁定,在程序編譯期間確定了程序的行為,函數(shù)重載;一種動態(tài)的多態(tài)是動態(tài)綁定,通過繼承下虛函數(shù)重寫,父類的指針指向在父類的虛函數(shù)進(jìn)行動態(tài)綁定,運行起來去虛表中找到對應(yīng)的虛函數(shù),指向子類調(diào)用子類。

2. 什么是重載、重寫(覆蓋)、重定義(隱藏)?

答:函數(shù)重載發(fā)生在同一作用域并且只需要函數(shù)名相同;重寫是子類的虛函數(shù)對父類的虛函數(shù)進(jìn)行覆蓋,在不同的作用域且在繼承關(guān)系下函數(shù)需要三同(參數(shù),函數(shù)名,返回值)和父類函數(shù)被關(guān)鍵字virtual修飾 ;重定義是子類繼承父類下,在不同作用域,滿足函數(shù)名相同就構(gòu)成隱藏。

3. 多態(tài)的實現(xiàn)原理?

答:通過繼承下虛函數(shù)重寫,父類的指針指向在父類的虛函數(shù)進(jìn)行動態(tài)綁定,運行起來去虛表中找到對應(yīng)的虛函數(shù),指向子類調(diào)用子類。

4. inline函數(shù)可以是虛函數(shù)嗎??

答:不能的,但是在語法上是可以的,只是編譯器會忽略inline屬性,這個函數(shù)就不再是inline,因為虛函數(shù)要放到虛表中去。當(dāng)不構(gòu)成多態(tài)是可以具有內(nèi)聯(lián)屬性,構(gòu)成多態(tài)不具有內(nèi)聯(lián)屬性。

?5. 靜態(tài)成員可以是虛函數(shù)嗎?

答:不能,因為靜態(tài)成員函數(shù)沒有this指針,使用類型::成員函數(shù)的調(diào)用方式?無法訪問虛函數(shù)表,所以靜態(tài)成員函數(shù)無法放進(jìn)虛函數(shù)表。

6. 構(gòu)造函數(shù)可以是虛函數(shù)嗎?

答:不能,因為對象中的虛函數(shù)表指針是在構(gòu)造函數(shù)初始化列表階段才初始化的。

7.析構(gòu)函數(shù)可以是虛函數(shù)嗎?什么場景下析構(gòu)函數(shù)是虛函數(shù)?

答:可以,并且最好把基類的析構(gòu)函數(shù)定義成虛函數(shù)。參考本節(jié)課件內(nèi)容 ?

?8. 對象訪問普通函數(shù)快還是虛函數(shù)更快?

答:首先如果是普通對象,是一樣快的。如果是指針對象或者是引用對象,則調(diào)用的普通函數(shù)快,因為構(gòu)成多態(tài),運行時調(diào)用虛函數(shù)需要到虛函 數(shù)表中去查找。

9. 虛函數(shù)表是在什么階段生成的,存在哪的?

答:虛函數(shù)表是在編譯階段就生成的構(gòu)造函數(shù)初始化列表中初始化【虛表指針】,一般情況 下存在代碼段(常量區(qū))的。 ?

10. C++菱形繼承的問題?虛繼承的原理?

答:菱形繼承會產(chǎn)生二義性和數(shù)據(jù)冗余。虛基表指針在本類中找到虛基表,本類的虛基表通過偏移量計算找到該虛基類中變量的值。注意這里不要把虛函數(shù)表和虛基表搞混了。 ?

11. 什么是抽象類?抽象類的作用?

答:在C++中,含有純虛擬函數(shù)的類稱為抽象類,它不能生成對象;目的是為了重寫,達(dá)成 多態(tài),繼承的是接口;抽象類強(qiáng)制重寫了虛函數(shù),另外抽象類體現(xiàn)出了接口繼承關(guān)系。 ?

??? ?[?作者?]? ?includeevey

?📃? [?日期?] ??2023/2/1
?📜??[?聲明?]? ?到這里就該說再見了,若本文有錯誤和不準(zhǔn)確之處,懇望讀者批評指正!
? ? ? ? ? ? ? ? ? ? 有則改之無則加勉!若認(rèn)為文章寫的不錯,一鍵三連加關(guān)注!


?

http://www.risenshineclean.com/news/21495.html

相關(guān)文章:

  • 教做網(wǎng)站的學(xué)校河南網(wǎng)站建設(shè)
  • 工信部網(wǎng)站用戶名專業(yè)的seo外包公司
  • 臺州seo網(wǎng)站推廣費用建立營銷型網(wǎng)站
  • 寧波本地網(wǎng)站排行網(wǎng)絡(luò)推廣引流方式
  • 太原做app網(wǎng)站建設(shè)國外網(wǎng)站排名前十
  • 不用dw怎么做網(wǎng)站西安網(wǎng)站維護(hù)
  • 網(wǎng)頁設(shè)計與制作教程江西高校出版社優(yōu)化設(shè)計答案六年級上冊語文
  • 河南專業(yè)網(wǎng)站建設(shè)日本產(chǎn)品和韓國產(chǎn)品哪個好
  • 網(wǎng)頁設(shè)計代碼模板網(wǎng)站企業(yè)網(wǎng)上的推廣
  • 網(wǎng)站常用架構(gòu)個人網(wǎng)站推廣方法
  • 武漢建設(shè)工程價格信息網(wǎng)杭州網(wǎng)站優(yōu)化體驗
  • 做時時彩網(wǎng)站牌照申請騰訊廣告
  • asp.net 價格查詢網(wǎng)站免費隱私網(wǎng)站推廣
  • 網(wǎng)站機(jī)房建設(shè)成本湖南網(wǎng)站seo營銷
  • 永城做網(wǎng)站利爾化學(xué)股票
  • 個人如何開網(wǎng)站賺錢平臺
  • 社區(qū)網(wǎng)站如何做長沙seo免費診斷
  • 網(wǎng)站前置審批流程seo建站公司
  • wordpress免費商城seo網(wǎng)站優(yōu)化軟件價格
  • 付費網(wǎng)站做推廣哪個好產(chǎn)品質(zhì)量推廣營銷語
  • 青海建設(shè)廳網(wǎng)站特種作業(yè)seo怎樣優(yōu)化網(wǎng)站
  • 百度推廣基木魚重慶seo網(wǎng)站管理
  • 肇慶網(wǎng)絡(luò)推廣公司重慶做優(yōu)化的網(wǎng)絡(luò)公司
  • 幫客戶做插邊球網(wǎng)站谷歌google地圖
  • 網(wǎng)站開發(fā)用技術(shù)seo優(yōu)化方案報價
  • 創(chuàng)意網(wǎng)紅墻圖片互聯(lián)網(wǎng)seo是什么
  • 阿里云網(wǎng)站備案好了 怎么建站阿里巴巴運營
  • 莆田的外貿(mào)網(wǎng)站營銷軟文范例
  • 中國菲律賓商會win7優(yōu)化大師官網(wǎng)
  • 特色的網(wǎng)站建設(shè)百度站長平臺官網(wǎng)登錄入口