欧美free性护士vide0shd,老熟女,一区二区三区,久久久久夜夜夜精品国产,久久久久久综合网天天,欧美成人护士h版

首頁綜合 正文
目錄

柚子快報邀請碼778899分享:c語言 【C++融會貫通】多態(tài)

柚子快報邀請碼778899分享:c語言 【C++融會貫通】多態(tài)

http://yzkb.51969.com/

目錄

?編輯

一、多態(tài)的概念

1.1 概念

二、多態(tài)的定義及實現(xiàn)

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

2.2 虛函數(shù)?

2.3虛函數(shù)的重寫

2.4 C++11 override 和 ?nal

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

三、抽象類?

3.1 概念

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

四、多態(tài)的原理

4.1虛函數(shù)表

4.2多態(tài)的原理

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

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

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

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

5.3. 菱形繼承、菱形虛擬繼承?

結(jié)尾

一、多態(tài)的概念

1.1 概念

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

舉個栗子:比如買票這個行為,當(dāng)普通人買票時,是全價買票;學(xué)生買票時,是半價買票;軍人 買票時是優(yōu)先買票。

再舉個栗子: 最近為了爭奪在線支付市場,支付寶年底經(jīng)常會做誘人的掃紅包-支付-給獎勵金的 活動。那么大家想想為什么有人掃的紅包又大又新鮮8塊、10塊...,而有人掃的紅包都是1毛,5 毛....。其實這背后也是一個多態(tài)行為。支付寶首先會分析你的賬戶數(shù)據(jù),比如你是新用戶、比如 你沒有經(jīng)常支付寶支付等等,那么你需要被鼓勵使用支付寶,那么就你掃碼金額 = random()%99;比如你經(jīng)常使用支付寶支付或者支付寶賬戶中常年沒錢,那么就不需要太鼓勵你 去使用支付寶,那么就你掃碼金額 = random()%1;總結(jié)一下:同樣是掃碼動作,不同的用戶掃 得到的不一樣的紅包,這也是一種多態(tài)行為。ps:支付寶紅包問題純屬瞎編,大家僅供娛樂。

二、多態(tài)的定義及實現(xiàn)

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

多態(tài)是在不同繼承關(guān)系的類對象,去調(diào)用同一函數(shù),產(chǎn)生了不同的行為。比如Student繼承了 Person。Person對象買票全價,Student對象買票半價。

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

必須通過基類的指針或者引用調(diào)用虛函數(shù)被調(diào)用的函數(shù)必須是虛函數(shù),且派生類必須對基類的虛函數(shù)進行重寫

2.2 虛函數(shù)?

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

class Person {

public:

virtual void BuyTicket() { cout << "買票-全價" << endl;}

};

2.3虛函數(shù)的重寫

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

class Person {

public:

virtual void BuyTicket() { cout << "買票-全價" << endl; }

};

class Student : public Person {

public:

virtual void BuyTicket() { cout << "買票-半價" << endl; }

/*注意:在重寫基類虛函數(shù)時,派生類的虛函數(shù)在不加virtual關(guān)鍵字時,雖然也可以構(gòu)成重寫(因

為繼承后基類的虛函數(shù)被繼承下來了在派生類依舊保持虛函數(shù)屬性),但是該種寫法不是很規(guī)范,不建議

這樣使用*/

/*void BuyTicket() { cout << "買票-半價" << endl; }*/

};

void Func(Person& p)

{

p.BuyTicket();

}

int main()

{

Person ps;

Student st;

Func(ps);

Func(st);

return 0;

}

虛函數(shù)重寫的兩個例外:

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

派生類重寫基類虛函數(shù)時,與基類虛函數(shù)返回值類型不同。即基類虛函數(shù)返回基類對象的指針或者引用,派生類虛函數(shù)返回派生類對象的指針或者引用時,稱為協(xié)變。

注意:這里的基類和派生類的指針或引用可以是當(dāng)前基類和派生類的,也可以是其他具有繼承關(guān)系的基類和派生類的。

class A {};

class B : public A {};

class Person {

public:

virtual A* f() {

cout << "買票-全價" << endl;

return new A;

}

};

class Student : public Person {

public:

virtual B* f() {

cout << "買票-半價" << endl;

return new B;

}

};

void Func(Person& p)

{

p.f();

}

int main()

{

Person ps;

Student st;

Func(ps);

Func(st);

return 0;

}

2. 析構(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:

~Person()

{

cout << "~Person()" << endl;

}

};

class Student : public Person {

public:

~Student()

{

cout << "~Student()" << endl;

}

};

int main()

{

Person* p = new Student();

delete p;

return 0;

}

上述代碼會導(dǎo)致內(nèi)存泄漏,由于new的是Student的對象,但是調(diào)用析構(gòu)函數(shù)的時候是調(diào)用Person的,Studentd的成員卻沒有被釋放,所以導(dǎo)致內(nèi)存泄漏問題,要想解決這個問題,就得使用這兩個函數(shù)構(gòu)成多態(tài)。

class Person {

public:

virtual ~Person() { cout << "~Person()" << endl; }

};

class Student : public Person {

public:

virtual ~Student() { cout << "~Student()" << endl; }

};

// 只有派生類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ù)。

int main()

{

Person* p1 = new Person;

Person* p2 = new Student;

delete p1;

delete p2;

return 0;

}

2.4 C++11 override 和 ?nal

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

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

class Car

{

public:

virtual void Drive() final {}

};

class Benz :public Car

{

public:

virtual void Drive() { cout << "Benz-舒適" << endl; }

};

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

class Car {

public:

virtual void Drive() {}

};

class Benz :public Car {

public:

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

};

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

三、抽象類?

3.1 概念

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

class Car

{

public:

virtual void Drive() = 0;

};

class Benz :public Car

{

public:

virtual void Drive()

{

cout << "Benz-舒適" << endl;

}

};

class BMW :public Car

{

public:

virtual void Drive()

{

cout << "BMW-操控" << endl;

}

};

void Test()

{

//Car Dtian;抽象類不可以實例化

//但可以定義指針

Car* pBenz = new Benz;

pBenz->Drive();

Car* pBMW = new BMW;

pBMW->Drive();

}

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

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

四、多態(tài)的原理

4.1虛函數(shù)表

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

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

class Base

{

public:

virtual void Func1()

{

cout << "Func1()" << endl;

}

private:

int _b = 1;

};

// 針對上面的代碼我們做出以下改造

// 1.我們增加一個派生類Derive去繼承Base

// 2.Derive中重寫Func1

// 3.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ù),所以放進了虛表,F(xiàn)unc3也繼承下來了,但是不是虛函數(shù),所以不會放進虛表。虛函數(shù)表本質(zhì)是一個存虛函數(shù)指針的指針數(shù)組,一般情況這個數(shù)組最后面放了一個nullptr??偨Y(jié)一下派生類的虛表生成:

先將基類中的虛表內(nèi)容拷貝一份到派生類虛表中 如果派生類重寫了基類中某個虛函數(shù),用派生類自己的虛函數(shù)覆蓋虛表中基類的虛函數(shù) 派生類自己新增加的虛函數(shù)按其在派生類中的聲明次序增加到派生類虛表的最后。這里還有一個童鞋們很容易混淆的問題:虛函數(shù)存在哪的?虛表存在哪的? 答:虛函數(shù)存在虛表,虛表存在對象中。注意上面的回答的錯的。但是很多童鞋都是這樣深以為然的。注意虛表存的是虛函數(shù)指針,不是虛函數(shù),虛函數(shù)和普通函數(shù)一樣的,都是存在代碼段的,只是他的指針又存到了虛表中。另外對象中存的不是虛表,存的是虛表指針。

接下倆研究一個問題,虛表存在哪的呢?

我們用一段代碼來實踐驗證

class BaseClass

{

public:

virtual void Func1()

{

cout << "BaseClass::Func1()" << endl;

}

private:

int _i = 1;

double _d = 2;

};

void func()

{

cout << "func" << endl;

}

int main()

{

BaseClass bc;

int i = 1; // 棧

static double d = 1.1; // 靜態(tài)區(qū)

int* pi = new int(1); // 堆

const char* pc = "LXP"; // 代碼段

printf("靜態(tài)區(qū):%p\n", &d);

printf("棧:%p\n", &i);

printf("堆:%p\n", pi);

printf("代碼段:%p\n", pc);

printf("虛表:%p\n", *((int*)&bc));

printf("虛函數(shù)地址:%p\n", &BaseClass::Func1);

printf("普通函數(shù)地址:%p\n", func);

return 0;

}

從上面顯示結(jié)果不難看出虛函數(shù)地址與代碼段最近,那我們就可以大膽的認為虛函數(shù)表存放在代碼段里。

4.2多態(tài)的原理

上面分析了這個半天了那么多態(tài)的原理到底是什么?還記得這里Func函數(shù)傳Person調(diào)用的Person::BuyTicket,傳Student調(diào)用的是Student::BuyTicket

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 Mike;

Func(Mike);

Student Johnson;

Func(Johnson);

return 0;

}

觀察下圖的紅色箭頭我們看到,p是指向mike對象時,p->BuyTicket在mike的虛表中找到虛 函數(shù)是Person::BuyTicket。觀察下圖的藍色箭頭我們看到,p是指向johnson對象時,p->BuyTicket在johson的虛表中 找到虛函數(shù)是Student::BuyTicket。這樣就實現(xiàn)出了不同對象去完成同一行為時,展現(xiàn)出不同的形態(tài)。反過來思考我們要達到多態(tài),有兩個條件,一個是虛函數(shù)覆蓋,一個是對象的指針或引用調(diào) 用虛函數(shù)。反思一下為什么?再通過下面的匯編代碼分析,看出滿足多態(tài)以后的函數(shù)調(diào)用,不是在編譯時確定的,是運行 起來以后到對象的中取找的。不滿足多態(tài)的函數(shù)調(diào)用時編譯時確認好的。

class Person {

public:

virtual void BuyTicket() { cout << "買票-全價" << endl; }

};

··18 3

void Func(Person* p)

{

p->BuyTicket();

}

int main()

{

Person P;

Func(&P);

P.BuyTicket();

return 0;

}

// 以下匯編代碼中跟你這個問題不相關(guān)的都被去掉了

void Func(Person* p)

{

...

p->BuyTicket();

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

001940DE mov eax, dword ptr[p]

// [eax]就是取eax值指向的內(nèi)容,這里相當(dāng)于把P對象頭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

00頭1940EC cmp esi, esp

}

int main()

{

...

// 首先BuyTicket雖然是虛函數(shù),但是P是對象,不滿足多態(tài)的條件,所以這里是普通函數(shù)的調(diào)

用轉(zhuǎn)換成地址時,是在編譯時已經(jīng)從符號表確認了函數(shù)的地址,直接call 地址

P.BuyTicket();

00195182 lea ecx, [P]

00195185 call Person::BuyTicket(01914F6h)

...

}

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

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

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

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

5.1 單繼承中的虛函數(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;

};

int main()

{

Base b;

Derive d;

return 0;

}

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

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

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()

{

Derive d;

VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);

PrintVTable(vTableb1);

VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));

PrintVTable(vTableb2);

return 0;

}

觀察下圖可以看出:多繼承派生類的未重寫的虛函數(shù)放在第一個繼承基類部分的虛函數(shù)表中

5.3. 菱形繼承、菱形虛擬繼承?

實際中我們不建議設(shè)計出菱形繼承及菱形虛擬繼承,一方面太復(fù)雜容易出問題,另一方面這樣的 模型,訪問基類成員有一定得性能損耗。所以菱形繼承、菱形虛擬繼承我們的虛表我們就不看 了,一般我們也不需要研究清楚,因為實際中很少用。

結(jié)尾

如果有什么建議和疑問,或是有什么錯誤,希望大家可以在評論區(qū)提一下。 希望大家以后也能和我一起進步??! 如果這篇文章對你有用的話,請大家給一個三連支持一下??!

謝謝大家收看??

柚子快報邀請碼778899分享:c語言 【C++融會貫通】多態(tài)

http://yzkb.51969.com/

相關(guān)文章

評論可見,查看隱藏內(nèi)容

本文內(nèi)容根據(jù)網(wǎng)絡(luò)資料整理,出于傳遞更多信息之目的,不代表金鑰匙跨境贊同其觀點和立場。

轉(zhuǎn)載請注明,如有侵權(quán),聯(lián)系刪除。

本文鏈接:http://gantiao.com.cn/post/19592329.html

發(fā)布評論

您暫未設(shè)置收款碼

請在主題配置——文章設(shè)置里上傳

掃描二維碼手機訪問

文章目錄