C++ 四种类型转换运算符

隐式类型转换是安全的,显式类型转换是有风险的,C语言之所以增加强制类型转换的语法,就是为了强调风险,让程序员意识到自己在做什么。

但是,这种强调风险的方式还是比较粗放,粒度比较大,它并没有表明存在什么风险,风险程度如何。再者,C风格的强制类型转换统一使用( ),而( )在代码中随处可见,所以也不利于使用文本检索工具定位关键代码。

为了使潜在风险更加细化,使问题追溯更加方便,使书写格式更加规范,C++ 对类型转换进行了分类,并新增了四个关键字来予以支持,它们分别是:

关键字说明
static_cast用于良性转换,一般不会导致意外发生,风险很低。
const_cast用于 const 与非 const、volatile 与非 volatile 之间的转换。
reinterpret_cast高度危险的转换,这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,但是可以实现最灵活的 C++ 类型转换。
dynamic_cast借助 RTTI,用于类型安全的向下转型(Downcasting)。

这四个关键字的语法格式都是一样的,具体为:

xxx_cast<newType>(data)

newType 是要转换成的新类型,data 是被转换的数据。C语言风格的 double 转 int 的写法为:

double scores = 95.5;
int n = (int)scores;

C++ 风格的写法为:

double scores = 95.5;
int n = static_cast<int>(scores);

static_cast 关键字

static_cast 只能用于良性转换,这样的转换风险较低,一般不会发生什么意外,例如:

	原有的自动类型转换,例如 short 转 int、int 转 double、const 转非 const、向上转型等;void 指针和具体类型指针之间的转换,例如void *转int *、char *转void *等;有转换构造函数或者类型转换函数的类与其它类型之间的转换,例如 double 转 Complex(调用转换构造函数)、Complex 转 double(调用类型转换函数)。

注意:static_cast 不能用于无关类型之间的转换,因为这些转换都是有风险的,例如:

1 . 两个具体类型指针之间的转换,例如int *转double *、Student *转int *等。不同类型的数据存储格式不一样,长度也不一样,用 A 类型的指针指向 B 类型的数据后,会按照 A 类型的方式来处理数据:如果是读取操作,可能会得到一堆没有意义的值;如果是写入操作,可能会使 B 类型的数据遭到破坏,当再次以 B 类型的方式读取数据时会得到一堆没有意义的值。

2 . int 和指针之间的转换。将一个具体的地址赋值给指针变量是非常危险的,因为该地址上的内存可能没有分配,也可能没有读写权限,恰好是可用内存反而是小概率事件。

static_cast 也不能用来去掉表达式的 const 修饰和 volatile 修饰。换句话说,不能将 const/volatile 类型转换为非 const/volatile 类型。

static_cast 是“静态转换”的意思,也就是在编译期间转换,转换失败的话会抛出一个编译错误。

static_cast 的正确用法:

#include <iostream>
#include <cstdlib>
using namespace std;class Complex{
public:Complex(double real = 0.0, double imag = 0.0): m_real(real), m_imag(imag){ }
public:operator double() const { return m_real; }  //类型转换函数
private:double m_real;double m_imag;
};int main(){//下面是正确的用法int m = 100;Complex c(12.5, 23.8);long n = static_cast<long>(m);  //宽转换,没有信息丢失char ch = static_cast<char>(m);  //窄转换,可能会丢失信息int *p1 = static_cast<int*>( malloc(10 * sizeof(int)) );  //将void指针转换为具体类型指针void *p2 = static_cast<void*>(p1);  //将具体类型指针,转换为void指针double real= static_cast<double>(c);  //调用类型转换函数return 0;
}

const_cast 关键字

const_cast 比较好理解,它用来去掉表达式的 const 修饰或 volatile 修饰。换句话说,const_cast 就是用来将 const/volatile 类型转换为非 const/volatile 类型。

下面我们以 const 为例来说明 const_cast 的用法:

#include <iostream>
using namespace std;int main(){const int n = 100;int *p = const_cast<int*>(&n);*p = 234;cout<<"n = "<<n<<endl;cout<<"*p = "<<*p<<endl;return 0;
}

运行结果:

n = 100
*p = 234

&n用来获取 n 的地址,它的类型为const int *,必须使用 const_cast 转换为int *类型后才能赋值给 p。由于 p 指向了 n,并且 n 占用的是栈内存,有写入权限,所以可以通过 p 修改 n 的值。

有人可能会问,为什么通过 n 和 *p 输出的值不一样呢?这是因为 C++ 对常量的处理更像是编译时期的#define,是一个值替换的过程,代码中所有使用 n 的地方在编译期间就被替换成了 100。换句话说,第 8 行代码被修改成了下面的形式:

cout<<"n = "<<100<<endl;

这样以来,即使程序在运行期间修改 n 的值,也不会影响 cout 语句了。

使用 const_cast 进行强制类型转换可以突破 C/C++ 的常数限制,修改常数的值,因此有一定的危险性;

reinterpret_cast 关键字

reinterpret 是“重新解释”的意思,顾名思义,reinterpret_cast 这种转换仅仅是对二进制位的重新解释,不会借助已有的转换规则对数据进行调整,非常简单粗暴,所以风险很高。

reinterpret_cast 可以认为是 static_cast 的一种补充,一些 static_cast 不能完成的转换,就可以用 reinterpret_cast 来完成,例如两个具体类型指针之间的转换、int 和指针之间的转换(有些编译器只允许 int 转指针,不允许反过来)。

下面的代码代码演示了 reinterpret_cast 的使用:

#include <iostream>
using namespace std;class A{
public:A(int a = 0, int b = 0): m_a(a), m_b(b){}
private:int m_a;int m_b;
};int main(){//将 char* 转换为 float*char str[]="http://c.biancheng.net";float *p1 = reinterpret_cast<float*>(str);cout<<*p1<<endl;//int 转换为 int*int *p = reinterpret_cast<int*>(100);//将 A* 转换为 int*p = reinterpret_cast<int*>(new A(25, 96));cout<<*p<<endl;return 0;
}

运行结果:

3.0262e+29
25

可以想象,用一个 float 指针来操作一个 char 数组是一件多么荒诞和危险的事情,这样的转换方式不到万不得已的时候不要使用。将A转换为int,使用指针直接访问 private 成员刺穿了一个类的封装性,更好的办法是让类提供 get/set 函数,间接地访问成员变量。

dynamic_cast 关键字

dynamic_cast 用于在类的继承层次之间进行类型转换,它既允许向上转型(Upcasting),也允许向下转型(Downcasting)。向上转型是无条件的,不会进行任何检测,所以都能成功;向下转型的前提必须是安全的,要借助 RTTI 进行检测,所有只有一部分能成功。

dynamic_cast 与 static_cast 是相对的,dynamic_cast 是“动态转换”的意思,static_cast 是“静态转换”的意思。dynamic_cast 会在程序运行期间借助 RTTI 进行类型转换,这就要求基类必须包含虚函数;static_cast 在编译期间完成类型转换,能够更加及时地发现错误。

dynamic_cast 的语法格式为:

dynamic_cast <newType> (expression)

newType 和 expression 必须同时是指针类型或者引用类型。换句话说,dynamic_cast 只能转换指针类型和引用类型,其它类型(int、double、数组、类、结构体等)都不行。

对于指针,如果转换失败将返回 NULL;对于引用,如果转换失败将抛出std::bad_cast异常。

1 向上转型(Upcasting)
向上转型时,只要待转换的两个类型之间存在继承关系,并且基类包含了虚函数(这些信息在编译期间就能确定),就一定能转换成功。因为向上转型始终是安全的,所以 dynamic_cast 不会进行任何运行期间的检查,这个时候的 dynamic_cast 和 static_cast 就没有什么区别了。

「向上转型时不执行运行期检测」虽然提高了效率,但也留下了安全隐患,请看下面的代码:

#include <iostream>
#include <iomanip>
using namespace std;class Base{
public:Base(int a = 0): m_a(a){ }int get_a() const{ return m_a; }virtual void func() const { }
protected:int m_a;
};class Derived: public Base{
public:Derived(int a = 0, int b = 0): Base(a), m_b(b){ }int get_b() const { return m_b; }
private:int m_b;
};int main(){//情况①Derived *pd1 = new Derived(35, 78);Base *pb1 = dynamic_cast<Derived*>(pd1);cout<<"pd1 = "<<pd1<<", pb1 = "<<pb1<<endl;cout<<pb1->get_a()<<endl;pb1->func();//情况②int n = 100;Derived *pd2 = reinterpret_cast<Derived*>(&n);Base *pb2 = dynamic_cast<Base*>(pd2);cout<<"pd2 = "<<pd2<<", pb2 = "<<pb2<<endl;cout<<pb2->get_a()<<endl;  //输出一个垃圾值pb2->func();  //内存错误return 0;
}

情况①是正确的,没有任何问题。对于情况②,pd 指向的是整型变量 n,并没有指向一个 Derived 类的对象,在使用 dynamic_cast 进行类型转换时也没有检查这一点,而是将 pd 的值直接赋给了 pb(这里并不需要调整偏移量),最终导致 pb 也指向了 n。因为 pb 指向的不是一个对象,所以get_a()得不到 m_a 的值(实际上得到的是一个垃圾值),pb2->func()也得不到 func() 函数的正确地址。

pb2->func()得不到 func() 的正确地址的原因在于,pb2 指向的是一个假的“对象”,它没有虚函数表,也没有虚函数表指针,而 func() 是虚函数,必须到虚函数表中才能找到它的地址。

2 向下转型(Downcasting)
向下转型是有风险的,dynamic_cast 会借助 RTTI 信息进行检测,确定安全的才能转换成功,否则就转换失败。那么,哪些向下转型是安全地呢,哪些又是不安全的呢?下面我们通过一个例子来演示:

#include <iostream>
using namespace std;class A{
public:virtual void func() const { cout<<"Class A"<<endl; }
private:int m_a;
};class B: public A{
public:virtual void func() const { cout<<"Class B"<<endl; }
private:int m_b;
};class C: public B{
public:virtual void func() const { cout<<"Class C"<<endl; }
private:int m_c;
};class D: public C{
public:virtual void func() const { cout<<"Class D"<<endl; }
private:int m_d;
};int main(){A *pa = new A();B *pb;C *pc;//情况①pb = dynamic_cast<B*>(pa);  //向下转型失败if(pb == NULL){cout<<"Downcasting failed: A* to B*"<<endl;}else{cout<<"Downcasting successfully: A* to B*"<<endl;pb -> func();}pc = dynamic_cast<C*>(pa);  //向下转型失败if(pc == NULL){cout<<"Downcasting failed: A* to C*"<<endl;}else{cout<<"Downcasting successfully: A* to C*"<<endl;pc -> func();}cout<<"-------------------------"<<endl;//情况②pa = new D();  //向上转型都是允许的pb = dynamic_cast<B*>(pa);  //向下转型成功if(pb == NULL){cout<<"Downcasting failed: A* to B*"<<endl;}else{cout<<"Downcasting successfully: A* to B*"<<endl;pb -> func();}pc = dynamic_cast<C*>(pa);  //向下转型成功if(pc == NULL){cout<<"Downcasting failed: A* to C*"<<endl;}else{cout<<"Downcasting successfully: A* to C*"<<endl;pc -> func();}return 0;
}

运行结果:

Downcasting failed: A* to B*
Downcasting failed: A* to C*
-------------------------
Downcasting successfully: A* to B*
Class D
Downcasting successfully: A* to C*
Class D

这段代码中类的继承顺序为:A --> B --> C --> D。pa 是A类型的指针,当 pa 指向 A 类型的对象时,向下转型失败,pa 不能转换为B或C类型。当 pa 指向 D 类型的对象时,向下转型成功,pa 可以转换为B或C*类型。同样都是向下转型,为什么 pa 指向的对象不同,转换的结果就大相径庭呢?

编译器会将存在继承关系的类的类型信息使用指针“连接”起来,从而形成一个继承链(Inheritance Chain),也就是如下图所示的样子:
在这里插入图片描述

当使用 dynamic_cast 对指针进行类型转换时,程序会先找到该指针指向的对象,再根据对象找到当前类(指针指向的对象所属的类)的类型信息,并从此节点开始沿着继承链向上遍历,如果找到了要转化的目标类型,那么说明这种转换是安全的,就能够转换成功,如果没有找到要转换的目标类型,那么说明这种转换存在较大的风险,就不能转换。

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

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

相关文章

Android leak内存,GitHub - jin870132/memoryleakdemo: 安卓内存泄露几种常见形式及解决方案...

安卓内存泄露几种常见形式及解决方案一.前言1.内存溢出与内存泄露内存溢出(oom)&#xff0c;是指程序在申请内存时&#xff0c;没有足够的内存空间供其使用&#xff0c;出现oom&#xff1b;比如申请了一个integer,但给它存了long才能存下的数&#xff0c;那就是内存溢出。内存泄…

第二批鸿蒙手机排行,鸿蒙系统第二批升级机型有哪些 鸿蒙系统第二批升级机型名单一览...

华为6月2日召开开启鸿蒙发布会&#xff0c;很多华为手机的用户都想第一时间用上鸿蒙手机系统&#xff0c;今天就给大家带来鸿蒙系统第二批升级机型名单一览&#xff0c;一起来看看吧鸿蒙系统第二批升级机型名单一览具体机型&#xff1a;HUAWEI Mate20 SeriesHUAWEl nova 8 Seri…

VC2010 项目的创建

在VC2010中创建一个项目 1 . 创建新项目。打开我们的VC2010&#xff0c;点工具栏第一个按钮&#xff08;New Project&#xff09;&#xff0c;或者菜单 File -> New -> Project…&#xff0c;或者按快捷键 CtrlShiftN&#xff0c;几种方式都可以。 2 . 在 “New Project…

html5教学案例撰写,怎样撰写教育教学案例

怎样撰写教育教学案例教学是教师的教和学生的学所组成的一种人类特有的人才培养活动。那么&#xff0c;怎样撰写教育教学案例呢?下面是小编收集整理的撰写教育教学案例的相关内容&#xff0c;希望对您有所帮助!1.撰写教育教学案例的思想准备要写好教育、教学案例&#xff0c;首…

error C2143: syntax error : missing ';' before '}'

我们在运行C程序的时候经常会遇到错误&#xff0c;如果你遇到了这个错误&#xff1a;error C2143: syntax error : missing ‘;’ before ‘}’&#xff0c;那麽我将帮你解决这个错误。 错误展示 完整代码 #include <stdio.h> #define exchange(a,b){int t; ta;ab;bt} …

VC2010运行C程序时黑框一闪就没

黑框一闪就没如何解决的呢&#xff1f; 首先我们要知道为什么黑框一闪就没 闪一下是因为它执行完输出函数(printf)后直接返回系统了。 解决办法 在程序里加一个system(“pause”)&#xff0c;这个是调用系统函数&#xff0c;到时候会显示"按任意键退出"。 使用方…

厦门大学计算机科学与技术学院考研分数线,2020年厦门大学计算机科学与技术考研经验分享...

原标题&#xff1a;2020年厦门大学计算机科学与技术考研经验分享大家好&#xff0c;我是育明考研小赵老师关于2020年厦门大学计算机科学与技术考研信息汇总&#xff0c;请参考一、院校介绍厦门大学(Xiamen University)&#xff0c;简称厦大(XMU)&#xff0c;是中华人民共和国教…

Redis ops详解

Redis缓存数据库的ops问题 我们使用Java操作Redis数据库的时候&#xff0c;往往会输出和ops相关的内容&#xff0c;下面给大家讲解一下ops相关的内容。 ops是什么&#xff1f; redis中的OPS 即operation per second 每秒操作次数。意味着每秒对Redis的持久化操作。 所以我们…

Redis的6种数据类型

Redis 是一种基于内存的数据库&#xff0c;并且提供一定的持久化功能&#xff0c;它是一种键值&#xff08;key-value&#xff09;数据库&#xff0c;使用 key 作为索引找到当前缓存的数据&#xff0c;并且返回给程序调用者。 当前的 Redis 支持 6 种数据类型&#xff0c;它们…

Redis字符串深入

字符串是 Redis 最基本的数据结构&#xff0c;它将以一个键和一个值存储于 Redis 内部&#xff0c;它犹如 Java 的 Map 结构&#xff0c;让 Redis 通过键去找到值。Redis 字符串的数据结构如下图所示。 Redis 会通过 key 去找到对应的字符串&#xff0c;比如通过 key1 找到 v…

Redis哈希数详解

Redis 中哈希结构就如同 Java 的 map 一样&#xff0c;一个对象里面有许多键值对&#xff0c;它是特别适合存储对象的&#xff0c;如果内存足够大&#xff0c;那么一个 Redis 的 hash 结构可以存储 2 的 32 次方减 1 个键值对&#xff08;40 多亿&#xff09;。 一般而言&…

Redis链表结构深入

链表结构是 Redis 中一个常用的结构&#xff0c;它可以存储多个字符串&#xff0c;而且它是有序的&#xff0c;能够存储 2 的 32 次方减 1 个节点&#xff08;超过 40 亿个节点&#xff09;。 Redis 链表是双向的&#xff0c;因此即可以从左到右&#xff0c;也可以从右到左遍历…

家用使用计算机组装,不能再简单了!家用电脑DIY组装实操

【天极网DIY硬件频道】【天极网硬件频道】近期有网友对DIY组装电脑比较感兴趣&#xff0c;因为自从智能手机和平板电脑横行霸道之后&#xff0c;家中的PC电脑被使用的时间变少了许多&#xff0c;可是偶尔有工作需求或是别有任务等&#xff0c;所以大家依然是不会让电脑远离的。…

Redis集合深入

Redis 的集合不是一个线性结构&#xff0c;而是一个哈希表结构&#xff0c;它的内部会根据 hash 分子来存储和查找数据&#xff0c;理论上一个集合可以存储 2 的 32 次方减 1 个节点&#xff08;大约 42 亿&#xff09;个元素&#xff0c;因为采用哈希表结构&#xff0c;所以对…

Redis有序集合详解

有序集合和集合类似&#xff0c;只是说它是有序的&#xff0c;和无序集合的主要区别在于每一个元素除了值之外&#xff0c;它还会多一个分数。分数是一个浮点数&#xff0c;在 Java 中是使用双精度表示的&#xff0c;根据分数&#xff0c;Redis 就可以支持对分数从小到大或者从…

如何为你的网站启用HTTPS

步骤一&#xff1a;获取SSL/TLS证书 选择SSL证书提供商&#xff1a; 选择一家可信赖的SSL证书提供商。对于小型网站&#xff0c;JoySSL提供的免费证书是一个不错的选择。购买或申请证书&#xff1a; 根据你的网站需求&#xff0c;购买相应类型的SSL证书。证书的类型包括单域、…

Redis HyperLogLog常用命令

基数并不是存储元素&#xff0c;存储元素消耗内存空间比较大&#xff0c;而是给某一个有重复元素的数据集合&#xff08;一般是很大的数据集合&#xff09;评估需要的空间单元数&#xff0c;所以它没有办法进行存储&#xff0c;加上在工作中用得不多&#xff0c;我们要介绍一下…

Redis的基础事务

Redis 存在事务&#xff0c;尽管它没有数据库那么强大&#xff0c;但是它还是很有用的&#xff0c;尤其是在那些需要高并发的网站当中。 使用 Redis 读/写数据要比数据库快得多&#xff0c;如果使用 Redis 事务在某种场合下去替代数据库事务&#xff0c;则可以在保证数据一致性…

Redis事务回滚深入

对于 Redis 而言&#xff0c;不单单需要注意其事务处理的过程&#xff0c;其回滚的能力也和数据库不太一样&#xff0c;这也是需要特别注意的一个问题——Redis 事务遇到的命令格式正确而数据类型不符合&#xff0c;如图所示。 从图中可知&#xff0c;我们将 key1 设置为字符串…

计算机辅助设计的发展及应用,计算机辅助设计的发展与应用

计算机维普资讯 http://doc.xuehai.net第 1卷第 34 2、期19 98年 9月株洲工学院学报V 11 o34 o 2N .、 .J OURNAL OF HUZ Z HOU NS TUTE OF TEC I TI HNOL OGY S p 1 9 e .98⑦计算机辅助设计的发展与应用7摘要廖建勇(洲工学院株株洲 420) 1 0 87讨论计算机辅助设计( AD) c技术…