【C++】C++的四种强制类型转换

1、C语言中的类型转换

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

  • 隐式类型转化(截断或提升):编译器在编译阶段自动进行,能转就转,不能转就编译失败。
  • 显式类型转化(强转):需要用户自己处理。

C++对于C语言中的隐式类型转换并不太“满意”,因为其中有很多的坑,比如我们之前模拟实现string类中的insert函数,如果按照如下的方式写就是错的:

class myString
{
public:void insert(size_t pos, char ch){//扩容……//挪动数据size_t end = _size;while (end >= pos){_str[end + 1] = _str[end];--end;}//放入插入数据……}
private:char* _str;size_t _size;size_t _capacity;
};

当pos等于0时,就会出现问题了,程序会进入死循环,当pos=0时此段程序的终止条件是end<0,但是end的数据类型为unsigned int,所以无论end怎么–都会始终>=0,可能有人会说那把end换成int类型不就行了吗,大错特错,此时就会发生整型提升(隐式类型转换),你end是int类型,但是pos是unsigned int无符号整型,这里会把int提升转换为unsigned int类型,又导致end无论怎么–都会始终>=0,最终程序陷入死循环。所以迫不得已我们当时的解决办法是把end放到_size + 1的位置,从而防止后续出现整型提升等问题。

此外,只有相近类型之间才能发生隐式类型转换,比如int、char、double、unsigned int表示的都是数据的大小,只不过它们表示的范围和精度不同,它们之间可以发生隐式类型转换,而指针类型表示的是地址编号,因此整型和指针类型之间不能发生隐式类型转换,若需要转换只能显示类型转换。如下示例:

int main()
{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);return 0;
}

2、为什么C++需要四种类型转换

C风格的转换格式很简单,但是有不少缺点的:

  • 隐式类型转化有些情况下可能会出问题:比如数据精度丢失。
  • 显式类型转换将所有情况混合在一起,代码不够清晰。

因此C++提出了自己的类型转化风格,注意因为C++要兼容C语言,所以C++中还可以使用C语言的转化风格。

3、C++强制类型转换

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

  • static_cast、reinterpret_cast、const_cast、dynamic_cast。

下面来分开来讨论。

static_cast

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

//static_cast 相近类型之间的转换
int main()
{double d = 12.34;int a = static_cast<int>(d);cout << a << endl;//12/*int* p = &a;int x = static_cast<int>(p);不是相近类型,不支持转换*/return 0;
}

reinterpret_cast

reinterpret_cast操作符通常为操作数的位模式提供较低层次的重新解释,用于将一种类型转换为另一种不同的类型。

//reinterpret_cast 不相近类型之间的转换
int main()
{double d = 12.34;int a = static_cast<int>(d);cout << a << endl;//12//int *p = static_cast<int*>(&a);这里使用static_cast会报错,应该使用reinterpret_castint* p = reinterpret_cast<int*>(&a);cout << *p << endl;//12return 0;
}

reinterpret_cast还有一个非常bug的用法,比如下面的代码中将带参带返回值的函数指针转换成了无参无返回值的函数指针,并且还可以用转换后的函数指针调用此函数。

typedef void (*FUNC)();
int DoSomething(int i)
{cout << "DoSomething" << endl;return 0;
}
int main()
{//下面转换函数指针的代码是不可移植的FUNC f = reinterpret_cast<FUNC>(DoSomething);f();//DoSomethingreturn 0;
}

reinterpret_cast可以让编译器以FUNC的定义方式去看待DoSomething函数,所以非常的bug,C++不保证所有的函数指针都被一样的使用,这样使用有时会产生不确定的结果,所以并不建议这样使用。

const_cast

const_cast也是不同类型之间的转换,最常用的用途就是删除变量的const属性,方便赋值,转换后就可以对const变量的值进行修改,如下:

int main()
{const int a = 2;int* p = const_cast<int*>(&a);//取消变量a的const属性*p = 3;cout << a << endl; //2cout << *p << endl;//3return 0;
}

在一开始我定义了const属性的变量a,随后使用const_cast取消了a的const属性,这样就可以通过此指针来修改变量a的值。

为什么我取消了a的const属性,并后续通过修改*p的方式,为何a还是2呢?原因如下:

  • 这里设计到了编译器的优化,编译器默认cosnt修饰的变量是不会被修改的,因此会将cosnt修饰的变量a放到寄存器中,当需要读取const变量时就会直接从寄存器中读取,但是我们实际修改的是内存中a的值,所以最终导致输出的a是未修改前的值2。

如果我非要修改a呢?该如何解决呢?

  • 如果不想让编译器将const变量优化到寄存器当中,只需要加一个关键字volatile对const变量进行修饰即可,这个关键字的作用是让编译器强制去内存中读取,这样我们就能看到修改后的结果了。
int main()
{//volatile强制每次访问变量a都去内存中去读取,防止编译器的优化volatile const int a = 2;int* p = const_cast<int*>(&a);//取消变量a的const属性*p = 3;cout << a << endl; //3cout << *p << endl;//3return 0;
}

在C语言中,没有const_cast,但是C语言是通过强制类型转换的方式完成上述目的的:

int main()
{//volatile强制每次访问变量a都去内存中去读取,防止编译器的优化volatile const int a = 2;//int* p = const_cast<int*>(&a);//C++取消变量a的const属性int* p = (int*)&a;//C语言强转*p = 3;cout << a << endl; //3cout << *p << endl;//3return 0;
}

总结:

  • C++继续兼容C的类型转换,但是期望大家使用上面规范的转换,可读性会提升,出错的概率会降低。
  • C++中的static_cast对应C语言中的隐式类型转换。
  • C++中的reinterpret_cast和const_cast对应C语言中的强制类型转换。

dynamic_cast

dynamic_cast用于将一个父类对象的指针/引用转换为子类对象的指针或引用(动态转换),即向下转换,当然也有向上转换,如下的介绍:

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

注意:

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

向上转换就是我们先前学过的切割、切片,是语法天然支持的,不需要进行转换,而向下转换是语法不支持的,需要进行强制类型转换,并且只有指针和引用才支持向下转换,对象不支持。为什么要支持向下转换呢?看如下的代码:

class A
{
public:virtual void f() {}
};
class B : public A
{};
//pa可能指向父类对象,也可能指向子类对象
void fun(A* pa)
{//……
}
int main()
{A a;B b;fun(&a);fun(&b);return 0;
}

上述代码中,我fun函数中的父类指针pa到底是指向父类的对象,还是指向子类的对象呢?针对这两种情况我做出下面的讨论:

  • 如果父类的指针(或引用)指向的是一个父类对象,那么将其转换为子类的指针(或引用)是不安全的,因为转换后可能会访问到子类的资源,而这个资源是父类对象所没有的。
  • 如果父类的指针(或引用)指向的是一个子类对象,那么将其转换为子类的指针(或引用)则是安全的。

使用C语言的强制类型转换进行向下转型是不安全的,因为此时无论父类的指针(或引用)指向的是父类对象还是子类对象都会进行转换。而使用dynamic_cast进行向下转型则是安全的,如果父类的指针(或引用)指向的是子类对象那么dynamic_cast会转换成功,但如果父类的指针(或引用)指向的是父类对象那么dynamic_cast会转换失败并返回一个空指针。比如:

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

在这里插入图片描述

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

4、RTTI(了解)

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

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

5、常见面试题

1、C++中的4中类型转化分别是:static_cast、reinterpret_cast、const_cast、dynamic_cast

2、说说4中类型转化的应用场景。

  • static_cast

    • 没有运行时类型检查来保证转换的安全性。
    • 进行向上转换(把派生类的指针或引用转换成基类表示)是安全的。
    • 进行向下转换(把基类的指针或引用转为派生类表示),由于没有动态类型检查,所以是不安全的。

使用:

  1. 用于基本数据类型之间的转换,如把int转换为char。
  2. 把任何类型的表达式转换为void类型。
  • reinterpret_cast

    • 可以将整型转换为指针,也可以把指针转换为数组,可以在指针和引用力进行肆无忌惮的转换,平台移植性比较差。
  • const_cast

    • 常量指针转换为非常量指针,并且仍然指向原来的对象,常量引用被转换为非常量引用,并且仍然指向原来的对象。去掉类型的const或volatile属性。
  • dynamic_cast

    • 在进行向下转换时,dynamic_cast具有类型检查(信息在虚函数中)的功能,比static_cast更安全。
    • 转换后必须是类的指针、引用或者void*,基类要有虚函数,可以交叉转换。
    • dynamic本身只能用于存在虚函数的父子关系的强制类型转换;对于指针,转换失败则返回nullptr,对于引用,转换失败会抛出异常。

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

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

相关文章

我国每年研究生的毕业数量统计分享

本数据集详细记录了自1949年至2021年我国每年研究生的毕业数量&#xff08;包括硕士和博士学位的毕业生&#xff09;。在2021年&#xff0c;我国的研究生毕业生人数达到了772,761人&#xff0c;此数字比上一年度增加了44,000人。 统计的数据单位使用的是人数。 数据展示&…

rust学习(tokio协程分析二)

例子&#xff1a; 我们如果使用new_current_thread来创建tokio的协程运行runtime时&#xff0c; let rt tokio::runtime::Builder::new_current_thread().enable_all().build().unwrap(); 发现只有调用rt.block_on(...)才能触发。这里我们分析一下为何在new_current_thread…

20240301作业

1.使用fwrite、fread将一张随意的bmp图片&#xff0c;修改成德国的国旗 #include <stdio.h> #include <string.h> #include <unistd.h> #include <stdlib.h> int main(int argc, const char *argv[]) {FILE* fp fopen("./gaoda.bmp","…

SpringBoot+Vue实战:打造企业级项目管理神器

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…

vue面试:MVVM、MVC、MVP的区别?

vue面试&#xff1a;MVVM、MVC、MVP的区别&#xff1f; MVVM、MVC、MVP是什么&#xff1f;&#xff08;1&#xff09;MVC&#xff08;2&#xff09;MVVM&#xff08;3&#xff09;MVP MVVM、MVC、MVP是什么&#xff1f; MVC、MVP 和 MVVM 是三种常见的软件架构设计模式&#x…

【复现】蓝凌OA SQL注入漏洞_61

目录 一.概述 二 .漏洞影响 三.漏洞复现 1. 漏洞一&#xff1a; 四.修复建议&#xff1a; 五. 搜索语法&#xff1a; 六.免责声明 一.概述 蓝凌智能OA是由深圳市蓝凌软件股份有限公司开发&#xff0c;是一款针对中小企业的移动化智能办公产品&#xff0c;融合了钉钉数字…

【前端素材】推荐优质在线大气数码商城电商网页ClassiList平台模板(附源码)

一、需求分析 1、系统定义 电子数码电商平台是专门销售电子数码产品&#xff08;如手机、电脑、相机、智能设备等&#xff09;的在线电子商务平台。这些平台提供了一个便捷的购物环境&#xff0c;让消费者可以方便地浏览、比较和购买各种电子数码产品。 2、功能需求 在线大…

苹果备忘录导出方法

文章目录 前言方法1&#xff1a;iCloud 导出方法2&#xff1a;Pages 文稿导出&#xff08;最推荐&#xff09;方法3&#xff1a;借助Mac软件导出总结 前言 苹果生态真是让我们又爱又恨&#xff0c;其得益于无缝整合、安全性和应用程序生态系统&#xff0c;能够让在用户在自己的…

渗透测试靶场环境搭建

1.DVWA靶场 DVWA&#xff08;Damn Vulnerable Web Application&#xff09;是一个用来进行安全脆弱性鉴定的PHP/MySQL Web应用&#xff0c;包含了OWASP TOP10的所有攻击漏洞的练习环境&#xff0c;旨在为安全专业人员测试自己的专业技能和工具提供合法的环境&#xff0c;同时…

叠纸探索开放世界,女性向游戏的新生机?

女性向游戏也能搭载开放世界&#xff1f; 随着新一批游戏版号的发放&#xff0c;叠纸的新作《无限暖暖》毫无疑问成为了最受关注的游戏之一&#xff0c;换装冒险开放世界的组合为女性向游戏市场带来了新的惊喜。 作为暖暖系列的第五部作品&#xff0c;《无限暖暖》对比前作可以…

数据库常见理论常见面试题(总结)

一、前言 这里呢&#xff0c;博主会介绍一些平时关于数据库的理论的问题&#xff0c;比如数据库的三级模式两级映射、触发器、范式、存储过程、视图等等概念知识&#xff0c;但是像索引、事务、锁等内容&#xff0c;之前的文章就讲解过了&#xff0c;这里就不作过多的介绍了&am…

transformer--编码器2(前馈全连接层、规范化层、子层链接结构、编码器层、编码器)

前馈全连接层 什么是前馈全连接层: 在Transformer中前馈全连接层就是具有两层线性层的全连接网络 前馈全连接层的作用: 考虑注意力机制可能对复杂过程的拟合程度不够,通过增加两层网络来增强模型的能力 code # 前馈全连接层 class PositionwiseFeedForward(nn.Module):de…

图片如何无损放大?分享三个方法帮你快速解决

在数字时代&#xff0c;图片已成为我们生活的重要组成部分&#xff0c;无论是社交媒体的分享&#xff0c;还是专业设计的呈现&#xff0c;都离不开高质量的图片。然而&#xff0c;当我们试图放大一张图片以获取更多细节时&#xff0c;常常会遇到一个令人头疼的问题——失真。此…

多版本jdk共享jar

多版本jdk共享jar Java 9的一个有趣特性是支持多版本JAR文件。这是什么意思?过去&#xff0c;库开发人员在支持新版本的Java时有三种选择: 提供两个(或更多)不同的JAR文件&#xff0c;每个文件对应他们想要支持的Java版本。它们的版本号可能是“1.2-java-5”和“1.2-java-1.…

小工具——抖音短视频评论自动同步

很多时候喜欢看抖音的评论&#xff0c;有时候评论也是一个查疑解惑的好地方&#xff0c;很多人也喜欢把抖音的评论集中起来做分析。 因为一个朋友问过我这回事&#xff0c;闲着的时候也研究了下抖音&#xff0c;所以自己做了个小工具&#xff0c;自动同步你观看的抖音短视频的…

解读人工智能的理论基石

1956年的一个夏天&#xff0c;在达特茅斯学院的一个小会议室里&#xff0c;一群充满好奇和野心的年轻科学家聚集在一起&#xff0c;他们有一个共同的梦想&#xff1a;创造能够模仿人类智能的机器。这不仅仅是科幻小说的情节&#xff0c;更是人工智能历史上一个真实的起点。从那…

深度强化学习入门(待修改)

目录 前言 一、强化学习 1.马可洛夫链 2.蒙地卡罗 3.时序差分TD 4.gym学习​编辑 FrozenLake 二、RL基本算法 1.Q-learning和SARSA 2.DQN Deep network Qlearning DQN 三、PG策略算法 总结 前言 这段时间学习深度强化学习的总结。 一、强化学习 强化学习是做出最佳决策的科学…

useState多次渲染页面卡顿 useMemo

useState多次渲染页面卡顿 state变化了组件自然应该重新进行渲染&#xff0c;但有时我们并不需要。 React.memo()(useMemo)是一个高阶组件&#xff0c;它接收另一个组件作为参数&#xff0c;并且会返回一个包装过的新组件&#xff0c;包装过的新组件就会具有缓存作用&#xff…

Freesia 构建本地项目与远程仓库

构建远程仓库 1. 创建一个项目目录&#xff0c;根据创建远程仓库后的提示来进行 2. 本地仓库初始化 git init 初始化git文件 cd 项目目录 git init创建并提交README.md文件 touch README.md git commit -m "first commit"将本地仓库连接到远程仓库 git remote …

基于session注册JAva篇springboot

springboot3全家桶&#xff0c;数据库 &#xff1a;redis&#xff0c;mysql 背景环境&#xff1a;邮箱验证码&#xff0c;验证注册 流程&#xff1a;先通过邮箱验证&#xff0c;发送验证码&#xff0c;将获取到的session和验证码&#xff0c;存入redis里&#xff08;发送邮箱…