標籤:type 存在 mes false names 固定 常見 原理 衍生類別
1.1 多態性的概念
在物件導向語言中,介面的多種不同實現方式即為多態,可以簡單地概括為“一個介面,多種方法”。
多態性(polymorphism)是物件導向程式設計的一個重要特徵。如果一種語言只支援類而不支援多態,是不能被稱為物件導向語言的,只能說是基於對象的。
引用Charlie Calverts對多態的描述——多態性是允許你將父物件設定成為一個或更多的他的子物件相等的技術,賦值之後,父物件就可以根據當前賦值給它的子物件的特性以不同的方式運作(摘自“Delphi4 編程技術內幕”)。簡單的說,就是一句話:允許將子類類型的指標賦值給父類類型的指標。多態性在Object Pascal和C++中都是通過虛函數實現的。
1.2 形式
多態:同一操作作用於不同的對象,可以有不同的解釋,產生不同的執行結果。在運行時,可以通過指向基類的指標,來調用實現衍生類別中的方法。多態就是允許方法重名,參數或傳回值可以是父類型傳入或返回。
C++中的多態性具體體現在運行和編譯兩個方面。運行時多態是動態多態,其具體引用的對象在運行時才能確定。編譯時間多態是靜態多態,在編譯時間就可以確定對象使用的形式。多態與非多態的實質區別就是函數地址是早綁定還是晚綁定。如果函數的調用,在編譯器編譯期間就可以確定函數的調用地址,並生產代碼,是靜態,就是說地址是早綁定的。而如果函數調用的地址不能在編譯器期間確定,需要在運行時才確定,這就屬於晚綁定。
C++中,實現多態有以下方法:虛函數,抽象類別,覆蓋,模板(重載和多態無關)。 OC中的多態:不同對象對同一訊息的不同響應.子類可以重寫父類的方法 C++通過虛函數來實現多態,虛函數允許子類重新定義成員函數,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫。(重寫的話可以有兩種,直接重寫成員函數和重寫虛函數,只有重寫了虛函數的才能算作是體現了C++多態性)
而重載則是允許有多個同名的函數,而這些函數的參數列表不同,允許參數個數不同,參數類型不同,或者兩者都不同。編譯器會根據這些函數的不同列表,將同名的函數的名稱做修飾,從而產生一些不同名稱的預先處理函數,來實現同名函數調用時的重載問題。但這並沒有體現多態性。
1.3 作用 封裝可以使得代碼模組化,繼承可以擴充已存在的代碼,他們的目的都是為了代碼重用。而多態的目的則是為了介面重用。也就是說,不論傳遞過來的究竟是那個類的對象,函數都能夠通過同一個介面調用到適應各自對象的實現方法。
最常見的用法就是聲明基類的指標,利用該指標指向任意一個子類對象,調用相應的虛函數,可以根據指向的子類的不同而實現不同的方法。如果沒有使用虛函數的話,即沒有利用C++多態性,則利用基類指標調用相應的函數的時候,將總被限制在基類函數本身,而無法調用到子類中被重寫過的函數。因為沒有多態性,函數調用的地址將是一定的,而固定的地址將始終調用到同一個函數,這就無法實現一個介面,多種方法的目的了。
1.4 執行個體
#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()都很好理解,本身是基類指標,指向的又是基類對象,調用的都是基類本身的函數,因此輸出結果就是1、2。
第二個輸出結果就是1、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()函數的地址,因此調用了基類的函數。由此可見多態性的強大,可以適應各種變化,不論指標是基類的還是子類的,都能找到正確的實現方法。
//小結:1、有virtual才可能發生多態現象 // 2、不發生多態(無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++純虛函數
一、定義
純虛函數是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何衍生類別都要定義自己的實現方法。在基類中實現純虛函數的方法是在函數原型後加“=0”
virtual void funtion()=0
二、引入原因
1、為了方便使用多態特性,我們常常需要在基類中定義虛擬函數。
2、在很多情況下,基類本身產生對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身產生對象明顯不合常理。
為瞭解決上述問題,引入了純虛函數的概念,將函數定義為純虛函數(方法:virtual ReturnType Function()= 0;),則編譯器要求在衍生類別中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱為抽象類別,它不能產生對象。這樣就很好地解決了上述兩個問題。
三、相似概念
1、多態性
指相同對象收到不同訊息或不同對象收到相同訊息時產生不同的實現動作。C++支援兩種多態性:編譯時間多態性,運行時多態性。
a、編譯時間多態性:通過重載函數實現
b、運行時多態性:通過虛函數實現。
2、虛函數
虛函數是在基類中被聲明為virtual,並在衍生類別中重新定義的成員函數,可實現成員函數的動態覆蓋(Override)
3、抽象類別
包含純虛函數的類稱為抽象類別。由於抽象類別包含了沒有定義的純虛函數,所以不能定義抽象類別的對象。
C++多態性