1.什么是多态
C++面向对象三大特征:封装、继承、多态
多态分两类:
- 静态多态:包括 函数重载、运算符重载,复用函数名
- 动态多态:包括 派生类、虚函数实现运行时多态
静态多态与动态多态区别:
- 静态多态的函数地址早绑定——编译阶段确定函数地址
- 动态多态的函数地址晚绑定——运行阶段确定函数地址
动态多态满足条件:
- 有继承关系
- 子类重写父类的虚函数(重写:函数返回值类型、函数名、参数列表完全相同),重写时virtual关键字可写可不写
动态多态使用情景:父类指针或引用指向子类对象
多态案例:
class Shizuma
{
public:
virtual void eat() //virtual关键字
{
cout << "始祖马吃草"<<endl;
}
};
class Ma : public Shizuma
{
public:
void eat()//重写父类虚函数时virtual关键字可写可不写
{
cout << "马吃草" << endl;
}
};
class Lv : public Shizuma
{
public:
void eat()
{
cout << "驴吃草" << endl;
}
};
void myVirtualTest()//调用函数
{
Shizuma *sz;
Ma ma;
Lv lv;
sz=&lv;//父类指针指向子类对象
(*sz).eat();//父类成员函数不加virtual输出“始祖马吃草”,加virtual后输出“驴吃草”
sz=&ma;
(*lv).eat();
}
2.动态多态的原理
实验:
/*===========TEST-1==============*/
class Shizuma
{
public:
void eat() //不加virtual关键字
{
cout << "始祖马吃草"<<endl;
}
};
void szofClass()
{
cout<<sizeof(Shizuma);//输出:1,即此处的Shizuma类是个空类
}
/*===========TEST-2==============*/
class Shizuma
{
public:
virtual void eat() //加了virtual关键字
{
cout << "始祖马吃草"<<endl;
}
};
void szofClass()
{
cout<<sizeof(Shizuma);//输出:4 ,为什么是4呢? 立即推:指针!
}
//注:任何类型的指针都只占4字节
思考:
上述指针是什么类型?指向何方?有何意图?
该指针为:vfptr
—— virtual function pointer ——虚函数(表)指针
该指针指向:vftable
—— virtual function table —— 虚函数表
虚函数表的作用:记录虚函数的函数入口地址
在前述多态案例中,考虑到:马类继承了始祖马类,那么,始祖马类(父类)的vfptr
也给了马类(子类)一份,而且,作为子类的马类还重写了父类的虚函数virtual void eat()
,这时,子类中的虚函数表内部会替换成子类的虚函数地址,在本案例中也即:马类重写的虚函数地址覆盖了继承自始祖马类的虚函数地址(始祖马类自己的vfptr
指向的虚函数表没变)。当父类的指针或者引用指向子类对象时,父类接受的谁的对象,就利用虚函数(表)指针在谁的虚函数表中找对应的函数。
验证:
父类(Shizuma
):
子类没有重写虚函数时:vfptr
指向的虚函数表内容为&Shizuma::eat
子类重写父类虚函数后:vfptr
指向的虚函数表内容为&Ma::eat