C++的类和new和delete和菱形继承机制

2024-06-02 11:28
文章标签 c++ 继承 机制 new delete 菱形

本文主要是介绍C++的类和new和delete和菱形继承机制,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 参考
  • 虚函数
  • 使用虚函数的class结构
  • 相关实现
  • 源码
  • IDA反编译
  • 子类虚表和父类虚表
  • 调用函数
  • 菱形继承

参考

https://showlinkroom.me/2017/08/21/C-%E9%80%86%E5%90%91%E5%88%86%E6%9E%90/
https://www.cnblogs.com/bonelee/p/17299985.html
https://xz.aliyun.com/t/5242?time__1311=n4%2BxnD07itGQ%3DD54AKDsA3xCuU%3DwQNY4D&alichlgref=https%3A%2F%2Fxz.aliyun.com%2Fu%2F15779#toc-3

虚函数

在C++中,虚函数(virtual function)是一种特殊的成员函数,它允许在派生类中重写或覆盖基类中定义的函数,从而实现多态性(polymorphism)。虚函数的关键特征和用途可以概括如下:

  1. 声明与定义
    要声明一个虚函数,需要在基类的成员函数声明前加上virtual关键字。例如:

    class Base {
    public:virtual void someFunction() {// 基类的实现}
    };
    

    派生类可以重写该函数,也可以不重写,如果重写,则在派生类中提供新的实现:

    class Derived : public Base {
    public:void someFunction() override {// 派生类的实现}
    };
    
  2. 虚函数表(VTable)
    实现虚函数的机制通常涉及到虚函数表(Virtual Table,简称vtable)。每个含有虚函数的类都会有一个虚函数表,表中存储着该类所有虚函数的地址。每个对象都有一个指向相应类虚函数表的指针(称为vptr)。当通过基类指针或引用调用虚函数时,程序会根据对象的vptr查找到正确的虚函数表,从而调用实际对象类型的函数实现。

  3. 纯虚函数与抽象类
    如果基类中的虚函数没有提供实现,并且在声明时被= 0初始化,那么这个函数被称为纯虚函数。含有纯虚函数的类不能实例化,只能作为基类被继承,这样的类称为抽象类。

使用虚函数的class结构

在这里插入图片描述

相关实现

在这里插入图片描述

  • new的本质,实际上就是malloc+构造函数
  • delete的本质就是析构函数+free
    在这里插入图片描述

源码

#include <iostream>
#include <string>using namespace std;class Person
{public:Person() {age = 0;name = "";};~Person() {};virtual void setName(string name) = 0;virtual void setAge(int age) = 0;
protected:int age;string name;};class Student :public Person {public:Student() {age = 17;}void setName(string name) {this->name = name;}string getName() {return this->name;}void setAge(int age) {if (age > 30 || age < 6) {cout << "not suitable for school!" << endl;return;}this->age = age;}int getAge() {return this->age;}
};int main() {Student* stu = new Student();stu->setName("Link");stu->setAge(18);cout << stu->getName() << " with age " << stu->getAge() << endl;return 0;
}

IDA反编译

在这里插入图片描述
子类构造函数
在这里插入图片描述
父类构造函数
在这里插入图片描述

子类虚表和父类虚表

在这里插入图片描述
纯虚函数的抽象类的虚表由__cxa_pure_virtual填充,有几个纯虚函数就有几个__cxa_pure_virtual,student类虚表分别有各自实现的虚函数,虚表上面是该类的typeinfo的地址
在这里插入图片描述

  • RTTI (Run-Time Type Information) 是C++的一项特性,允许程序在运行时识别对象的类型。
  • typeid运算符和dynamic_cast都是基于RTTI实现的。
  • 每个包含虚函数的类实例,在内存中都有一个隐藏的指针(称为vptr,虚函数指针),指向该类的虚函数表(vtable)。这个vptr是在对象创建时由编译器自动初始化的。
  • RTTI信息通常与vtable一起管理,其中可能包括类型描述信息和继承链信息等。
  1. Aclass* ptra = new Bclass;:这行代码创建了一个指向Bclass实例的指针,但这个指针被声明为Aclass*类型。因为BclassAclass的子类(假设如此),所以这是向上转型,是多态的基础。

  2. int ** ptrvf = (int**)(ptra);:这里进行了一次不安全的类型转换,将Aclass指针转换为了int**。这种转换通常是为了直接访问虚函数指针(vptr),因为在许多系统上,vptr是对象内存布局的第一个元素,可以被视为一个指针的指针。但是,请注意,这种做法非常危险且非标准,违反了类型安全原则。

  3. *((int*)ptrvf[0]-1):这一部分是为了找到RTTI信息的位置。ptrvf[0]访问的是第一个虚函数(按指针算起),减1则是尝试访问vptr前的一个位置,期望那里存放着RTTI相关数据的指针。这一步同样非常危险,依赖于特定编译器和平台的实现细节。

  4. *((RTTICompleteObjectLocator*)(...)):这里进一步将找到的地址强制转换为RTTICompleteObjectLocator指针,并解引用它。RTTICompleteObjectLocator是一个内部使用的结构(非标准公开接口),用于存储有关对象类型的完整信息,比如类的类型描述符、偏移量到类型名称字符串等。

调用函数

在这里插入图片描述
对于虚函数

  1. 先通过对象开始八个字节得到虚表地址
  2. 通过虚表地址和相关偏移得到函数地址
  3. 调用函数
    对于非虚函数
  4. 直接调用再text段实现的函数

菱形继承

#include<stdio.h>
#include<stdlib.h>//间接基类
class A {
public:virtual void function() {printf("A virtual function\n");}int a;
};//直接基类
class B :virtual public A { //虚继承
public:virtual void func() {printf("B virtual func()\n");}int b;
};//直接基类
class C :virtual public A { //虚继承
public:virtual void func() {printf("C virtual func()");}int c;
};//派生类
class D :public B, public C {
public:virtual void function() {printf("D virtual function()");}int d;
};int main(int argc, char** argv) {A* A_ptr = (A*)new D();A_ptr->function();return 0;
}

B和C继承A,D继承B和C

在这里插入图片描述通过虚表地址减去24后的位置的内容是32,然后再加上起始地址,对应到的V4为D内的A的对象内存位置,offset vbase应该是与虚基类的偏移也就是和A的偏移
在这里插入图片描述
在这里插入图片描述
这里给各个父类对象在本对象的空间的前八个字节赋值时,用的是本对象虚表对应到的不同父类的虚函数的地址
在这里插入图片描述

此时对应的虚表地址,所以**两次得到D::function的地址,然后参数为对象自己
在这里插入图片描述

这篇关于C++的类和new和delete和菱形继承机制的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1023833

相关文章

C++使用栈实现括号匹配的代码详解

《C++使用栈实现括号匹配的代码详解》在编程中,括号匹配是一个常见问题,尤其是在处理数学表达式、编译器解析等任务时,栈是一种非常适合处理此类问题的数据结构,能够精确地管理括号的匹配问题,本文将通过C+... 目录引言问题描述代码讲解代码解析栈的状态表示测试总结引言在编程中,括号匹配是一个常见问题,尤其是在

使用C++实现链表元素的反转

《使用C++实现链表元素的反转》反转链表是链表操作中一个经典的问题,也是面试中常见的考题,本文将从思路到实现一步步地讲解如何实现链表的反转,帮助初学者理解这一操作,我们将使用C++代码演示具体实现,同... 目录问题定义思路分析代码实现带头节点的链表代码讲解其他实现方式时间和空间复杂度分析总结问题定义给定

C++初始化数组的几种常见方法(简单易懂)

《C++初始化数组的几种常见方法(简单易懂)》本文介绍了C++中数组的初始化方法,包括一维数组和二维数组的初始化,以及用new动态初始化数组,在C++11及以上版本中,还提供了使用std::array... 目录1、初始化一维数组1.1、使用列表初始化(推荐方式)1.2、初始化部分列表1.3、使用std::

C++ Primer 多维数组的使用

《C++Primer多维数组的使用》本文主要介绍了多维数组在C++语言中的定义、初始化、下标引用以及使用范围for语句处理多维数组的方法,具有一定的参考价值,感兴趣的可以了解一下... 目录多维数组多维数组的初始化多维数组的下标引用使用范围for语句处理多维数组指针和多维数组多维数组严格来说,C++语言没

Spring排序机制之接口与注解的使用方法

《Spring排序机制之接口与注解的使用方法》本文介绍了Spring中多种排序机制,包括Ordered接口、PriorityOrdered接口、@Order注解和@Priority注解,提供了详细示例... 目录一、Spring 排序的需求场景二、Spring 中的排序机制1、Ordered 接口2、Pri

c++中std::placeholders的使用方法

《c++中std::placeholders的使用方法》std::placeholders是C++标准库中的一个工具,用于在函数对象绑定时创建占位符,本文就来详细的介绍一下,具有一定的参考价值,感兴... 目录1. 基本概念2. 使用场景3. 示例示例 1:部分参数绑定示例 2:参数重排序4. 注意事项5.

使用C++将处理后的信号保存为PNG和TIFF格式

《使用C++将处理后的信号保存为PNG和TIFF格式》在信号处理领域,我们常常需要将处理结果以图像的形式保存下来,方便后续分析和展示,C++提供了多种库来处理图像数据,本文将介绍如何使用stb_ima... 目录1. PNG格式保存使用stb_imagephp_write库1.1 安装和包含库1.2 代码解

C++实现封装的顺序表的操作与实践

《C++实现封装的顺序表的操作与实践》在程序设计中,顺序表是一种常见的线性数据结构,通常用于存储具有固定顺序的元素,与链表不同,顺序表中的元素是连续存储的,因此访问速度较快,但插入和删除操作的效率可能... 目录一、顺序表的基本概念二、顺序表类的设计1. 顺序表类的成员变量2. 构造函数和析构函数三、顺序表

使用C++实现单链表的操作与实践

《使用C++实现单链表的操作与实践》在程序设计中,链表是一种常见的数据结构,特别是在动态数据管理、频繁插入和删除元素的场景中,链表相比于数组,具有更高的灵活性和高效性,尤其是在需要频繁修改数据结构的应... 目录一、单链表的基本概念二、单链表类的设计1. 节点的定义2. 链表的类定义三、单链表的操作实现四、

MySQL 缓存机制与架构解析(最新推荐)

《MySQL缓存机制与架构解析(最新推荐)》本文详细介绍了MySQL的缓存机制和整体架构,包括一级缓存(InnoDBBufferPool)和二级缓存(QueryCache),文章还探讨了SQL... 目录一、mysql缓存机制概述二、MySQL整体架构三、SQL查询执行全流程四、MySQL 8.0为何移除查