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…

代码随想录算法训练营第五十一天|309. 买卖股票的最佳时机含冷冻期、714. 买卖股票的最佳时机含手续费。

309. 买卖股票的最佳时机含冷冻期 题目链接&#xff1a;买卖股票的最佳时机含冷冻期 题目描述&#xff1a; 给定一个整数数组prices&#xff0c;其中第 prices[i] 表示第 i 天的股票价格 。​ 设计一个算法计算出最大利润。在满足以下约束条件下&#xff0c;你可以尽可能地完成…

java项目启动脚本

在Linux系统上发布Java项目通常涉及以下步骤&#xff1a; 打包项目&#xff1a;首先&#xff0c;需要将Java项目打包成可执行的 JAR 文件。你可以使用构建工具如 Maven 或 Gradle 来构建项目并生成 JAR 文件。上传JAR文件&#xff1a;将打包好的 JAR 包上传到服务器的目标位置…

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的系统内置配置,无法…

使用 Tesseract 在 C# 中进行光学字符识别(OCR)

使用 Tesseract 在 C# 中进行光学字符识别&#xff08;OCR&#xff09;&#xff1a;完整教程 引言一、准备工作步骤 1&#xff1a;安装 Tesseract OCR 引擎步骤 2&#xff1a;安装 Tesseract.NET 包 二、进行光学字符识别步骤 1&#xff1a;创建 Tesseract 实例步骤 2&#xff…

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;可逆列架构…

父子组件嵌套时候,生命周期的加载顺序

1.当子组件同步引入时&#xff1a; 父组件&#xff1a;beforecreate 父组件&#xff1a;created 父组件&#xff1a;beforeMount 子组件&#xff1a;beforecreate 子组件&#xff1a;created 子组件&#xff1a;beforeMount 子组件&#xff1a;mounted 父组件&#xff…

令马斯克眼红到起诉的GPT-4到底是什么?

令马斯克眼红到起诉的GPT-4到底是什么&#xff1f; 在人工智能&#xff08;AI&#xff09;的发展历程中&#xff0c;GPT-4的问世无疑是一大里程碑。 但就在这项技术引领AI行业走向新高度之时&#xff0c;特斯拉CEO埃隆马斯克因与OpenAI及其CEO萨姆奥尔特曼等人在合同协议上的…

Git命令(持续更新中...)

命令说明git config --globl user.name “用户名”配置本地git用户git config --global user.email “邮箱”配置本地git邮箱git init初始化git代码仓库git add .把当前目录的所有改动提交至暂存区git add xxx.txt只把xxx.txt某个指定的文件提交至暂存区git commit -m “提交信…

JVM学习目录

JVM ✅ JVM运行时内存结构 ✅ JVM常用启动参数 ✅ JVM内存分配与垃圾收集流程 ✅ 什么是垃圾回收机制&#xff08;Garbage Collection&#xff0c;简称GC&#xff09; ✅ 如何调用垃圾回收器的方法 ✅ GC如何判定对象已死 ✅ 方法区的垃圾收集 ✅ 垃圾收集算法 ✅ JVM垃圾回…