C++數(shù)據(jù)結(jié)構(gòu)分析多態(tài)的實(shí)現(xiàn)與原理及抽象類(lèi)
??上一篇博客我和大家聊了聊關(guān)于繼承的內(nèi)容,繼承是C++的三大特性之一,今天要和大家聊一聊有關(guān)C++的三大特性中的最后一個(gè)——多態(tài)。 ??博客代碼已上傳至gitee:https://gitee.com/byte-binxin/cpp-class-code
??多態(tài)的
??概念
多態(tài): 從字面意思來(lái)看,就是事物的多種形態(tài)。用C++的語(yǔ)言說(shuō)就是不同的對(duì)象去完成同一個(gè)行為會(huì)產(chǎn)生不同的效果。
??虛函數(shù)
虛函數(shù): 被virtual關(guān)鍵字修飾的類(lèi)成員函數(shù)叫做虛函數(shù)。
實(shí)例演示: 看一下代碼,其中BuyTicket成員函數(shù)被virtual關(guān)鍵字修飾
class Person { public: // 虛函數(shù) virtual void BuyTicket() { cout << "買(mǎi)票全價(jià)" << endl; } };
??多態(tài)構(gòu)成的條件
多態(tài)是在不同繼承關(guān)系的類(lèi)對(duì)象,去調(diào)用同一函數(shù),產(chǎn)生了不同的行為。
繼承中構(gòu)成多態(tài)有兩個(gè)條件:
- 必須有基類(lèi)的指針或引用調(diào)用
- 被調(diào)用的函數(shù)必須是虛函數(shù),其派生類(lèi)必須對(duì)基類(lèi)的虛函數(shù)進(jìn)行重寫(xiě)
虛函數(shù)的重寫(xiě)是什么?
虛函數(shù)的重寫(xiě)(覆蓋): 派生類(lèi)中有一個(gè)跟基類(lèi)完全相同的虛函數(shù)(即派生類(lèi)虛函數(shù)與基類(lèi)虛函數(shù)的返回值類(lèi)型、函數(shù)名字、參數(shù)列表完全相同),稱子類(lèi)的虛函數(shù)重寫(xiě)了基類(lèi)的虛函數(shù)。(重寫(xiě)是對(duì)函數(shù)體進(jìn)行重寫(xiě))
實(shí)例演示:
class Person { public: virtual void BuyTicket() { cout << "買(mǎi)票全價(jià)" << endl; } }; class Student : public Person { public: virtual void BuyTicket() // 這里也可以不寫(xiě)virtual,因?yàn)榛?lèi)的虛函數(shù)屬性已經(jīng)被保留下來(lái)了,這里只是完成虛函數(shù)的重寫(xiě) { 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í),稱為協(xié)變。(也就是基類(lèi)虛函數(shù)的返回類(lèi)型和派生類(lèi)的虛函數(shù)的返回類(lèi)型是父子類(lèi)型的指針或引用)
// 協(xié)變 返回值類(lèi)型不同,但它們之間是父子或父父關(guān)系 返回類(lèi)型是指針或者引用 // 基類(lèi)虛函數(shù) 返回類(lèi)型 是 基類(lèi)的指針或者引用 // 派生類(lèi)虛函數(shù) 返回類(lèi)型 是 基類(lèi)或派生類(lèi)的返回類(lèi)型是基類(lèi)的指針或引用 class A {}; class B : public A {}; class Person { public: virtual A* f() { return new A; } }; class Student : public Person { public: virtual A* f() { return new B; } };
2.析構(gòu)函數(shù)的重寫(xiě) 基類(lèi)與派生類(lèi)的析構(gòu)函數(shù)的函數(shù)名不同
我在上一篇博客中說(shuō)到過(guò),基類(lèi)和派生類(lèi)的析構(gòu)函數(shù)的函數(shù)名會(huì)被編譯器統(tǒng)一處理成destructor,所以只要基類(lèi)的析構(gòu)函數(shù)加了關(guān)鍵字virtual,就會(huì)和派生類(lèi)的析構(gòu)函數(shù)構(gòu)成重寫(xiě)。
我們?cè)倩氐蕉鄳B(tài)構(gòu)成的兩個(gè)條件中,完成基類(lèi)虛函數(shù)的重寫(xiě)我已經(jīng)介紹了,還有一個(gè)必須由基類(lèi)的指針或引用調(diào)用的條件,這個(gè)應(yīng)該很好理解吧。下面舉個(gè)例子: 實(shí)例演示:
class Person { public: virtual void BuyTicket() { cout << "買(mǎi)票全價(jià)" << endl; } }; class Student : public Person { public: virtual void BuyTicket() // 這里也可以不寫(xiě)virtual,因?yàn)榛?lèi)的虛函數(shù)屬性已經(jīng)被保留下來(lái)了,這里只是完成虛函數(shù)的重寫(xiě) { cout << "買(mǎi)票半價(jià)" << endl; } }; void Func1(Person& p) { p.BuyTicket(); } void Func2(Person* p) { p->BuyTicket(); } void Func3(Person p) { p.BuyTicket(); } int main() { Person p; Student s; // 滿足多態(tài)的條件:與類(lèi)型無(wú)關(guān),父類(lèi)指針指向的是誰(shuí)就調(diào)用誰(shuí)的成員函數(shù) // 不滿足多態(tài)的條件:與類(lèi)型有關(guān),類(lèi)型是誰(shuí)就調(diào)用誰(shuí)的成員函數(shù) cout << "基類(lèi)的引用調(diào)用:" << endl; Func1(p); Func1(s); cout << "基類(lèi)的指針調(diào)用:" << endl; Func2(&p); Func2(&s); cout << "基類(lèi)的對(duì)象調(diào)用:" << endl; Func3(p); Func3(s); return 0; }
代碼運(yùn)行結(jié)果:
總結(jié):
- 滿足多態(tài)的條件:成員函數(shù)調(diào)用與對(duì)象類(lèi)型無(wú)關(guān),指向那個(gè)對(duì)象就調(diào)用哪個(gè)的虛函數(shù)
- 不滿足多態(tài)的條件:成員函數(shù)的調(diào)用與對(duì)象類(lèi)型有關(guān),是哪個(gè)對(duì)象類(lèi)型就調(diào)用哪個(gè)對(duì)象的虛函數(shù)。
思考: 析構(gòu)函數(shù)是否要加virtual? 答案是需要的。先給大家看一個(gè)例子:
class Person { public: /*virtual*/ ~Person() { cout << "~Person()" << endl; } }; class Student: public Person { public: ~Student() { cout << "~Student()" << endl; } }; int main() { Person* p = new Person; Person* ps = new Student;// 不加virtual,不構(gòu)成多態(tài),父類(lèi)指針只會(huì)根據(jù)類(lèi)型去調(diào)用對(duì)于的析構(gòu)函數(shù) // 加了virtual,構(gòu)成多態(tài),父類(lèi)指針會(huì)根據(jù)指向的對(duì)象去調(diào)用他的析構(gòu)函數(shù) delete p; delete ps; return 0; }
下面分別是基類(lèi)析構(gòu)函數(shù)不加virtual和加virtual的代碼運(yùn)行結(jié)果:
可以看出,不加virtual關(guān)鍵字時(shí),第二個(gè)對(duì)象delete時(shí)沒(méi)有調(diào)用子類(lèi)的析構(gòu)函數(shù)清理釋放空間。為什么呢?因?yàn)椴患觱irtual關(guān)鍵字時(shí),兩個(gè)析構(gòu)函數(shù)不構(gòu)成多態(tài),所以調(diào)用析構(gòu)函數(shù)時(shí)是與類(lèi)型有關(guān)的,因?yàn)槎际嵌际歉割?lèi)類(lèi)型,所以只會(huì)調(diào)用父類(lèi)的析構(gòu)函數(shù)。加了virtual關(guān)鍵字時(shí),因?yàn)閮蓚€(gè)析構(gòu)函數(shù)被編譯器處理成同名函數(shù)了,所以完成了虛函數(shù)的重寫(xiě),且是父類(lèi)指針調(diào)用,所以此時(shí)兩個(gè)析構(gòu)函數(shù)構(gòu)成多態(tài),所以調(diào)用析構(gòu)函數(shù)時(shí)是與類(lèi)型無(wú)關(guān)的,因?yàn)楦割?lèi)指針指向的是子類(lèi)對(duì)象,所以會(huì)調(diào)用子類(lèi)的析構(gòu)函數(shù),子類(lèi)調(diào)用完自己的析構(gòu)函數(shù)又會(huì)自動(dòng)調(diào)用父類(lèi)的析構(gòu)函數(shù)來(lái)完成對(duì)父類(lèi)資源的清理。 所以總的來(lái)看,基類(lèi)的析構(gòu)函數(shù)是要加virtual的。
??C++11override和final
final: 修飾虛函數(shù),表示該虛函數(shù)不可以被重寫(xiě)(還可以修飾類(lèi),表示該類(lèi)不可以被繼承)
實(shí)例演示:
class Car { public: // final 表示該虛函數(shù)不能被重寫(xiě) 也可以修飾類(lèi),表示該類(lèi)不可以被繼承 virtual void Drive() final {} }; class Benz :public Car { public: virtual void Drive() { cout << "Benz-舒適" << endl; } };
編譯器檢查結(jié)果: 由于dirve字母編寫(xiě)錯(cuò)誤,所以編譯器檢查出沒(méi)有重寫(xiě)基類(lèi)的虛函數(shù)
2.overide: 檢查派生類(lèi)虛函數(shù)是否重寫(xiě)了基類(lèi)的某個(gè)虛函數(shù) 實(shí)例演示:
class Car { public: // final 表示該虛函數(shù)不能被重寫(xiě) 也可以修飾類(lèi),表示該類(lèi)不可以被繼承 virtual void Drive() final {} }; class Benz :public Car { public: virtual void Drive() { cout << "Benz-舒適" << endl; } };
編譯器檢查結(jié)果:
??重載、重寫(xiě)和重定義(隱藏)
名稱 | 作用域 | 函數(shù)名 | 其他 |
---|---|---|---|
重載 | 兩個(gè)函數(shù)在同一作用域 | 相同 | 參數(shù)類(lèi)型不同 |
重寫(xiě) | 兩個(gè)函數(shù)分別再基類(lèi)和派生類(lèi)的作用域 | 相同 | 函數(shù)返回類(lèi)型和參數(shù)類(lèi)型一樣 |
重定義(隱藏) | 兩個(gè)函數(shù)分別再基類(lèi)和派生類(lèi)的作用域 | 相同 | 兩個(gè)基類(lèi)和派生類(lèi)的同名函數(shù)不是構(gòu)成重寫(xiě)就是重定義 |
??抽象類(lèi)
概念: 在虛函數(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í)例化象純虛函數(shù)規(guī)范了派生類(lèi)必須重寫(xiě),另外純虛函數(shù)更體現(xiàn)出了接口繼承。
總結(jié)出幾個(gè)特點(diǎn):
- 虛函數(shù)后面加上=0
- 不能實(shí)例化出對(duì)象
- 派生類(lèi)如果不重寫(xiě)基類(lèi)的純虛函數(shù)那么它也是抽象類(lèi),不能實(shí)例化出對(duì)象
- 抽象類(lèi)嚴(yán)格限制派生類(lèi)必須重寫(xiě)基類(lèi)的純虛函數(shù)
- 體現(xiàn)了接口繼承
實(shí)例演示:
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 () override { cout << "BMW" << endl; } }; int main() { Car* pBenZ = new Benz; pBenZ->Drive(); Car* pBMW = new BMW; pBMW->Drive(); delete pBenZ; delete pBMW; return 0; }
代碼運(yùn)行結(jié)果:
抽象類(lèi)的意義?
- 強(qiáng)制子類(lèi)完成父類(lèi)虛函數(shù)的重寫(xiě)
- 表示該類(lèi)是抽象類(lèi),沒(méi)有實(shí)體(例如:花、車(chē)和人等)
接口繼承和實(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ù)。
??多態(tài)的原理
??虛函數(shù)表
概念: 一個(gè)含有虛函數(shù)的類(lèi)中至少有一個(gè)虛函數(shù)指針,這個(gè)指針指向了一張表——虛函數(shù)表(簡(jiǎn)稱虛表),這張表中存放了這個(gè)類(lèi)中所有的虛函數(shù)的地址。
計(jì)算一下下面這個(gè)類(lèi)的大?。?/p>
class Base { public: virtual void func1() {} virtual void func2() {} public: int _a; }; int main() { cout << sizeof(Base) << endl; return 0; }
代碼運(yùn)行結(jié)果如下:
這個(gè)類(lèi)中存放了一個(gè)虛表指針和一個(gè)成員變量,所以總大小就是8。給大家看一下它的類(lèi)對(duì)象模型:
實(shí)例演示:
class Person { public: virtual void BuyTicket() { cout << "買(mǎi)票全價(jià)" << endl; } virtual void func() { cout << "func()" << endl; } int _p = 1; }; class Student : public Person { public: virtual void BuyTicket() // 這里也可以不寫(xiě)virtual,因?yàn)榛?lèi)的虛函數(shù)屬性已經(jīng)被保留下來(lái)了,這里只是完成虛函數(shù)的重寫(xiě) { cout << "買(mǎi)票半價(jià)" << endl; } int _s = 1; }; int main() { Person p; Student s; return 0; }
類(lèi)對(duì)象模型如下:
可以看出,兩個(gè)虛函數(shù)地址是不一樣的,其實(shí)子類(lèi)會(huì)先把父類(lèi)的虛表拷貝一份下來(lái),如果子類(lèi)重寫(xiě)了虛函數(shù),那么子類(lèi)的虛函數(shù)的地址將會(huì)覆蓋虛表中的地址,如果沒(méi)有重寫(xiě),那么將不覆蓋。
總結(jié)幾點(diǎn):
- 子類(lèi)對(duì)象由兩部分構(gòu)成,一部分是父類(lèi)繼承下來(lái)的成員,虛表指針指向的虛表有父類(lèi)的虛函數(shù),也有子類(lèi)新增的虛函數(shù)
- 子類(lèi)完成父類(lèi)虛函數(shù)的重寫(xiě)其實(shí)是對(duì)繼承下來(lái)的虛表的中重寫(xiě)了的虛函數(shù)進(jìn)行覆蓋,把地址更換了,語(yǔ)法層是稱為覆蓋
- 虛函數(shù)表本質(zhì)是一個(gè)存虛函數(shù)指針的指針數(shù)組,一般情況這個(gè)數(shù)組最后面放了一個(gè)nullptr
- 虛表生成的過(guò)程:先將基類(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)虛表的最后
下面我們來(lái)討論一下虛表存放的位置和虛表指針存放的位置
虛表指針肯定是存在類(lèi)中的,從上面的類(lèi)對(duì)象模型中可以看出。其次虛表存放的是虛函數(shù)的地址,這些虛函數(shù)和普通函數(shù)一樣,都會(huì)被編譯器編譯成指令,然后放進(jìn)代碼段。虛表也是存在代碼段的,因?yàn)橥?lèi)型的對(duì)象共用一張?zhí)摫?。下面帶大家?yàn)證一下(環(huán)境:vs2019)
驗(yàn)證代碼:
class Base { public: virtual void func1() { cout << "Base::func1" << endl; } virtual void func2() { cout << "Base::func2" << endl; } virtual void func3() { cout << "Base::func3" << endl; } void func() {} int b = 0; }; class Derive :public Base { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func2() { cout << "Derive::func2" << endl; } virtual void func4() { cout << "Derive::func4" << endl; } virtual void func5() { cout << "Derive::func5" << endl; } int d = 0; }; void func() {} int globalVar = 10; int main() { Base b; Derive d; const char* pChar = "hello"; int c = 1; static int s = 20; int* p = new int; const int i = 10; printf("棧變量:%p\n", &c); printf("虛表指針:%p\n", (int*)&b); printf("對(duì)象成員:%p\n", ((int*)&b + 1)); printf("堆變量:%p\n", p); printf("代碼段常量:%p\n", pChar); printf("普通函數(shù)地址:%p\n", func); printf("成員函數(shù)地址:%p\n", &Base::func); printf("虛函數(shù):%p\n", &Base::func1); printf("虛函數(shù)表:%p\n", *(int*)&b); printf("數(shù)據(jù)段:%p\n", &s); printf("數(shù)據(jù)段:%p\n", &globalVar); delete p; return 0; }
代碼運(yùn)行結(jié)果如下:
容易看出,代碼段常量存放的地址和虛表存放的地址很接近,和數(shù)據(jù)段的地址也很接近,所以可以猜測(cè)虛表存放在數(shù)據(jù)段或代碼段,更可能是在代碼段。
??原理
多態(tài)是在運(yùn)行時(shí)到指向的對(duì)象中的虛表中查找要調(diào)用的虛函數(shù)的地址,然后進(jìn)行調(diào)用。
總結(jié):
- 多態(tài)滿足的兩個(gè)條件:一個(gè)是虛函數(shù)的覆蓋,一個(gè)是對(duì)象的指針和引用調(diào)用
- 滿足多態(tài)后,函數(shù)的調(diào)用不是編譯時(shí)確認(rèn)的,而是在運(yùn)行時(shí)確認(rèn)的。
動(dòng)態(tài)綁定和靜態(tài)綁定
- 靜態(tài)綁定: 發(fā)生在編譯時(shí),也就是早期綁定,就是我們之前說(shuō)過(guò)的函數(shù)重載就是屬于靜態(tài)綁定,也稱靜態(tài)多態(tài)。
- 動(dòng)圖綁定: 發(fā)生在運(yùn)行時(shí),也就是后期綁定,多態(tài)就是發(fā)生在運(yùn)行時(shí),也稱動(dòng)態(tài)多態(tài)。
??單繼承和多繼承的虛表
??單繼承的虛表
先看下面的代碼(單繼承)
class Base { public: virtual void func1() { cout << "Base::func1" << endl; } virtual void func2() { cout << "Base::func2" << endl; } virtual void func3() { cout << "Base::func3" << endl; } void func() {} int b = 0; }; class Derive :public Base { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func2() { cout << "Derive::func2" << endl; } virtual void func4() { cout << "Derive::func4" << endl; } virtual void func5() { cout << "Derive::func5" << endl; } int d = 0; };
觀察它的類(lèi)對(duì)象模型:
在上面的類(lèi)對(duì)象模型中,派生類(lèi)中只可以看見(jiàn)func1和func2,后面兩個(gè)函數(shù)看不見(jiàn),這是因?yàn)榫幾g器把這兩個(gè)新增的虛函數(shù)給隱藏了,為了我們能夠更好的觀察,我們可以通過(guò)寫(xiě)代碼來(lái)看。 先定義一個(gè)函數(shù)指針:
typedef void(*VF_PTR)(); // 給函數(shù)指針typedef
下面是打印虛表的代碼:
void PrintVFTable(VF_PTR* pTable) { for (size_t i = 0; pTable[i] != nullptr; ++i) { printf("vfTable[%d]:%p->", i, pTable[i]); VF_PTR f = pTable[i]; f();// 通過(guò)函數(shù)地址調(diào)用函數(shù) } cout << endl; }
下面我們只需要通過(guò)傳虛表地址的方式來(lái)調(diào)用函數(shù)打印虛表,虛表地址如何獲取呢?從上面的類(lèi)對(duì)象模型可以知道,類(lèi)對(duì)象的前四個(gè)地址存放的是虛表指針,虛表指針也就是虛表的指針,所以我們要獲取類(lèi)對(duì)象的前四個(gè)字節(jié)。下面是獲取方法:
(VF_PTR*)*(int*)&b;
先將類(lèi)對(duì)象的地址取出,然后強(qiáng)轉(zhuǎn)為整形,解引用就會(huì)按照四個(gè)字節(jié)來(lái)獲取內(nèi)容,這四個(gè)字節(jié)的內(nèi)容是虛表指針,其實(shí)也是虛表的地址,我們可以把這個(gè)整形強(qiáng)轉(zhuǎn)為函數(shù)地址的類(lèi)型就可以了。
打印虛表:
int main() { Base b; Derive d; PrintVFTable((VF_PTR*)*(int*)&b); PrintVFTable((VF_PTR*)*(int*)&d); return 0; }
打印結(jié)果如下:
可以看出派生類(lèi)對(duì)象中新增的虛函數(shù)會(huì)按照虛函數(shù)函數(shù)次序聲明放在虛表的最后。
??多繼承的虛函數(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 = 1; }; class Derive : public Base1 , public Base2 { public: virtual void func1() { cout << "Derive::func1" << endl; } virtual void func3() { cout << "Derive::func3" << endl; } private: int d1 = 1; };
類(lèi)對(duì)象模型如下:
為了更好地觀察,我們還是通過(guò)打印虛表來(lái)觀察:
int main() { Derive d; cout << sizeof(Derive) << endl; cout << "Base1的虛表:" << endl; PrintVFTable((VF_PTR*)*(int*)&d); cout << "Base2的虛表:" << endl; PrintVFTable((VF_PTR*)*(int*)((char*)&d+sizeof(Base1))); cout << "Derive的成員變量d:" << endl; //PrintVFTable((VF_PTR*)*(int*)((char*)&d + sizeof(Base1) + sizeof(Base2))); cout << *(int*)((char*)&d + sizeof(Base1) + sizeof(Base2)) << endl; return 0; }
打印結(jié)果如下:
可以看出,派生類(lèi)新增的虛函數(shù)放在了第一個(gè)繼承的對(duì)象的虛表中最后了。
??幾個(gè)值得思考的問(wèn)題
- 內(nèi)斂函數(shù)可以是虛函數(shù)嗎?
答:不可以,內(nèi)聯(lián)(inline)函數(shù)沒(méi)有地址,因?yàn)樘摵瘮?shù)要把地址放到虛表中去。
- 構(gòu)造函數(shù)可以是虛函數(shù)嗎?
答:不可以,因?yàn)閷?duì)象中虛函數(shù)指針是在構(gòu)造函數(shù)初始化列表階段才初始化的。
- 析構(gòu)函數(shù)可以是虛函數(shù)嗎?
答:可以,且建議設(shè)計(jì)成虛函數(shù),具體原因前面說(shuō)了。
- 對(duì)象訪問(wèn)普通函數(shù)快還是虛函數(shù)更快?
答:首先如果是普通對(duì)象,是一樣快的。如果是指針對(duì)象或者是引用對(duì)象,則調(diào)用的普通函數(shù)快,因?yàn)闃?gòu)成多態(tài),運(yùn)行時(shí)調(diào)用虛函數(shù)需要到虛函數(shù)表中去查找。
- 虛函數(shù)表是在什么階段生成的?
答:在編譯階段生成的,存在于代碼段。
- 什么是抽象類(lèi)?有什么意義?
答:前面介紹過(guò)了,可以參考前文。
??總結(jié)
多態(tài)也是C++的三大特性之一,之前也介紹過(guò)兩個(gè),就是封裝和繼承。多態(tài)也是十分的重要,我們要理清其中一些改了,更好地理解這些。今天的內(nèi)容就到這里了,喜歡的話,歡迎點(diǎn)贊支持和關(guān)注~
到此這篇關(guān)于C++數(shù)據(jù)結(jié)構(gòu)分析多態(tài)的實(shí)現(xiàn)與原理及抽象類(lèi)的文章就介紹到這了,更多相關(guān)C++ 多態(tài)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章

基于Qt制作一個(gè)定時(shí)關(guān)機(jī)的小程序

詳解C語(yǔ)言中index()函數(shù)和rindex()函數(shù)的用法

vscode和cmake編譯多個(gè)C++文件的實(shí)現(xiàn)方法

使用c++實(shí)現(xiàn)OpenCV圖像橫向&縱向拼接

C++實(shí)現(xiàn)圖書(shū)信息管理系統(tǒng)

C++中可正確獲取UTF-8字符長(zhǎng)度的函數(shù)分享