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

news/2024/7/27 11:33:25

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://www.mrgr.cn/p/40061546

相关文章

mysql,for循环执行sql

遇到一个问题&#xff0c;我需要模拟上百万数据来优化sql&#xff0c;线上数据down不下来&#xff0c;测试库又没有&#xff0c;写代码执行要么慢要么就是sql语句太长。 于是&#xff0c;直接用mysql自带的功能去实现&#xff01; 简单而简单 mysql可以for循环&#xff1f;没…

Docker技术概论(4):Docker CLI 基本用法解析

Docker技术概论&#xff08;4&#xff09; Docker CLI 基本用法解析 - 文章信息 - Author: 李俊才 (jcLee95) Visit me at: https://jclee95.blog.csdn.netMy WebSite&#xff1a;http://thispage.tech/Email: 291148484163.com. Shenzhen ChinaAddress of this article:http…

Spring AI上架

Spring AI 来了 Spring AI 是 AI 工程师的一个应用框架,它提供了一个友好的 API 和开发 AI 应用的抽象,旨在简化 AI 应用的开发工序。 提供对常见模型的接入能力,目前已经上架 https://start.spring.io/,提供大家测试访问。(请注意虽然已经上架 start.spring.io,但目前还…

文件基础和文件fd

文章目录 预备知识C语言的文件接口系统调用文件fd 正文开始前给大家推荐个网站&#xff0c;前些天发现了一个巨牛的 人工智能学习网站&#xff0c; 通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。 点击跳转到网站。 预备知识 我们平时说文件就是说文件里…

cyi青少年CTF擂台挑战赛 2024 #Round 1 wp

cyi青少年CTF擂台挑战赛 2024 #Round 1 wpWEB EasyMD5 靶机真不敢恭维 一个文件上传界面,得上传pdf传两个pdfhttps://www.cnblogs.com/wysngblogs/p/15905398.html 这篇文章看到md5碰撞,找到个工具fastcoll_v1.0.0.5 https://www.win.tue.nl/hashclash/后续写的wp,flag可能…

Gitlab Runner自动推送Docker映像

接上文,增加两个stage 最简单的推送,其实是在docker build后边带上--push的开关即可。 但是不经过测试就上传,Docker仓库里很快会堆满垃圾。 所以我们设计新增两个场景,经过测试之后才push映像去仓库。 stages:- build-docker-image- test- push-image variables:PAY_IMAGE…

黑马JavaWeb课程中安装vue脚手架出现的问题

1 安装node.js 要想前端工程化&#xff0c;必须安装node.js&#xff0c;前端工程化的环境。 在成功安装node.js后&#xff0c; 修改全局包安装路径为Node.js安装目录&#xff0c; 修改npm镜像源为淘宝镜像源&#xff0c;这里出现第一个问题&#xff0c;视频中给的淘宝镜像为&…

python实现常见一元随机变量的概率分布

一. 随机变量 随机变量是一个从样本空间 Ω \Omega Ω到实数空间 R R R的函数&#xff0c;比如随机变量 X X X可以表示投骰子的点数。随机变量一般可以分为两类&#xff1a; 离散型随机变量&#xff1a;随机变量的取值为有限个。连续型随机变量&#xff1a;随机变量的取值是连…

关于Windows 10的兼容模式,看这篇文章就够了

这篇文章解释了如何使用Windows兼容模式在Windows 10上完美地运行旧版本的Windows程序。 如何更改Windows 10兼容模式设置 如果疑难解答没有完成任务&#xff0c;并且你知道该程序以前使用过哪个版本的Windows&#xff0c;则可以手动更改Windows 10兼容模式的设置&#xff1a…

Spring-自动配置

自动配置流程细节梳理: 1、导入starter-web:导入了web开发场景1、场景启动器导入了相关场景的所有依赖:starter-json、starter-tomcat、springmvc 2、每个场景启动器都引入了一个spring-boot-starter,核心场景启动器。(上面三个也有) 3、核心场景启动器引入了spring-boot-a…

[回归指标]R2、PCC(Pearson’s r )

R2相关系数 R2相关系数很熟悉了&#xff0c;就不具体解释了。 皮尔逊相关系数&#xff08;PCC&#xff09; 皮尔逊相关系数是研究变量之间线性相关程度的量&#xff0c;R方和PCC是不同的指标。R方衡量x和y的接近程度&#xff0c;PCC衡量的是x和y的变化趋势是否相同。R方是不…

《TCP/IP详解 卷一》第9章 广播和组播

目录 9.1 引言 9.2 广播 9.2.1 使用广播地址 9.2.2 发送广播数据报 9.3 组播 9.3.1 将组播IP地址转换为组播MAC地址 9.3.2 例子 9.3.3 发送组播数据报 9.3.4 接收组播数据报 9.3.5 主机地址过滤 9.4 IGMP协议和MLD协议 9.4.1 组成员的IGMP和MLD处理 9.4.2 组播路由…

Gitlab Runner自动制作C#网站项目的Docker映像

概述 代码签入Gitlab后,Gitlab Runner自动执行docker build,构建网站应用的Docker映像。 在Visual Studio 2022中创建解决方案在Gitlab中创建项目 这一步省略。 签入源代码到Gitlab为项目添加Dockerfile在解决方案根目录下创建“.gitlab-ci.yml” stages:- build-docker-imag…

Docker容器(3)单容器管理

一、单容器 1.1概念简介 Docker三个重要概念: 仓库(Repository); 镜像(Image); 容器(Container). *Docker的三个重要概念是仓库(Repository)、镜像(Image)和容器(Container)**。具体如下&#xff1a; **镜像(Image)**&#xff1a;Docker镜像是创建容器的基础&#xff0c;它类似…

LabVIEW非接触式电阻抗层析成像系统

LabVIEW非接触式电阻抗层析成像系统 非接触式电阻抗层析成像&#xff08;NEIT&#xff09;技术以其无辐射、非接触、响应速度快的特点&#xff0c;为实时监测提供了新的解决方案。基于LabVIEW的电阻抗层析成像系统&#xff0c;实现了数据的在线采集及实时成像&#xff0c;提高…

工作感受月记(2024年03月)

2024年03月01日 周五下午班,自己手中事情多了起来了。 今日工作事项: 1/ 一个aks 中应用连接redis案例,为什么会出现间歇性的连接到redis的public ip地址呢? 2/ key vault案例,客户从aws上连接azure服务。应该是她们内网环境没有打通引起的。 3/ 自己另外做了什么事情呢? …

FRM模型十二:极值理论

目录 极值理论介绍GEVPOT 代码实现 极值理论介绍 在风险管理中&#xff0c;将事件分为高频高损、高频低损、低频高损、低频低损。其中低频高损是一种非常棘手的损失事件&#xff0c;常出现在市场大跌、金融体系崩溃、金融危机以及自然灾害等事件中。 由于很难给极端事件一个准…

《哈利波特》1-7册全集高清PDF

《哈利波特》简介 《哈利波特》这个系列的图书自1997年在英国问世以来,迄今在全世界已发行超过四亿多册,创造了出版史上的奇迹,全套共7册,分别是:《Harry Potter and the Philosopher’s Stone 哈利波特与魔法石》、《Harry Potter and the Chamber of Secrets 哈利波特与…

【Vue3】自定义 Vue3 插件(全局实现页面加载动画)

// main.ts import { createApp } from vue import App from ./App.vue import Loading from "./components/Loading/index.ts";const app createApp(App) type Lod {show: () > void,hide: () > void } //编写ts loading 声明文件放置报错 和 智能提示 decl…

十八:Java8新特性

文章目录 01、Java8概述02、Java8新特性的好处03、并行流与串行流04、Lambda表达式4.1、Lambda表达式使用举例4.2、Lambda表达式语法的使用14.3、Lambda表达式语法的使用2 05、函数式(Functional)接口5.1、函数式接口的介绍5.2、Java内置的函数式接口介绍及使用举例 06、方法引…