【C++ 第二十二章】C++的类型转换

news2024/11/13 10:32:15




在这里插入图片描述



1.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风格的转换格式很简单,但是有不少缺点的:

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



2.C++强制类型转换

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

先列个提纲:
(1)内置类型 = 内置类型:隐式类型转换 或 强制类型转换 使用 static_cast、reinterpret_cast
(2)内置类型 = 自定义类型:自定义类型内部重载 operator int() 这样的函数(int x = A.operator int();)
(3)自定义类型 = 内置类型:隐式类型转换(string s = “hehe”;)
(4)自定义类型 = 自定义类型:自定义类型内部重载构造函数


2.1 static_cast 与 reinterpret_cast

// static_cast:这个就代表 隐式类型转换,目的是为了规范化,提示别人这里存在隐式类型转换
int a1 = 10;
double b1 = static_cast<double>(a1);

// reinterpret_cast:reinterpret 表示重新解释,这里就是 强制类型转换
int a2 = 10;
double* p1 = reinterpret_cast<double*>(a2);

2.2 const_cast

这段代码有点诡异!!

int main() {
	// const_cast:将常变量去除常性,使其可以修改
	const int a3 = 5;
	int* p2 = const_cast<int*>(&a3);
	*p2 = 6;

	cout << a3 << '\n';
	cout << *p2 << '\n';
	return 0;
}

在这里插入图片描述


我们明明通过强制类型转换,通过 a3 的地址修改了 a3,但是打印结果:a3 居然还是 5???

甚至我们通过调试窗口查看 a3 的变化:a3 也是变成了 6

为什么打印出来还是 5 ?

这里其实是编译器的优化手段,将 a3 定义成常量后,编译器将 a3 这个符号直接类似定义宏,直接宏替换成 5,之后程序遇到 a3 的地方会直接替换成 5


解决办法:加上 volatile 关键字,意思是去掉编译器这个优化

volatile const int a3 = 5;

小结:因此说 const_cast 的修改是有一定风险的,涉及这种直接去掉常性,修改常变量的,一定要加上 volatile



2.3 dynamic_cast

dynamic_cast 用于将一个父类对象的指针/引用 转换 子类对象的指针或引用(动态转换)

注意是父子类指针或引用之间的转换,不是父子类对象之间的转换

(子类对象可以直接赋值给父类对象,父类对象不可以赋值给子类对象)

看注释理解:

class A
{
public:
	//virtual void func() {};
	int _a = 2;
};

class B : public A
{
public:
	int _b = 3;
};

void Func(A* pa) {
	// 父类指针 pa 指向 子类对象,下面将  子类指针转换成子类指针,没问题
	// 父类指针 pa 指向 父类对象,下面将  父类指针转换成子类指针,则该指针就会存在越界访问的风险
	//(比如你父类对象大小为5字节,子类对象大小为5字节, 而你将你父类指针强转为子类指针,那么就是本来只有5字节空间,你却可以访问10字节空间,这显然是有越界访问的风险的)

	B* pb = reinterpret_cast<B*>(pa); 

	// 越界读数据:可能不报错
	cout << "pa:" << pa->_a << '\n';
	cout << "pb:" << pb->_b << '\n';
	// 越界写数据:直接报错
	pa->_a++;
	pb->_b++;
	cout << "pa:" << pa->_a << '\n';
	cout << "pb:" << pb->_b << '\n';
}

int main() {
	A a;
	B b;
	Func(&a);
	Func(&b);
	return 0;
}




由上面代码演示可知:父类指针转换为子类指针存在风险

因此需要使用 dynamic_cast 检查是否可以转换成功,若转换失败会返回空指针,以此来防范这个风险




dynamic_cast 的使用要求:

1.dynamic_cast 只能用于父类含有虚函数的类:因此象征性的给父类A加一个 虚函数

2.dynamic_cast 会先检查是否能转换成功,能成功则转换,不能则返回 0。



class A
{
public:
	virtual void func() {};
	int _a = 2;
};

class B : public A
{
public:
	int _b = 3;
};

void Func(A* pa) {
	// 父类指针 pa 指向 子类对象,下面将  子类指针转换成子类指针,没问题
	// 父类指针 pa 指向 父类对象,下面将  父类指针转换成子类指针,则该指针就会存在越界访问的风险
	//(比如你父类对象大小为5字节,子类对象大小为5字节, 而你将你父类指针强转为子类指针,那么就是本来只有5字节空间,你却可以访问10字节空间,这显然是有越界访问的风险的)

	B* pb = dynamic_cast<B*>(pa);

	if (pb) {
		// 越界读数据:可能不报错
		cout << "pa:" << pa->_a << '\n';
		cout << "pb:" << pb->_b << '\n';
		// 越界写数据:直接报错
		pa->_a++;
		pb->_b++;
		cout << "pa:" << pa->_a << '\n';
		cout << "pb:" << pb->_b << '\n';
	}
	else cout << "转换失败" << '\n';
}

int main() {
	A a;
	B b;
	cout << "Func(&a):";
	Func(&a); 

	cout << '\n';

	cout << "Func(&b):\n";
	Func(&b);
	return 0;
}




为什么只能用于父类含有虚函数的类?

因为 dynamic_cast 本质上是通过虚表来实现的,类似于判断虚表中的 父子类是否有某种标记,判断是否可以转换成功

(这里不重要,可以自己了解)


2.4 内置类型 = 自定义类型

这个其实是在 自定义类型中添加一个函数,若该自定义类型要转换为 int 类型的数据,则添加 operator int() 函数;同理,若要转换为 double类型,则添加 operator double()

class A
{
public:
	operator int() {
		// 里面写什么东西都是自定义的了
		return _a;
	}
private:
	int _a = 10;
};

int main() {
	A a;
	int x = a;
	cout << x << '\n';
	return 0;
}

智能指针中也有使用相关重载函数:

在这里插入图片描述
在这里插入图片描述


2.5 自定义类型 = 内置类型

这个其实平时都有运用,本质上是隐式类型转换

int main() {
	// "hehehe" 是内置类型 char*,编译器底层将其隐式类型转换为 string 类型
	string s = "hehehe";
	return 0;
}



2.6 自定义类型 = 自定义类型

前面讲解过,两个类型若要进行类型转换操作,则这两个对象必须具有某种特定联系
但是通常,两个不同的自定义类型都不会有直接的联系,因此不能轻易的类型转换
需要在 左边的自定义类型 中重载参数为 右边自定义类型 的构造函数:

将自定义类型 A 的变量 拷贝给 自定义类型 B 的变量,需要在 类B 中重载新的构造函数:

B(const A& a)
	:_b(a.get())
{}

#include"List.h"
#include<list>
// 和自定义类型相关的类型转换基本都和构造函数有关

// 类型转换的两者一定要有一定的关联
// 自定义类型的转换,一定要产生某种关联


class A
{
public:
	A(const int& x = 0, const int& y = 0)
		:_a1(x)
		, _a2(y)
	{}

	 explicit :该关键字是禁止掉 隐式类型转换,但是可以显式类型转换
	//explicit operator int() {
	//	return _a1 + _a2;
	//}

	int get() const {
		return _a1 + _a2;
	}

	 这里写这个函数会报一堆奇怪的错:因为类 B 声明在下面,程序运行时向上查询就找不到类 B,导致报错
	//A(const B& b)
	//	:_a1(b.get())
	//{}
private:
	int _a1;
	int _a2;
};

class B
{
public:
	B(const int& x = 0)
		:_b(x)
	{}
	// 这里加上 const,为什么 a.get() 不能使用(A类中的 get 没有 const 修饰):这就设计到 const 权限放大的问题
	//先理清一个概念:我们平时通过一个对象或对象指针去调用成员函数,因为成员函数隐含着第一个参数 this 指针,因此本质上都要传递一个this指针过去,
	// 涉及到 this 指针的传参,就要注意 this 指针的权限问题(是否被 const 修饰)
	// B(const A& a)  这里对象 a 的this指针类型:const A*,而 get 函数this指针类型:A* 当然会权限放大!!!
	B(const A& a)
		:_b(a.get())
	{}


	// 这个也能匹配给 b1 = a1,但是会优先选择上面的构造 B(const A& a)  
	operator int() {
		cout << "operator int()" << '\n';
		return _b;
	}

private:
	int _b;
};


int main() {
	B b1 = 10;
	A a1 = 20;
	b1 = a1;

	return 0;
}

本质上,其实和自定义类型相关的类型转换基本都和构造函数有关 控制好自定义类型的构造函数,就可以通过不同类型的数据来构造一个自定义类型

【思考题】这里是 const 的权限缩小吗?

有人说,将非const类型的 iterator 赋值给 const 类型的迭代器 const_iterator 是一种 const 的权限缩小???

list<int> lt1 = { 1, 2, 3, 4 };
list<int>::const_iterator it = lt1.begin();  // 这个是 const 权限的缩小?

要注意,迭代器是一种类,两个迭代器之间的拷贝赋值,是自定义类型之间的类型转换。

而 const 的权限问题只会出现在 内置类型之间 与 内置类型指针或引用之间



因此,我们直接在自定义类型 list 迭代器类 内部写一个新的构造函数即可
(我们这里的 list 是自己模拟实现的,现在就是演示添加类型转换功能给你看)
(C++STL库中的 list 的迭代器也可以类型转换(可以自己试一试))



由于迭代器类是模板,其中的构造函数参数固定写成 非const类型的迭代器 iterator

// 迭代器就是将指向一个节点的指针 node 封装成一个类
template<class T, class Ref, class Ptr>
struct ListIterator
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;  // 自己这种类型
	Node* _node;



	// 类型转换的构造:这里参数固定写成 iterator
	// 当该模板实例化为 iterator :这里就是拷贝构造
	// 当该模板实例化为 const_iterator :这里就是普通构造(支持 iterator 转换成 const_iterator)
	ListIterator(const ListIterator<T, T&, T*>& it)
		:_node(it._node)
	{}


	//......其他函数
};




测试一下:

int main() {
	// 这里是 const 的权限缩小吗?
	// 不是,这里是 自定义之间类型转换,从 iterator类 转换为 const_iterator类,其本质是在const_iterator类中重载一个参数类型为 iterator 的构造函数
	// 因此,自定义类型 可以转换成 自定义类型,内置类型也可以转换为自定义类型:本质都是自定义类型重载了相关联的构造函数
	my::list<int> lt1 = { 1, 2, 3, 4 };
	my::list<int>::const_iterator it = lt1.begin();
	while (it != lt1.end()) {
		cout << *it << '\n';
		it++;
	}
	return 0;
}

结果:
在这里插入图片描述



2.7 注意事项

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



3.RTTI(了解)

RTTI:Run-time Type identification的简称,即:运行时类型识别。

  1. typeid运算符
  2. dynamic_cast运算符
  3. decltype

这几个运算符为什么也是运行时识别?
其实他们都是编译时就已经得出结果,这里的运行时识别是语法语义上,在运行时判断类型或做选择,而底层其实就是编译时识别



4. 常见面试题

  1. C++中的 四种 类型转化分别是
  2. 说说 四种 类型转化的应用场景。

这些问题上面文章都有讲解了


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

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

相关文章

CDA数据分析一级考试备考攻略

一、了解考试内容和结构 CDA一级考试主要涉及的内容包括&#xff1a;数据分析概述与职业操守、数据结构、数据库基础与数据模型、数据可视化分析与报表制作、Power BI应用、业务数据分析与报告编写等。 CDA Level Ⅰ 认证考试大纲:https://www.cdaglobal.com/certification.h…

一文还原时序数据库 IoTDB 在 TPCx-IoT 的测试全流程!

在云服务硬件环境下&#xff0c;IoTDB 写入、查询、利用资源能力均表现出色&#xff01; 之前&#xff0c;我们为大家介绍了基于 IoTDB 的企业级产品 TimechoDB&#xff0c;在 TPCx-IoT 基准测试中打破世界纪录&#xff0c;取得的双指标第一成绩&#xff0c;和选择 TPCx-IoT 的…

【Python机器学习】核心数、进程、线程、超线程、L1、L2、L3级缓存

如何知道自己电脑的CPU是几核的,打开任务管理器(同时按下:Esc键、SHIFT键、CTRL键) 然后,点击任务管理器左上角的性能选项,观察右下角中的内核:后面的数字,就是你CPU的核心数,下图中我的是16个核心的。 需要注意的是,下面的逻辑处理器:32 表示支持 32 线程(即超线…

【爬虫软件】批量采集短视频博主的主页作品

用python开发的DY爬虫采集软件&#xff0c;可自动按博主抓取其已发布视频数据。 软件界面&#xff1a; 采集结果: 日志记录&#xff1a; 软件说明&#xff1a; 演示视频&#xff1a; https://www.bilibili.com/video/BV1Kb42187qf 讲解文章&#xff1a; https://www.bi…

2024数学建模国赛选题建议+团队助攻资料

目录 一、题目特点和选题建议 二、模型选择 1、评价模型 2、预测模型 3、分类模型 4、优化模型 5、统计分析模型 三、white学长团队助攻资料 1、助攻代码 2、成品论文PDF版 3、成品论文word版 9月5日晚18&#xff1a;00就要公布题目了&#xff0c;根据历年竞赛题目…

QT: Unable to create a debugging engine.

1.问题场景&#xff1a; 第一次安装QT&#xff0c;没有配置debug功能 打开控制面板》程序》找到Kit 重启电脑即可 2.问题场景&#xff1a; qt原本一直好好的&#xff0c;突然有天打开运行调试版本&#xff0c;提示Unable to create a debugging engine.错误。这个是指无法创…

CIOE中国光博会&电巢科技即将联办“智能消费电子创新发展论坛”

在科技浪潮汹涌澎湃的当下&#xff0c;从通信领域的高速光传输&#xff0c;到消费电子中的高清显示与先进成像技术&#xff0c;光电技术的应用范围不断拓展且日益深化。而AIGC 凭借其丰富的内容供给与个性化反馈能力&#xff0c;正为新一代消费电子及智能穿戴产品开辟崭新的发展…

具身智能猜想 ——机器人进化

设想一个机器人进化的仿真模拟环境&#xff0c;可以通过 “基因突变” 产生新功能&#xff0c;让机器人逐步进化。以下是这个进化系统的关键要素和可能的实现步骤&#xff1a; 1. 仿真环境 虚拟世界&#xff1a;创建一个包含多样化任务和挑战的虚拟环境&#xff0c;如探索、抓…

uniapp 实现tabbar图标凸起

实现tabbar图标凸起有两种&#xff0c;第一种是自定义tabbar&#xff0c;第二种就是使用官方的tabbar跟api实现&#xff0c;自定义在体验中不如原生的tabbar&#xff0c;所以我下面展示的是使用官方的tabbar跟api实现 效果如图&#xff1a; 左边是未选中中间的凸起&#xff0c…

深入解密 Elasticsearch 查询优化:巧用 Profile 工具/API 提升性能

1、Elasticsearch Profile 工具介绍 在使用 ES 进行检索查询时&#xff0c;我们常常要去优化一些复杂的查询语句&#xff0c;这里 ES 结合 lucene 的生态制作了 Profile API 和图形化的 Profile 分析界面以供用户使用。 这里我们来简单讲解一下这个工具 API&#xff0c;希望能给…

全双工语音交互

文章目录 微软小冰全双工字节大模型语音交互[Language Model Can Listen While Speaking](https://arxiv.org/html/2408.02622v1) 微软小冰全双工 全双工的定义&#xff1a;一路持续的听&#xff0c;upload audio&#xff1b;一路持续的输出&#xff0c;download audio&#xf…

C#中的Graphics类和SetQuality()自定义方法

在 C# 中&#xff0c;Graphics 类是 System.Drawing 命名空间的一部分&#xff0c;它提供了一组方法和属性&#xff0c;用于在 Windows Forms 应用程序中进行二维绘图。Graphics 对象可以绘制文本、线条、曲线、形状和图像&#xff0c;并可以对它们进行变换和剪辑。 Graphics …

【JAVA入门】Day33 - Collections

【JAVA入门】Day33 - Collections 文章目录 【JAVA入门】Day33 - Collections Collections 是集合的工具类。其包含的方法如下表所示&#xff0c;其中前两个方法最为常用。 以下代码演示了如何创建集合并批量添加数据&#xff0c;然后打乱集合元素顺序&#xff0c;然后用二分法…

数据结构:(LeetCode203)移除链表元素

给你一个链表的头节点 head 和一个整数 val &#xff0c;请你删除链表中所有满足 Node.val val 的节点&#xff0c;并返回 新的头节点 。 示例 1&#xff1a; 输入&#xff1a;head [1,2,6,3,4,5,6], val 6 输出&#xff1a;[1,2,3,4,5]示例 2&#xff1a; 输入&#xff1…

代码随想录:343. 整数拆分

343. 整数拆分 class Solution { public:int integerBreak(int n) {int dp[100]{0};//拆分i的最大乘积为dp[i]dp[1]1;//初始化&#xff0c;主要是为了dp[2]初始for(int i2;i<n;i){for(int j1;j<i;j){ dp[i]max(dp[i],max(j,dp[j])*max(i-j,dp[i-j]));//取最大值&#x…

深入Linux轻量级进程管理:线程创建、线程ID解析与进程地址空间页表探究

&#x1f351;个人主页&#xff1a;Jupiter. &#x1f680; 所属专栏&#xff1a;Linux从入门到进阶 欢迎大家点赞收藏评论&#x1f60a; 目录 &#x1f6b2;Linux线程控制&#x1f40f;POSIX线程库&#x1f415;创建线程&#x1f41f;指令查看轻量级进程指令&#xff1a;ps -a…

Python(TensorFlow)和Java及C++受激发射损耗导图

&#x1f3af;要点 神经网络监督去噪预测算法聚焦荧光团和检测模拟平台伪影消除算法性能优化方法自动化多尺度囊泡动力学成像生物研究多维分析统计物距粒子概率算法 Python和MATLAB图像降噪算法 消除噪声的一种方法是将原始图像与表示低通滤波器或平滑操作的掩模进行卷积。…

汇编伪指令 GNU 风格(24)

先来看看关于标号的内容。 这里的局部标号是需要注意的。 全局标号&#xff0c;以及注释 可以不看。 来看一个例子&#xff1b; 这里的 BSYM 我不知道是什么意思。 在来看看关于伪操作的内容&#xff0c; 一般是以 . 开头的。 这是基本的一些操作。 然后是举例&#xff1a; …

前后端时间正确传递

引言&#xff1a;后端传递给前端去展示时间的时候将时间转化为String类型的时间数据去返回。此时就需要加上 JsonFormat 注解来解决这个问题。 框架环境配置中&#xff0c;时间配置为 情形一&#xff1a; 数据库字段exam_time类型为date 后端实体类中examTime类型为Date 1.前…

搭建VUE+VScode+elementUI环境遇到的问题

手把手教你搭建VUEVScodeelementUI开发环境-CSDN博客 vue : 无法加载文件 C:\Users\xxx\AppData\Roaming\npm\vue.ps1&#xff0c;因为在此系统上禁止运行脚本 vue : 无法加载文件 C:\Users\xxx\AppData\Roaming\npm\vue.ps1&#xff0c;因为在此系统上禁止运行脚本 - 知乎 更…