多态
函数重写和多态
如果将基类中的某个成员函数声明为虚函数,那么子类中与该函数具有相同原型的成员函数就也是虚函数,并且对基类中版本形成覆盖,即函数重写.
如果子类提供了对基类虚函数有效的覆盖,那么通过指向子类对象的基类指针,或者通过引用子类对象基类引用,调用该虚函数,实际被执行将是子类中的覆盖版本,而不再是基类中原始版本,这种语法现象被称为多态.
多态的重要意义在于,一般情况下,调用哪个类的成员函数是由调用者指针或引用本身的类型决定的,而当多态发生时,调用哪个类的成员函数则由调用者指针或引用的实际目标对象的类型决定。
虚函数覆盖条件
函数重写的要求(虚函数覆盖的条件)
只有类中的成员函数才能声明为虚函数,而全局函数、静态成员函数、构造函数都不能被声明为虚函数
只有在基类中以virtual关键字声明的虚函数,才能作为虚函数被子类覆盖,而与子类中的virtual关键字无关
虚函数在子类中的版本和基类中版本要具有相同的函数签名,即函数名、参数表、常属性一致
如果基类虚函数返回基本类型的数据,那么子类中的版本必须返回相同类型的数据
如果基类虚函数返回类类型指针(A*)或引用(A&),那么允许子类中的版本返回其子类类型指针(B*)或引用(B&)
多态条件
多态的语法特性除了要满足函数重写的语法要求,还必须是通过指针或引用调用虚函数,才能表现出来
调用虚函数的指针也可以是this指针,当使用子类对象调用基类中的成员函数时,该函数里面this指针将是一个指向子类对象的基类指针,再通过this去调用满足重写要求的虚函数同样可以表现多态的语法特性(重要)
#include <iostream>
using namespace std;
class Base{
public:
virtual void func(void)
{
cout<<"Base::func"<<endl;
}
}
class Derived:public Base{
public:
void func(void)
{
cout<<"Derived::func"<<endl;
}
}
int main()
{
/*该情况不会产生多态语法*/
//Derived d;
//Base b = d;
//b.func();
/*该情况会产生多态语法*/
Derived d;
Base& b = d;
b.func();
return 0;
}
//this指针实现多态
#include <iostream>
using namespace std;
class Base{
public:
virtual void func(void)
{
cout<<"Base::func"<<endl;
}
//void foo(Base* this = &d)
void foo(void)
{//this->func()
func();
}
}
class Derived:public Base{
public:
void func(void)
{
cout<<"Derived::func"<<endl;
}
}
int main()
{
Derived d;//子类对象
Base b = d;
b.func();
//该情况可以实现多态
d.foo();//foo(&d)
return 0;
}
纯虚函数和抽象类
只如果一个虚函数仅表达抽象的行为,没有具体的功能,即只有声明没有定义,这样的虚函数被称为纯虚函数或抽象方法,其语法特性如下:
clss 类名{
public:
virtual 返回类型 函数名(形参表)= 0;
};
抽象类和纯抽象类
如果类中包含了纯虚函数,那么这个类就是抽象类
如果类中的所有成员函数都是纯虚函数则可以称为纯抽象类
抽象类往往用来表示在对问题进行分析、设计的过程中所得出的抽象概念,是对一系列看上去不同,但本质上相同的具体概念的抽象描述
注:无论是直接定义,还是通过new运算符,抽象类永远不能实例化为对象
虚析构函数
基类的析构函数不会自动调用子类的析构函数,所以当delete一个指向子类对象的基类指针,实际被执行的仅是基类的析构函数,子类的析构函数不会被执行,有内存泄漏的风险