深入理解C++的多態(tài)性
C++編程語言是一款應(yīng)用廣泛,支持多種程序設(shè)計(jì)的計(jì)算機(jī)編程語言。我們今天就會為大家詳細(xì)介紹其中C++多態(tài)性的一些基本知識,以方便大家在學(xué)習(xí)過程中對此能夠有一個充分的掌握。
多態(tài)性可以簡單地概括為“一個接口,多種方法”,程序在運(yùn)行時才決定調(diào)用的函數(shù),它是面向?qū)ο缶幊填I(lǐng)域的核心概念。多態(tài)(polymorphisn),字面意思多種形狀。
C++多態(tài)性是通過虛函數(shù)來實(shí)現(xiàn)的,虛函數(shù)允許子類重新定義成員函數(shù),而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。(這里我覺得要補(bǔ)充,重寫的話可以有兩種,直接重寫成員函數(shù)和重寫虛函數(shù),只有重寫了虛函數(shù)的才能算作是體現(xiàn)了C++多態(tài)性)而重載則是允許有多個同名的函數(shù),而這些函數(shù)的參數(shù)列表不同,允許參數(shù)個數(shù)不同,參數(shù)類型不同,或者兩者都不同。編譯器會根據(jù)這些函數(shù)的不同列表,將同名的函數(shù)的名稱做修飾,從而生成一些不同名稱的預(yù)處理函數(shù),來實(shí)現(xiàn)同名函數(shù)調(diào)用時的重載問題。但這并沒有體現(xiàn)多態(tài)性。
多態(tài)與非多態(tài)的實(shí)質(zhì)區(qū)別就是函數(shù)地址是早綁定還是晚綁定。如果函數(shù)的調(diào)用,在編譯器編譯期間就可以確定函數(shù)的調(diào)用地址,并生產(chǎn)代碼,是靜態(tài)的,就是說地址是早綁定的。而如果函數(shù)調(diào)用的地址不能在編譯器期間確定,需要在運(yùn)行時才確定,這就屬于晚綁定。
那么多態(tài)的作用是什么呢,封裝可以使得代碼模塊化,繼承可以擴(kuò)展已存在的代碼,他們的目的都是為了代碼重用。而多態(tài)的目的則是為了接口重用。也就是說,不論傳遞過來的究竟是那個類的對象,函數(shù)都能夠通過同一個接口調(diào)用到適應(yīng)各自對象的實(shí)現(xiàn)方法。
最常見的用法就是聲明基類的指針,利用該指針指向任意一個子類對象,調(diào)用相應(yīng)的虛函數(shù),可以根據(jù)指向的子類的不同而實(shí)現(xiàn)不同的方法。如果沒有使用虛函數(shù)的話,即沒有利用C++多態(tài)性,則利用基類指針調(diào)用相應(yīng)的函數(shù)的時候,將總被限制在基類函數(shù)本身,而無法調(diào)用到子類中被重寫過的函數(shù)。因?yàn)闆]有多態(tài)性,函數(shù)調(diào)用的地址將是一定的,而固定的地址將始終調(diào)用到同一個函數(shù),這就無法實(shí)現(xiàn)一個接口,多種方法的目的了。
筆試題目:
#include<iostream> using namespace std; class A { public: void foo() { printf("1\n"); } virtual void fun() { printf("2\n"); } }; class B : public A { public: void foo() { printf("3\n"); } void fun() { printf("4\n"); } }; int main(void) { A a; B b; A *p = &a; p->foo(); p->fun(); p = &b; p->foo(); p->fun(); return 0; }
第一個p->foo()和p->fuu()都很好理解,本身是基類指針,指向的又是基類對象,調(diào)用的都是基類本身的函數(shù),因此輸出結(jié)果就是1、2。
第二個輸出結(jié)果就是1、4。p->foo()和p->fuu()則是基類指針指向子類對象,正式體現(xiàn)多態(tài)的用法,p->foo()由于指針是個基類指針,指向是一個固定偏移量的函數(shù),因此此時指向的就只能是基類的foo()函數(shù)的代碼了,因此輸出的結(jié)果還是1。而p->fun()指針是基類指針,指向的fun是一個虛函數(shù),由于每個虛函數(shù)都有一個虛函數(shù)列表,此時p調(diào)用fun()并不是直接調(diào)用函數(shù),而是通過虛函數(shù)列表找到相應(yīng)的函數(shù)的地址,因此根據(jù)指向的對象不同,函數(shù)地址也將不同,這里將找到對應(yīng)的子類的fun()函數(shù)的地址,因此輸出的結(jié)果也會是子類的結(jié)果4。
筆試的題目中還有一個另類測試方法。即
B *ptr = (B *)&a; ptr->foo(); ptr->fun();
問這兩調(diào)用的輸出結(jié)果。這是一個用子類的指針去指向一個強(qiáng)制轉(zhuǎn)換為子類地址的基類對象。結(jié)果,這兩句調(diào)用的輸出結(jié)果是3,2。
并不是很理解這種用法,從原理上來解釋,由于B是子類指針,雖然被賦予了基類對象地址,但是ptr->foo()在調(diào)用的時候,由于地址偏移量固定,偏移量是子類對象的偏移量,于是即使在指向了一個基類對象的情況下,還是調(diào)用到了子類的函數(shù),雖然可能從始到終都沒有子類對象的實(shí)例化出現(xiàn)。
而ptr->fun()的調(diào)用,可能還是因?yàn)镃++多態(tài)性的原因,由于指向的是一個基類對象,通過虛函數(shù)列表的引用,找到了基類中fun()函數(shù)的地址,因此調(diào)用了基類的函數(shù)。由此可見多態(tài)性的強(qiáng)大,可以適應(yīng)各種變化,不論指針是基類的還是子類的,都能找到正確的實(shí)現(xiàn)方法。
//小結(jié):1、有virtual才可能發(fā)生多態(tài)現(xiàn)象 // 2、不發(fā)生多態(tài)(無virtual)調(diào)用就按原類型調(diào)用 #include<iostream> using namespace std; class Base { public: virtual void f(float x) { cout<<"Base::f(float)"<< x <<endl; } void g(float x) { cout<<"Base::g(float)"<< x <<endl; } void h(float x) { cout<<"Base::h(float)"<< x <<endl; } }; class Derived : public Base { public: virtual void f(float x) { cout<<"Derived::f(float)"<< x <<endl; //多態(tài)、覆蓋 } void g(int x) { cout<<"Derived::g(int)"<< x <<endl; //隱藏 } void h(float x) { cout<<"Derived::h(float)"<< x <<endl; //隱藏 } }; int main(void) { Derived d; Base *pb = &d; Derived *pd = &d; // Good : behavior depends solely on type of the object pb->f(3.14f); // Derived::f(float) 3.14 pd->f(3.14f); // Derived::f(float) 3.14 // Bad : behavior depends on type of the pointer pb->g(3.14f); // Base::g(float) 3.14 pd->g(3.14f); // Derived::g(int) 3 // Bad : behavior depends on type of the pointer pb->h(3.14f); // Base::h(float) 3.14 pd->h(3.14f); // Derived::h(float) 3.14 return 0; }
令人迷惑的隱藏規(guī)則
本來僅僅區(qū)別重載與覆蓋并不算困難,但是C++的隱藏規(guī)則使問題復(fù)雜性陡然增加。
這里“隱藏”是指派生類的函數(shù)屏蔽了與其同名的基類函數(shù),規(guī)則如下:
(1)如果派生類的函數(shù)與基類的函數(shù)同名,但是參數(shù)不同。此時,不論有無virtual
關(guān)鍵字,基類的函數(shù)將被隱藏(注意別與重載混淆)。
(2)如果派生類的函數(shù)與基類的函數(shù)同名,并且參數(shù)也相同,但是基類函數(shù)沒有virtual
關(guān)鍵字。此時,基類的函數(shù)被隱藏(注意別與覆蓋混淆)。
上面的程序中:
(1)函數(shù)Derived::f(float)覆蓋了Base::f(float)。
(2)函數(shù)Derived::g(int)隱藏了Base::g(float),而不是重載。
(3)函數(shù)Derived::h(float)隱藏了Base::h(float),而不是覆蓋。
C++純虛函數(shù)
一、定義
純虛函數(shù)是在基類中聲明的虛函數(shù),它在基類中沒有定義,但要求任何派生類都要定義自己的實(shí)現(xiàn)方法。在基類中實(shí)現(xiàn)純虛函數(shù)的方法是在函數(shù)原型后加“=0”
virtual void funtion()=0
二、引入原因
1、為了方便使用多態(tài)特性,我們常常需要在基類中定義虛擬函數(shù)。
2、在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。
為了解決上述問題,引入了純虛函數(shù)的概念,將函數(shù)定義為純虛函數(shù)(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實(shí)現(xiàn)多態(tài)性。同時含有純虛擬函數(shù)的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。
三、相似概念
1、多態(tài)性
指相同對象收到不同消息或不同對象收到相同消息時產(chǎn)生不同的實(shí)現(xiàn)動作。C++支持兩種多態(tài)性:編譯時多態(tài)性,運(yùn)行時多態(tài)性。
a、編譯時多態(tài)性:通過重載函數(shù)實(shí)現(xiàn)
b、運(yùn)行時多態(tài)性:通過虛函數(shù)實(shí)現(xiàn)。
2、虛函數(shù)
虛函數(shù)是在基類中被聲明為virtual,并在派生類中重新定義的成員函數(shù),可實(shí)現(xiàn)成員函數(shù)的動態(tài)覆蓋(Override)
3、抽象類
包含純虛函數(shù)的類稱為抽象類。由于抽象類包含了沒有定義的純虛函數(shù),所以不能定義抽象類的對象。
相關(guān)文章
C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法
這篇文章主要介紹了C語言查找數(shù)組里數(shù)字重復(fù)次數(shù)的方法,涉及C語言針對數(shù)組的遍歷與判斷技巧,具有一定參考借鑒價值,需要的朋友可以參考下2015-07-07C++編程中的或||、與&&、非!邏輯運(yùn)算符基本用法整理
這篇文章主要介紹了C++中的或||、與&&、非!邏輯運(yùn)算符基本用法整理,是C++入門學(xué)習(xí)中的基礎(chǔ)知識,需要的朋友可以參考下2016-01-01牛頓迭代法求多項(xiàng)式在1.5附近的值2*x的3次冪--4x平方+3*x-6=0的實(shí)現(xiàn)代碼
以下代碼是使用了牛頓迭代法求多項(xiàng)式在1.5附近的值 2*x的3次冪 - 4x的平方 + 3*x -6=0的實(shí)例。需要的朋友參考下吧2013-05-05C++ new/delete相關(guān)知識點(diǎn)詳細(xì)解析
C語言用一堆標(biāo)準(zhǔn)庫函數(shù)malloc和free在自由存儲區(qū)中分配存儲空間,而C++則用new和delete表達(dá)式實(shí)現(xiàn)相同的功能2013-09-09Qt開發(fā)之使用socket實(shí)現(xiàn)遠(yuǎn)程控制
本篇文章將會介紹下位機(jī)通過心跳包和上位機(jī)之間進(jìn)行數(shù)據(jù)交互和遠(yuǎn)程功能控制的實(shí)現(xiàn)方法。文中的示例代碼講解詳細(xì),感興趣的可以了解一下2022-11-11