多态的原理

1.类对象模型的分析

1.1具有虚函数的类对象模型

请问下面这个类的对象占多少字节的内存空间?

class A
{
public:
    virtual void Func1()
    {
        cout << "Func1()" << endl;
    }
private:
    int _a = 1;
};

答案: 8 字节

这是因为一个类一旦有虚函数,那它的对象中就会多一个指针类型的成员变量,这个指针叫虚函数表指针,这个指针指向了一张表,这张表叫虚函数表,虚函数表中存的是虚函数的地址,所以,虚函数表的本质就是一个函数指针数组;如下图所示:

1.2继承具有虚函数的类 的类对象模型

我们可以借助内存窗口分析类对象模型,如下图所示:

从图中可以看出,当一个类继承一个具有虚函数的类,这个类中也会有一个虚函数指针;但是,这个虚函数指针和父类中的不是同一个虚函数指针;说明,父类和子类各自具有一张虚函数表。并且,在VS环境下,虚函数表是以空结尾的。

因为该测试样例中子类中并没有重写父类中的虚函数,所以父子类的虚函数表中的虚函数地址是一样的,但是,当子类中重写父类的虚函数的时候,重写的虚函数的地址就会被覆盖,所以重写也叫覆盖,重写是语法层的概念,覆盖是原理层的概念。当子类添加新的虚函数的时候,虚函数的地址会添加在虚函数表的后面。但始终是以空结尾。

2.多态的原理

对于普通的类(没有虚函数的类),类对象当中只存储成员变量,成员方法是存储在公共的代码区的,调用的时候是通过函数名修饰规则,在符号表中找到调用的函数的地址进行调用;这种调用是通的调用。 

对于具有虚函数的类,类对象中不仅存储了成员变量,还存储了一个虚函数表指针,通过这个指针可以找到独属于这个类的虚函数表;子类重写虚函数,会将父类中被重写的虚函数的地址覆盖;运行时,如果符合多态调用的条件,编译器就会去 所指向对象的虚函数表中 找到对应的虚函数进行调用,这就是多态的原理

反思一下多态的条件:多态需要两个条件:1.子类中需要完成虚函数的重写。2.通过父类的指针or引用去调用对应的虚函数。

  • 通过父类的指针or引用去调用是因为 子类对象的指针or应用可以通过赋值兼容转换,转换成父类对象的指针or引用;通过这个父类的指针or引用去调用对应的虚函数的时候,这个指针or引用只能看到父类的那一部分。
  • 完成虚函数的重写是因为 要保证去不同的类的虚函数表中找对应的虚函数。

总结一下:成员函数的调用分为两种调用,一种是多态调用,一种是普通调用。

多态调用:当调用函数的时候,满足多态的条件,就是多态调用;多态调用是运行时去虚函数表中找到函数的地址,进行调用;所以如果指向的对象是父类的对象,调用的是父类对象中的虚函数;指向的是子类,调用的是子类对象中的虚函数。这样就实现了多态。

  • 多态调用关注的是所指向对象的类型,指向谁调用谁里面的成员方法。

 普通调用:当调用函数的时候,如果不满足多态调用,就是普通调用;普通调用是编译时,通过调用者类型确定函数地址,也就是通过函数名修饰规则,在符号表中找到函数的地址进行调用。

  • 普通调用关注的是调用者本身的类型,调用的是该类型中的成员方法。

3.几个疑问

虚函数存在哪?虚函数也是函数,函数编译完之后是一串指令,所以虚函数和普通函数一样是存在代码段的;

虚函数表存在哪?虚函数表的存放位置我们可以验证一下,如下图所示:

可以看出虚函数表的地址离常量区是最近的,所以虚函数表应该是存在常量区的。 虚函数表是在编译时生成的,是存在常量区的。

虚函数表指针存在哪?虚函数表指针是存在对象中的,作为对象的成员变量,在构造函数的初始化列表阶段初始化,并且是第一个初始化的

  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值