目錄
- 1.菱形繼承
- 1.1.菱形繼承的問題
- 1.2.解決辦法
- 2.虛擬函式與多型
- 2.1.普通函式不能實現多型
- 2.2.虛擬函式(子類重寫)+ 父類指向子類——實現多型
- 2.3.多型原理
- 3.c++記憶體模型
- 4.參考
1.菱形繼承
先看下面的例子,SheepTuo
同時繼承了Sheep
和Tuo
,而他們同時繼承Animal
類
#include <iostream>
using namespace std;
class Animal
{
int mAge;
};
class Sheep : public Animal {};
class Tuo : public Animal {};
class SheepTuo : public Sheep, public Tuo {};
int main()
{
SheepTuo st;
////// 1.報錯,"SheepTuo::mAge" is ambiguous,mAge成員在兩個子類都存在,二義性
// st.mAge = 18;
////// 2.可以宣告作用域,避免成員的二義性
st.Sheep::mAge = 18;
st.Tuo::mAge = 100;
////// 3.但是在SheepTuo類中mAge成員會複製兩份,造成記憶體浪費
return 0;
}
1.1.菱形繼承的問題
- 共享成員二義性——增加作用域可以解決
- 記憶體複製兩份-浪費——虛繼承解決
1.2.解決辦法
#include <iostream>
using namespace std;
// 虛基類
class Animal
{
public:
int mAge;
};
// virtual --> 虛繼承
class Sheep : virtual public Animal {};
class Tuo : virtual public Animal {};
class SheepTuo : public Sheep, public Tuo {};
int main()
{
SheepTuo st;
////// 1.不會報錯了
// st.mAge = 18;
////// 2.這樣寫,下面的mAge都會是100,因此虛繼承後,成員不會被複制,只在基類中有一份,子類中維護vbptr(管理不同的偏移量)指向它
st.Sheep::mAge = 18;
st.Tuo::mAge = 100;
return 0;
}
SheepTuo
繼承了Sheep
和Tuo
的虛基類指標vbptr
,這倆的指標會指向他們的虛基類表vbtable
,虛基類表中存著其vbptr的偏移量,透過偏移量可以幫助子類正確找到從虛基類繼承來的資料
2.虛擬函式與多型
2.1.普通函式不能實現多型
#include <iostream>
using namespace std;
class Animal
{
public:
void Speak()
{
cout << "動物 在說話" << endl;
}
};
class Cat : public Animal
{
void Speak()
{
cout << "小貓 在說話" << endl;
}
};
class Dog : public Animal
{
void Speak()
{
cout << "小狗 在說話" << endl;
}
};
// 常物件只能呼叫常函式
// void DoSpeak(const Animal &animal)
void DoSpeak(Animal &animal)
{
animal.Speak();
}
int main()
{
Cat cat;
DoSpeak(cat);
return 0;
}
輸出:動物 在說話
上面的Animal
中Speak
是一個普通成員函式,雖然有繼承的條件,但是編譯器在編譯階段,會DoSpeak
函式中把animal.Speak();
的animal
繫結為Animal
的地址,無法實現多型
2.2.虛擬函式(子類重寫)+ 父類指向子類——實現多型
#include <iostream>
using namespace std;
class Animal
{
public:
virtual void Speak()
{
cout << "動物 在說話" << endl;
}
};
class Cat : public Animal
{
void Speak()
{
cout << "小貓 在說話" << endl;
}
};
class Dog : public Animal
{
void Speak()
{
cout << "小狗 在說話" << endl;
}
};
void DoSpeak(Animal &animal)
{
animal.Speak();
}
int main()
{
Cat cat;
DoSpeak(cat);
return 0;
}
輸出:小貓 在說話
和2.1.節相比,只增加了virtual
關鍵字,使得父類普通函式變為虛擬函式,使得DoSpeak
函式中animal.Speak()
的animal在執行階段才會指向真正呼叫的物件,實現了多型
其執行流程先說明:物件指標->取其虛表指標->取其虛表中函式->call呼叫
2.3.多型原理
- case1.普通函式的基類所佔記憶體大小, sizeof(Animal) = 1
class Animal
{
public:
void Speak()
{
cout << "動物 在說話" << endl;
}
};
- case2.虛擬函式的基類所佔記憶體大小, sizeof(Animal) = 4
class Animal
{
public:
virtual void Speak()
{
cout << "動物 在說話" << endl;
}
};
case1和case2說明了增加virtual
會在類中多用記憶體,增加的是虛擬函式指標和虛擬函式表
- case3.子類繼承虛基類,並且子類重寫虛擬函式
class Animal
{
public:
virtual void Speak()
{
cout << "動物 在說話" << endl;
}
};
class Cat : public Animal
{
void Speak()
{
cout << "小貓 在說話" << endl;
}
};
Cat
中的虛擬函式表發生覆蓋
- 總體來看看
3.c++記憶體模型
C++類中記憶體儲存情況比較複雜,涉及成員資料、函式、靜態成員、虛擬函式等情況
記錄結論,詳情參考C++類的記憶體佈局
4.參考
C++類的記憶體佈局
C++多型剖析