c++多态的实现原理与图解

1 实现多态的三步骤

1、 有继承关系的类
2、父类有虚函数,子类重写父类的虚函数
3、子类的指针或应用赋值给父类

2 虚函数注意点

  1. 派生类中定义虚函数必须与基类中的虚函数同名外,还必须同参数表,同返回类型。否则被认为是重载,而不是虚函数。但是基类中虚函数的返回值返回基类指针,派生类中对应的虚函数返回值返回派生类指针是允许的,这是一个例外

  2. 只有类的成员函数才能说明为虚函数。这是因为虚函数仅适用于有继承关系的类对象。即全局函数不能说明为虚函数

  3. 静态成员函数,是所有同一类对象共有,不受限于某个对象,不能作为虚函数。

  4. 内联函数每个对象一个拷贝,无映射关系,不能作为虚函数。

  5. 析构函数可定义为虚函数,构造函数不能定义虚函数,因为在调用构造函数时对象还没有完成实例化。在基类中及其派生类中都动态分配的内存空间时,必须把析构函数定义为虚函数,实现撤消对象时的多态性。如delete一个父类指针时,这个指针是new一个子类赋值给父类指针的,如果子类中有动态分配的内存空间,那么需要将父类的析构函数声明为虚函数,否则子类的析构函数在delete父类指针时不会被调用造成内存泄漏。

  6. 如果定义放在类外,virtual只能加在函数声明前面,不能(再)加在函数定义前面。即virtual关键字只能出现在函数的声明或定义处,不能同时出现

3 纯虚函数与抽象类

定义纯虚函数的一般格式为:
virtual 返回类型 函数名(参数表)=0;
纯虚函数(pure virtual function)是指被标明为不具体实现的虚拟成员函数。它用于这样的情况:定义一个基类时,会遇到无法定义基类中虚函数的具体实现,其实现依赖于不同的派生类。
含有纯虚函数的基类为抽象类纯虚函数没有实现部分,不能产生对象,因此抽象类是不能用来实例化对象的,但是可以定义抽象类指针或引用来实现多态。

4 多态实现原理

1、基类中的虚函数
类中若定义了虚函数,那么类会多出一个成员指针,称为虚函数指针,虚函数指针指向一张虚函数表,虚函数表中存放的是类中的每一个虚函数地址,注意非虚函数的成员函数不会放入虚函数表中
2、继承
子类继承父类,那么子类会继承除了构造函数和析构函数的所有成员,当然也会继承父类的虚函数指针和虚函数表,可以看做子类的构成中有一个父类
3、重写子类的虚函数 以及子类指针或引用赋值给父类
在重写子类的虚函数后,子类的虚函数地址与父类对应的虚函数地址会有不同,在子类指针或引用赋值给父类时,虚函数表中原先父类的虚函数地址会被重写的子类的虚函数地址覆盖掉,从而完成不同的子类指针或引用赋值给同一父类指针或引用时,在调用父类的虚函数时回去调用对应子类的虚函数,最终实现多态。注意若在子类定义了父类不具有的虚函数,这个虚函数也会加入子类继承而来的虚函数表中,但是不能通过父类指针或引用去调用这个父类不具有的虚函数,即子类指针或引用赋值给父类后,父类指针或引用的作用范围不能超过父类的成员范围

5 虚函数表图解

5.1 一般继承(无虚函数覆盖)

在这里插入图片描述
请注意,在这个继承关系中,子类没有重载任何父类的函数。那么,在派生类的实例中,对于实例Derive d 的虚函数表如下所示:
在这里插入图片描述
我们可以看到下面几点:
1)虚函数按照其声明顺序放于表中。
2)父类的虚函数在子类的虚函数前面

5.2 一般继承(有虚函数覆盖)

覆盖父类的虚函数是很显然的事情,不然,虚函数就变得毫无意义。如果子类中有虚函数重载了父类的虚函数,我们有下面这样的一个继承关系
在这里插入图片描述
在这个类的设计中,只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:
在这里插入图片描述
我们从表中可以看到下面几点,
1)覆盖的f()函数被放到了虚表中原来父类虚函数的位置。
2)没有被覆盖的函数依旧。
这样,我们就可以看到对于下面这样的程序,
Base *b = new Derive();
b->f();
由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

5.3 多重继承(无虚函数覆盖)

下面,再让我们来看看多重继承中的情况,假设有下面这样一个类的继承关系。注意:子类并没有覆盖父类的函数
在这里插入图片描述
对于子类实例中的虚函数表,是下面这个样子:
在这里插入图片描述
我们可以看到:
1) 每个父类都有自己的虚表。
2) 子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
这样做就是为了解决不同的父类类型的指针指向同一个子类实例,而能够调用到实际的函数。

5.4 多重继承(有虚函数覆盖)

下面我们再来看看,如果发生虚函数覆盖的情况。 下图中,我们在子类中覆盖了父类的f()函数。
在这里插入图片描述
下面是对于子类实例中的虚函数表的图:
在这里插入图片描述
我们可以看见,三个父类虚函数表中的f()的位置被替换成了子类的函数指针。这样,我们就可以任一静态类型的父类来指向子类,并调用子类的f()了。如:
Derive d;
Base1 *b1 = &d;
Base2 *b2 = &d;
Base3 *b3 = &d;
b1->f(); //Derive::f()
b2->f(); //Derive::f()
b3->f(); //Derive::f()
b1->g(); //Base1::g()
b2->g(); //Base2::g()
b3->g(); //Base3::g()

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值