日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程语言 > c/c++ >内容正文

c/c++

C++虚函数

發(fā)布時(shí)間:2023/12/14 c/c++ 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 C++虚函数 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

C++三大特性

1.繼承
一個(gè)對(duì)象直接使用另一個(gè)對(duì)象的屬性和方法,減少重復(fù)的代碼,增加了類的耦合性。但父類通常定義了子類的部分行為,父類的改變可能影響子類的行為。
2. 多態(tài)
C++中有靜多態(tài)(編譯時(shí)多態(tài))和動(dòng)多態(tài)(運(yùn)行時(shí)多態(tài))兩種。靜多態(tài)主要通過(guò)模板來(lái)實(shí)現(xiàn),宏也是實(shí)現(xiàn)靜多態(tài)的一種途徑,所有調(diào)用在編譯期就可確定,因此它是靜態(tài)的;動(dòng)多態(tài)在C++中是通過(guò)虛函數(shù)實(shí)現(xiàn)的,即在基類中存在一些接口(一般為純虛函數(shù)),子類必須重載這些接口,通過(guò)使用基類的指針或者引用指向子類的對(duì)象,通過(guò)基類指針實(shí)現(xiàn)調(diào)用子類對(duì)應(yīng)的函數(shù)的功能,函數(shù)調(diào)用在執(zhí)行期才能進(jìn)行確定,所以它是動(dòng)態(tài)的。

/*****靜多態(tài)*****/ #include <iostream> using namespace std;class line { public: void draw() { cout << "line is drawing!" << endl; } };class circle { public: void draw() { cout << "circle is drawing!" << endl; } };template<typename T> void drawShape(T & shape) { shape.draw(); }int main() { line lining; circle circling; drawShape( lining ); drawShape( circling );return 0; }/*****動(dòng)多態(tài)*****/ #include <iostream> using namespace std;class shape { public: virtual void draw() = 0; };class line : public shape { public: void draw() { cout << "line is drawing!" << endl; } };class circle : public shape { public: void draw() { cout << "circle is drawing!" << endl; } };int main() { /*基類的指針指向子類的對(duì)象*/shape* pLine = new line; shape* pCircle = new circle;pLine->draw(); pCircle->draw(); if( pCircle ) delete pCircle ; if( pLine ) delete pLine ; /*基類的引用指向子類的對(duì)象*///line line; //circle circl; //shape& referenceLine = line; // //shape& referenceCircl = circl; //referenceLine.draw(); //referenceCircl.draw();return 0; }

動(dòng)多態(tài)代碼運(yùn)行結(jié)果:

3. 封裝
隱藏對(duì)象的屬性和實(shí)現(xiàn)細(xì)節(jié),僅僅對(duì)外提供接口和方法。
重載:寫(xiě)一個(gè)與已有函數(shù)同名但是參數(shù)表不同的函數(shù)
覆蓋:派生類中改寫(xiě)基類中的虛函數(shù)

虛函數(shù)與純虛函數(shù)

虛函數(shù)使用的其核心目的是通過(guò)基類訪問(wèn)派生類定義的函數(shù)(virtual),實(shí)現(xiàn)了多態(tài)的機(jī)制。在使用虛函數(shù)的過(guò)程中存在兩個(gè)常見(jiàn)錯(cuò)誤:無(wú)意的重寫(xiě)(具有相同的簽名的成員函數(shù))、虛函數(shù)簽名不匹配(函數(shù)名、參數(shù)列表 或 const 屬性不一樣)。針對(duì)上述情況,C++ 11 增加了兩個(gè)繼承控制關(guān)鍵字:override 和 final。override:防止虛函數(shù)的覆蓋,保證基類的虛函數(shù)在派生類重載;final:防止基類的虛函數(shù)在派生類重載,保證虛函數(shù)的覆蓋。

/*override:防止虛函數(shù)覆蓋*/ class Base { public:virtual void display(int x); // 虛函數(shù) };class Derived : public Base { public:virtual void display(int x) const override; // const 屬性不一樣,新的虛函數(shù) }/*final:防止虛函數(shù)重載*/ class Base { public:virtual void display(int x) final; // 虛函數(shù) };class Derived : public Base { public:virtual void display(int x) override; // 重寫(xiě)提示錯(cuò)誤 };

當(dāng)想要在基類中定義虛函數(shù),以便在派生類中重新定義該函數(shù)更好地適用于對(duì)象,但是您在基類中又不能對(duì)虛函數(shù)給出有意義的實(shí)現(xiàn),這個(gè)時(shí)候就會(huì)用到純虛函數(shù)(virtual 返回類型 函數(shù)名() = 0;)。

虛繼承


在上圖菱形繼承中,類 A 中的成員變量和成員函數(shù)繼承到類 D 中變成了兩份,在一個(gè)派生類中保留間接基類的多份同名成員,雖然可以在不同的成員變量中分別存放不同的數(shù)據(jù),但大多數(shù)情況下這是多余的:因?yàn)楸A舳喾莩蓡T變量不僅占用較多的存儲(chǔ)空間,還容易產(chǎn)生命名沖突。為了解決多繼承時(shí)的命名沖突和冗余數(shù)據(jù)問(wèn)題,C++ 提出了虛繼承(繼承方式前面加上 virtual 關(guān)鍵字),使得在派生類中只保留一份間接基類的成員。虛繼承機(jī)制下,不論虛基類在繼承體系中出現(xiàn)了多少次,在派生類中都只包含一份虛基類的成員。

虛指針與虛表

C++實(shí)現(xiàn)虛函數(shù)的方法是:為每個(gè)類對(duì)象添加一個(gè)隱藏成員,隱藏成員保存了一個(gè)指針,這個(gè)指針叫虛表指針(vptr),它指向一個(gè)虛函數(shù)表(vtbl),位于該類的首地址(系統(tǒng)為32位時(shí)地址為前4個(gè)字節(jié),系統(tǒng)為64位時(shí)地址為前8個(gè)字節(jié))。

基類對(duì)象包含一個(gè)虛表指針,指向基類的虛函數(shù)表,派生類對(duì)象也將包含一個(gè)虛表指針,指向派生類虛函數(shù)表,如果派生類重寫(xiě)了基類的虛方法,該派生類虛函數(shù)表將保存重寫(xiě)的虛函數(shù)的地址,而不是基類的虛函數(shù)地址,如果基類中的虛方法沒(méi)有在派生類中重寫(xiě),那么派生類將繼承基類中的虛方法,而且派生類中虛函數(shù)表將保存基類中未被重寫(xiě)的虛函數(shù)的地址,但如果派生類中定義了新的虛方法,則該虛函數(shù)的地址也將被添加到派生類虛函數(shù)表中。
例子:

#include <iostream> int main() {class A{public:virtual void vfunc() { std::cout << "A::vfunc" << std::endl; }virtual void vfuncA() { std::cout << "A::vfuncA" << std::endl; }public:double m_data = 1.57;};class B:public A{public:virtual void vfunc() { std::cout << "B::vfunc" << std::endl; }virtual void vfuncB() { std::cout << "B::vfuncB" << std::endl; }public:double m_data = 3.14;};typedef void(*Fun)(void);//聲明一個(gè)函數(shù)指針A aObj;B bObj;A *abObj = new B;Fun pFun = NULL;//指向void* pf(void)類的函數(shù)的指針pFun/*_WIN64--只有64位程序才有,_WIN32--32位和64位程序都有*//*_WIN64 用來(lái)判斷編譯環(huán)境是 x86 還是 x64,_WIN32 可以用來(lái)判斷是否 Windows 系統(tǒng)*/ #if _WIN64 std::cout << "aObj實(shí)例對(duì)象的數(shù)據(jù)地址:" << &(aObj.m_data) << std::endl;std::cout << "A虛函數(shù)表的地址:" << (long long int*) * (long long int*)(&aObj) << std::endl;std::cout << "A虛函數(shù)表的第一個(gè)函數(shù)地址:" << (long long int*) * (long long int*) * (long long int*)(&aObj) << std::endl;std::cout << "A虛函數(shù)表的第二個(gè)函數(shù)地址:" << (long long int*) * ((long long int*) * (long long int*)(&aObj) + 1) << std::endl;std::cout << std::endl;std::cout << "bObj實(shí)例對(duì)象的數(shù)據(jù)地址:" << &(bObj.m_data) << std::endl;std::cout << "B虛函數(shù)表的地址:" << (long long int*) * (long long int*)(&bObj) << std::endl;std::cout << "B虛函數(shù)表的第一個(gè)函數(shù)地址:" << (long long int*) * (long long int*) * (long long int*)(&bObj) << std::endl;std::cout << "B虛函數(shù)表的第二個(gè)函數(shù)地址:" << (long long int*) * ((long long int*) * (long long int*)(&bObj) + 1) << std::endl;std::cout << "B虛函數(shù)表的第三個(gè)函數(shù)地址:" << (long long int*) * ((long long int*) * (long long int*)(&bObj) + 2) << std::endl;std::cout << std::endl;//再次取址得到第一個(gè)虛函數(shù)的地址//A第一個(gè)虛函數(shù)pFun = (Fun) * (long long int*) * (long long int*)(&aObj);pFun();//A第二個(gè)虛函數(shù)pFun = (Fun) * ((long long int*) * (long long int*)(&aObj) + 1);pFun();std::cout << std::endl;//B第一個(gè)虛函數(shù)pFun = (Fun) * (long long int*) * (long long int*)(&bObj);pFun();//B第二個(gè)虛函數(shù)pFun = (Fun) * ((long long int*) * (long long int*)(&bObj) + 1);pFun();//B第三個(gè)虛函數(shù)pFun = (Fun) * ((long long int*) * (long long int*)(&bObj) + 2);pFun();std::cout << std::endl;//基類指針指向派生類,調(diào)用派生類函數(shù)vfunc()aObj.vfunc();abObj->vfunc();#elsestd::cout << "aObj實(shí)例對(duì)象的數(shù)據(jù)地址:" << &(aObj.m_data) << std::endl;std::cout << "A虛函數(shù)表的地址:" << (int*)*(int*)(&aObj) << std::endl;std::cout << "A虛函數(shù)表的第一個(gè)函數(shù)地址:" << (int*)*(int*)*(int*)(&aObj) << std::endl;std::cout << "A虛函數(shù)表的第二個(gè)函數(shù)地址:" << (int*)*((int*)*(int*)(&aObj) + 1) << std::endl;std::cout << std::endl;std::cout << "bObj實(shí)例對(duì)象的數(shù)據(jù)地址:" << &(bObj.m_data) << std::endl;std::cout << "B虛函數(shù)表的地址:" << (int*)*(int*)(&bObj) << std::endl;std::cout << "B虛函數(shù)表的第一個(gè)函數(shù)地址:" << (int*)*(int*)*(int*)(&bObj) << std::endl;std::cout << "B虛函數(shù)表的第二個(gè)函數(shù)地址:" << (int*)*((int*)*(int*)(&bObj) + 1) << std::endl;std::cout << "B虛函數(shù)表的第三個(gè)函數(shù)地址:" << (int*)*((int*)*(int*)(&bObj) + 2) << std::endl;std::cout << std::endl;//再次取址得到第一個(gè)虛函數(shù)的地址//A第一個(gè)虛函數(shù)pFun = (Fun) * (int*)*(int*)(&aObj);pFun();//A第二個(gè)虛函數(shù)pFun = (Fun) * ((int*)*(int*)(&aObj) + 1);pFun();std::cout << std::endl;//B第一個(gè)虛函數(shù)pFun = (Fun) * (int*)*(int*)(&bObj);pFun();//B第二個(gè)虛函數(shù)pFun = (Fun) * ((int*)*(int*)(&bObj) + 1);pFun();//B第三個(gè)虛函數(shù)pFun = (Fun) * ((int*)*(int*)(&bObj) + 2);pFun();std::cout << std::endl;//基類指針指向派生類,調(diào)用派生類函數(shù)vfunc()aObj.vfunc();abObj->vfunc(); #endifdelete abObj;return 0; }

運(yùn)行結(jié)果:

上例虛函數(shù)存儲(chǔ)方式如圖所示:

總結(jié)

以上是生活随笔為你收集整理的C++虚函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。