【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++也給我們了另一個解決方案--虛繼承。