多型
多型的定義
多型按字面的意思就是多種形態,當類之間存在層次結構,并且類之間是通過繼承關聯時,就會用到多型,C++ 多型意味著呼叫成員函式時,會根據呼叫函式的物件的型別來執行不同的函式,
簡單來說就是完成某個行為,不同的物件會產生不同的結果
多型的構成條件
1. 必須通過基類的指標或者參考呼叫虛函式
2. 被呼叫的函式必須是虛函式,且派生類必須對基類的虛函式進行重寫
class A { public: virtual void fun1() { cout << "A" << endl; } }; class B:public A { public: virtual void fun1() { cout << "B" << endl; } }; void fun(A* p)//指標 { p->fun1(); } void fun(A& p)//參考 { p.fun1(); } int main() { A a; B b; A* p = &a; A* p1 = &b; fun(a); fun(b); return 0; }
虛函式
被virtual修飾的成員函式為虛函式
虛函式的重寫
派生類中有一個跟基類完全相同的虛函式(即派生類虛函式與基類虛函式的 回傳值型別、函式名字、引數串列完全相同),稱子類的虛函式重寫了基類的虛函式,
協變
派生類重寫基類虛函式時,與基類虛函式回傳值型別不同,即基類虛函式回傳基類物件的指 針或者參考,派生類虛函式回傳派生類物件的指標或者參考時,稱為協變,但實際協變用的比較少
解構式的重寫
因為解構式是完成清理作業,若沒有重寫時,析構會自動呼叫父類的解構式,完成不必要的析構,那么就必須完成子類的解構式清理子類的資源就好,不要再去呼叫父類的解構式,
如果基類的解構式為虛函式,此時派生類解構式只要定義,無論是否加virtual關鍵字, 都與基類的解構式構成重寫,雖然基類與派生類解構式名字不同,雖然函式名不相同, 看起來違背了重寫的規則,其實不然,這里可以理解為編譯器對解構式的名稱做了特殊處 理,編譯后解構式的名稱統一處理成destructor,
override關鍵字
功能:檢查子類是否重寫了父類的的虛函式
class A { public: virtual void fun1() { cout << "A" << endl; } }; class B:public A { public: virtual void fun1() override { cout << "B" << endl; } };
final關鍵字
功能:修飾虛函式,表示此虛函式不允許被重寫
class A { public: virtual void fun1() final { cout << "A" << endl; } }; class B:public A { public: //virtual void fun1() override //{ // cout << "B" << endl; //} };
多載、重寫、重定義對比

有虛函式的類都有虛函式表 計算類的大小要對其 虛表大小為4/8
計算帶有虛函式的 都要加上4/8 虛表
class A { public: virtual void func(int val = 1) { cout << "A->" << val << endl; } virtual void test() { func(); } }; class B :public A { public: virtual void func(int val = 0) { cout << "B->" << val << endl; } //virtual void test() 這是繼承下來了 這里為了方便看 //{ // func(); //} }; int main() { B* p = new B;// B繼承下來的test 去呼叫B類的func //了解多型的特性 因為多型是繼承父類的介面 而重寫的是實作 所以這里的預設用的是父類 實作是繼續用子類 p->test(); return 0; } //運行為 B->1
多型是介面繼承,呼叫子類時,介面用的依舊時父類 預設也是如此 所以預設都是用的父類 而子類時重寫實作 所以呼叫時 實作呼叫的是子類,
總結:多型為介面繼承,呼叫時,介面用父類,實作用子類重寫后的,
介面繼承與實作繼承
普通函式的繼承是一種實作繼承,派生類繼承了基類函式,可以使用函式,繼承的是函式的實 現,虛函式的繼承是一種介面繼承,派生類繼承的是基類虛函式的介面,目的是為了重寫,達成 多型,繼承的是介面,所以如果不實作多型,不要把函式定義成虛函式,
抽象類
在虛函式的后面寫上 =0 ,則這個函式為純虛函式,包含純虛函式的類叫做抽象類(也叫介面 類),抽象類不能實體化出物件,派生類繼承后也不能實體化出物件,只有重寫純虛函式,派生 類才能實體化出物件,純虛函式規范了派生類必須重寫,另外純虛函式更體現出了介面繼承,
只要包含純虛函式的類,就叫抽象類,抽象類表示,子類必須重寫父類的虛函式,不然無法實體化物件
class A { public: virtual void fun1()=0 }; class B :public A { public: virtual void fun1() { cout << "重寫" << endl; } };
虛函式表
1.派生類物件d中也有一個虛表指標,d物件由兩部分構成,一部分是父類繼承下來的成員,虛 表指標也就是存在部分的另一部分是自己的成員,
2. 基類b物件和派生類d物件虛表是不一樣的,這里我們發現Func1完成了重寫,所以d的虛表 中存的是重寫的Derive::Func1,所以虛函式的重寫也叫作覆寫,覆寫就是指虛表中虛函式 的覆寫,重寫是語法的叫法,覆寫是原理層的叫法,
3. 另外Func2繼承下來后是虛函式,所以放進了虛表,Func3也繼承下來了,但是不是虛函 數,所以不會放進虛表,
4. 虛函式表本質是一個存虛函式指標的指標陣列,一般情況這個陣列最后面放了一個nullptr,
5. 總結一下派生類的虛表生成:a.先將基類中的虛表內容拷貝一份到派生類虛表中 b.如果派生 類重寫了基類中某個虛函式,用派生類自己的虛函式覆寫虛表中基類的虛函式 c.派生類自己 新增加的虛函式按其在派生類中的宣告次序增加到派生類虛表的最后,
6. 虛函式存在哪的?虛表存在哪的? 答:虛函式存在 虛表,虛表存在物件中,注意上面的回答的錯的,但是很多童鞋都是這樣深以為然的,注意 虛表存的是虛函式指標,不是虛函式,虛函式和普通函式一樣的,都是存在代碼段的,只是 他的指標又存到了虛表中,另外物件中存的不是虛表,存的是虛表指標,
靜態系結與動態系結
1. 靜態系結又稱為前期系結(早系結),在程式編譯期間確定了程式的行為,也稱為靜態多型, 比如:函式多載
2. 動態系結又稱后期系結(晚系結),是在程式運行期間,根據具體拿到的型別確定程式的具體 行為,呼叫具體的函式,也稱為動態多型,
滿足多型以后的函式呼叫,不是在編譯時確定的,是運行 起來以后到物件的中取找的,不滿足多型的函式呼叫時編譯時確認好的,
相關問題
-
nline函式可以是虛函式嗎?答:可以,不過編譯器就忽略inline屬性,這個函式就不再是 inline,因為虛函式要放到虛表中去,
-
靜態成員可以是虛函式嗎?答:不能,因為靜態成員函式沒有this指標,使用型別::成員函式 的呼叫方式無法訪問虛函式表,所以靜態成員函式無法放進虛函式表,
-
建構式可以是虛函式嗎?答:不能,因為物件中的虛函式表指標是在建構式初始化串列 階段才初始化的,
-
解構式可以是虛函式嗎?什么場景下解構式是虛函式?答:可以,并且最好把基類的析 構函式定義成虛函式,參考本節課件內容
-
物件訪問普通函式快還是虛函式更快?答:首先如果是普通物件,是一樣快的,如果是指標 物件或者是參考物件,則呼叫的普通函式快,因為構成多型,運行時呼叫虛函式需要到虛函 數表中去查找,
-
虛函式表是在什么階段生成的,存在哪的?答:虛函式表是在編譯階段就生成的,一般情況 下存在代碼段(常量區)的,
-
虛函式表是存虛函式的指標,而虛基表是用來解決二義性和資料沉余問題,
這就是本篇的全部內容,若對您有所幫助,希望能獲得您的贊,您的贊就是對我的最大支持
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/500174.html
標籤:其他
