首先,多態是什麼?
摘抄自 多態(維基百科):
—— 多態(英語:polymorphism),是指計算機程序運行時,相同的消息可能會送給多個不同的類別之對象,而系統可依據對象所屬類別,引發對應類別的方法,而有不同的行爲。簡單來說,所謂多態意指相同的消息給予不同的對象會引發不同的動作稱之。在cplusclpus中有這麼一句話:
One of the key features of class inheritance is that a pointer to a derived class is type-compatible with a pointer to its base class. Polymorphism is the art of taking advantage of this simple but powerful and versatile feature.
即,在C++中指向子類的指針和指向基類的指針是兼容的。
所以,在C++中,我們可以通過指針指向不同的對象來實現多態(引用和指針類似,雖然有些差別,但是這裏不展開討論)
虛函數機制
參考《Effective C++》條款07中對虛表指針以及虛函數表的講解,這裏闡述下虛函數機制的原理:
—- 在C++中,若在類中定義了虛函數,則編譯器會爲改類生成一個虛函數表,這個表中保存了該類以及父類所有的虛函數,並在實例化該類時,會在類對象中添加一個虛表指針,這個指針就只向該虛表指針,當使用指針或引用調用函數時,C++會根據對象中保存的虛表指針查找需要調用的函數,具體的細節下面慢慢分析。
class Fa
{
int Fa_a;
public:
void foo()
{
cout << "父類的foo函數 \n";
}
};
class childA:public Fa
{
int CA_a;
public:
void foo()
{
cout << "子類A的foo函數 \n";
}
};
- 在沒有定義虛函數時,內存佈局中只存在父類和子類的數據成員對象
class Fa
{
int Fa_a;
public:
virtual void foo()
{
cout << "父類的foo函數 \n";
}
};
class childA:public Fa
{
int CA_a;
public:
void foo()
{
cout << "子類A的foo函數 \n";
}
};
- 在添加了虛函數後,內存佈局中增加了一個vfptr,並且生成了一個vftable,分別是虛表指針和虛函數表。
class Fa
{
int Fa_a;
public:
virtual void foo()
{
cout << "父類的foo函數 \n";
}
};
class childA:public Fa
{
int CA_a;
};
class Fa
{
int Fa_a;
};
class childA:public Fa
{
int CA_a;
public:
virtual void foo()
{
cout << "子類A的foo函數 \n";
}
};
在這兩個例子中需要注意兩點:
- 當在父類中先定義虛函數時,vfptr出現在父類下面,當子類先定義虛函數時,vfptr出現在子類下面
- 當在子類中定義了和父類中虛函數相同的函數時,虛函數表中保存的是子類的虛函數,當子類中沒有定義與父類虛函數相同的函數時,虛函數表中保存的是父類的虛函數。
—- 這裏不難得出一下兩點結論:
- 子類會繼承父類的虛函數表,並且當子類定義了與父類相同的函數(返回值,函數簽名都相同)時,子類的虛函數會覆蓋父類的虛函數保存到虛函數表中。
- 當子類出現父類中沒有的虛函數時,子類的虛函數會被添加到虛函數表中,並且偏移量大於任意父類的虛函數。
— 上面的例子還不是很清晰的說明了第二點,下面在放一個清晰一點的例子:
class Fa
{
int Fa_a;
public:
virtual void foo()
{
cout << "父類的foo函數 \n";
}
virtual void FFF()
{
cout << "";
}
};
class childA:public Fa
{
int CA_a;
public:
virtual void foo()
{
cout << "子類A的foo函數 \n";
}
virtual void funcA()
{
cout << "子類A的func函數\n";
}
};
這個例子可以讓我們清楚的理解上面兩點結論,父類中先定義了虛函數,於是vfptr在Fa下面,子類定義了與父類相同的函數,於是子類的虛函數覆蓋了父類的虛函數,子類新增了虛函數,於是虛函數表中添加了該虛函數,並且在父類虛函數的後面。
理解了原理後我們就可以使用虛函數來實現多態了:
#include<iostream>
using namespace std;
class Fa
{
int Fa_a;
public:
virtual void foo()
{
cout << "父類的foo函數 \n";
}
};
class childA:public Fa
{
int CA_a;
public:
virtual void foo()
{
cout << "子類A的foo函數 \n";
}
};
class childB :public Fa
{
int CB_a;
public:
virtual void foo()
{
cout << "子類B的foo函數\n";
}
};
void func()
{
Fa * ff = new Fa();
ff->foo();
ff =new childA();
ff->foo();
ff = new childB();
ff->foo();
}
int main()
{
func();
system("pause");
return 0;
}
- 可以看到,我們使用了同一個ff指針,卻調用了三個不同的函數,這就是多態了。