C++虚函数介绍以及在多层继承中调用 & 在多重继承中调用

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议。

1 虚函数表,以及虚函数指针

1.1 介绍

  • 每个有虚函数的类都有自己的虚函数表,每个包含虚函数的类对象都有虚函数表指针。
  • 对于多重继承,如果多个基类都有虚函数,则继承类中包含多个基类虚函数表,子类的虚函数地址放在声明的第一个基类虚函数表后面。
  • 计算类对象的内存大小的时候,需要计算有多少个虚函数指针。
  • 虚函数表(Virtual Table)简称为V-Table,在这个表中,主是要一个类的虚函数的地址表,这张表解决了继承、覆盖的问题,保证其容真实反应实际的函数。

1.2 虚函数与虚表

1.2.1 代码打印虚函数地址

class Base {
 public:
    virtual void f() { std::cout << "Base::f" << std::endl; }
    virtual void g() { std::cout << "Base::g" << std::endl; }
    virtual void h() { std::cout << "Base::h" << std::endl; }
};

typedef void(*Fun)(void);
int main(int argc, char *argv[])
{
    std::cout << "########################################" << std::endl;
    Base b;
    Fun pFun = NULL;
    std::cout << "v-table address:" << (int*)(&b) << std::endl;
    std::cout << "v-table-first function address:" << (int*)*(int*)(&b) << std::endl;
    // Invoke the first virtual function
    pFun = (Fun)*((int*)*(int*)(&b)+0);  // Base::f()
    pFun();
    pFun = (Fun)*((int*)*(int*)(&b)+1);  // Base::g()
    pFun();
    pFun = (Fun)*((int*)*(int*)(&b)+2);  // Base::h()
    pFun();
    std::cout << "########################################" << std::endl;
}

在这里插入图片描述
通过强行把&b转成int *,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是Base::f()
在这里插入图片描述
在上面这个图中,我在虚函数表的最后多加了一个结点,这是虚函数表的结束结点,就像字符串的结束符“/0”一样,其标志了虚函数表的结束。这个结束标志的值在不同的编译器下是不同的。

1.2.2 继承中虚表变化情况

  • 一般继承(无虚函数覆盖)
    在这里插入图片描述
    对于实例:Derive d; 的虚函数表如下:
    在这里插入图片描述
  • 一般继承(有虚函数覆盖)
    在这里插入图片描述
    只覆盖了父类的一个函数:f()。那么,对于派生类的实例,其虚函数表会是下面的一个样子:
    在这里插入图片描述
Base *b = new Derive();
b->f();

由b所指的内存中的虚函数表的f()的位置已经被Derive::f()函数地址所取代,于是在实际调用发生时,是Derive::f()被调用了。这就实现了多态。

  • 多重继承(无虚函数覆盖)
    在这里插入图片描述
    在这里插入图片描述
    每个父类都有自己的虚表。
    子类的成员函数被放到了第一个父类的表中。(所谓的第一个父类是按照声明顺序来判断的)
  • 多重继承(有虚函数覆盖)
    下图中,我们在子类中覆盖了父类的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()

2 纯虚函数

2.1 介绍

含有虚函数的类称为抽象类,当抽象类没有必要创建对象时,便使用纯虚函数来避免该类被用来创建对象

  • 纯虚函数需要在声明函数名前面加上virtual,在最后面加个=0;
  • 例如
class Shape
{
public:
       virtual double area()=0;               //不需要实现函数内容
};

2.2 接口

2.2.1当类满足下面条件,则称为接口

  • 类中没有定义任何成员变量
  • 所有的成员函数都是公有的,并且都是纯虚函数
  • 接口是一种特殊的抽象类

2.2.2 示例

例如对多个设备进行TCP/IP通信,都需要处理信息,并发出对应的信号(Qt)或者是事件传递(C# winform),都用到函数 MsgHandler(),所以它们的父类IMsgDealer只需要构造纯虚函数,所以便被称为接口,该父类代码如下

class IMsgDealer
{
public:
    virtual void MsgHandler(QString qStr) = 0;
};

3 多层继承

1.1 介绍

在一个类中的虚函数说明,只对派生类中重定义的函数有影响,对它的基类中的函数不起作用。

1.2 示例

class A
    {
    public:
        virtual void show()
        {
            std::cout << "The show in A." << std::endl;
        }
    };

    class B: public A
    {
    public:
        virtual void show()
        {
            std::cout << "The show in B." << std::endl;
        }
    };

    class C:public B
    {
    public:
        void show()
        {
            std::cout << "The show in C." << std::endl;
        }
    };

    std::cout << "########################################" << std::endl;
    C cobj;
    cobj.show();//调用c的show方法。
    cobj.B::show();//调用B的show方法。
    cobj.A::show();//调用a的show方法。
    std::cout << "########################################" << std::endl;

在这里插入图片描述

4 多重继承

参考上面的1.2

5 安全性

5.1 通过父类型的指针访问子类自己的虚函数

使用父类指针想调用子类中的未覆盖父类的成员函数的行为都会被编译器视为非法。但在运行时,我们可以通过指针的方式访问虚函数表来达到违反C++语义的行为。

5.2 访问non-public的虚函数

如果父类的虚函数是private或是protected的,但这些非public的虚函数同样会存在于虚函数表中,所以,我们同样可以使用访问虚函数表的方式来访问这些non-public的虚函数,这是很容易做到的。

参考

1、C++ 虚函数表解析(比较清楚,还可打印虚函数地址)
2、C++的精髓——虚函数
3、24.C+± 抽象类(存虚函数)、接口、多重继承
4、C++ 虚函数表解析–陈浩

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 技术黑板 设计师: CSDN官方博客
应支付0元
点击重新获取
扫码支付

支付成功即可阅读