C++多态讲解以及常见面试题
多態的概念
什么是多態
? 多態就是在不同繼承關系的類對象,去調用同一函數,產生了不同的行為。
實現多態的條件
調用函數的對象是指針或引用。
被調用函數必須是虛函數,并且完成了虛函數的重寫。
函數重載
多態的作用
??封裝是為了代碼模塊化,繼承是擴展已存在的代碼,他們的目的都是為了 實現代碼的復用,但是多態的目的是為了實現接口的重用,也就是多不管傳遞過來的是哪個類對象,函數都能狗狗通過這個接口調用到適應自己對象的實現方法。
??虛函數重寫:子類重寫父類的函數(協變:子類中的返回值和父類中的返回值是父子關系,并且是引用或指針)。如果只在父類中添加virtual關鍵字,子類中的重寫函數也會稱為虛函數,童謠可以構成多態,否則為重定義。
函數重載、重定義、重寫的區別:
虛函數和純虛函數
??虛函數就是在類的成員函數錢錢添加了virtual關鍵字。主要是為了實現多態,通過一張虛函數表來實現。它允許子類重寫來自父類的成員函數。
??純虛函數就是在基類中只對對應的虛函數進行聲明,在最后加=0,定義純虛函數的類是一個抽象類,抽象類不能被實例化,它體現的是接口繼承,子類只是繼承了這個接口的形式,不需要使用他里面的功能,要實現自己的功能。在子類中必須實現父類的純虛函數,如果不實現父類的純虛函數,這個子類也是一個抽象類,同樣不能區實例化對象。
??那么什么是抽象類呢?抽象類就是包含純虛函數的類,這個類不能被實例化,因為類定義的不完整,成員函數都沒有實現,這種類只是為了接口繼承的實現,繼承他的子類并不想去用它,子類要實現自己的功能。
??無論是虛函數還是純虛函數,都是在基類中為派生類提供編程接口,面向對象最核心的思想就是對接口編程,而不是對實現編程,在C++中,就是使用繼承和多態來事項這種思想。如果想讓基類為派生類提供缺省的處理方法,那么就將這個函數設為虛函數,如果是想讓派生類必須重寫該虛函數,就將這個函數設為純虛函數。
1、虛函數和純虛函數可以定義在同一個類中,一旦某個類包含了純虛函數,這個類就是一個抽象類。
2、虛函數可以直接被使用,但是純虛函數必須要在派生類中實現之后才可以使用,一i那位純虛函數在積累中只聲明沒有定義,所以無法直接使用。
3、虛函數和純虛函數都可以在子了中被重寫,一墮胎的形式被調用。
4、虛函數和純虛函數都是為了實現接口繼承而出現。
5、虛函數的定義:virtual + 函數純虛函數定義:virtual + 函數 + =0;
6、虛函數和純虛函數都不能設為static,因為static在編譯的時候就要被綁定,但是虛函數和純虛函數實在運行時在確定的。
虛函數表
??虛函數表是一個函數指針數組,在末尾存放的時一個空指針nullptr,在VS中存放在代碼段,在虛函數表中只存放虛函數執政,他不存放普通函數的指針,將新創建的虛函數指針存放在虛函數表末尾。
??虛標指針:存放在對象模型中,在32位機上,存放在對象模型的頭4個字節中。
單繼承中的虛表
??在單繼承中所有的虛函數都存放在虛表中,如果有被重寫的,直接將虛表中的虛函數指針換成重寫后的虛函數指針即可,如果在子類中的添加了新的虛函數,按照新的虛函數的聲明順序將其添加到虛表中。在虛表中,先添加父類的虛函數指針,再添加子類的虛函數指針。
多繼承中的虛表
#include <iostream> using namespace std; 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 printFunc(VFPTR* vftable) {cout << "虛表地址" << vftable << endl;for (int i = 0; (*vftable) != nullptr; ++i){cout << "第" << i << "個函數地址" << *vftable << "--->";(*vftable)();//調這個函數++vftable;//指針向后走,打印下一個函數地址} }int main() {derive d;//取虛標地址的方法://先取到d的地址,由于虛標指針存放在對象的頭四個字節中,所以要將他轉為int*類型(int* 位四個字節),這樣就可以得到頭四個字節的內容,對這個已經得到的四個字節解引用就可以得到它的值,但現在他是整型的,我們要的是函數指針,所以要將他強轉位函數指針類型的指針就可以得到虛表的地址(VFPTR*)*((int*)&b);//如果要調用虛表中的第一個函數,對他向后偏移1個單位再解引用就可以調用虛表中的第一個函數(*(((VFPTR*)*((int*)&b))+1))();//打印第二個虛標地址//方法一://給出一個base2的指針,讓他存放d的地址,這時候就發生了一個天然的轉換,這個指針中存放的就是第二個虛表的地址base2* pd = &d;//方法二//讓第一個虛表的地址向后偏移base1的大小(VFPTR*)*((int*)((char*)&b+sizeof(base1)))return 0; }接口繼承和實現繼承
??普通函數的繼承就是一種實現繼承,派生類繼承了基類函數,繼承的是函數的實現。
??虛函數的繼承是接口繼承,派生類繼承的是基類函數的接口,目的是為了重寫,從而達成多態,繼承的是接口。如果不是為了實現多態,不要把函數設為虛函數。
—>常見面試題<—
總結
以上是生活随笔為你收集整理的C++多态讲解以及常见面试题的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 检测系列--YOLO系列
- 下一篇: 使用C与C++混合编程封装UDP协议