虚函数,覆盖,多态
- 虚函数
- 成员函数前加
virtual
关键字
- 成员函数前加
- 覆盖
- 子类中会覆盖父类中的虚函数
#include <iostream>
using namespace std;
class Base
{
public:
/*virtual*/void func(void)
{
cout << "我是Base的func函数" << endl;
}
};
class A : public Base
{
public:
void func(void)
{
cout << "我是A的func函数" << endl;
}
};
int main()
{
A a;
Base *p = &a;
p->func();
a.func();
}
运行结果
我是Base的func函数
我是A的func函数
virtual运行结果
我是A的func函数
我是A的func函数
- 多态
-
当子类覆盖了父类的虚函数时,通过父类指针指向子类对象时,调用虚函数,会根据具体的对象是谁来决定执行谁的函数
-
注意 在覆盖版本的函数中所得到的this指针依然是实际对象的地址,依然能够调用子类中的函数
覆盖和多态的条件
- 覆盖的条件
- 必须是成员函数
- 必须是虚函数
- 必须是父子类之间
- 函数签名必须相同(参数列表完全一致,const属性也会影响覆盖结果)
- 返回值必须是同类型,或者父子类(子类的返回值要能向父类隐式转换)
- 访问属性不会影响覆盖
- 常函数属性也会影响覆盖
- 重载,隐藏,覆盖(重写)的区别
- 重载
- 发生在同一作用域下的同名函数,函数签名不同(+const修饰的指针和引用),构成重载
- 覆盖
- 如上
- 隐藏
- 父子类的同名成员,如果没有形成覆盖,且能通过编译,必定构成隐藏
- 重载
- 多态的条件
- 父子类之间的函数有覆盖关系
- 父类的指针或引用指向子类的对象
- 在构造,析构函数中调用虚函数
- 在父类的构造函数中调用虚函数,此时子类还没有创建完成(构造函数调用的顺序),因此只能调用父类的虚函数,而不是覆盖版本的虚函数
- 在父类的析构函数中调用虚函数,此时子类已经释放完成,因此只能调用父类的虚函数,而不是覆盖版本的虚函数
纯虚函数和抽象类
- 纯虚函数
- 在虚函数的声明的后面添加
= 0
,可以不实现,如果实现,必须在类外(只能在父类的构造函数和析构函数中才能调用)virtual 返回值 函数名(参数) = 0;
- 在虚函数的声明的后面添加
- 抽象类
- 成员函数中有纯虚函数,这种类叫抽象类,这种类不能实例化(不能创建对象)
- 抽象类必须被继承且纯虚函数被覆盖,由子类实例化对象
- 如果继承了抽象类,但子类没有覆盖纯虚函数,那么子类也将成为抽象类,不能实例化
- 纯抽象类
- 所有成员函数都是纯虚函数,这种只能被继承的类叫纯抽象类
- 一般用来设计接口,这种类在子类被替换后不需要修改或少量的修改即可继续使用