虚函数和多态
一、虚函数&多态基础
虚函数是加了virtual关键词之后的类成员函数。
虚函数重写:当在子类中定义了一个与父类完全相同的虚函数时,则称子类的这个函数重写(也叫覆盖)了父类的这个虚函数。
上图中,类Person和类Student中的虚函数Buy一样,且Student继承了Person,所以,Student中的Buy函数重写(覆盖)了Person中的Buy。
至于多态,其形成的条件有来两个:
- 使用父类的指针或引用调用虚函数。
- 子类的虚函数重写了父类的虚函数(上述被调用函数)。
另外,有关虚函数和多态还有一些需要注意的知识点:
- 父类成员函数无virtual时无法构成多态(此时构成重定义),子类中无virtual时可以构成多态(因为子类继承父类,默认子类也是虚函数)。
- 父类与子类返回类型(值)不同时,先判断是否是协变,若不是,则调用时编译无法通过。
二、协变
协变:返回值不同,但为父子关系的指针或引用。
eg:
void Person* Buy()
{
}
void Student* Buy()
{
}
调用时:
void Fun (Person* p)
{
p->Buy();
}
发生协变时,虽然两函数的返回类型(值)不同,但可以成功运行。此时也能构成多态。
三、总结
- 子类重写父类的虚函数实现多态,要求函数名、参数列表、返回值完全相同(协变除外)。
- 父类中定义了虚函数,在子类中该函数始终保持虚函数的特性。
- 只有类的成员函数才能定义为虚函数。
- 静态成员函数不能定义为虚函数。(因为没有this指针)
- 如果在类外定义虚函数,只能在声明函数时加virtual,类外定义函数时不能加virtual。
- 构造函数不能为虚函数,虽然可以将operator=定义为虚函数,但最好不要这样,因为容易在使用时引起混淆。
- 不要在构造函数与析构函数里面调用虚函数,在构造函数和析构函数中,对象是不完整的,可能发生未定义的行为。
- 最好把父类的析构函数声明为虚函数。
对于第8点,这里作一下简单说明:
eg:
假如~A()函数未加virtual,定义 A* _pa=new B; 然后 delete _pa; ,那么因为~A()未加virtual无法构成多态,所以调析构时,释放空间只与类型有关,此时只调~A()。(正常情况下,调子类的析构时会自动调父类的析构,这样才能清理子类自身中父类的成分)
四、继承体系同名成员函数的关系