天天看點

談談C++中的多态(一)

1.    什麼是多态 多态是C++中的一個重要的基礎,可以這樣說,不掌握多态就是C++的門外漢。然而長期以來,C++社群對于多态的内涵和外延一直争論不休。大 有隻見樹木不見森林之勢。多态到底是怎麼回事呢?說實在的,我覺的多态這個名字起的不怎麼好(或是譯的不怎麼好)。要是我給起名的話,我就給它定一個這樣 的名字--“調用’同名函數’卻會因上下文不同會有不同的實作的一種機制”。這個名字長是長了點兒,可是比“多态”清楚多了。看這個長的定義,我們可以從 中找出多态的三個重要的部分。一是“ 相同函數名”,二是“依據上下文 ”,三是“實作卻不同 ”。嘿,還是個順口溜呢。我們且把它們叫做多态三要素吧。 2.    多态帶來的好處 多态帶來兩個明顯的好處:一是不用記大量的函數名了,二是它會依據調用時的上下文來确定實作。确定實作的過程由C++本身完成另外還有一個不明顯但卻很重要的好處是:帶來了面向對象的程式設計。 3.    C++ 中實作多态的方式 C++中共有三種實作多态的方式。由“容易說明白”到“不容易說明白”排序分别為。第一種是函數重載;第二種是模闆函數;第三種是虛函數。 4.    細說用函數重載實作的多态 函數重載是這樣一種機制:允許有不同參數的函數有相同的名字。 具體一點講就是:假如有如下三個函數: 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++的對這三個函數的标記: [email protected]@[email protected] [email protected]@[email protected] [email protected]@[email protected] 是不是短多了。但卻不好看明白了。好在這是給計算機看的,人看不大明白是可以了解的。 還記得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;   // 輸出函數 為每一種要輸出的類型起一個函數名,這豈不是很麻煩呀。 不過函數重載有一個美中不足之處就是不能為傳回值不同的函數進行重載。那是因為人們常常不為函數調用指出傳回值。并不是技術上不能通過傳回值來進行重載。 5.    細說用模闆函數實作的多态 所謂模闆函數(也有人叫函數模闆)是這樣一個概念:函數的内容有了,但函數的參數類型卻是待定的(注意:參數個數不是待定的)。比如說一個(準确的說是一類或一群)函數帶有兩個參數,它的功能是傳回其中的大值。這樣的函數用模闆函數來實作是适合不過的了。如下。 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 } 現在看到結合了模闆的多态的威力了吧。比隻用函數重載厲害多了。 6.    小結 上面的兩種多态在C++中有一個總稱:靜态多态。之是以叫它們靜态多态是因為它們的多态是在編譯期間就确定了。也就是說前面所說的函數1,2, 3代碼段1,2,3,4,5這些,在編譯完成後,應該在什麼樣的上下文的調用中執行哪一些就确定了。比如:如果調用getMax(0.1, 0.2, 0.3)就會執行代碼段5。如果調用test(5)就執行函數1。這些是在編譯期間就能确定下來的。 靜态多态還有一個特點,就是:“總和參數較勁兒”。 下面所要講的一種多态就是必需是在程式的執行過程中才能确定要真正執行的函數。是以這種多态在C++中也被叫做動态多态。