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.hqwc.cn/news/517991.html

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

相关文章

App 测试必备 - 建议所有测试人收藏

移动端App性能测试需要关注多个方面&#xff0c;包括响应时间、稳定性、内存使用、CPU使用率、网络性能、电池消耗以及设备兼容性等。通过综合考虑这些方面&#xff0c;并在不同条件下进行全面的测试&#xff0c;可以确保应用程序在各种情况下都能够提供优质的用户体验&#xf…

<C++>【继承篇】

​ ✨前言✨ &#x1f393;作者&#xff1a;【 教主 】 &#x1f4dc;文章推荐&#xff1a; ☕博主水平有限&#xff0c;如有错误&#xff0c;恳请斧正。 &#x1f4cc;机会总是留给有准备的人&#xff0c;越努力&#xff0c;越幸运&#xff01; &#x1f4a6;导航助手&#x1…

东南亚媒体发稿案例分析 海外媒体宣传首选CloudNEO

近年来&#xff0c;东南亚地区以其快速发展的经济和多元化的文化成为全球企业竞相进军的热门目的地之一。在这样一个竞争激烈的市场中&#xff0c;有效的媒体宣传成为企业拓展业务、树立品牌形象的重要手段。本文将以东南亚媒体发稿案例为例&#xff0c;分析如何利用CloudNEO提…

PaddleOCR CPU 文本文字识别 docker部署

需求&#xff1a; 需要把所有滑块图片的数据文字提取出来 启动服务 mkdir paddle cd paddle docker run -itd --name ppocr -v $PWD:/paddle --networkhost -it registry.baidubce.com/paddlepaddle/paddle:2.1.3-gpu-cuda10.2-cudnn7 /bin/bash docker exec -it ppocr bash …

大唐杯学习笔记:Day6

1.1小区选择 一、概述 1.UE在RRC_IDLE和RRC——INACTIVATE状态下进行的过程&#xff1b; 2.UE首先需要完成PLMN的选择,在已选择的PLMN上寻找合适的小区,获取合适的服务,监听控制信道,这个过程即小区选择过程&#xff1b; 3.根据小区重选准则,UE寻找其他更适合的小区进行小区…

直播预告|小白开箱: 云数据库在五朵云上的评测

3 月 7 日&#xff0c;周四晚上 19:00-20:30 由明说三人行组织&#xff0c;邀请了 NineData 国际总经理(GM) Ni Demai、云猿生数据 CTO &#xff06; 联合创始人子嘉&#xff0c;和《明说三人行》创始人 &主持人明叔&#xff0c;共同围绕《小白开箱: 云数据库在五朵云上的评…

OpenCV 视频处理(关于摄像头和视频文件的读取、显示、保存等等)

1、前言 OpenCV不仅能够处理图像&#xff0c;还能够处理视频 视频是由大量的图像构成的&#xff0c;这些图像是以固定的时间间隔从视频中获取的。这样&#xff0c;就能够使用图像处理的方法对这些图像进行处理&#xff0c;进而达到处理视频的目的。要想处理视频&#xff0c;需…

JVM运行时数据区——对象的实例化内存布局与访问定位

文章目录 1、对象的实例化1.1、创建对象的方式1.2、创建对象的步骤 2、对象的内存布局3、对象的访问定位3.1、对象访问的定位方式3.2、使用句柄访问3.3、使用指针访问 4、小结 平时大家经常使用new关键字来创建对象&#xff0c;那么我们创建对象的时候&#xff0c;怎么去和运行…

蓝桥杯备战刷题four(自用)

1.砝码称重 #include <iostream> #include <vector> using namespace std; const int N110; const int M100010; int w[N]; int n; int f[N][M]; int m; int ans; //f[i][j]表示到第i个砝码进行放置时的称得的重量为j的方案数 int main() {cin>>n;for(int i1…

【MySQL】用户管理 -- 详解

如果我们只能使用 root 用户&#xff0c;这样存在安全隐患。这时就需要使用 MySQL 的用户管理。 一、 用户 1、用户信息 MySQL 中的用户都存储在系统数据库 MySQL 的 user 表中。 字段解释&#xff1a; host&#xff1a;表示这个用户可以从哪个主机登陆&#xff0c;如果…

【C语言】走迷宫之推箱子

前言&#xff1a; 在上一篇文章当中我介绍了一个走迷宫的写法&#xff0c;但是那个迷宫没什么可玩性和趣味性&#xff0c;所以我打算在迷宫的基础上加上一个推箱子&#xff0c;使之有更好的操作空间&#xff0c;从而增强了游戏的可玩性和趣味性。 1. 打印菜单 void menu() {…

Sqli-labs靶场第18关详解[Sqli-labs-less-18]自动化注入-SQLmap工具注入

Sqli-labs-Less-18 通过测试发现&#xff0c;在登录界面没有注入点&#xff0c;通过已知账号密码admin&#xff0c;admin进行登录发现&#xff1a; 返回了User Agent&#xff0c;设想如果在User Agent尝试加上注入语句&#xff08;报错注入&#xff09;&#xff0c;测试是否会…