C++多态细节,反汇编

文章目录

1.C++的三大特性

1.1封装

将对象的属性和方法封装起来。为了模块化,便于使用者操作,同时可以隔离外部使用者对内部数据的干扰,提高了安全性。

类成员的三种属性:
private:本类使用。设置get和set方法,因为通过接口来访问和修改数据是可控的,相对安全。
protected:本类和子类使用。
public:公开的,都可以访问。

1.2继承

允许通过继承原有类的某些特性或全部特性而产生新的类,原有的类称为基类(父类),产生的类称为派生类(子类)。为了扩展和重用,减少重复代码。

1.3多态

发生在继承关系中,不同的对象,对于相同的方法有不同的操作逻辑。为了接口重用,提高代码的可复用性、可维护性和可扩充性。

多态的实现机制为虚函数。

1.3.1 虚函数

关键字:virtual

作用:允许在子类中重新定义与基类同名的函数,并且可以通过基类指针或引用来访问基类和子类的同名函数。

最常见的用法:声明基类的指针,利用该指针指向任意一个子类对象,调用相应的虚函数,可以根据指向的子类的不同而实现不同的方法。

实现原理:虚表+虚表指针。
在这里插入图片描述

1.3.1.1.【注意】当类存在虚函数时,编译器会为类创建一个虚表,虚表是一个数组,数组的元素存放的是虚函数地址。即虚表在编译的时候就确定了,且只有一份。同时为每个类对象添加一个隐藏数据成员,即虚表指针,它是在运行阶段确定的,有多少个对象,就有多少个虚表指针。另外,虚表指针被定义在对象首地址处。

1.3.1.2.【注意】派生类会生成兼容基类的虚表,如果子类重写了相应的虚函数,那么子类虚表中相应的虚函数地址就会改变,指向自身的虚函数实现,否则指向的是基类的虚函数实现。另外,如果子类也有虚函数,那么子类虚表就会添加该虚函数地址。

1.3.1.3.【注意】每个类都有虚表时,单继承的子类维护一张虚表,子类对象有一个虚表指针。多继承的子类维护多张虚表,子类对象包含多个虚表指针。

1.3.1.4. 为什么构造函数通常不定义为虚函数?

  • 虚函数的调用需要虚表指针,而该指针存放在对象的内存空间中。由于初始时,对象还未创建,还没有内存空间,更没有虚表指针用来调用虚构造函数。

1.3.1.5. 引用是否能实现动态绑定?

  • 可以,因为引用或指针既可以指向基类对象也可以指向子类对象,所以它是在运行时根据所指对象,调用相应的虚函数。

1.3.2 多态代码+反汇编分析。

1.基类Shape,数据成员为price和area,虚析构函数,两个虚函数为getDescribe()和getPrice(),;
2.子类Circle,继承Shape,数据成员为radius,重写父类方法getPrice();
3.main()里,基类指针s1指向Circle类对象,基类对象s2。代码如下。

#include<iostream>
using namespace std;


class Shape
{
protected:
    double price;
    double area;

public:
    Shape() :price(100), area(2) {}
    virtual ~Shape() { printf("%s\n", "Delete shape"); }

    virtual void getDescribe() { printf("%s\n", "Base shape"); }
    virtual void getPrice() { printf("%s%f\n", "Shape price ", price); }
};

class Circle : public Shape
{
private:
    double radius;

public:
    Circle(double r) : radius(r) { area = 3.14 * radius * radius; price = 100 + area * 6; }
    ~Circle() { printf("%s%f\n", "Delete circle with radius ", radius); }

    void getPrice() { printf("%s%f%s%f\n", "Circle with area ", area, " price ", price); }
};



int main() {

    Shape* s1 = new Circle(5.0);
    s1->getPrice();
    delete s1;

    Shape s2;
    s2.getPrice();

    return 0;
}

程序执行结果,如下图。
在这里插入图片描述

反汇编分析1——基类指针指向子类对象,构造过程。
  • ecx保存了子类对象地址0x00DD1A38,如下图。

在这里插入图片描述

  • 调用完父类构造函数,如下图。

    • 子类对象保存的数据内容分别是基类虚表指针,price,area(按基类数据成员的声明顺序)。
    • 基类虚表保存的虚函数地址分别是基类析构函数,getDescribe(),getPrice()(按基类虚函数声明顺序)。

在这里插入图片描述

  • 设置子类虚表指针后,如下图。

    • 子类对象首地址处保存的基类虚表指针替换成子类虚表指针。
    • 子类虚表保存的虚函数地址分别是子类析构函数,getDescribe(),getPrice()。
    • 因为子类没有重写getDescribe(),所以地址不变,指向基类的getDescribe()实现。
    • 因为子类重写getPrice(),所以地址改变,指向子类的getPrice()实现。

在这里插入图片描述

  • 紧接着构造子类的数据成员,如下图。

在这里插入图片描述

  • 最后执行子类的构造代码,先修改area值,再修改price值,如下图。

在这里插入图片描述

  • 总结:先构造基类,再构造子类。
反汇编分析2——基类指针指向子类对象,调用虚函数getPrice()过程。
  • 取子类对象地址。
  • 取子类虚表指针地址。
  • 取子类虚函数getPrice()地址,然后调用,如下图。

在这里插入图片描述

反汇编分析3——基类对象,调用虚函数getPrice()过程。
  • 基类对象调用自身虚函数时,没有构成多态性,所以没必要查表访问,编译器使用了直接调用方式,如下图。

在这里插入图片描述

反汇编分析4——基类指针指向子类对象,析构过程。
  • 子类对象保存的数据内容,如下图。

在这里插入图片描述

  • 执行子类的析构代码,如下图。

在这里插入图片描述

  • 紧接着执行基类的析构代码,如下图。

在这里插入图片描述

  • 最后释放指针内存。

在这里插入图片描述

  • 总结:先析构子类,再析构基类。
反汇编分析5——基类指针指向子类对象,析构过程,基类析构函数不是虚函数时。
  • 只调用基类析构函数,如下图。
  • 【注意】 由于子类析构函数不会被调用,子类资源没有正确释放,造成内存泄漏。

在这里插入图片描述
在这里插入图片描述

1.3.3 静态多态vs动态多态

1.静态多态在编译期完成,由模板实现;而动态多态在运行期完成,由继承、虚函数实现。
2.静态多态中接口是隐式的,以有效表达式为中心;而动态多态中接口是显式的,以函数签名为中心。
3. 另外,静态多态代码如下。

template <typename T>
class Calculator 
{
public:
    T add(T a, T b) {
        return a + b;
    }
};

int main() {
   
    Calculator<int> intCalculator;
    cout <<  intCalculator.add(5, 10) << endl;

    Calculator<double> doubleCalculator;
    cout <<  doubleCalculator.add(3.5, 2.5) << endl;

    return 0;
}
  • 14
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值