1. 程式人生 > >面嚮物件語言和多型

面嚮物件語言和多型

什麼是多型呢?
字面意思就是同一事物有多種形態。
在面向物件程式設計中,多型指的是介面的多種不同的實現方式。程式設計其實就是一個將具體世界進行抽象化的過程,多型就是抽象化的一種體現,把一系列具體事物的共同點抽象出來, 再通過這個抽象的事物, 與不同的具體事物進行對話。
對不同類的物件發出相同的訊息將會有不同的行為。
那麼多型又分為靜態多型和動態多型。
靜態多型:靜態型別,物件宣告時的型別,在編譯期間就確定下來了,實現方式有:函式過載,泛型程式設計。
動態多型:動態型別,目前所指物件型別,在程式執行期間所呼叫的型別。實現方式有:虛擬函式。
動態多型產生的條件:
1:在繼承體系下,基類必須要有虛擬函式,派生類中必須要對基類的虛擬函式進行重寫(函式原型必須一模一樣)。
2:通過基類的指標或者引用來呼叫。
協變:重寫基類虛擬函式時返回值為基類/派生類的指標或者引用
多型的作用:
1. 應用程式不必為每一個派生類編寫功能呼叫,只需要對抽象基類進行處理即可。大大提高程式的可複用性。//繼承
2. 派生類的功能可以被基類的方法或引用變數所呼叫,這叫向後相容,可以提高可擴充性和可維護性。 //多型的真正作用,以前需要用switch實現
多型的使用:
多型允許將子類的物件當作父類的物件使用,某父型別的引用指向其子型別的物件,呼叫的方法是該子型別的方法。這裡引用和呼叫方法的程式碼編譯前就已經決定了,而引用所指向的物件可以在執行期間動態繫結。
通過例子看多型的實現:

#include <iostream>
using namespace std;
//靜態多型
int Add(int left, int right)
{
    return left + right;
}
float Add(float left, float right)
{
    return left + right;
}
int main()
{
    cout<< Add(10, 20) << endl;
    cout << Add(12.34f, 34.12f) << endl;
    system("pause"
); return 0; } //靜態多型:編譯器在編譯期間完成的,編譯器根據函式實參的型別(可能會進行隱式型別轉換),可推斷出要呼叫那個函式,如果有對應的函式就呼叫該函式,否則出現編譯錯誤。

【動態多型】
動態繫結:在程式執行期間(非編譯期)判斷所引用物件的實際型別,根據其實際型別呼叫相應的方法。
使用virtual關鍵字修飾類的成員函式時,指明該函式為虛擬函式,派生類需要重新實現,編譯器將實現動態繫結。

class A
{
public:

    virtual void  Funtest()
    {
        cout << "A::Funtest"
<< endl; } public: char* _a; }; class B:public A { virtual void Funtest() { cout << "B::Funtest" << endl; } public: char* _b; }; int main() { B b; A& a1 = b;//發生動態繫結 a1.Funtest(); system("pause"); return 0; }

這裡寫圖片描述
【動態繫結條件】
1、必須是虛擬函式
2、通過基類型別的引用或者指標呼叫虛擬函式
這裡寫圖片描述
【純虛擬函式】
在成員函式的形參列表後面寫上=0,則成員函式為純虛擬函式。包含純虛擬函式的類叫做抽象類(也叫介面類),抽象類不能例項化出物件。純虛擬函式在派生類中重新定義以後,派生類才能例項化出物件。

class Person
{
virtual void Display () = 0; // 純虛擬函式
protected :
string _name ; // 姓名
};
class Student : public Person
{};

總結:
1、派生類重寫基類的虛擬函式實現多型,要求函式名、引數列表、返回值完全相同。(協變除外)
2、基類中定義了虛擬函式,在派生類中該函式始終保持虛擬函式的特性。
3、只有類的非靜態成員函式才能定義為虛擬函式,靜態成員函式不能定義為虛擬函式。
4、如果在類外定義虛擬函式,只能在宣告函式時加virtual關鍵字,定義時不用加。
5、建構函式不能定義為虛擬函式,雖然可以將operator=定義為虛擬函式,但最好不要這麼做,使用時容易混淆
6、不要在建構函式和解構函式中呼叫虛擬函式,在建構函式和解構函式中,物件是不完整的,可能會出現未定義的行為。
7、最好將基類的解構函式宣告為虛擬函式。(解構函式比較特殊,因為派生類的解構函式跟基類的解構函式名稱不一樣,但是構成覆蓋,這裡編譯器做了特殊理)
8、虛表是所有類物件例項共用的
【虛表剖析】
什麼是虛表呢?
類的物件裡面有虛擬函式那麼在類物件模型中前四個位元組存放的就是虛表指標!

//class CTest
//{
//public:
//  CTest()
//  {
//      iTest = 10;
//  }
//private:
//  int iTest;
//};
//int main()
//{
//  cout << sizeof(CTest) << endl;//結果為4
//  system("pause");
//  return 0;
//}
class CTest
{
public:
    CTest()
    {
        iTest = 10;
        cout << "this = " << this << endl;
    }
    virtual ~CTest() {};
private:
    int iTest;
};
int main()
{
    CTest test;
    cout << sizeof(test) << endl;//大小為8
    system("pause");
}

【沒有覆蓋的情況下】
1:虛擬函式按照其宣告的順序存在於虛表中
2:在派生類中,前面是基類的虛擬函式,後面是派生類中虛擬函式
【有覆蓋的情況下】
通過基類的引用或者指標呼叫虛擬函式,呼叫基類還是派生類的虛擬函式,要根據執行時根據引用(指標)實際引用(指向)的型別確定呼叫非虛擬函式,則無論基類指向的是何種型別,都呼叫的是基類的函式。
派生類虛擬函式表的生成:
1:先拷貝基類的虛擬函式表
2:如果派生類重寫了基類的某個虛擬函式,就覆蓋同位置的基類虛擬函式
3:跟上派生類自己新定義的虛擬函式