一.如何实现多态
1.多态的两个条件:
(1) 必须通过基类的指针或者引用调用虚函数
(2) 被调用的函数必须是虚函数,且派生类必须对基类的虚函数进行重写,重写必须返回值,函数名,参数类型相同,同时virtual只能修饰成员函数(这个和继承那个不同,继承的virtual解决的是菱形继承问题)。
class person {
public:
virtual void buy()
{
cout << "全价" << endl;
}
};
class student:public person{
public:
void buy()//这里写不写virtual都可以,只要基类写了,规范是都写。
{
cout << "半价" << endl;
}
};
void fun(person& p)
{
p.buy();
}
2.多态的两个例外
(1)协变.
我们重写虚函数的时候,要保证返回值和参数,函数名相同,不过有个特列。允许返回值为父类或者子类的指针或者引用。它照样可以实现多态
(2)析构函数的重写
在继承中,析构函数由于多态的原因,会被编译器处理成同名函数,如果不加virtual,那么用基类的指针或者引用指向基类或者派生类,析构函数构成隐藏关系。会有内存泄漏的问题。
我们希望各自调用自己的析构,由于析构函数的函数名相同,参数为空,只要加基类析构函数加virtual就可以实现多态。
多态调用:通过父类的指针或者引用,来调用不同的虚函数.看对象不是看类型,指向什么对象就调用哪个对象的虚函数。它是在运行的时候来通过虚函数表里调用函数地址
普通调用:是在编译链接的时候就确定了函数的地址
二.常用的关键字:final,override
加强检查。
1. final:
修饰虚函数,表示该虚函数不能再被重写
2.override:
检查派生类虚函数是否重写了基类某个虚函数,如果没有重写编译报错,加强检查。
三.重写和重载和隐藏关系
重写是重定义(隐藏)的子集。
三.抽像类
在虚函数的后面写上 =0 ,则这个函数为纯虚函数。包含纯虚函数的类叫做抽象类(也叫接口类),抽象类不能实例化出对象。派生类继承后也不能实例化出对象,派生类只有重写抽象类的纯虚函数,派生类才能实例化出对象。
抽象类要实现多态,必须子类实现对纯虚函数的重写。这也体现出,如果基类是抽象类,派生类必须要重写纯虚函数。
class animal {
public:
virtual void eat() = 0;
};
class cat:public animal {
public:
virtual void eat()
{
cout << "鱼" << endl;
}
};
class rabbit:public animal {
public:
virtual void eat()
{
cout << "草" << endl;
}
};
四.多态的原理
(1)单继承
多态调用是运行阶段通过虚函数表中的调用函数,实现多态。我们发现,person p成员有个a,但是通过监视窗口看还有一个指针_vfptr.指向的是一个函数指针数组,这个是实现多态。
虚函数的重写也称虚函数的覆盖。为什么是父类指针和引用实现多态?父类中和派生类中构成多态,那么父类和子类成员会有个指针,都指向一个函数指针数组,数组中存的是虚函数的地址。
例如,子类(Derive)和父类(Base)各有一个虚函数表的指针,子类继承父类的时候会把父类的虚基表拷贝一份到自己的虚函数表中,如果父类某个虚函数被子类重写了,那么子类的虚函数表就把父类的虚函数地址覆盖 成子类的重写的,如果父类的指针或者引用指向子类。子类对象的地址前四个字节是虚函数表的地址,由于切割的原理这时看到是父类的部分,由与子类重写了func1,原来子类虚函数表中拷贝父类func1函数的地址已经子类重写的func1覆盖,子类虚函数表(地址003F7894)调用func1,这个时候调用的就是子类的func1,而不是父类的。
对于多继承的情况。多继承派生类的未重写的虚函数放在第一个继承基类部分的虚函数表中,其中涉及到this指针的修正问题,对于菱形继承的多态比较复杂。