C++編程語言是一款應用普遍,支持多種程序設計的計算機編程語言。咱們今天就會爲你們詳細介紹其中C++多態性的一些基本知識,以方便你們在學習過程當中對此可以有一個充分的掌握。
多態性能夠簡單地歸納爲「一個接口,多種方法」,程序在運行時才決定調用的函數,它是面向對象編程領域的核心概念。多態(polymorphisn),字面意思多種形狀。
C++多態性是經過虛函數來實現的,虛函數容許子類從新定義成員函數,而子類從新定義父類的作法稱爲覆蓋(override),或者稱爲重寫。(這裏我以爲要補充,重寫的話能夠有兩種,直接重寫成員函數和重寫虛函數,只有重寫了虛函數的才能算做是體現了C++多態性)而重載則是容許有多個同名的函數,而這些函數的參數列表不一樣,容許參數個數不一樣,參數類型不一樣,或者二者都不一樣。編譯器會根據這些函數的不一樣列表,將同名的函數的名稱作修飾,從而生成一些不一樣名稱的預處理函數,來實現同名函數調用時的重載問題。但這並無體現多態性。
多態與非多態的實質區別就是函數地址是早綁定仍是晚綁定。若是函數的調用,在編譯器編譯期間就能夠肯定函數的調用地址,並生產代碼,是靜態的,就是說地址是早綁定的。而若是函數調用的地址不能在編譯器期間肯定,須要在運行時才肯定,這就屬於晚綁定。
那麼多態的做用是什麼呢,封裝可使得代碼模塊化,繼承能夠擴展已存在的代碼,他們的目的都是爲了代碼重用。而多態的目的則是爲了接口重用。也就是說,不論傳遞過來的到底是那個類的對象,函數都可以經過同一個接口調用到適應各自對象的實現方法。
最多見的用法就是聲明基類的指針,利用該指針指向任意一個子類對象,調用相應的虛函數,能夠根據指向的子類的不一樣而實現不一樣的方法。若是沒有使用虛函數的話,即沒有利用C++多態性,則利用基類指針調用相應的函數的時候,將總被限制在基類函數自己,而沒法調用到子類中被重寫過的函數。由於沒有多態性,函數調用的地址將是必定的,而固定的地址將始終調用到同一個函數,這就沒法實現一個接口,多種方法的目的了。 ios
筆試題目: 編程
- #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()都很好理解,自己是基類指針,指向的又是基類對象,調用的都是基類自己的函數,所以輸出結果就是一、2。
第二個輸出結果就是一、4。p->foo()和p->fuu()則是基類指針指向子類對象,正式體現多態的用法,p->foo()因爲指針是個基類指針,指向是一個固定偏移量的函數,所以此時指向的就只能是基類的foo()函數的代碼了,所以輸出的結果仍是1。而p->fun()指針是基類指針,指向的fun是一個虛函數,因爲每一個虛函數都有一個虛函數列表,此時p調用fun()並非直接調用函數,而是經過虛函數列表找到相應的函數的地址,所以根據指向的對象不一樣,函數地址也將不一樣,這裏將找到對應的子類的fun()函數的地址,所以輸出的結果也會是子類的結果4。
筆試的題目中還有一個另類測試方法。即
B *ptr = (B *)&a; ptr->foo(); ptr->fun();
問這兩調用的輸出結果。這是一個用子類的指針去指向一個強制轉換爲子類地址的基類對象。結果,這兩句調用的輸出結果是3,2。
並非很理解這種用法,從原理上來解釋,因爲B是子類指針,雖然被賦予了基類對象地址,可是ptr->foo()在調用的時候,因爲地址偏移量固定,偏移量是子類對象的偏移量,因而即便在指向了一個基類對象的狀況下,仍是調用到了子類的函數,雖然可能從始到終都沒有子類對象的實例化出現。
而ptr->fun()的調用,可能仍是由於C++多態性的緣由,因爲指向的是一個基類對象,經過虛函數列表的引用,找到了基類中fun()函數的地址,所以調用了基類的函數。因而可知多態性的強大,能夠適應各類變化,不論指針是基類的仍是子類的,都能找到正確的實現方法。
- //小結:一、有virtual纔可能發生多態現象
- // 二、不發生多態(無virtual)調用就按原類型調用
- #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; //多態、覆蓋
- }
- 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;
- }
使人迷惑的隱藏規則
原本僅僅區別重載與覆蓋並不算困難,可是C++的隱藏規則使問題複雜性陡然增長。
這裏「隱藏」是指派生類的函數屏蔽了與其同名的基類函數,規則以下:
(1)若是派生類的函數與基類的函數同名,可是參數不一樣。此時,不論有無virtual
關鍵字,基類的函數將被隱藏(注意別與重載混淆)。
(2)若是派生類的函數與基類的函數同名,而且參數也相同,可是基類函數沒有virtual
關鍵字。此時,基類的函數被隱藏(注意別與覆蓋混淆)。
上面的程序中:
(1)函數Derived::f(float)覆蓋了Base::f(float)。
(2)函數Derived::g(int)隱藏了Base::g(float),而不是重載。
(3)函數Derived::h(float)隱藏了Base::h(float),而不是覆蓋。
C++純虛函數
1、定義
純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義本身的實現方法。在基類中實現純虛函數的方法是在函數原型後加「=0」
virtual void funtion()=0
2、引入緣由
一、爲了方便使用多態特性,咱們經常須要在基類中定義虛擬函數。
二、在不少狀況下,基類自己生成對象是不合情理的。例如,動物做爲一個基類能夠派生出老虎、孔雀等子類,但動物自己生成對象明顯不合常理。
爲了解決上述問題,引入了純虛函數的概念,將函數定義爲純虛函數(方法:virtual ReturnType Function()= 0;),則編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱爲抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。
3、類似概念
一、多態性
指相同對象收到不一樣消息或不一樣對象收到相同消息時產生不一樣的實現動做。C++支持兩種多態性:編譯時多態性,運行時多態性。
a、編譯時多態性:經過重載函數實現
b、運行時多態性:經過虛函數實現。
二、虛函數
虛函數是在基類中被聲明爲virtual,並在派生類中從新定義的成員函數,可實現成員函數的動態覆蓋(Override)
三、抽象類
包含純虛函數的類稱爲抽象類。因爲抽象類包含了沒有定義的純虛函數,因此不能定義抽象類的對象。