C++編程之多態(tài)的使用
多態(tài)是面向?qū)ο缶幊痰娜筇匦灾唬ǚ庋b、繼承、多態(tài)),它允許使用統(tǒng)一的接口來(lái)處理不同類(lèi)型的對(duì)象。在C++中,多態(tài)主要通過(guò)虛函數(shù)和繼承機(jī)制來(lái)實(shí)現(xiàn)。
1. 多態(tài)的基本概念
多態(tài)分為兩種:
- 編譯時(shí)多態(tài)(靜態(tài)多態(tài)):通過(guò)函數(shù)重載和運(yùn)算符重載實(shí)現(xiàn)
- 運(yùn)行時(shí)多態(tài)(動(dòng)態(tài)多態(tài)):通過(guò)虛函數(shù)和繼承實(shí)現(xiàn)
2. 靜態(tài)多態(tài)
2.1 函數(shù)重載
class Print { public: void show(int i) { cout << "整數(shù): " << i << endl; } void show(double f) { cout << "浮點(diǎn)數(shù): " << f << endl; } void show(string s) { cout << "字符串: " << s << endl; } }; int main() { Print obj; obj.show(5); // 調(diào)用show(int) obj.show(3.14); // 調(diào)用show(double) obj.show("Hello"); // 調(diào)用show(string) return 0; }
2.2 運(yùn)算符重載
class Complex { private: double real, imag; public: Complex(double r = 0, double i = 0) : real(r), imag(i) {} Complex operator + (const Complex& obj) { return Complex(real + obj.real, imag + obj.imag); } void display() { cout << real << " + " << imag << "i" << endl; } }; int main() { Complex c1(3, 4), c2(5, 6); Complex c3 = c1 + c2; // 運(yùn)算符重載 c3.display(); // 輸出: 8 + 10i return 0; }
3. 動(dòng)態(tài)多態(tài)(運(yùn)行時(shí)多態(tài))
動(dòng)態(tài)多態(tài)通過(guò)虛函數(shù)和繼承實(shí)現(xiàn),是C++中最常用的多態(tài)形式。
3.1 虛函數(shù)
class Base { public: virtual void show() { // 虛函數(shù) cout << "Base class show()" << endl; } void print() { // 非虛函數(shù) cout << "Base class print()" << endl; } }; class Derived : public Base { public: void show() override { // 重寫(xiě)虛函數(shù) cout << "Derived class show()" << endl; } void print() { // 隱藏基類(lèi)的print() cout << "Derived class print()" << endl; } }; int main() { Base* bptr; Derived d; bptr = &d; // 運(yùn)行時(shí)多態(tài),根據(jù)實(shí)際對(duì)象類(lèi)型調(diào)用函數(shù) bptr->show(); // 輸出: Derived class show() // 非虛函數(shù),根據(jù)指針類(lèi)型調(diào)用函數(shù) bptr->print(); // 輸出: Base class print() return 0; }
3.2 純虛函數(shù)和抽象類(lèi)
class Shape { // 抽象類(lèi) public: virtual float area() = 0; // 純虛函數(shù) virtual void draw() = 0; // 純虛函數(shù) }; class Circle : public Shape { private: float radius; public: Circle(float r) : radius(r) {} float area() override { return 3.14 * radius * radius; } void draw() override { cout << "Drawing Circle" << endl; } }; class Square : public Shape { private: float side; public: Square(float s) : side(s) {} float area() override { return side * side; } void draw() override { cout << "Drawing Square" << endl; } }; int main() { Shape* shapes[2]; shapes[0] = new Circle(5); shapes[1] = new Square(4); for (int i = 0; i < 2; i++) { shapes[i]->draw(); cout << "Area: " << shapes[i]->area() << endl; } delete shapes[0]; delete shapes[1]; return 0; }
4. 虛析構(gòu)函數(shù)
當(dāng)基類(lèi)指針指向派生類(lèi)對(duì)象時(shí),如果基類(lèi)析構(gòu)函數(shù)不是虛函數(shù),刪除該指針只會(huì)調(diào)用基類(lèi)的析構(gòu)函數(shù),可能導(dǎo)致內(nèi)存泄漏。
class Base { public: Base() { cout << "Base constructor" << endl; } virtual ~Base() { cout << "Base destructor" << endl; } // 虛析構(gòu)函數(shù) }; class Derived : public Base { public: Derived() { cout << "Derived constructor" << endl; } ~Derived() { cout << "Derived destructor" << endl; } }; int main() { Base* b = new Derived(); delete b; // 會(huì)調(diào)用Derived的析構(gòu)函數(shù),然后是Base的析構(gòu)函數(shù) return 0; }
5. override和final關(guān)鍵字(C++11)
override
:顯式指明函數(shù)重寫(xiě)基類(lèi)虛函數(shù)final
:禁止派生類(lèi)重寫(xiě)虛函數(shù)或禁止類(lèi)被繼承
class Base { public: virtual void foo() {} virtual void bar() final {} // 不能被子類(lèi)重寫(xiě) }; class Derived : public Base { public: void foo() override {} // 正確:重寫(xiě)基類(lèi)虛函數(shù) // void bar() override {} // 錯(cuò)誤:bar是final的 }; class FinalClass final {}; // 不能被繼承 // class TryInherit : public FinalClass {}; // 錯(cuò)誤
6. 多態(tài)的實(shí)現(xiàn)原理
多態(tài)是C++面向?qū)ο缶幊痰暮诵奶匦灾?,其底層?shí)現(xiàn)機(jī)制非常精妙。
C++通過(guò)虛函數(shù)表(virtual table,簡(jiǎn)稱(chēng)vtable)和虛指針(vptr)實(shí)現(xiàn)運(yùn)行時(shí)多態(tài):
- 每個(gè)包含虛函數(shù)的類(lèi)都有一個(gè)虛函數(shù)表
- 每個(gè)對(duì)象有一個(gè)指向虛函數(shù)表的指針(vptr)
- 調(diào)用虛函數(shù)時(shí),通過(guò)vptr找到虛函數(shù)表,再找到實(shí)際要調(diào)用的函數(shù)
這種機(jī)制雖然有一定開(kāi)銷(xiāo),但提供了強(qiáng)大的運(yùn)行時(shí)多態(tài)能力,是C++面向?qū)ο缶幊痰幕F(xiàn)代CPU的預(yù)測(cè)執(zhí)行可以部分緩解這種開(kāi)銷(xiāo)。
1. 虛函數(shù)表(vtable)機(jī)制
1.1 基本結(jié)構(gòu)
虛函數(shù)表(vtable):
- 編譯器為每個(gè)包含虛函數(shù)的類(lèi)創(chuàng)建一個(gè)虛函數(shù)表
- 表中按聲明順序存儲(chǔ)該類(lèi)所有虛函數(shù)的地址
- 是一個(gè)靜態(tài)數(shù)組,在編譯時(shí)確定
虛指針(vptr):
- 每個(gè)對(duì)象內(nèi)部包含一個(gè)隱藏的指針成員(vptr)
- vptr指向該對(duì)象所屬類(lèi)的虛函數(shù)表
- 由編譯器自動(dòng)添加和維護(hù)
1.2 內(nèi)存布局示例
class Base { public: virtual void func1() { cout << "Base::func1" << endl; } virtual void func2() { cout << "Base::func2" << endl; } void func3() { cout << "Base::func3" << endl; } int a; }; class Derived : public Base { public: void func1() override { cout << "Derived::func1" << endl; } virtual void func4() { cout << "Derived::func4" << endl; } int b; };
內(nèi)存布局示意圖:
Base類(lèi)對(duì)象內(nèi)存布局:
+----------------+
| vptr | --> 指向Base的vtable
+----------------+
| int a |
+----------------+Base的vtable:
+----------------+
| &Base::func1 |
+----------------+
| &Base::func2 |
+----------------+Derived類(lèi)對(duì)象內(nèi)存布局:
+----------------+
| vptr | --> 指向Derived的vtable
+----------------+
| int a (繼承) |
+----------------+
| int b |
+----------------+Derived的vtable:
+----------------+
| &Derived::func1| // 重寫(xiě)的func1
+----------------+
| &Base::func2 | // 未重寫(xiě)的func2
+----------------+
| &Derived::func4| // 新增的func4
+----------------+
2. 多態(tài)調(diào)用的底層過(guò)程
當(dāng)通過(guò)基類(lèi)指針或引用調(diào)用虛函數(shù)時(shí):
Base* ptr = new Derived(); ptr->func1(); // 多態(tài)調(diào)用
實(shí)際執(zhí)行步驟:
- 通過(guò)ptr找到對(duì)象的vptr(編譯器知道vptr在對(duì)象中的偏移量)
- 通過(guò)vptr找到虛函數(shù)表
- 在虛函數(shù)表中找到func1對(duì)應(yīng)的條目
- 調(diào)用該地址處的函數(shù)
3. 構(gòu)造和析構(gòu)過(guò)程中的vptr
3.1 構(gòu)造函數(shù)中的vptr初始化
- 在進(jìn)入構(gòu)造函數(shù)體之前,編譯器插入代碼初始化vptr
- 在構(gòu)造過(guò)程中,vptr會(huì)隨著構(gòu)造的進(jìn)行而改變
Derived::Derived() { // 1. 首先初始化Base部分,此時(shí)vptr指向Base的vtable // 2. 然后初始化Derived成員,vptr改為指向Derived的vtable // 3. 最后執(zhí)行構(gòu)造函數(shù)體 }
3.2 析構(gòu)函數(shù)中的vptr處理
- 在進(jìn)入析構(gòu)函數(shù)體之后,vptr首先指向當(dāng)前類(lèi)的vtable
- 析構(gòu)完成后,vptr會(huì)被設(shè)置為指向基類(lèi)的vtable
Derived::~Derived() { // 1. 執(zhí)行析構(gòu)函數(shù)體(此時(shí)vptr指向Derived的vtable) // 2. 析構(gòu)Derived特有成員 // 3. vptr改為指向Base的vtable // 4. 調(diào)用Base的析構(gòu)函數(shù) }
4. 多繼承下的虛函數(shù)表
多繼承情況下,虛函數(shù)表會(huì)更復(fù)雜:
class Base1 { public: virtual void f1() {} int a; }; class Base2 { public: virtual void f2() {} int b; }; class Derived : public Base1, public Base2 { public: void f1() override {} void f2() override {} virtual void f3() {} int c; };
內(nèi)存布局:
Derived對(duì)象:
+----------------+
| Base1::vptr | --> 指向Derived的Base1 vtable
+----------------+
| Base1::a |
+----------------+
| Base2::vptr | --> 指向Derived的Base2 vtable
+----------------+
| Base2::b |
+----------------+
| Derived::c |
+----------------+Derived的Base1 vtable:
+----------------+
| &Derived::f1 |
+----------------+
| &Derived::f3 |
+----------------+Derived的Base2 vtable:
+----------------+
| &Derived::f2 |
+----------------+
5. 虛繼承的虛函數(shù)表
虛繼承(virtual inheritance)會(huì)使得虛函數(shù)表更加復(fù)雜,通常會(huì)引入額外的虛基類(lèi)指針。
6. RTTI(運(yùn)行時(shí)類(lèi)型信息)
dynamic_cast和typeid也依賴(lài)于虛函數(shù)表,通常vtable的第一個(gè)條目指向類(lèi)型信息。
7. 總結(jié)
C++多態(tài)的實(shí)現(xiàn)依賴(lài)于:
- 每個(gè)類(lèi)有自己的虛函數(shù)表
- 每個(gè)對(duì)象有自己的虛指針
- 調(diào)用虛函數(shù)時(shí)通過(guò)虛指針間接查找
- 繼承關(guān)系反映在虛函數(shù)表的布局中
性能考慮:
多態(tài)調(diào)用相比普通函數(shù)調(diào)用有以下開(kāi)銷(xiāo):
- 通過(guò)vptr間接訪問(wèn)虛函數(shù)表
- 通過(guò)虛函數(shù)表間接調(diào)用函數(shù)
- 通常無(wú)法內(nèi)聯(lián)
多態(tài)的應(yīng)用場(chǎng)景:
- 實(shí)現(xiàn)接口與實(shí)現(xiàn)的分離
- 設(shè)計(jì)模式(如工廠模式、策略模式等)
- 回調(diào)函數(shù)
- 容器存儲(chǔ)不同類(lèi)型的對(duì)象但統(tǒng)一處理
多態(tài)是C++面向?qū)ο缶幊讨蟹浅?qiáng)大的特性,合理使用可以提高代碼的靈活性和可擴(kuò)展性。
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
C++類(lèi)中const修飾的成員函數(shù)及日期類(lèi)小練習(xí)
將const修飾的“成員函數(shù)”稱(chēng)之為const成員函數(shù),const修飾類(lèi)成員函數(shù),表明在該成員函數(shù)中不能對(duì)類(lèi)的任何成員進(jìn)行修改,下面這篇文章主要給大家介紹了關(guān)于C++類(lèi)中const修飾的成員函數(shù)及日期類(lèi)小練習(xí)?的相關(guān)資料,需要的朋友可以參考下2023-01-01通過(guò)c++11改進(jìn)我們的模式之改進(jìn)命令模式
這篇我要講的是如何使用c++11改進(jìn)命令模式,感興趣的朋友可以看下2013-11-11詳解在C++中顯式默認(rèn)設(shè)置的函數(shù)和已刪除的函數(shù)的方法
這篇文章主要介紹了在C++中顯式默認(rèn)設(shè)置的函數(shù)和已刪除的函數(shù)的方法,文中講到了C++11標(biāo)準(zhǔn)中的新特性,需要的朋友可以參考下2016-01-01C++實(shí)現(xiàn)LeetCode(87.攪亂字符串)
這篇文章主要介紹了C++實(shí)現(xiàn)LeetCode(87.攪亂字符串),本篇文章通過(guò)簡(jiǎn)要的案例,講解了該項(xiàng)技術(shù)的了解與使用,以下就是詳細(xì)內(nèi)容,需要的朋友可以參考下2021-07-07C語(yǔ)言的進(jìn)制轉(zhuǎn)換及算法實(shí)現(xiàn)教程
這篇文章主要介紹了C語(yǔ)言的進(jìn)制轉(zhuǎn)換及算法實(shí)現(xiàn)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-01-01C++的動(dòng)態(tài)內(nèi)存管理你真的了解嗎
這篇文章主要為大家詳細(xì)介紹了C++的動(dòng)態(tài)內(nèi)存管理,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來(lái)幫助2022-02-02