一.多态
多态通俗来说,就是多种形态,具体点就是去完成某个行为,当不同的对象去完成时会 产生出不同的状态
1.多态的条件
1.必须通过基类的指针或者引用调用虚函数
2. 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写
执行基类调基类,指向派生类调派生类的虚函数,比如
2.虚函数重写
派生类中有一个跟基类完全相同的虚函数(即派生类虚函数与基类虚函数的 返回值类型、函数名字、参数列表完全相同),称子类的虚函数重写了基类的虚函数。比如上面的A类与B类里面的fun函数,函数相同,参数相同,返回值相同,并使用virtual关键字,构成重写
特例:
1.协变
虚函数可以返回值不相同,但必须是父子类的指针或引用 比如
但反过来就不行
2.析构函数
如果我们用基类的指针指向派生类,再delete这个指针,但这只会调用派生类里基类部分的析构函数,属于派生类的部分没有释放,会造成内存泄漏,所以这个地方我们需要多态解决
析构函数被做了一个特殊处理,析构函数的名称会被统一为destructor,加上virtual关键字即可实现多态调用
实现多态调用 没有多态调用
3.抽象类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口 类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,只有重写纯虚函数,派生 类才能实例化出对象。纯虚函数规范了派生类必须重写
4.重载,重写,隐藏
重载:在同一作用域里,函数名相同,参数不相同构成重载
重写:在基类和派生类里,函数名相同,参数相同,返回值相同(协变除外),俩函数是虚函数构成重写
隐藏:在基类和派生类里,只要函数名相同就构成隐藏
二.多态的原理
1.虚函数表
比如这个实现多态,在B类里面,会多一个指针
这是一个函数指针数组,我们称对象里的这个指针叫虚函数表指针,存放虚函数指针,虚表存在常量区
虚表在编译时生成,在对象构造时把地址赋值给对象里面的虚表指针,
2.多态的原理
1.单继承的虚表
我们来看这个虚函数表,a对象里的虚函数表有A类的fun,A类的func,b对象里面的虚函数表有A类的func,有B类重写的fun,我们打印一下虚表(因为在VS中,虚表是以空为结尾,可以利用这个打印)
2.多基础的虚表
与单继承不同的是,多继承会有多份虚表,自己新增的虚函数一般会放在第一个虚表里面
3.菱形虚拟继承的虚表
class A
{
public:
virtual void fun()
{
cout << "这是A类的fun()" << endl;
}
virtual void func()
{
cout << "这是A类的func()" << endl;
}
};
class B :virtual public A
{
public:
virtual void fun()
{
cout << "这是B类的fun()" << endl;
}
virtual void f()
{
cout << "这是B类的f()" << endl;
}
};
class C :virtual public A
{
public:
virtual void fun()
{
cout << "这是C类的fun()" << endl;
}
};
class D :public C, public B
{
public:
virtual void fun()
{
cout << "这是D类的fun()" << endl;
}
int a=0;
};
菱形虚拟继承中只会存在一张虚表,如果这是菱形继承,那么就会存在B类的虚表,C类的虚表,现在只剩下一张虚表,所以就会存在一种问题,那就是重写的不明确,比如上面的B,C类都重写了fun函数,然后D类就会出现重写不明确的问题,解决方法是D类重写fun,或者B,C,类一个不重写fun函数