50-51 - C++对象模型分析

news2025/7/19 6:22:10

---- 整理自狄泰软件唐佐林老师课程

1. 回归本质

1.1 class是一种特殊的struct

  • 在内存中class依旧可以看作 变量的集合
  • class与struct遵循相同的 内存对齐 规则
  • class中的成员函数与成员变量是 分开存放
    • 每个对象有独立的成员变量
    • 所有对象共享类中的成员函数
      在这里插入图片描述

1.2 值得思考的问题

在这里插入图片描述

1.3 编程实验:对象内存布局初探

#include <iostream>
#include <string>

using namespace std;

class A
{
    int i;
    int j;
    char c;
    double d;
public:
    void print()
    {
        cout << "i = " << i << ", "
             << "j = " << j << ", "
             << "c = " << c << ", "
             << "d = " << d << endl;
    }
};

struct B
{
    int i;
    int j;
    char c;
    double d;
};

int main()
{
    A a;
    
    cout << "sizeof(A) = " << sizeof(A) << endl;    // 20 bytes
    cout << "sizeof(a) = " << sizeof(a) << endl;
    cout << "sizeof(B) = " << sizeof(B) << endl;    // 20 bytes
    
    a.print();
    
    B* p = reinterpret_cast<B*>(&a);
    
    p->i = 1;
    p->j = 2;
    p->c = 'c';
    p->d = 3;
    
    a.print();
    
    p->i = 100;
    p->j = 200;
    p->c = 'C';
    p->d = 3.14;
    
    a.print();
    
    return 0;
}

在这里插入图片描述

2. C++对象模型分析

2.1 对象本质分析

  • 运行时的对象退化为 结构体 的形式

  • 所有成员变量在内存中 依次排布

  • 成员变量间 可能存在内存空隙

  • 可以通过对象的内存地址直接访问成员变量

  • 访问权限 在运行时失效
    访问权限在编译时有效,一旦编译通过,运行时就可以通过指针修改成员变量的值

  • 类中的成员函数位于代码段中

  • 调用成员函数时 对象地址 作为参数 隐式传递this指针)

  • 成员函数通过 对象地址 访问成员变量

  • C++语法规则 隐藏了 对象地址的传递过程

2.1.1 编程实验:对象本质分析

#include <iostream>
#include <string>

using namespace std;

class Demo
{
    int mi;
    int mj;
public:
    Demo(int i, int j)
    {
        mi = i;
        mj = j;
    }
    
    int getI()
    {
        return mi;
    }
    
    int getJ()
    {
        return mj;
    }
    
    int add(int value)
    {
        return mi + mj + value;
    }
};

int main()
{
    Demo d(1, 2);
    
    cout << "sizeof(d) = " << sizeof(d) << endl;
    cout << "d.getI() = " << d.getI() << endl;
    cout << "d.getJ() = " << d.getJ() << endl;
    cout << "d.add(3) = " << d.add(3) << endl;
    
    return 0;
}

在这里插入图片描述

2.1.2 用C语言模拟

#include "50-2.h"
#include "malloc.h"

struct ClassDemo
{
    int mi;
    int mj;
};

Demo* Demo_Create(int i, int j)
{
    struct ClassDemo* ret = (struct ClassDemo*)malloc(sizeof(struct ClassDemo));
    
    if( ret != NULL )
    {
        ret->mi = i;
        ret->mj = j;
    }
    
    return ret;
}

int Demo_GetI(Demo* pThis)
{
     struct ClassDemo* obj = (struct ClassDemo*)pThis;
     
     return obj->mi;
}

int Demo_GetJ(Demo* pThis)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
     
    return obj->mj;
}

int Demo_Add(Demo* pThis, int value)
{
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
     
    return obj->mi + obj->mj + value;
}

void Demo_Free(Demo* pThis)
{
    free(pThis);
}

在这里插入图片描述

2.2 继承对象模型

  • 在C++编译器的内部,类可以理解为结构体
  • 子类是由父类成员叠加子类新成员得到的( 父类在前,子类在后

在这里插入图片描述

  • 编程实验:继承对象模型初探
#include <iostream>
#include <string>

using namespace std;

class Demo
{
protected:
    int mi;
    int mj;
public:
    virtual void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << endl;
    }
};

class Derived : public Demo
{
    int mk;
public:
    Derived(int i, int j, int k)
    {
        mi = i;
        mj = j;
        mk = k;
    }
    
    void print()
    {
        cout << "mi = " << mi << ", "
             << "mj = " << mj << ", "
             << "mk = " << mk << endl;
    }
};

struct Test
{
    void* p;
    int mi;
    int mj;
    int mk;
};

int main()
{
    cout << "sizeof(Demo) = " << sizeof(Demo) << endl;         
    cout << "sizeof(Derived) = " << sizeof(Derived) << endl;  
    
    Derived d(1, 2, 3);
    Test* p = reinterpret_cast<Test*>(&d);
    
    cout << "Before changing ..." << endl;
    
    d.print();
    
    p->mi = 10;
    p->mj = 20;
    p->mk = 30;
    
    cout << "After changing ..." << endl;
    
    d.print();
    
    return 0;
}

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

2.3 多态对象模型

  • C++多态的实现原理

    • 当类中声明虚函数时,编译器会在类中生成一个 虚函数表

    • 虚函数表是一个 存储 成员函数地址 的数据结构

    • 虚函数表是由编译器自动生成与维护的

    • virtual成员函数 会被编译器放入虚函数表中

    • 存在虚函数时,每个对象 中都有一个 指向虚函数表的指针

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

  • 编程实验:多态本质分析

在这里插入图片描述

  • 指向虚函数表的指针放在 最开始的4个字节处,可通过如下实验验证:
    在这里插入图片描述

  • 用C语言实现多态:

/* demo.c */
#include "demo.h"
#include "malloc.h"

static int Demo_Virtual_add(Demo* pThis, int value);
static int Derived_Virtual_add(Demo* pThis, int value);

struct VTable // 2、定义虚函数表数据结构
{
    int (*pAdd)(void*, int); // 3、虚函数表里面存储什么??
};

struct ClassDemo
{
    struct VTable* vptr; // 1、定义虚函数表的指针==>虚函数表指针类型??
    int mi;
    int mj;
};

struct ClassDerived {
    struct ClassDemo d;
    int mk;
};

static struct VTable g_Demo_vtbl = {
    Demo_Virtual_add
};

static struct VTable g_Derived_vtbl = {
    Derived_Virtual_add
};

Demo* Demo_Create(int i, int j) {
    struct ClassDemo* ret = \
            (struct ClassDemo*)malloc(sizeof(struct ClassDemo));

    if (ret != NULL) {
        ret->vptr = &g_Demo_vtbl; // 4、关联对象和指向的具体的虚函数表
        ret->mi = i;
        ret->mj = j;
    }
    return ret;
}

int Demo_GetI(Demo* pThis) {
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mi;
}

int Demo_GetJ(Demo* pThis) {
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mj;
}

// 6、定义虚函数表中指针所指向的具体函数
static int Demo_Virtual_add(Demo* pThis, int value) {
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->mi + obj->mj + value;
}

// 5、分析具体的虚函数!!!
int Demo_Add(Demo* pThis, int value) {
    struct ClassDemo* obj = (struct ClassDemo*)pThis;
    return obj->vptr->pAdd(pThis, value);
    // 通过对象,找到指向虚函数表的指针,然后在虚函数表中找到具体要调用的函数地址
}

void Demo_Free(Demo* pThis) {
    free(pThis);
}

Derived* Derived_Create(int i, int j, int k) {
    struct ClassDerived* ret = \
        (struct ClassDerived*)malloc(sizeof(struct ClassDerived));
    
    if( ret != NULL ) {
        ret->d.vptr = &g_Derived_vtbl;
        ret->d.mi = i;
        ret->d.mj = j;
        ret->mk = k;
    }
    
    return ret;
}

int Derived_GetK(Derived* pThis) {
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    return obj->mk;
}

static int Derived_Virtual_add(Demo* pThis, int value) {
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    return obj->mk + value;
}

int Derived_Add(Derived* pThis, int value) {
    struct ClassDerived* obj = (struct ClassDerived*)pThis;
    return obj->d.vptr->pAdd(pThis, value);
}
/* demo.h */
#ifndef _DEMO_H_
#define _DEMO_H_

typedef void Demo;
typedef void Derived;

Demo* Demo_Create(int i, int j);
int Demo_GetI(Demo* pThis);
int Demo_GetJ(Demo* pThis);
int Demo_Add(Demo* pThis, int value);
void Demo_Free(Demo* pThis);

Derived* Derived_Create(int i, int j, int k);
int Derived_GetK(Derived* pThis);
int Derived_Add(Derived* pThis, int value);

#endif
#include <stdio.h>
#include "demo.h"

void run(Demo* p, int v)
{
    int r = Demo_Add(p, v);
    printf("r = %d\n", r);
}

int main()
{
    Demo* pb = Demo_Create(1, 2);
    Derived* pd = Derived_Create(1, 22, 333);
    
    printf("pb->add(3) = %d\n", Demo_Add(pb, 3));
    printf("pd->add(3) = %d\n", Derived_Add(pd, 3));
    
    run(pb, 3);
    run(pd, 3);
    
    Demo_Free(pb);
    Demo_Free(pd);
    
    return 0;
}
 

3. 小结

  • C++中的类对象在内存布局上与结构体相同
  • 成员变量和成员函数在内存中分开存放
  • 访问权限关键字在运行时失效
  • 调用成员函数时对象地址作为参数隐式传递
  • 继承的 本质 是父子间成员变量的 叠加
  • C++中的多态是通过虚函数表实现的
  • 虚函数表是由编译器自动生成与维护的
  • 虚函数的调用效率低于普通函数

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/37888.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenGL ES 学习(四) -- 正交投影

这里的内容基本参考于 https://www.jianshu.com/p/51a405bc52ed &#xff0c;因为写得很好&#xff0c;也没啥补充的&#xff0c;就当做记录一下。 这里先简单解决变形的问题&#xff0c;关于 OpenGL 更多图形矩阵变换&#xff0c;等后面再详细讲。 一. 归一化设备坐标 在Ope…

forplo | 冲冲冲!这个画森林图的包好flexible哦~

1写在前面 我想大家肯定都用过森林图&#xff0c;应用比较多的场景可能是展示meta分析&#xff0c;回归分析结果的时候。&#x1f973; 画森林图的包还是挺多的&#xff0c;今天介绍一下forplo包的用法。&#x1f618; 2用到的包 rm(list ls())library(tidyverse)library(forp…

(第九十三篇)C规范编辑笔记(五)

往期文章&#xff1a; C规范编辑笔记(一) C规范编辑笔记(二) C规范编辑笔记(三) C规范编辑笔记(四) 正文&#xff1a; 继第四篇C规范编辑笔记之后&#xff0c;我们今天来分享第五篇C规范编辑笔记&#xff0c;讲解部分类型初始化时候的建议&#xff0c;话不多说&#xff0c;我…

wodP2P ActiveX 最新版 Crack

wodP2P ActiveX 组件 网络P2P ActiveX 客户端 OCX/DLL&#xff0c;V-P-N 组件&#xff0c;P2P 组件&#xff0c;P2P 库 wodP2P 是 P2P 点对点 ActiveX 组件&#xff0c;用于在两个对等点之间建立虚拟专用网络。所有 P2P 通信都经过加密和保护。对等点能够转发本地和远程端口、发…

Ubuntu 20.04系统中Sage(sagemath)安装及使用详细过程

文章目录一、安装方式一&#xff1a;预编译二进制版本二、安装方式二&#xff1a;源码编译最近在做实验遇到要安装Sage&#xff0c;也是花了将近三天时间才弄好&#xff0c;一波三折整理了一下&#xff0c;以便后续还要安装时能少走弯路。首先&#xff0c;了解一下sage是什么。…

fiddler抓包

首先安装fiddler官网地址 安装完毕之后&#xff0c;这时fiddler软件是抓取不了https的请求数据包的 fiddler 抓取https请求数据包 打开fiddler 一次点击 工具 -> 选项 -> HTTPS 勾选上面选中 的选项 依次进行以下步骤即可 解压fiddler包&#xff0c; 安装fidder 打开…

Servlet到底是什么(非常透彻)

Servlet到底是什么&#xff1f;1. Servlet的概念2. Servlet是一种规范3. Servlet的接口4. JSP是什么学习顺序1. Servlet的概念 Servlet 是 Server Applet 的缩写&#xff0c;译为“服务器端小程序”&#xff0c;是一种使用 Java 语言来开发动态网站的技术。 Servlet 虽然被称…

deepvariant 基因变异识别算法docker版使用

参考&#xff1a;https://github.com/google/deepvariant docker版安装 参考&#xff1a;https://github.com/google/deepvariant/blob/r1.4/docs/deepvariant-quick-start.md 本文是windows上安装的deepvariant 1.4.0版本 docker pull google/deepvariant:1.4.0docker版使用…

用SPDK实现存储加速

个人理解nvme能提高存储性能&#xff0c;就像4G比3G快一样&#xff0c;电磁波还是光速&#xff0c;但协议变了&#xff0c;所以快了。rdma应用跑在用户态能减小存储时延&#xff0c;spdk在用户态实现nvme驱动&#xff0c;天然能和rdma结合&#xff0c;而且两者的队列能一一映射…

python内存泄漏浅析

一、概述 以前没有对内存泄漏有过相关的排查手段&#xff0c;一般个人使用python写的程序&#xff0c;不是那种长时间运行的程序&#xff0c;很少会去注意内存是否出现泄漏&#xff0c;但是如果程序是作为服务器的服务&#xff0c;需要长时间运行的&#xff0c;即使是很小的内…

毕业设计-基于机器视觉的口罩佩戴检测识别

目录 前言 课题背景和意义 实现技术思路 数据来源 COCO数据集预训练模型 图片检测 视频检测 训练&评估结果 实现效果图样例 前言 &#x1f4c5;大四是整个大学期间最忙碌的时光,一边要忙着备考或实习为毕业后面临的就业升学做准备,一边要为毕业设计耗费大量精力。近…

结冰过程渲染-Ovito实现

结冰过程渲染-Ovito实现结冰过程渲染后结果一、渲染步骤-主要突出内容二、识别并区分冰晶和溶液三、渲染溶液中的水四、渲染出溶液中的冰五、突出溶液中溶质、金属板的显示六、data测试文件下载结冰过程渲染后结果 一、渲染步骤-主要突出内容 这里我们主要研究掺杂溶质如何影响…

k8s网络插件之Flannel

Flannel简介 Flannel官网&#xff1a;https://github.com/coreos/flannel Flannel是由CoreOS开源的针对k8s的网络服务&#xff0c;其目的是为解决k8s集群中各主机上Pod之间的通信问题&#xff0c;其借助etcd维护网络IP地址分配&#xff0c;并为每个Node节点分配一个不同的IP地…

学生HTML个人网页作业作品 HTML+CSS校园环保(大学生环保网页设计与实现)

&#x1f380; 精彩专栏推荐&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb;&#x1f447;&#x1f3fb; ✍️ 作者简介: 一个热爱把逻辑思维转变为代码的技术博主 &#x1f482; 作者主页: 【主页——&#x1f680;获取更多优质源码】 &#x1f393; web前端期末大作业…

redis : 持久化

redis通过将数据放在内存里实现高速访问&#xff0c;为了防止意外情况&#xff0c;其数据也可以存放起来 持久化的实现方式有两种方案&#xff1a;一种是直接保存当前已经存储的数据&#xff0c;相当于复制内存中的数据到硬盘上&#xff0c;需要恢复数据时直接读取即可&#x…

代理模式与动态代理深入理解

一&#xff0c;代理模式的简单认识 1.参与者&#xff1a; 代理对象&#xff0c;被代理对象 代理对象相当于现实生活中的房产中介&#xff0c;被代理对象 相当于 房东 2.目的&#xff1a;保护被代理对象 避免外界直接修改被代理对象&#xff0c;破坏掉被代理对象原本的功能。…

KubeGems容器云平台体验

KubeGems容器云平台体验 KubeGems 是一款开源的企业级多租户容器云平台。围绕云原生社区&#xff0c;KubeGems 提供了多 Kubernetes 集群接入能力&#xff0c;并具备丰富的组件管理和资源成本分析功能&#xff0c;能够帮助企业快速的构建和打造一个本地化、功能强大且低成本的…

关于BigInteger和BigDecimal

BigInteger BigInteger类是用于解决整形类型(含基本数据类型及对应的包装类,)无法表示特别大的数字及运算的问题,即使是占用字节数最多的整形long,能表示的范围也是有限的.理论上,你可以使用BigInteger表示任意整数基于java8中BigInteger的构造方法. BigDecimal的构造方法2 …

Java代码审计基础——RMI原理和反序列化利用链

目录 &#xff08;一&#xff09;何为RMI &#xff08;二&#xff09;、 RMI的模式与交互过程 0x01 设计模式 0x02 交互过程 0x03 Stub和Skeleton &#xff08;三&#xff09;简单的 RMI Demo 1、Server 2、Registry 3、Client 补充——动态类加载机制 几个函数 (…

Java集合框架详解(四)——Map接口、HashMap类、LinkedHashMap类

一、Map接口 Map接口的特点&#xff1a; &#xff08;1&#xff09;映射键值对的形式&#xff08;key和value&#xff09;&#xff1b; &#xff08;2&#xff09;Map集合中&#xff0c;key是不能重复的&#xff0c;value是可以重复的&#xff1b; &#xff08;3&#xff09;…