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
    评论
C++中的多态(Polymorphism)是指在父类和子类之间的相互转换,以及在不同对象之间的相互转换。 C++中的多态性有两种:静态多态和动态多态。 1. 静态多态 静态多态是指在编译时就已经确定了函数的调用,也称为编译时多态C++中实现静态多态的方式主要有函数重载和运算符重载。 函数重载是指在同一作用域内定义多个同名函数,但它们的参数列表不同。编译器根据传递给函数的参数类型和数量来确定调用哪个函数。例如: ```c++ void print(int num) { std::cout << "This is an integer: " << num << std::endl; } void print(double num) { std::cout << "This is a double: " << num << std::endl; } int main() { int a = 10; double b = 3.14; print(a); // 调用第一个print函数 print(b); // 调用第二个print函数 } ``` 运算符重载是指对C++中的运算符进行重新定义,使其能够用于自定义的数据类型。例如: ```c++ class Complex { public: Complex(double real, double imag) : m_real(real), m_imag(imag) {} Complex operator+(const Complex& other) const { return Complex(m_real + other.m_real, m_imag + other.m_imag); } private: double m_real; double m_imag; }; int main() { Complex a(1.0, 2.0); Complex b(3.0, 4.0); Complex c = a + b; // 调用Complex类中重载的+运算符 } ``` 2. 动态多态 动态多态是指在运行时根据对象的实际类型来确定调用哪个函数,也称为运行时多态C++中实现动态多态的方式主要有虚函数和纯虚函数。 虚函数是在父类中定义的可以被子类重写的函数,使用virtual关键字声明。当一个对象的指针或引用指向一个子类对象时,调用虚函数时会根据实际的对象类型来确定调用哪个函数。例如: ```c++ class Shape { public: virtual void draw() { std::cout << "Drawing a shape." << std::endl; } }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ``` 纯虚函数是在父类中定义的没有实现的虚函数,使用纯虚函数声明(如virtual void func() = 0;)。父类中包含纯虚函数的类称为抽象类,抽象类不能被实例化,只能作为基类来派生子类。子类必须实现父类的纯虚函数才能实例化。例如: ```c++ class Shape { public: virtual void draw() = 0; }; class Circle : public Shape { public: void draw() override { std::cout << "Drawing a circle." << std::endl; } }; int main() { Shape* shape_ptr = new Circle(); shape_ptr->draw(); // 调用Circle类中重写的draw函数 } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值