C++之类型转换

C语言中的类型转换

在C语言中, 如果赋值运算符左右两侧类型不同, 或者形参与实参类型不匹配, 或者返回值类型与
接收返回值类型
不一致时, 就需要发生类型转化, C语言中总共有两种形式的类型转换:

隐式类型转换和显式类型转换

1. 隐式类型转化是关联度很强, 意义相近的类型之间的转换, 编译器在编译阶段自动进行, 能转就转, 不能转就编译失败, 
2. 显式类型转化是有一定关联的类型, 需要用户自己处理.

void Test ()
{int i = 1;// 隐式类型转换double d = i;printf("%d, %.2f\n" , i, d);int* p = &i;// 显示的强制类型转换int address = (int) p;printf("%x, %d\n" , p, address);
}

转换的可视性比较差, 所有的转换形式都是以一种相同形式书写, 难以跟踪错误的转换


C++强制类型转换 

标准C++为了加强类型转换的可视性, 引入了四种命名的强制类型转换操作符:

1. static_cast

2. reinterpret_cast

3. const_cast

4. dynamic_cast 

static_cast 

static_cast用于非多态类型的转换(静态转换), 编译器隐式执行的任何类型转换都可用
static_cast, 但它不能用于两个不相关的类型进行转换, static_cast对应于C语言的隐式类型转换.

void Test2()
{int a = 12;double b = static_cast<double>(a);//对应double b = a;C语言的隐式类型转换cout << b << endl;
}

 一种错误写法是这样的:

void test2()
{    //错误写法int* pa = static_cast<int*>(a);cout << pa << endl;
}

 

这就不属于相关类型即(隐式类型转换)的范畴, 这就要用到reinterpret_cast转换了

 reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释, 适用于不相关类型之间的转换,即重新解释一种类型的含义, 用于将一种类型转换为另一种不同的类型, 对应C语言中的显示类型转换.

void Test2()
{int a = 12;//static_castdouble b = static_cast<double>(a);//对应double b = a;C语言的隐式类型转换cout << b << endl;//reinterpret_castint* pa = reinterpret_cast<int*>(a);cout << pa << endl;
}

const_cast

const_cast最常用的用途就是删除变量的const属性, 方便赋值.

常用方法: 利用const_cast 转换为同类型非 const 引用或者指针

  • <>内为转换的目标,()内为要转换的值
  • const_cast只针对指针、引用、this指针
void Test4()
{const int a = 2;int* p = const_cast<int*>(&a);*p = 3;cout << a << endl;cout << *p << endl;cout << &a << endl;cout << p << endl;
}

这里我们将 a 的地址通过 const_cast 转换之后赋值给指针变量 p, 取消了&a的底层const属性, 然后通过p将a的值修改为3, 通过输出可以看到a输出的值是2, *p的值是3, 而p和&a实际确实是同一块地址, 为什么呢?

因为变量a在定义时被 const 修饰, 而编译器认为const修饰值不会被修改, 所以编译器会进行一些优化, 比如将a的值放入一个寄存器中, 以后每次使用 a 都直接从该寄存器中读取, 而不再从内存中读取, 提高了效率, 这就导致我们虽然通过指针变量p修改了内存中a的值, 但寄存器中保存的仍然是a修改之前的值, 所以打印出来的是 2.

要解决这个问题很简单, 我们在定义常变量 a 时使用 volatile 关键字进行修饰即可, volatile 关键字的作用是保持内存可见性, 即取消编译器的优化, 每次都从内存中读取变量的值.

void Test4()
{volatile const int a = 2;int* p = const_cast<int*>(&a);//等价于int* p = (int*)&a;*p = 3;cout << a << endl;cout << *p << endl;cout << (void*) & a << endl;cout << p << endl;
}

 这里需要注意:

再举个例子: 

void Test5()
{char ch = 'x';cout << &ch << endl;
}

void Test5()
{char ch = 'x';cout << (void*)&ch << endl;
}

 

回到主题, 之前const_cast的例子其实可以反映出为什么 C++ 要重新专门去设计一系列的类型转换, 比如const_cast 强制类型转换操作符来用于 const 类型和非const类型之前的转换, 比如这里它就提醒了程序员这里有const属性的删除, 要考虑是否需要加volatile之类的注意事项. 虽然直接把 &a 强制转换为int*也可以, 但不容易看出来问题出在哪.

dynamic_cast

之前在继承中提到过, 基类对象不能赋值给派生类对象:

向上转型: 子类对象指针/引用->父类指针/引用(不需要转换, 赋值兼容规则)
向下转型: 父类对象指针/引用->子类指针/引用(用dynamic_cast转型是安全的) 

C++为我们提供了更安全的父类与子类对象之间的转换: dynamic_cast

dynamic_cast用于将一个父类对象指针/引用转换为子类对象指针/引用(动态转换), 也就是说dynamic_cast应对的是向下转型.

注意:

1. dynamic_cast只能用于父类含有虚函数的类
2. dynamic_cast会先检查是否能转换成功, 能成功则转换, 不能则返回0

class A
{
public:virtual void f() {}
};class B : public A
{};void fun(A* pa)
{// dynamic_cast会先检查是否能转换成功,能成功则转换,不能则返回B* pb1 = static_cast<B*>(pa);B* pb2 = dynamic_cast<B*>(pa);cout << "pb1:" << pb1 << endl;cout << "pb2:" << pb2 << endl;
}int main()
{A a;B b;fun(&a);cout << "------------------------------" << endl;fun(&b);return 0;
}

当fun传入&a时, dynamic_cast会检查发现pa指向的是一个A类型的对象, 所以不能转换为B类型对象的指针, 所以给pb2返回了一个0, 而static_cast则只是相近类型的类型转换,  不会进行检查.

给A和B类添加成员变量, 并且我在fun内想通过pb2去修改_a和_b:

class A
{
public:virtual void f() {}int _a = 0;
};class B : public A
{
public:int _b = 1;
};void fun(A* pa)
{B* pb2 = (B*)pa;pb2->_a++;pb2->_b++;
}int main()
{A a;B b;fun(&a);fun(&b);return 0;
}

程序崩溃了, 因为对原本就指向A类型对象的指针强转为B类型指针, 再去访问B类型的对象, 就是越界访问了, 是错误的行为. 

我们可以用dynamic_cast这样修改: 

void fun(A* pa)
{B* pb = dynamic_cast<B*>(pa);if (pb){pb->_a++;pb->_b++;cout << "转换成功" << endl;}else{cout << "转换错误" << endl;}
}


注意

强制类型转换关闭或挂起了正常的类型检查, 每次使用强制类型转换前, 程序员应该仔细考虑是否还有其他不同的方法达到同一目的, 如果非强制类型转换不可, 则应限制强制转换值的作用域, 以减少发生错误的机会, 建议避免使用强制类型转换.


 RTTI

RTTI: Run-time Type identification的简称, 即运行时类型识别
C++通过以下方式来支持RTTI:

  • typeid: 在运行时识别出一个对象的类型.
  • decltype: 在运行时推演出一个表达式或函数返回值的类型.
  • dynamic_cast: 在运行时识别出一个父类的指针/引用指向的是父类对象还是子类对象.

注意: C++ 中的 auto 并不属于 RTTI, auto 是一种变量类型推导机制, 它能够根据变量的初始化表达式自动推导出变量的类型, 属于编译时识别, 而 RTTI 是一种运行时类型识别机制.

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

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

相关文章

【CSP试题回顾】201512-2-消除类游戏

CSP-201512-2-消除类游戏 解题思路 输入棋盘大小和颜色: 首先&#xff0c;程序从标准输入读取两个整数n和m&#xff0c;分别代表棋盘的行数和列数。然后&#xff0c;程序读取接下来的n行输入&#xff0c;每行包含m个整数&#xff0c;代表棋盘上每个方格中的棋子颜色。 初始化…

[蓝桥杯 2017 省 A] 油漆面积 Java代码及一些个人理解

[蓝桥杯 2017 省 A] 油漆面积 题目描述 X 星球的一批考古机器人正在一片废墟上考古。 该区域的地面坚硬如石、平整如镜。 管理人员为方便&#xff0c;建立了标准的直角坐标系。 每个机器人都各有特长、身怀绝技。它们感兴趣的内容也不相同。 经过各种测量&#xff0c;每个…

【论文速读】 | AI驱动修复:漏洞自动化修复的未来

本次分享论文为&#xff1a;AI-powered patching: the future of automated vulnerability fixes 基本信息 原文作者&#xff1a;Jan Nowakowski, Jan Keller 作者单位&#xff1a;Google Security Engineering 关键词&#xff1a;AI, 安全性漏洞, 自动化修复, LLM, sanitiz…

Objective-C blocks 概要

1.block的使用 1.1什么是block&#xff1f; Blocks是C语言的扩充功能&#xff1a;带有自动变量&#xff08;局部变量&#xff09;的匿名函数。 “带有自动变量”在Blocks中表现为“截取自动变量" “匿名函数”就是“不带名称的函数” 块&#xff0c;封装了函数调用及调用…

全方位碾压chatGPT4的全球最强模型Claude 3发布!速通指南在此!保姆级教学拿脚都能学会!

&#x1f389;&#x1f389;欢迎光临&#xff0c;终于等到你啦&#x1f389;&#x1f389; &#x1f3c5;我是苏泽&#xff0c;一位对技术充满热情的探索者和分享者。&#x1f680;&#x1f680; &#x1f31f;持续更新的专栏《Spring 狂野之旅&#xff1a;从入门到入魔》 &a…

Android开发岗还不会这些问题,温故而知新

前言 前前后后经历过大项目、小项目&#xff0c;跨平台&#xff0c;小程序&#xff0c;Nodejs服务等等&#xff0c;目前在做的Rom开发&#xff0c;定制各种手机中的奇葩需求&#xff0c;从应用层到Framework层&#xff0c;再到C层&#xff0c;再到驱动&#xff0c;最终到Linux&…

MybatisPlus入门详解

一、MyBatisPlus 简介 1.1 创建新模块 <dependency><groupId>com.baomidou</groupId><artifactId>mybatis-plus-boot-starter</artifactId><version>3.4.1</version></dependency> 由于mp并未被收录到idea的系统内置配置,无法…

Solidity Uniswap V2 Pair中添加流动性

添加流动性的功能的用户入口&#xff0c;UniswapV2在UniswapV2Router中实现&#xff0c;它用来计算新的流动性并发行LP-Token&#xff0c;流动性管理简单地视为LP-Token管理。当你为一个pair增加流动性时&#xff0c;合约会创造LP Token;当你移除流动性时&#xff0c;LP-Token就…

【debug】element-ui时间控件回显后不可编辑且显示为空

问题&#xff1a;使用element-ui的时间控件回显数据&#xff0c;编辑数据没有反应&#xff1a;点时间和“确认”按钮都没反应。 输入框中会显示数据&#xff0c;但提交时的校验显示为空。 <el-form-item label"开始时间" prop"limitStartTime"><…

局部最小值问题

局部最小值问题&#xff1a; 条件&#xff1a;n个数的无序数组 array&#xff0c;相邻两个数一定不相等&#xff0c;找出其中的一个局部最小值。 如果array[0]< array[1] ,返回 array[0];如果array[n-1]<array[n-2]&#xff0c;返回array[n-1];其他的位置需要满足 array[…

xss.haozi.me:0X0D

alert(1) -> 记住要回车一下-->是js的一个注释符但是只能用在最前面前面有一个空格都不行

【LeetCode】升级打怪之路 Day 14:二叉树的遍历

今日题目&#xff1a; 144. 二叉树的前序遍历94. 二叉树的中序遍历145. 二叉树的后序遍历102. 二叉树的层序遍历107. 二叉树的层序遍历 II199. 二叉树的右视图637. 二叉树的层平均值429. N 叉树的层序遍历515. 在每个树行中找最大值116. 填充每个节点的下一个右侧节点指针117. …

【YOLO v5 v7 v8 v9小目标改进】RevCol:解决深度学习信息从低层(输入)传递至高层(输出)的过程中,信息会逐层丢失问题

RevCol&#xff1a;解决深度学习信息从低层&#xff08;输入&#xff09;传递至高层&#xff08;输出&#xff09;的过程中&#xff0c;信息会逐层丢失问题 学习解耦表示可逆列网络&#xff08;RevCol&#xff09;子特征1&#xff1a;多级可逆单元子特征2&#xff1a;可逆列架构…

ARM电源策略单元Power Policy Unit

本规范描述了电源策略单元&#xff08;PPU&#xff09;架构。它定义了PPU架构的1.1版本。 PPU规范的1.1版本增加了操作模式支持。这使得能够为功能或更高粒度的电源控制原因提供组件特定的电源模式支持。 在本文档中&#xff0c;对PPU或PPU的引用指的是实现了此PPU架构的设备…

十四、重写与多态

重写、多态 上一讲是&#xff0c;子类对父类横向上的扩展 这一讲是&#xff0c;子类对父类纵向上的扩展 方法重写 使用override关键字重写父类的方法 将父类原本方法的逻辑更新成新版本的逻辑 注&#xff1a;仅能重写可见的父类成员&#xff0c;并且重写要保持签名一致。 签名一…

GEE 将裁剪后的Sentinel-2影像 在ArcGIS中去除空值

在ArcGIS中&#xff0c;可以使用栅格计算器&#xff08;Raster Calculator&#xff09;工具来设置NoData值为空。以下是在ArcGIS中将NoData值设置为空的步骤&#xff1a; 打开ArcGIS软件并加载下载的Sentinel-2影像数据。 影像Nodata空值以黑色背景呈现&#xff0c;影响矢量数据…

SpringCloud2023最新版本该如何进行组件选型?

前言 Developing distributed systems can be challenging. Complexity is moved from the application layer to the network layer and demands greater interaction between services. Making your code ‘cloud-native’ means dealing with 12-factor issues such as exte…

鸿蒙4.0-DevEco Studio界面工程

DevEco Studio界面工程 DevEco Studio 下载与第一个工程新建的第一个工程界面回到Project工程结构来看 DevEco Studio 下载与第一个工程 DevEco Studio 下载地址&#xff1a;点击跳转 https://developer.harmonyos.com/cn/develop/deveco-studio#download 学习课堂以及文档地址…

《金三银四求职攻略》:程序员面试季倒计时

程序员的金三银四求职宝典 大家好&#xff0c;我是小明&#xff0c;一位即将面临春季求职季的程序员。在这个黄金时段&#xff0c;如何在众多应聘者中脱颖而出&#xff0c;拿下理想的offer&#xff0c;成为了我思考的重点。今天&#xff0c;我将分享一些我个人的求职攻略&…

增强型YOLOv5应用于扩展PASCAL VOC数据集

目录 摘要关键词 1. 引言2. 性能研究2.1 YOLO2.2 PASCAL VOC 2007 数据集2.3 MS COCO 数据集 3 实验方法3.1 利用COCO数据集扩展PASCAL VOC 2007&#xff08;以elephant大象为例&#xff09;3.2 方法实现&#xff08;以YOLOv5为例&#xff09;3.2.1 YOLOv5介绍3.2.2 YOLOv5…