目录
一:多态的思想
父类的指针或引用可以指向子类对象。
对于子类son与son1来说,父类指针(b1)可以接收这两个子类实例化后的对象 。但是对于两个子类之间,他们并没有任何的关系,所以在使用son类指针去接收son1类对象时,编译器会报错,son1类型值不能用于初始化son类型的实体。这就是多态的核心了:父类的指针或引用可以接收他所有子类的类型对象。
但是多态也分为静态多态和动态多态,我们下面通过一些实例来了解一下他们间的区别
二:静态多态(函数地址早绑定)
class base1
{
public:
void show()
{
cout << "父类中的show函数" << endl;
}
};
class son :public base1
{
public:
void show()
{
cout << "子类son中的show函数" << endl;
}
};
class son1 :public base1
{
public:
void show()
{
cout << "子类son1中的show函数" << endl;
}
};
void do_work(base1* b)
{
b->show();
}
我们在父类与两个子类中都实现一个show方法,然后创建一个全局函数,函数参数是父类的指针,那么我们在主函数中分别使用两个子类对象去调用这个do_work函数,发现最终的结果打印的都是“父类中的show函数".
这里就属于静态多态,函数地址在程序编译,还未运行时就已经绑定好了,绑定的就是父类中的show函数地址。
三:动态多态(函数地址晚绑定)
首先呢,它的各种实现方式都和静态多态相同。唯一的区别就在于他引入了一个虚函数的概念,“virtual”。在父类中,我们使用这个关键字来修饰一下show函数,然后再次运行代码。
可以看到,再次运行后,使用两个子类调用do_work函数时,确实调用了两个子类中各自的show函数。这里我们将被virtual修饰过的函数称为虚函数。
3.1:虚函数与纯虚函数
虚函数在上面的例子中已经了解过了,那么纯虚函数又是怎么一回事呢,纯虚函数与虚函数相同,就是一个普通成员函数,前面被virtual修饰,但是没有函数体,直接等于0.
3.2:抽象类与函数重写
(1)抽象类
一个类中,只要存在一个纯虚函数那么就称这个类为抽象类,而抽象类是不能实例化对象的。因为类的对象是可以调用类内成员函数,那么如果有一个函数是纯虚函数,那么对象在调用这个纯虚函数,编译器根本不知道该干什么。
(2)函数重写
父类中的虚函数与纯虚函数都可以进行函数重写。(父类中纯虚函数子类必须重写,不然就算做抽象类)函数重写与函数重载是不同的概念,(这里就不解释函数重载了)
要进行函数重写,那么被重写的函数的函数名,函数返回类型,函数参数个数,顺序,类型都必须一致。
其实上面的动态多态中的show函数的例子,就是函数重写了,返回类型都i是void,都是无参函数,函数名都相同。
四:多态的本质
(1)虚函数表与虚函数表指针
这个概念与虚基类表,和虚基类表指针相似,如果想了解可以点进去看看
只要类中存在虚函数或纯虚函数,那么类内就会自动生成一个虚函数表指针,指向一张虚函数表,在这个虚函数表中,存放着类内所有虚函数的地址 。
当这个类被它的子类继承时,子类也会继承这个虚函数表指针,与虚函数表,(注意两张虚函数表不是同一张表,可以理解为复制了一张虚函数表交给子类的虚函数标配指针。)
现在只要子类中对父类的虚函数和纯虚函数进行函数重写,那么子类中重写后的函数地址就会将原来的函数地址覆盖掉。这样就实现了动态的多态。
多态性提供了接口与具体实现之间的另一层隔离。
当然一般来说多态实现的都是动态的多态。
五:析构函数的重写
在发生多态时,我们使用父类指针或者引用去操作子类对象,使用完这个对象后,对其进行析构时,它只会调用父类中的析构函数,并不会调用子类的析构函数。
所以当子类中有向堆区申请的空间时,就会出现内存泄漏的情况。那么我们就需要对父类的析构函数进行虚声明,并在子类中,提供自己的析构函数,那么在释放相应的子类对象时,就会调用相应的子类析构函数了。