动态多态
多态指的是同一名称不同的功能实现方式,C++对多态的实现有静态多态(编译时)和动态多态(运行时)两种。静态多态有重载和模板,动态多态为虚函数。
在类中声明为virtual的函数就是虚函数,虚函数的核心理念是用基类去访问派生类定义的函数。利用基类指针或对象访问派生类中的成员函数是C++中多态性的重要体现。
动态多态的意义在于如果多个派生类是一个基类的继承,这多个派生类又有类似的功能会被调用,那么就可以将这个函数在基类和派生类中都定义为虚函数,在需要调用不同的派生类函数功能时,只需要对基类的虚函数进行调用就可以实现对不同的派生类函数调用。
语法:
class 类名
{
访问权限:
virtual 类型 函数名(形参列表)
{函数体}
}
虚函数要求函数的声明要完全一样(基类虚函数声明时派生类已经存在虚函数,派生类虚函数前加virtual易于阅读),在派生类虚函数后加override关键字可以检查虚函数是否被正确重写,如下例:
class A {
public:
virtual void output() {
cout << "A::output" << endl;
}
};
class B : public A {
public:
virtual void output() override{
cout << "B::output" << endl;
}
};
int main()
{
B b;
A &a = b;
A *p = &b;
a.output(); //输出"B::output"
p->output(); //输出"B::output"
retur 0;
}
虚析构函数
如果基类的指针指向派生类的对象,在使用delete释放这个指针指向的堆区空间时只会调用基类的析构函数,不会调用派生类的析构函数,就会造成内存泄漏。
对于申请派生类指针可能会造成的内存泄漏解决方法时将析构函数定义为虚函数,如果基类的析构函数定义为虚析构函数,那么派生类的析构函数会自动成为虚析构函数。使用基类指针释放派生类对象时,派生类的析构函数和基类的析构函数都可以被调用。
语法:
class 类名
{
访问权限:
virtual ~类名()
{函数体}
};
纯虚函数
纯虚函数指在基类中只声明函数但没有实现,要求在派生类中必须定义的成员函数。
语法:
class 类名
{
访问权限:
virtual 类型 函数名(形参列表) = 0; //无函数体
};
纯虚函数注意点:
- 有纯虚函数的类称为抽象类或虚基类
- 抽象类不能被实例化(不能定义对象)
- 从抽象类继承而来的派生类,可以不实现纯虚函数,如果不实现,派生类还是抽象类,不可以实例化。
- 可以声明抽象类的指针和引用
虚函数表
C++的虚函数通过虚函数表实现动态多态,表中存放了类中的虚函数地址信息。
注意点:
- 虚函数表占用类的存储空间,每个类只有一份虚函数表。
- 同一个类的不同对象使用一份虚函数表,对象地址前4/8个字节是指向虚函数表的指针。
图来源于网络
重载、重写、重定义
- 重载:在相同的作用域,函数名相同,参数个数或类型不同,const也可以重载
- 重写(覆盖):分别位于基类和派生类中,函数名相同,参数相同,基类函数必须是虚函数
- 重定义(隐藏):派生类的函数屏蔽了与其同名的基类函数,规则如下:
-
- 如果派生类的函数和基类的函数同名,但是参数不同,此时不管是否虚函数,基类的函数将被隐藏
-
- 如果派生类的函数与基类的函数同名,并且参数也相同,但是基类的函数不是虚函数,此时基类的函数将被隐藏
-
- 应尽量避免成员函数重定义