多型性是將介面與實現進行分離;用形象的語言來解釋就是實現以共同的方法,但因個體差異,而採用不同的策略。
1、什麼是多型
多型(polymorphism)按字面的意思就是「多種狀態」。在物件導向語言中,介面的多種不同的實現方式即為多型。它是物件導向程式設計(oop)的乙個重要特徵。如果乙個語言只支援類而不支援多型,只能說明它是基於物件的,而不是物件導向的。c++中的多型性具體體現在執行和編譯兩個方面。它可以簡單地概括為「乙個介面,多種方法」。
那麼多型的作用是什麼呢,封裝可以使得**模組化,繼承可以擴充套件已存在的**,他們的目的都是為了**重用。而多型的目的則是為了介面重用。也就是說,不論傳遞過來的究竟是那個類的物件,函式都能夠通過同乙個介面呼叫到適應各自物件的實現方法。
#define _crt_secure_no_warnings 1
#include
using namespace std;
class animal
public:
char ***[5]; int age;
};class person: public animal
public:
char notionality[20]; //國籍
char name[10];
};class horse: public animal
public:
char rase[20]; //種族
};int main()
輸出:
可以看到,雖然pa1、pa1都是基類的指標,但是呼叫時卻呼叫的是person、horse兩個派生類的方法。這是因為pa1和pa2分別指向了person、horse兩個派生類的物件。原來下來慢慢剖析。
2、多型的分類
①編譯時多型:也叫靜態多型,在編譯時就可以確定物件使用的形式。 實現機制使通過函式過載(運算子過載實質也屬於函式過載)。
②執行時多型:也叫動態多型,其具體引用的物件在執行時才能確定。 實現機制是通過虛函式。
靜態多型性與動態多型性的實質區別就是函式位址是早繫結還是晚繫結。如果函式的呼叫,在編譯器編譯期間就可以確定函式的呼叫位址,並生產**,是靜態的,就是說位址是早繫結的。而如果函式呼叫的位址不能在編譯器期間確定,需要在執行時才確定,這就屬於晚繫結。
3、動態多型性存在的三個必要條件
①要有類的繼承關係;
②要有虛函式(只有重寫了虛函式的才能算作是體現了c++多型性);
注意函式重寫、過載、重定義的區別,可以參考:
③要有父類指標/引用指向子類物件(用於訪問派生類中同名覆蓋成員函式)。
4、多型的實現
c++多型性是通過虛函式來實現的,通過指向派生類的基類指標或引用,訪問派生類中同名覆蓋成員函式。虛函式允許子類重新定義成員函式,而子類重新定義父類的做法稱為覆蓋(override),或者稱為重寫(重寫的話可以有兩種,直接重寫成員函式和重寫虛函式,只有重寫了虛函式的才能算作是體現了c++多型性)。
虛函式:在某基類中宣告為 virtual 並在乙個或多個派生類中被重新定 義的成員函式,用法格式為:
virtual 函式返回型別 函式名(參數列)
關於虛函式這裡不做過剖析,可以參考:
前面說了,多型的動態性實現是靠虛函式,它的實質是在基類定義乙個虛函式,在派生類中對其進行重寫,然後定義基類的指標指向派生類物件,然後用該指標呼叫虛函式,此時呼叫的就是指標指向的物件對應的類裡面的同名函式。那麼這種機制在底層是怎樣實現的呢?
想要弄清楚這個就必須要清楚乙個東西–>虛表,全稱為虛函式表。什麼是虛表,虛表就是一張表,它裡面存放虛函式的入口位址,在c++語言中,每個有虛函式的類或者基類有虛函式的派生類,編譯器都會為它生成乙個虛函式表(注意:虛表是從屬於類的)。此外,編譯器會為包含虛函式的類加上乙個成員變數,是乙個指向該虛函式表的指標(常被稱為vfptr,注意:虛表指標是從屬於物件的)。也就是說,如果乙個類含有虛表,則該類的所有物件都會含有乙個虛表指標,並且該虛表指標指向同乙個虛表。
虛表的內容是依據類中的虛函式宣告次序–填入函式指標。派生類會繼承基礎類別的虛表(以及所有其他可以繼承的成員),再在此虛表上新增上自己的虛函式指標,當我們在派生類中改寫虛函式時,虛表就受了影響,改寫後的的虛函式的位址會替換原虛函式的位址存放於虛表中。
①no_virtual類沒有任何資料成員,也沒有虛函式,按說它的大小應該是0,但這兒它的大小是1。我理解的原因有以下兩點:
這個是因為no_virtual既然是乙個型別,那麼就能用來定義變數,而定義變數肯定要分配空間,分配多大合適呢?0個?顯然不可能,2個、4個?太浪費了,它什麼都不用儲存,分配那麼大幹什麼,所以編譯器就為我們選了個折中的辦法,分配乙個,既不會浪費空間,也能用來定義變數。
既然no_virtual是型別,用來定義變數的,那麼要是它乙個空間都不佔的話,用它定義出來的變數就都從同一位置開始儲存了,那我們訪問那塊空間時,怎麼知道到底是訪問哪乙個變數呢?
②virtual類沒有任何資料成員,但是有虛函式,按我們上面的說法,它的大小是4,結果很給面子的正確了。
定義乙個virtual類的變數,看看是不是像上面說的那樣,有乙個從屬於它的虛表指標,從監視視窗看,確實是的。
void fun2() //注意這兒加了關鍵字virtual
};class derived: public base
virtual
void fun4() //注意這兒加了關鍵字virtual
};void printvirtable()
cout
<< "derived:"
<< endl;
vfptable = (int *)(*(int*)&d);
while (*vfptable != null)
}int main()
因為vs的監視視窗好像有bug:
像這兒,派生類的物件就只能看到繼承與基類的虛表,而不能看到派生類自己的,所以上面我乙個函式分別列印了基類與派生類的虛表,可以很直觀的看到,首先,派生類的虛表是繼承於基類的,然後在加上自己的虛函式的位址到虛表中去,就構成了自己的虛表。
現在來剖析一下列印虛表的函式是怎麼實現的:
首先得清楚帶有虛函式的類建立的物件,前四個位元組裡面儲存的就是虛表指標的位址,如下圖:
再回過頭來看看虛表的列印函式:
void printvirtable()
cout << "derived:"
<< endl;
vfptable = (int
*)(*(
int*)&d);
while (*vfptable != null)
}
它的原理其實是這樣的:
再來看乙個例子:
#define _crt_secure_no_warnings 1
#include
using
namespace
std;
class base
virtual
void fun2() //注意這兒加了關鍵字virtual
};class derived: public base
virtual
void fun4() //注意這兒加了關鍵字virtual
public:
int data;
};void printvirtable()
cout
<< "d2::virtable"
<< endl;
vfptable = (int *)(*(int*)&d2);
while (*vfptable != null)
}int main()
這兒就證明了如果乙個類有虛函式,那麼該不同物件公用同一張虛表。因為函式的位址是在編譯期間的確定的,乙個函式的位址只會被儲存一次,雖然累的物件不同,但它們呼叫該類的同名函式其實是呼叫的同一函式,因此它們的虛表會相同。 C 學習之多型
多型性是物件導向程式設計中的乙個重要特徵,利用多型性可以設計和實現乙個易於拓展的系統。在c 語言中,多型性是指具有不同功能的函式可以用同乙個函式名,這樣就可以用乙個函式名呼叫不同內容的函式,發出同樣的訊息被不同型別的物件接收時,導致完全不同的行為。多型性通過聯編實現。聯編是指乙個電腦程式自身彼此關聯...
C 特性之多型
今天學習了一下多型的有關知識。在 c primer 書中,多型特性似乎只在書的後半部分 oo 裡面提到。雖然是物件導向中很重要的乙個概念,但是並未單獨開出乙個章節來介紹。網上找了一些多型的介紹和程式,總結如下 其底層含義是大記憶體 父類 中套著許多小記憶體 子類 父類 class animal 子類...
C 基礎之多型
多型分為靜態多型和動態多型。靜態多型分為函式過載和泛型程式設計。動態多型是通過虛函式來實現的。靜態多型 叫靜態繫結或早繫結 編譯器在編譯期間完成的,編譯器可以根據函式實參的型別 可能會進行隱式的型別轉換 注意 巨集不是靜態多型,巨集是在預處理階段完成的動態多型 又叫動態繫結或者晚繫結 在程式執行期間...