1.2 C++编译器对指针的解释方式(深度理解c++指针)

news2024/11/25 6:44:35

1.2 指针

1.2.1 指针解释方式

从内存的角度,一个指向类对象的指针与一个指向整数类型的指针或一个指向数组的指针,三者之间是没有任何区别的,它们内部都只存储了一个机器地址值(word)。不同类型指针的区别仅在于其寻址出来的object类型的不同,也就是"指针类型"会教导编译器如何解释指针指向地址的内存内容以及大小。
编译器会根据指针指向的类型来进行寻址,例如对于一个指向整数的指针,在64位的机器上,编译器就会从指针指向的起始位置开始寻址8个字节。这也说明了为什么void*的指针只能持有一个地址,而不能操作它指向的对象,因为编译器并不知道这个指针的指向类型,也就无法寻址。当然也可以通过static_castvoid*类型指针转换为具体类型,这个操作并不改变一个指针所含的真实地址,它只会影响"指针指向内存的大小和其内容"的解释方式。
对于如下的代码是一个类定义题,一个指向整型1000的指针和一个指向ZooAnimal的指针在内存上如下所示,是没有任何区别的。但是因为两者指针类型的不同,所以编译器在解释它们指向地址时会有所不同,在64位系统上,int *则只会解释为向后8字节,而如下的ZooAnimal实际对象则会被解释为8+8+8=32字节。
Alt

class ZooAnimal {
public:
    ZooAnimal() = default;

    virtual ~ZooAnimal() = default;
    virtual void rotate();

    int getLoc() const { return loc; }
    const string &getName() const { return name; }

protected:
    int loc;
    std::string name;
};

1.2.2 继承后解释方式

对于如下含有继承与多态的代码,在主函数里定义了三个变量b、pb、rb,其内存布局如下所示:无论是指针还是引用,其本质上都是一个8字节的指针,而Bear对象b则需要48字节:自己的8字节加上基类的32字节。
Alt

class Bear : public ZooAnimal {
public:
    Bear() = default;

    virtual ~Bear() = default;
    virtual void dance();

    Dances getDancesKnown() const { return dances_known; }
    int getCellBlock() const { return cell_block; }

protected:
    enum class Dances {
    };

    Dances dances_known;
    int cell_block;
};

int main() {
    Bear b;
    Bear *pd = &b;
    Bear &rb = b;
}

在多态情况下,一个Bear指针与一个ZooAnimal指针是不同的,例如如下的定义,pz、pb都是指向Bear对象b的指针,但是它们两个的差别在于pb所涵盖的地址包含整个Bear object,而pz涵盖的地址只包含Bear object中的Zoo Animal的基类部分。对于上图即pb指向的是整个对象,而pz则只能指向深色部分。
Alt

Bear b;
ZooAnimal *pz = &b;
Bear *pb = &b;

所以父类指针是无法访问到子类的数据成员的,也无法访问到其相应的非虚成员函数。因为其本质上是一个父类类型的指针,编译器在调用时只会通过父类的大小来划分相应的内存块,是无法访问到子类成员的,也就是上图的白色部分,当然这里可以通过static_castdynamic_cast或实现向上转型。且这里更推荐使用dynamic_cast,但是这种转型是运行时转型,成本也会更高。

//    pz->getDancesKnown();  不合法, pz是无法访问到dances_known的

cout << (static_cast<Bear *>(pz))->getCellBlock();     //合法,通过显式的downcast

if (Bear *pb2 = dynamic_cast<Bear *>(pz)) {   //这个更好,但是这个是运行时操作,成本更高
    cout << pb2->getCellBlock();
}

1.2.3 指针实现多态

对于上述的BearZoo Animal类,其都含有一个虚函数rotate1.2.2部分已近介绍了是不能通过基类指针访问子类的数据成员与非虚函数的,这里有一个例外即虚函数机制。
在运行过程中,pz所实际指向的object类型可以决定rotate()所调用的实例。这个类型的裁决不是取决于pz的指针类型,而是取决于虚指针与虚表中。
在介绍虚机制之前,首先介绍为什么普通对象无法实现虚机制。对于如下所示的代码,第二行将子类对象b赋值给基类对象za,这时会引起切割,也就是在内存栈中生成一个新的只包含灰色部分的内存快,所以这个过程是一个子类数据成员的部分拷贝操作。因此可以看到基类对象是无法通过函数调用运算符来实现多态的,因为在内存里其已经不包含了子类的数据成员。
Alt

Bear b;
ZooAnimal za = b;
za.rotate();

当然这里还存在一个问题是既然子类对象被赋值给了基类对象,那为什么子类的虚函数指针没有被复制过去,这样就可以实现za.rotate调用Bearrotate函?
这是因为编译器在对基类对象用子类对象做初始化时,父类的虚函数指针不会被子类的虚函数指针所初始化,而是被编译器直接指定相应的值,这个值即父类实际的虚函数表指向。

下面介绍指针为什么就可以实现多态,假设新增了一个继承于Bear的新子类Panda,其继承体系如下所示:

Alt

下述的代码块内存布局如下所示。可以注意到,将父类指针pza指向子类对象b是完全合法的,虽然这个指针类型此时是基类ZooAnimal,也就是其只能寻找基类部分的数据成员,但是注意到这部分数据成员已经足够,因为这部分已经含有子类Pandas的虚表指针,虚函数的调用不是由指针类型而决定的,是由虚函数指针和虚函数表所决定的,而这里显然是Panda的虚函数指针,所以才能够实现父类指针调用子类对象。

ZooAnimal za;
ZooAnimal *pza;
    
Bear b;
Pandas **pp=new Pandas();
pza=&b;

Alt

总结:当一个父类对象被直接赋值为一个子类时,子类对象内部的值会被切割,以塞入到父类对象内存中,只保留父类相应的数据成员。同时子类的虚表指针也不会被直接初始化,父类虚表指针的值会由编译器直接指定。所以直接赋值后,子类的任何特征都不在基类中了,因此多态也不在呈现。同时,如果一个编译器比较优秀,当基类对象直接调用虚函数时例如za.rotate,编译器也可以在编译阶段就直接解析到相应的调用函数,进而回避virtual机制。
多态机制的强大之处在于对于一个抽象的pulic接口之后,还可以封装相应的类型,实现多类型调用。多态的实际利用可以参考:多态的应用

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

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

相关文章

div+css布局实现个人网页设计(HTML期末作业)

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

Spring源码深度解析:七、bean的加载① - doGetBean概述

我们先通过getBean()流程图&#xff0c;来了解Spring的getBean()方法的工作流程&#xff0c;接着根据这个工作流程一步一步的阅读源码 一、前言 文章目录&#xff1a;Spring源码分析&#xff1a;文章目录 getBean()方法是spring ioc的核心&#xff0c;阅读getBean()方法的源…

【Spring Boot+Vue.js+JPA+Mysql】实现前后端分离的名片系统(附源码 超详细必看 可作为大作业使用)

需要项目源码请点赞关注收藏后评论区留言并且私信~~~ 一、项目简介 前后端分离的核心思想时前端页面通过掉用后端的RESTfulApI进行数据交互。本次项目使用Spring BootSpring Data JPA实现后端系统&#xff0c;使用Vue.js实现前端系统&#xff0c;数据库采用mysql&#xff0c;集…

磨金石教育摄影技能干货分享||如何将平凡的窗户拍出美感

窗户有着天然的构图优势&#xff0c;一直是摄影爱好者们喜欢拍的场景。都说眼睛是心灵的窗户&#xff0c;窗户其实就是房间窥探世界的眼睛。 特别是在中国文化中&#xff0c;对窗户的艺术雕刻&#xff0c;总是那么侧重。一间房子好不好看&#xff0c;窗户的设计往往是较为重要…

Java里的异常机制

一、什么是异常 软件程序在运行过程中&#xff0c;遇到用户输入不符合要求、文件路径不存在、文件格式错误、非法参数等的异常问题&#xff0c;叫做异常&#xff08;Exception&#xff09;。 二、简单分类 1.检查性异常 最具代表的检查性异常就是用户错误或问题引起的异常&…

计算机网络:网络层

网络层 网络层主要是解决寻址连接问题&#xff0c;例如两个主机在网络上通过IP进行连接通信 1.网络层概述 网络层的主要任务是实现网络互联&#xff0c;进而实现数据包在各网络之间的传输 需要解决的主要问题&#xff1a; 因特网 使用TCP/IP协议栈通过学习TCP/IP协议栈的网…

Nginx:handler 模块的实现

文章目录1、模块的分类2、模块的基本结构2.1、模块配置结构2.2、模块配置命令2.3、模块上下文结构2.4、模块的定义3、http 请求处理3.1、请求处理阶段3.2、获取用户请求3.3、发送响应4、例&#xff1a;流量限制模块4.1、操作共享内存4.1.1、红黑树4.1.2、双向链表4.2、编写模块…

APS排程软件在压铸行业的应用

压铸是一种金属铸造工艺&#xff0c;其特点是利用模具内腔对融化的金属施加高压。模具通常是用强度更高的合金加工而成的&#xff0c;这个过程有些类似注塑成型。大多数压铸铸件都是不含铁的&#xff0c;例如锌、铜、铝、镁、铅、锡以及铅锡合金以及它们的合金。根据压铸类型的…

js:判断文本溢出隐藏生效text-overflow: ellipsis

效果展示 参数汇总 看上图&#xff0c;不难发现&#xff0c;文字有超出的条件是 target.scrollWidth > target.offsetWidth可以通过js判断是否生效&#xff0c;参考element-ui的代码实现 https://github.com/ElemeFE/element/blob/dev/packages/table/src/table-body.js#…

RocketMQ的主要组件及其功能

一、RocketMQ部署的组件 RocketMQ是啥就不多说了&#xff0c;一个基于主题的订阅发布机制的消息中间。下面就是我们部署时的架构&#xff0c;NameServer和Broker需要部署在服务器上&#xff0c;对于消费者和生产者则是我们在自己的程序里启动&#xff0c;去push/pull消息。 消…

rust变量与常量

变量绑定 在rust里有个核心原则&#xff0c;那就是所有权。在其它语言中&#xff0c;我们可以把一个值赋值给变量。但是在rust里&#xff0c;是把值绑定到变量上。任何内存对象都是有主人的&#xff0c;而且一般情况下完全属于它的主人&#xff0c;绑定就是把这块内存绑定给一…

路由器的工作原理,详细介绍

1、路由器的作用 路由器&#xff1a; router 作用&#xff1a;实现跨网段通信&#xff0c;不同的网络之间通信 交换机&#xff1a; switch 作用&#xff1a;组建局域网&#xff0c;就是将电脑通过网络连起来 交换机的原理参考文档&#xff1a;计算机网络之交换机的工作原理…

前端React项目的Next.js项目通过CSS引入自定义字体文件

最近在Web3的项目&#xff0c;需要引入自定义字体&#xff0c;做下记录&#xff1a; 1、 如果是下载的字体文件&#xff0c;直接能使用的就不需要转换&#xff0c;如果是TTF格式则需要转换成eot、svg、woff、woff2&#xff0c;这里提供一个网站Font Squirrel | Create Your Ow…

ROS之话题通信

文章目录理论模型分析流程1. 发布方2. 订阅方3.配置 CMakeLists.txt4.执行5.注意参考理论模型 话题通信实现模型是比较复杂的&#xff0c;该模型如下图所示,该模型中涉及到三个角色: ROS Master (管理者)Talker (发布者)Listener (订阅者) ROS Master 负责保管 Talker 和 Li…

简化javabean开发-->Lombok

目录 一.Lombok 1.Lombok 介绍 1.1Lombok 作用 1.2SpringBoot 和 IDEA 官方支持 2.Lombok 常用注解 3.Lombok 应用实例 3.1在 pom.xml 引入 lombok 3.2. 修改 Furn.java 3.3在 idea 安装 lombok 插件 一.Lombok 1.Lombok 介绍 1.1Lombok 作用 1. 简化 JavaBean 开…

Camtasia2023喀秋莎录屏软件下载操作教程

Camtasia软件2023最新版是一款电脑屏幕录制与视频剪辑的软件&#xff0c;功能强大且操作简单。可以使用该软件对视频进行添加滚动字幕的效果&#xff0c;并且还可以选择注释标注的样式、主题以及形状等。在内置的视频编辑器中对视频进行剪辑时还可以拖放文本、添加效果、添加过…

VTK-vtkImplicitFunction及其子类介绍

简介&#xff1a;本文主要介绍vtkImplicitFunction接口及其子类的实现原理和用途。 目录 1. vtkPlane 2. vtkPolyPlane 1. vtkPlane 描述&#xff1a;vtkPlane提供了各种平面的计算方法&#xff0c;包括点到面的投影&#xff0c;计算点到面的距离及面的法向量等。 Evaluat…

解析分布式数据库的技术框架及其在金融行业中的应用规划

早期银行业务系统处理的主要是交易型数据,数据量较少,传统关系型数据库(如Oracel、DB2等)已足够应对。随着互联网金融业务的快速发展,业务系统需要处理的数据呈爆炸式增长,传统数据库无法满足业务系统越来越高的数据处理能力要求。于是,新型的分布式数据库系统应运而生。…

Linux安装Redis 手把手教程

文章目录安装步骤1. 创建安装目录/usr/local/redis2. 进入安装包目录3. 编译环境准备&#xff1a;4. 下载redis 源码包5. 解压文件6. 进入到解压好的redis-5.0.2目录下&#xff0c;进行编译与安装7. 启动并指定配置文件8. 配置允许远程连接&#xff08;选做&#xff09;9. 启动…

文献|敬畏这种情绪,居然可以让世界变得更美好

Hello&#xff0c;大家好~ 这里是壹脑云科研圈&#xff0c;我是青书~ 在介绍今天推荐的文献之前&#xff0c;要先和大家宣布一个非常棒的消息&#xff0c;我们的第二季21天情绪文献对赌营圆满结营啦~ 在本期活动里&#xff0c;各位参加的小伙伴都有属于自己的收获&#xff1…