1. 程式人生 > >【c++】多型&多型物件模型

【c++】多型&多型物件模型

1.多型:

在C ++程式設計中,多型性是指具有不同功能的函式可以用同一個函式名,這樣就可以用一個函式名呼叫不同內容的函式。在面向物件方法中一般是這樣表述多型性的:向不同的物件傳送同一個訊息, 不同的物件在接收時會產生不同的行為(即方法)。也就是說,每個物件可以用自己的方式去響應共同的訊息。所謂訊息,就是呼叫函式,不同的行為就是指不同的實現,即執行不同的函式。從系統實現的角度看,多型性分為兩類:靜態多型性和動態多型性。以前學過的函式過載和運算子過載實現的多型性屬於靜態多型性,在程式編譯時系統就能決定呼叫的是哪個函式,因此靜態多型性又稱編譯時的多型性。靜態多型性是通過函式的過載實現的(運算子過載實質上也是函式過載)。動態多型性是在程式執行過程中才動態地確定操作所針對的物件。它又稱執行時的多型性。動態多型性是通過虛擬函式(Virtual fiinction)實現的。

當使用基類的指標或引用呼叫重寫的虛擬函式時,當指向父類呼叫的就是父類的虛擬函式,當指向子類呼叫的就是子類的虛擬函式。

例:

class Person  //父類
{
public:
 virtual void BuyTickets()
 {
  cout << "買票" << endl;
 }
protected:
 string _name;
};
class Student :public Person //子類
{
public:
 virtual void BuyTickets()
 {
  cout << "買票-半價" << endl;
 }
protected:
 int _num; //學號
};
void fun(Person&p)
{
 p.BuyTickets();
}
void test()
{
 Person p;
 Student s;
 fun(p);
 fun(s);
}

虛擬函式:類成員函式前加virtual

虛擬函式重寫:當在子類定義了一個與父類完全相同的虛擬函式,則稱子類的這個函式重寫了父類這個函式。

2.多型的物件模型(單繼承&多繼承)

1.單繼承

class Base
{
public:
 virtual void func1()
 {
  cout << "Base::func1" << endl;
 }
 virtual void func2()
 {
  cout << "Base::func2" << endl;
 }
private:
 int a;
};
class Derive :public Base
{
public:
 virtual void func1()
 {
  cout << "Derive::func1" << endl;
 }
 virtual void func3()
 {
  cout << "Derive::func3" << endl;
 }
 virtual void func4()
 {
  cout << "Derive::func4" << endl;
 }
private:
 int b;
};
typedef void(*FUNC)();
void PrintVTable(int *VTable)
{
 cout << "虛表地址:" << VTable << endl;
 for (int i = 0; VTable[i] != 0; ++i)
 {
  printf("第%d個虛擬函式地址:0X%x,->", i, VTable[i]);
  FUNC f = (FUNC)VTable[i];
  f();
 }
 cout << endl;
}
void Test1()
{
 Base b1;
 Derive d1;
 int*VTable1 = (int*)(*(int*)&b1);
 int*VTable2 = (int*)(*(int*)&d1);
 PrintVTable(VTable1);
 PrintVTable(VTable2);

}

可以看出派生類Derive::fun1重寫基類Base::fun1,覆蓋了相應虛表位置上的函式。

2.多繼承

class Base1
{
public:
 virtual void func1()
 {
  cout << "Base::func1" << endl;
 }
 virtual void func2()
 {
  cout << "Base::func2" << endl;
 }
private:
 int b1;
};
class Base2
{
public:
 virtual void func1()
 {
  cout << "Base::func1" << endl;
 }
 virtual void func2()
 {
  cout << "Base::func2" << endl;
 }
private:
 int b2;
};
class Derive :public Base1, public Base2
{
public:
 virtual void func1()
 {
  cout << "Derive::func1" << endl;
 }
 virtual void func3()
 {
  cout << "Derive::func3" << endl;
 }
private:
 int d1;
};
typedef void(*FUNC)();
void PrintVTable(int *VTable)
{
 cout << "虛表地址:" << VTable << endl;
 for (int i = 0; VTable[i] != 0; ++i)
 {
  printf("第%d個虛擬函式地址:0X%x,->", i, VTable[i]);
  FUNC f = (FUNC)VTable[i];
  f();
 }
 cout << endl;
}
void Test1()
{
 Derive d1;
 int*VTable = (int*)(*(int*)&d1);
 PrintVTable(VTable);
 //Base2虛擬函式表在物件Base1後面
 VTable = (int*)(*((int*)&d1 + sizeof(Base1) / 4));
 PrintVTable(VTable);
}

記憶體佈局:

3.菱形繼承

什麼是菱形繼承?通過這個圖大家就可以很直觀的理解啦

如上圖,菱形繼承即多個類繼承了同一個公共基類,而這些派生類又同時被一個類繼承。這麼做會引發什麼問題呢?

通過該程式碼,我們可以看出當我們想要呼叫我們從Base裡繼承的fun時就會出現呼叫不明確問題,並且會造成資料冗餘的問題。

我們可以使用域限定我們所要訪問的函式,c++也給我們了另一個解決方案--虛繼承。