C/C++使用C語(yǔ)言實(shí)現(xiàn)多態(tài)
1.多態(tài)的概念
1.1什么是多態(tài)?
多態(tài)是在不同繼承關(guān)系的類(lèi)對(duì)象,去調(diào)用同一函數(shù),產(chǎn)生了不同的行為。
簡(jiǎn)單的說(shuō):就是”一個(gè)接口多種實(shí)現(xiàn)“。
1.2為什么要用多態(tài)呢?
我們知道,封裝可以隱藏實(shí)現(xiàn)細(xì)節(jié),使得代碼模塊化;繼承可以擴(kuò)展已存在的代碼模塊(類(lèi));它們的目的都是為了代碼重用。而多態(tài)除了代碼的復(fù)用性外,還可以解決項(xiàng)目中緊偶合的問(wèn)題,提高程序的可擴(kuò)展性.。耦合度講的是模塊與模塊之間,代碼與代碼之間的關(guān)聯(lián)度,通過(guò)對(duì)系統(tǒng)的分析把他分解成一個(gè)個(gè)的子模塊,子模塊提供穩(wěn)定的接口,達(dá)到降低系統(tǒng)耦合度的的目的,模塊與模塊之間盡量使用模塊接口訪問(wèn),而不是隨意引用其他模塊的成員變量。
1.3多態(tài)有什么好處?
1.應(yīng)用程序不必為每一個(gè)派生類(lèi)編寫(xiě)功能調(diào)用,只需要對(duì)抽象基類(lèi)進(jìn)行處理即可。大大提高程序的可復(fù)用性。//繼承
2.派生類(lèi)的功能可以被基類(lèi)的方法或引用變量所調(diào)用,這叫向后兼容,可以提高可擴(kuò)充性和可維護(hù)性。 //多態(tài)的真正作用,
2.多態(tài)的定義及實(shí)現(xiàn)
2.1繼承中構(gòu)成多態(tài)的條件
1.必須通過(guò)基類(lèi)的指針或者引用調(diào)用虛函數(shù)
2.被調(diào)用的函數(shù)必須是虛函數(shù),且派生類(lèi)必須對(duì)基類(lèi)的虛函數(shù)進(jìn)行重寫(xiě)

2.2虛函數(shù)
虛函數(shù):即被virtual修飾的類(lèi)成員函數(shù)稱(chēng)為虛函數(shù)。
class Person {
public:
virtual void BuyTicket() { cout << "買(mǎi)票-全價(jià)" << endl;}
};
2.3虛函數(shù)的重寫(xiě)
虛函數(shù)的重寫(xiě)(覆蓋):派生類(lèi)中有一個(gè)跟基類(lèi)完全相同的虛函數(shù)(即派生類(lèi)虛函數(shù)與基類(lèi)虛函數(shù)的返回值類(lèi)型、函數(shù)名字、參數(shù)列表完全相同),稱(chēng)派生類(lèi)的虛函數(shù)重寫(xiě)了基類(lèi)的虛函數(shù)。
//void BuyTicket (),返回值類(lèi)型、函數(shù)名字、參數(shù)列表完全相同
class Person {
public:
virtual void BuyTicket() { cout << "買(mǎi)票-全價(jià)" << endl; }
};
class Student : public Person {
public:
virtual void BuyTicket() { cout << "買(mǎi)票-半價(jià)" << endl; }
/*注意:在重寫(xiě)基類(lèi)虛函數(shù)時(shí),派生類(lèi)的虛函數(shù)在不加virtual關(guān)鍵字時(shí),雖然也可以構(gòu)成重寫(xiě)(因?yàn)槔^承后
基類(lèi)的虛函數(shù)被繼承下來(lái)了在派生類(lèi)依舊保持虛函數(shù)屬性),但是該種寫(xiě)法不是很規(guī)范,不建議這樣使用*/
/*void BuyTicket() { cout << "買(mǎi)票-半價(jià)" << endl; }*/
};
虛函數(shù)重寫(xiě)的兩個(gè)例外:
1.協(xié)變(基類(lèi)與派生類(lèi)虛函數(shù)返回值類(lèi)型不同)
派生類(lèi)重寫(xiě)基類(lèi)虛函數(shù)時(shí),與基類(lèi)虛函數(shù)返回值類(lèi)型不同。即基類(lèi)虛函數(shù)返回基類(lèi)對(duì)象的指針或者引用,派生類(lèi)虛函數(shù)返回派生類(lèi)對(duì)象的指針或者引用時(shí),稱(chēng)為協(xié)變。
class Person {
public:
virtual A* f() {return new A;} //基類(lèi)的返回值為A*
};
class Student : public Person {
public:
virtual B* f() {return new B;} //派生類(lèi)的返回值為B*
};
2.析構(gòu)函數(shù)的重寫(xiě)(基類(lèi)與派生類(lèi)析構(gòu)函數(shù)的名字不同)
如果基類(lèi)的析構(gòu)函數(shù)為虛函數(shù),此時(shí)派生類(lèi)析構(gòu)函數(shù)只要定義,無(wú)論是否加virtual關(guān)鍵字,都與基類(lèi)的析構(gòu)函數(shù)構(gòu)成重寫(xiě),雖然析構(gòu)函數(shù)名字不同,看起來(lái)違背了重寫(xiě)的規(guī)則,其實(shí)不然,這里可以理解為編譯器對(duì)析構(gòu)函數(shù)的名稱(chēng)做了特殊處理,編譯后析構(gòu)函數(shù)的名稱(chēng)統(tǒng)一處
理成destructor。
class Person {
public:
virtual ~Person() {cout << "~Person()" << endl;}
};
class Student : public Person {
public:
virtual ~Student() { cout << "~Student()" << endl; }
};
// 只有派生類(lèi)Student的析構(gòu)函數(shù)重寫(xiě)了Person的析構(gòu)函數(shù),下面的delete對(duì)象調(diào)用析構(gòu)函數(shù),才能構(gòu)成多態(tài),才能保證p1和p2指向的對(duì)象正確的調(diào)用析構(gòu)函數(shù)。
int main()
{
Person* p1 = new Person;
Person* p2 = new Student;
delete p1;
delete p2;
return 0;
}
2.4C++11 override 和 final
C++11提供了override和final兩個(gè)關(guān)鍵字,可以幫助用戶(hù)檢測(cè)是否重寫(xiě)。
1.final:修飾虛函數(shù),表示該虛函數(shù)不能再被重寫(xiě)
class Car
{
public:
virtual void Drive() final {}
};
class Benz :public Car
{
public:
virtual void Drive() {cout << "Benz-舒適" << endl;}
};
2.override: 檢查派生類(lèi)虛函數(shù)是否重寫(xiě)了基類(lèi)某個(gè)虛函數(shù),如果沒(méi)有重寫(xiě)編譯報(bào)錯(cuò)。
class Car{
public:
virtual void Drive(){}
};
class Benz :public Car {
public:
virtual void Drive() override {cout << "Benz-舒適" << endl;}
};
2.5 重載、覆蓋(重寫(xiě))、隱藏(重定義)的對(duì)比

3.抽象類(lèi)
3.1概念
在虛函數(shù)的后面寫(xiě)上 =0 ,則這個(gè)函數(shù)為純虛函數(shù)。包含純虛函數(shù)的類(lèi)叫做抽象類(lèi)(也叫接口類(lèi)),抽象類(lèi)不能實(shí)例化出對(duì)象。派生類(lèi)繼承后也不能實(shí)例化出對(duì)象,只有重寫(xiě)純虛函數(shù),派生類(lèi)才能實(shí)例化出對(duì)象。純虛函數(shù)規(guī)范了派生類(lèi)必須重寫(xiě),另外純虛函數(shù)更體現(xiàn)出了接口繼承。
class Car
{
public:
virtual void Drive() = 0; //純虛函數(shù)
};
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* pBenz = new Benz;
pBenz->Drive();
Car* pBMW = new BMW;
pBMW->Drive();
}
3.2實(shí)現(xiàn)繼承和接口繼承
普通函數(shù)的繼承是一種實(shí)現(xiàn)繼承,派生類(lèi)繼承了基類(lèi)函數(shù),可以使用函數(shù),繼承的是函數(shù)的實(shí)現(xiàn)。
虛函數(shù)的繼承是一種接口繼承,派生類(lèi)繼承的是基類(lèi)虛函數(shù)的接口,目的是為了重寫(xiě),達(dá)成多態(tài),繼承的是接口。所以如果不實(shí)現(xiàn)多態(tài),不要把函數(shù)定義成虛函數(shù)。
4.多態(tài)的原理
4.1虛函數(shù)表
// 這里??家坏拦P試題:sizeof(Base)是多少?
class Base
{
public:
virtual void Func1()
{
cout << "Func1()" << endl;
}
private:
int _b = 1;
};
int main()
{
Base b;
return 0;
}

通過(guò)觀察測(cè)試我們發(fā)現(xiàn)b對(duì)象是8bytes,除了_b成員,還多一個(gè)__vfptr,我們稱(chēng)之為虛函數(shù)表指針(v代表virtual,f代表function)。一個(gè)含有虛函數(shù)的類(lèi)中都至少有一個(gè)虛函數(shù)表指針,因?yàn)樘摵瘮?shù)的地址要被放到虛函數(shù)表中,虛函數(shù)表也簡(jiǎn)稱(chēng)虛表。
// 針對(duì)上面的代碼我們做出以下改造
// 1.我們?cè)黾右粋€(gè)派生類(lèi)Derive去繼承Base
// 2.Derive中重寫(xiě)Func1
// 3.Base再增加一個(gè)虛函數(shù)Func2和一個(gè)普通函數(shù)Func3
class Base //基類(lèi)
{
public:
virtual void Func1()
{
cout << "Base::Func1()" << endl;
}
virtual void Func2() //虛函數(shù)
{
cout << "Base::Func2()" << endl;
}
void Func3() //普通函數(shù)
{
cout << "Base::Func3()" << endl;
}
private:
int _b = 1;
};
class Derive : public Base //派生類(lèi)
{
public:
virtual void Func1()
{
cout << "Derive::Func1()" << endl;
}
private:
int _d = 2;
};
int main()
{
Base b;
Derive d;
return 0;
}

1.派生類(lèi)對(duì)象d中也有一個(gè)虛表指針,d對(duì)象由兩部分構(gòu)成:父類(lèi)繼承下來(lái)的成員和自己的成員。
2.基類(lèi)b對(duì)象和派生類(lèi)d對(duì)象虛表是不一樣的,這里我們發(fā)現(xiàn)Func1完成了重寫(xiě),所以d的虛表中存的是重寫(xiě)的Derive::Func1,所以虛函數(shù)的重寫(xiě)也叫作覆蓋,覆蓋就是指虛表中虛函數(shù)的覆蓋。重寫(xiě)是語(yǔ)法的叫法,覆蓋是原理層的叫法。
3.另外Func2繼承下來(lái)后是虛函數(shù),所以放進(jìn)了虛表,F(xiàn)unc3也繼承下來(lái)了,但是不是虛函數(shù),所以不會(huì)放進(jìn)虛表。
4.虛函數(shù)表本質(zhì)是一個(gè)存虛函數(shù)指針的指針數(shù)組,一般情況這個(gè)數(shù)組最后面放了一個(gè)nullptr。
5.總結(jié)一下派生類(lèi)的虛表生成:a.先將基類(lèi)中的虛表內(nèi)容拷貝一份到派生類(lèi)虛表中 b.如果派生類(lèi)重寫(xiě)了基類(lèi)中某個(gè)虛函數(shù),用派生類(lèi)自己的虛函數(shù)覆蓋虛表中基類(lèi)的虛函數(shù) c.派生類(lèi)自己新增加的虛函數(shù)按其在派生類(lèi)中的聲明次序增加到派生類(lèi)虛表的最后。
6.還有一個(gè)很容易混淆的問(wèn)題:虛函數(shù)存在哪的?虛表存在哪的?
注意虛表存的是虛函數(shù)指針,不是虛函數(shù),虛函數(shù)和普通函數(shù)一樣的,都是存在代碼段的,只是他的指針又存到了虛表中。另外對(duì)象中存的不是虛表,存的是虛表指針。那么虛表存在哪的呢?
4.2多態(tài)的原理
1.觀察下圖的紅色箭頭我們看到,p是指向mike對(duì)象時(shí),p->BuyTicket在mike的虛表中找到虛函數(shù)是Person::BuyTicket。
2.觀察下圖的藍(lán)色箭頭我們看到,p是指向johnson對(duì)象時(shí),p->BuyTicket在johson的虛表中找到虛函數(shù)是Student::BuyTicket。
3.這樣就實(shí)現(xiàn)出了不同對(duì)象去完成同一行為時(shí),展現(xiàn)出不同的形態(tài)。
4.反過(guò)來(lái)思考我們要達(dá)到多態(tài),有兩個(gè)條件,一個(gè)是虛函數(shù)覆蓋,一個(gè)是對(duì)象的指針或引用調(diào)用虛函數(shù)。
反思一下為什么?
5.再通過(guò)下面的匯編代碼分析,看出滿(mǎn)足多態(tài)以后的函數(shù)調(diào)用,不是在編譯時(shí)確定的,是運(yùn)行起來(lái)以后到對(duì)象的中取找的。不滿(mǎn)足多態(tài)的函數(shù)調(diào)用時(shí)編譯是確認(rèn)好的。

4.3 動(dòng)態(tài)綁定與靜態(tài)綁定
1.靜態(tài)綁定又稱(chēng)為前期綁定(早綁定),在程序編譯期間確定了程序的行為,也稱(chēng)為靜態(tài)多態(tài),比如:函數(shù)重載
2.動(dòng)態(tài)綁定又稱(chēng)后期綁定(晚綁定),是在程序運(yùn)行期間,根據(jù)具體拿到的類(lèi)型確定程序的具體行為,調(diào)用具體的函數(shù),也稱(chēng)為動(dòng)態(tài)多態(tài)。
5.單繼承和多繼承關(guān)系的虛函數(shù)表
5.1 單繼承中的虛函數(shù)表

5.2 多繼承中的虛函數(shù)表
多繼承派生類(lèi)的未重寫(xiě)的虛函數(shù)放在第一個(gè)繼承基類(lèi)部分的虛函數(shù)表中

總結(jié)
本篇文章就到這里了,希望能給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
VC++ 中ListCtrl經(jīng)驗(yàn)總結(jié)
這篇文章主要介紹了VC++ 中ListCtrl經(jīng)驗(yàn)總結(jié)的相關(guān)資料,需要的朋友可以參考下2015-06-06
C語(yǔ)言編程深入理解取整取余取模問(wèn)題示例分析
這篇文章主要為大家介紹了C語(yǔ)言編程深入理解取整取余取模問(wèn)題的示例分析詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步2021-11-11
基于VC中使用ForceInclude來(lái)強(qiáng)制包含stdafx.h的解決方法
本篇文章是對(duì)VC中使用ForceInclude來(lái)強(qiáng)制包含stdafx.h的解決方法進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
C++實(shí)現(xiàn)簡(jiǎn)易的通訊錄管理系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了C++實(shí)現(xiàn)簡(jiǎn)易的通訊錄管理系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06

