c++三大特性:封裝、繼承、多態。封裝使代碼模塊化,繼承擴展已存在的代碼,多態的目的是為了接口重用
虛函數實現:虛函數表;指針放到虛函數表
多態:同名函數對應到不同的實現
構造父類指針指向子類的對象
father *p = new son();
多態性是允許你將父對象設置成為和一個或更多的他的子對象相等的技術,賦值之后,父對象就可以根據當前賦值給它的子對象的特性以不同的方式運作。簡單的說:允許將子類類型的指針賦值給父類類型的指針(一個接口,多種方法)
虛函數的目的就是通知系統在函數調用時能夠自動識別對應的類對象類型,從而能夠根據指針所指類型調用對應的類對象,實現函數調用時的多態性。
C++ 支持兩種多態性:編譯時多態性,運行時多態性
a、編譯時多態性(靜態多態):通過重載函數實現
b、運行時多態性(動態多態):通過虛函數實現
多態的作用:
那么多態的作用是什么呢,封裝可以使得代碼模塊化,繼承可以擴展已存在的代碼,他們的目的都是為了代碼重用。而多態的目的則是為了接口重用。也就是說,不論傳遞過來的究竟是那個類的對象,函數都能夠通過同一個接口調用到適應各自對象的實現方法
c++中共有三種實現多態的方式:第一種是函數重載;第二種是模板函數;第三種是虛函數
1.虛函數:
有virtual才可能發生動態多態現象,無virtual調用就按原類型調用
虛函數: 就是允許被其子類重新定義的成員函數,子類重新定義父類虛函數的做法,可實現成員函數的動態覆蓋(Override)。
純虛函數: 是在基類中聲明的虛函數,它在基類中沒有定義,但要求任何派生類都要定義自己的實現方法。在基類中實現純虛函數的方法是在函數原型后加“=0”
virtual void funtion()=0
抽象類: 包含純虛函數的類稱為抽象類。由於抽象類包含了沒有定義的純虛函數,所以不能進行實例化。
純虛函數的作用:
a.為了方便使用多態特性,我們常常需要在基類中定義虛擬函數。
b.在很多情況下,基類本身生成對象是不合情理的。例如,動物作為一個基類可以派生出老虎、孔雀等子類,但動物本身生成對象明顯不合常理。為了解決上述問題,引入了純虛函數的概念,將函數定義為純虛函數(方法:virtual ReturnType Function()= 0;
),則編譯器要求在派生類中必須予以重寫以實現多態性。同時含有純虛擬函數的類稱為抽象類,它不能生成對象。這樣就很好地解決了上述兩個問題。
析構函數能否為虛?
能。
為什么基類的析構函數必須為虛?
如果不為虛函數,只釋放基類,不釋放子類,造成內存泄漏
基類指針指向子類對象創建實例,而將這個對象釋放的時候,調用的是基類析構函數;如果基類析構函數不是虛函數,那么只會釋放基類的部分,而子類部分不會被釋放。基類析構函數是虛函數時,則調用子類析構函數,基類子類都釋放,這樣釋放才完整。
2.函數重載:
函數重載是這樣一種機制:允許有不同參數的函數有相同的名字
具體一點講就是:假如有如下三個函數:
void test(int arg){} //函數1 void test(char arg){} //函數2 void test(int arg1,int arg2){} //函數3
如果在C中編譯,將會得到一個名字沖突的錯誤而不能編譯通過。在C++中這樣做是合法的。可是當我們調用test的時候到底是會調用上面三個函數中的哪一個呢?這要依據你在調用時給的出的參數來決定。如下:
test(5); //調用函數1 test('c');//調用函數2 test(4,5); //調用函數3
C++是如何做到這一點的呢?原來聰明的C++編譯器在編譯的時候悄悄的在我們的函數名上根據函數的參數的不同做了一些不同的記號。具體說如下:
void test(int arg) //被標記為 ‘test有一個int型參數’ void test(char arg) //被標記為 ‘test有一個char型的參數’ void test(int arg1,int arg2) //被標記為 ‘test第一個參數是int型,第二個參數為int型’
這樣一來當我們進行對test的調用時,C++就可以根據調用時的參數來確定到底該用哪一個test函數了。噢,聰明的C++編譯器。其實C++做標記做的比我上面所做的更聰明。我上面哪樣的標記太長了。C++編譯器用的標記要比我的短小的多。看看這個真正的C++的對這三個函數的標記:
test@@YAXD@Z
test@@YAXH@Z
test@@YAXHH@Z
是不是短多了。但卻不好看明白了。好在這是給計算機看的,人看不大明白是可以理解的。 還記得cout吧。我們用<<可以讓它把任意類型的數據輸出。比如可以象下面那樣:
cout << 1; //輸出int型 cout << 8.9; //輸出double型 cout << 'a'; //輸出char型 cout << "abc";//輸出char數組型 cout << endl; //輸出一個函數
cout之所以能夠用一個函數名<<(<<是一個函數名)就能做到這些全是函數重載的功能。要是沒有函數重載,我們也許會這樣使用cout,如下:
cout int<< 1; //輸出int型 cout double<< 8.9; //輸出double型 cout char<< 'a'; //輸出char型 cout charArray<< "abc"; //輸出char數組型 cout function(…)<< endl; //輸出函數
為每一種要輸出的類型起一個函數名,這豈不是很麻煩呀。 不過函數重載有一個美中不足之處就是不能為返回值不同的函數進行重載。那是因為人們常常不為函數調用指出返回值。並不是技術上不能通過返回值來進行重載。
3.模版函數:
概念:函數的內容有了,但函數的參數類型卻是待定的(注意:參數個數不是待定的)
比如說一個(准確的說是一類或一群)函數帶有兩個參數,它的功能是返回其中的大值。這樣的函數用模板函數來實現是適合不過的了。如下:
template < typename T> T getMax(T arg1, T arg2) { return arg1 > arg2 ? arg1:arg2; //代碼段1 }
這就是基於模板的多態嗎?不是。因為現在我們不論是調用getMax(1, 2)還是調用getMax(3.0, 5.0)都是走的上面的函數定義。它沒有根據調用時的上下文不同而執行不同的實現。所以這充其量也就是用了一個模板函數,和多態不沾邊。怎樣才能和多態沾上邊呢?用模板特化呀!象這樣:
template<> char* getMax(char* arg1, char* arg2) { return (strcmp(arg1, arg2) > 0)?arg1:arg2;//代碼段2 }
這樣一來當我們調用getMax(“abc”, “efg”)的時候,就會執行代碼段2,而不是代碼段1。這樣就是多態了。 更有意思的是如果我們再寫這樣一個函數:
char getMax(char arg1, char arg2) { return arg1>arg2?arg1:arg2; //代碼段3 }
當我們調用getMax(‘a’, ‘b’)的時候,執行的會是代碼段3,而不是代碼段1或代碼段2。C++允許對模板函數進行函數重載,就象這個模板函數是一個普通的函數一樣。於是我們馬上能想到寫下面這樣一個函數來做三個數中取大值的處理:
int getMax( int arg1, int arg2, int arg3) { return getMax(arg1, max(arg2, arg3) ); //代碼段4 }
同樣我們還可以這樣寫:
template <typename T> T getMax(T arg1, T arg2, T arg3) { return getMax(arg1, getMax(arg2, arg3) ); //代碼段5 }
現在看到結合了模板的多態的威力了吧。比只用函數重載厲害多了
模版函數就是函數模版生成的具體的函數,比如 getmax(int arg1,int arg2,int arg3)
http://huqunxing.site/2016/09/08/C++%20%E4%B8%89%E5%A4%A7%E7%89%B9%E6%80%A7%E4%B9%8B%E5%A4%9A%E6%80%81/
https://my.oschina.net/zhjunjun/blog/1505594