理解C++四种强制类型转换

news2025/2/23 11:35:12

理解C++强制类型转换

文章目录

  • 理解C++强制类型转换
  • 理解C++强制转换运算符
  • 1 static_cast
    • 1.1. static_cast用于内置数据类型之间的转换
    • 1.2 用于指针之间的转换
    • 1.3 用于基类与派生类之间的转换
  • 2. const_cast
    • 2.1示例1
    • 2.2 示例2——this指针
  • 3.reinterpret_cast
    • 3.1 示例1
  • 4.dynamic_cast
    • 4.1 举例1_父类指针转换为子类,不安全
      • 4.1.1加入判断和使用`dynamic_cast`,确保程序的健壮性和安全性。
      • 4.1.2 如果父类没有虚函数,父类指针转换为子类,报错
    • 4.2 举例2_子类指针转换为父类,安全
    • 4.3 使用 dynamic_cast 对指针进行类型判断:

  1. C++认为C风格的类型转换过于松散,可能会带来隐患,不够安全。
  2. C++推出了新的类型转换来替代C风格的类型转换,采用更严格的语法检查,降低使用风险。
  3. C++新增了四个关键字static_cast、const_cast、reinterpret_cast和dynamic_cast,用于支持C++风格的类型转换。
  4. C++的类型转换只是语法上的解释,本质上与C风格的类型转换没什么不同,C语言做不到事情的C++也做不到。

C语言强制类型转换是有一定风险的,有的转换并不一定安全,如

  1. 整型数值转换成指针
  2. 基类指针转换成派生类指针
  3. 一种函数指针转换成另一种函数指针
  4. 常量指针转换成非常量指针等。

总结:C语言强制类型转换缺点;主要是为了克服C语言强制类型转换的以下三个缺点。

  • 没有从形式上体现转换功能和风险的不同。
  • 将多态基类指针转换成派生类指针时不检查安全性,即无法判断转换后的指针是否确实指向一个派生类对象。
  • 难以在程序中寻找到底什么地方进行了强制类型转换强制类型转换是引发程序运行时错误的一个原因,因此在程序出错时,可能就会想到是不是有哪些强制类型转换出了问题。

例如,将int 强制转换成 double是没有风险的,而将常量指针转换成非常量指针,将基类指针转换成派生类指针都是高风险的,而且后两者带来的风险不同(即可能引发不同种类的错误),C语言的强制类型转换形式对这些不同并不加以区分

举例1. 把整型数值转换成指针,编译阶段不报错,运行阶段报错
在这里插入图片描述

理解C++强制转换运算符

C++ 引入了四种功能不同的强制类型转换运算符以进行强制类型转换

  • static cast
  • const cast
  • reinterpret_cast
  • dynamic_cast

语法:(目标类型)表达式或目标类型(表达式);

  1. static_cast<目标类型>(表达式);
  2. const_cast<目标类型>(表达式);
  3. reinterpret_cast<目标类型>(表达式);
  4. dynamic_cast<目标类型>(表达式);


1 static_cast

1.1. static_cast用于内置数据类型之间的转换

用途:基本等价于隐式转换的一种类型转换运算符,可使用于需要明确隐式转换的地方

可以用于低风险的转换

  1. 整型和浮点型
  2. 字符与整形
  3. 转换运算符
  4. *空指针转换为任何目标类型的指针

不可以用与风险较高的转换

  • 不同类型的指针之间互相转换
  • 整型和指针之间的互相转换
  • 不同类型的引用之间的转换
#include <iostream>
using namespace std;
class CInt
{
public:
	operator int()
	{
		this->m_Int = 128;
		return m_Int;
	}
int m_Int;

};
int main(int argc, char* argv[])
{
	int i = 3;
	float f = 10.0f;
	f = i;                          //可以隐式转换,会出现警告int”转换到“float”,可能丢失数据	


	long m = i;                     // 绝对安全,可以隐式转换,不会出现警告。

	double dd = 1.23;
	long m1 = dd;                        // 可以隐式转换,会出现可能丢失数据的警告。
	long m2 = (long)dd;                 //  C风格:显式转换,不会出现警告。
	long m3 = static_cast<long>(dd);    //  C++风格:显式转换,不会出现警告。
	cout << "m1=" << m1 << ",m2=" << m2 << ",m3=" << m3 << endl;

	//低风险的转换:整型与浮点型;字符型与整型;void *指针转换为任意类型指针
	
	//字符型与整型
	char ch='a';
	int n = 5;
	n = static_cast<int>(ch);



	//void *指针转换为任意类型指针

	void *p = nullptr;
	int *p1 = static_cast<int *>(p);

	//转换运算符,类与其他类型
	CInt Obj;
	//int k=Obj ;//可以隐式转换
	int k = static_cast<int>(Obj);

	cout << "k=" << k << endl;

}

1.2 用于指针之间的转换

C风格可以把不同类型的指针进行转换。
C++不可以,需要借助void *。

#include <iostream>
using namespace std;
class CInt
{
public:
	operator int()
	{
		this->m_Int = 128;
		return m_Int;
	}
int m_Int;

};
int main(int argc, char* argv[])
{
	int i = 3;
	float f = 10.0f;
	f = i;                          //可以隐式转换,会出现警告int”转换到“float”,可能丢失数据	


	long m = i;                     // 绝对安全,可以隐式转换,不会出现警告。

	double dd = 1.23;
	long m1 = dd;                        // 可以隐式转换,会出现可能丢失数据的警告。
	long m2 = (long)dd;                 //  C风格:显式转换,不会出现警告。
	long m3 = static_cast<long>(dd);    //  C++风格:显式转换,不会出现警告。
	cout << "m1=" << m1 << ",m2=" << m2 << ",m3=" << m3 << endl;

	//低风险的转换:整型与浮点型;字符型与整型;void *指针转换为任意类型指针
	//高风险的转换:整型与指针类型转换
	//字符型与整型
	char ch='a';
	int n = 5;
	n = static_cast<int>(ch);



	//void *指针转换为任意类型指针

	void *p = nullptr;
	int *p1 = static_cast<int *>(p);

	//转换运算符,类与其他类型
	CInt Obj;
	//int k=Obj ;//可以隐式转换
	int k = static_cast<int>(Obj);

	cout << "k=" << k << endl;



	//

}

1.3 用于基类与派生类之间的转换

int main() 
{
	CFather* pFather = nullptr;
	CSon* pSon = nullptr;
	//父类转子类(不安全)
	//pSon = pFather;
	pSon = static_cast<cson*>(pFather); //不安全,没有提供运行时的检测,编译会通过
	//子类转父类(安全)
	pFather = pSon;
	pFather = static cast<CFather*>(pSon);


}





2. const_cast

  1. static_cast不能丢掉指针(引用)的const和volitale属性,const_cast可以。
  2. 仅用于进行去除 const 属性的转换,它也是四个强制类型转换运算符中唯一能够去除 const 属性的运算符。
  3. const_cast 只针对指针,引用,this指针

2.1示例1

在这里插入图片描述
示例1改为

#include <iostream>
#include <string>
int main()
{
	const int n = 5;
	const std::string s = "Inception";

	//const_cast  只针对指针,引用,this指针
	int *k = const_cast<int*>(&n);//const_cast<int*>指针类型  &n取出变量地址

	*k = 123456;
	std::cout <<"改变后的值  "<< *k << std::endl;

}

在这里插入图片描述

#include <iostream>
#include <string>
int main()
{
	const int n = 5;
	const std::string s = "Inception";

	//const_cast  只针对指针,引用,this指针
	int *k = const_cast<int*>(&n);//const_cast<int*>指针类型  &n取出变量地址
	int &k1 = const_cast<int&>(n);//const_cast<int&>引用类型 
	*k = 123456;
	k1 = 10000;


	std::cout <<"改变后的值  "<< *k << std::endl;
	std::cout << "改变后的值  " << k1 << std::endl;
}

在这里插入图片描述

2.2 示例2——this指针

常成员函数——不能修改成员变量的值,使用const_cast让常成员函数可以修改成员变量的值,这个做法感觉有点无聊

#include <iostream>
#include <string>
class CTest
{
public:
	int m_test=100;
	void foo(int test) const
	{
		//m_test = test;
		//void *p = this;
		const_cast<CTest* const>(this)->m_test = test;
		//const_cast<const CTest* const>(this)->m_test = test;//报错

		
	}
};



int main()
{

	int n = 5;
	int* const p=&n;
	//p = 0x123;
	CTest t;
	t.foo(1);
	
	std::cout << t.m_test << std::endl;










	//const int n = 5;
	//const std::string s = "Inception";

	const_cast  只针对指针,引用,this指针
	//int *k = const_cast<int*>(&n);//const_cast<int*>指针类型  &n取出变量地址
	//int &k1 = const_cast<int&>(n);//const_cast<int&>引用类型 
	//*k = 123456;
	//k1 = 10000;


	//std::cout <<"改变后的值  "<< *k << std::endl;
	//std::cout << "改变后的值  " << k1 << std::endl;
}

在这里插入图片描述

3.reinterpret_cast

  • static_cast不能用于转换不同类型的指针(引用)(不考虑有继承关系的情况),reinterpret_cast可以。
  • reinterpret_cast的意思是重新解释,能够将一种对象类型转换为另一种,不管它们是否有关系。

用途:用于进行各种不同类型的转换

  • 不同指针之间

  • 不同类型引用之间

  • 指针和能容纳指针的整数类型之间的转换

编译期处理,执行的是逐字节复制的操作
类似于显式强转,后果自负

语法:reinterpret_cast<目标类型>(表达式);
<目标类型>和(表达式)中必须有一个是指针(引用)类型。
reinterpret_cast不能丢掉(表达式)的const或volitale属性。
应用场景:
1)reinterpret_cast的第一种用途是改变指针(引用)的类型。
2)reinterpret_cast的第二种用途是将指针(引用)转换成整型变量。整型与指针占用的字节数必须一致,否则会出现警告,转换可能损失精度。
3)reinterpret_cast的第三种用途是将一个整型变量转换成指针(引用)。

3.1 示例1

#include <iostream>
using namespace std;
class CFather 
{


};
class CSon :public CFather
{



};

int main(int argc, char* argv[])
{
	int n = 1;
	int *p = (int *)n;//显式强转

	int *p1 = reinterpret_cast<int*>(n);//整型转整型指针

	char *ph = reinterpret_cast<char*>(p1);//各种类型之间的转换


	
	CSon* pSon;
	CFather* pFather = nullptr; 
	pSon = reinterpret_cast<CSon*>(pFather); //父类强转为子类指针 ,不存在检查




	return 0;
}

在这里插入图片描述

4.dynamic_cast

动态转换(dynamic_cast)用于基类和派生类之间的指针或引用的转换,但只能在运行时确定类型信息,因此只能用于多态类型。如果转换失败,将返回一个空指针。

语法:

dynamic_cast<目标类型> (原始类型)
  • 基类必须具备虚函数

原因: dynamic cast是运行时类型检查,需要运行时类型信息(RTTI),而这个信息是存储与类的虚函数表关系紧密,只有一个类定义了虚函数,才会有虚函数表。
即运行阶段类型识别(RTTI RunTime Type Identification)为程序在运行阶段确定对象的类型,只适用于包含虚函数的类。

  • 运行时检查,转型不成功则返回一个空指针
  • 非必要不要使用dynamic cast,有额外的函数开销

常见的转换方式

  • 基类指针或引用转派生类指针 (必须使用dynamic_cast)
  • 派生类指针或引用转基类指针(可以使用dynamic cast,但是更推荐使用static_cast)

4.1 举例1_父类指针转换为子类,不安全

#include <iostream>
using namespace std;
class CFather
{
public:
	virtual void foo()
	{
		cout << "CFather::void foo()" << endl;

	}
	int m_father;
};
class CSon :public CFather
{
public:
	virtual void foo()
	{
		cout << "CSon::void foo()" << endl;
	
	}

	int m_son;
};

int main(int argc, char* argv[])
{
	CFather f;
	CSon s;



	CFather* pFather = &f;//父类指针指向父类对象
	CSon* pSon = &s;//子类指针指向子类对象


	//子类指针转换为父类,安全
	//pFather = static_cast<CFather *>(pSon);//子类指针转换为父类



	//父类指针转换为子类,不安全
	//pSon = static_cast<CSon *>(pFather);//父类指针转换为子类
	//pSon->m_son = 125;//越界访问子类的成员变量,只有在程序运行阶段产生这个错误,编译器可能不报错


	//我们希望编译器可以检测出父类转换为子类是不安全的,使用dynamic_cast
	//在程序运行时做检测
	pSon = dynamic_cast<CSon *>(pFather);//父类指针转换为子类
	pSon->m_son = 125;//越界访问子类的成员变量,只有在程序运行阶段产生这个错误,编译器可能不报错

	return 0;
}

在这里插入图片描述

4.1.1加入判断和使用dynamic_cast,确保程序的健壮性和安全性。

#include <iostream>
using namespace std;
class CFather
{
public:
	virtual void foo()
	{
		cout << "CFather::void foo()" << endl;

	}
	int m_father;
};
class CSon :public CFather
{
public:
	virtual void foo()
	{
		cout << "CSon::void foo()" << endl;
	
	}

	int m_son;
};

int main(int argc, char* argv[])
{
	CFather f;
	CSon s;



	CFather* pFather = &f;//父类指针指向父类对象
	CSon* pSon = &s;//子类指针指向子类对象


	//子类指针转换为父类,安全
	//pFather = static_cast<CFather *>(pSon);//子类指针转换为父类



	//父类指针转换为子类,不安全
	//pSon = static_cast<CSon *>(pFather);//父类指针转换为子类
	//pSon->m_son = 125;//越界访问子类的成员变量,只有在程序运行阶段产生这个错误,编译器可能不报错


	//我们希望编译器可以检测出父类转换为子类是不安全的,使用dynamic_cast
	//在程序运行时做检测
	//pSon = dynamic_cast<CSon *>(pFather);//父类指针转换为子类
	//pSon->m_son = 125;//越界访问子类的成员变量,只有在程序运行阶段产生这个错误,编译器报错

	//改变为
	pSon = dynamic_cast<CSon *>(pFather);//父类指针转换为子类
	if (pSon != nullptr)
	{
		pSon->m_son = 125;//加入判断,确保程序的健壮性和安全性。

	}





	return 0;
}

4.1.2 如果父类没有虚函数,父类指针转换为子类,报错

#include <iostream>
using namespace std;
class CFather
{
public:
	void foo()
	{
		cout << "CFather::void foo()" << endl;

	}
	int m_father;
};
class CSon :public CFather
{
public:
	virtual void foo()
	{
		cout << "CSon::void foo()" << endl;

	}

	int m_son;
};

int main(int argc, char* argv[])
{
	CFather f;
	CSon s;



	CFather* pFather = &f; //父类指针指向父类对象
	CSon* pSon = &s;       //子类指针指向子类对象

	//dynamic_cast能够在运行的时刻,检测出被转换的指针的类型
	//pFather = dynamic_cast<CFather *>(pSon);//父类指针转换为子类,不推荐使用,避免开销

	pSon = dynamic_cast<CSon *>(pFather);//父类指针转换为子类
	if (pSon != nullptr)
	{
		pSon->m_son = 125;//加入判断,确保程序的健壮性和安全性。

	}




	return 0;
}

在这里插入图片描述

4.2 举例2_子类指针转换为父类,安全

//4.2 举例2_子类指针转换为父类,安全



#include <iostream>
using namespace std;
class CFather
{
public:
	virtual void foo()
	{
		cout << "CFather::void foo()" << endl;

	}
	int m_father;
};
class CSon :public CFather
{
public:
	virtual void foo()
	{
		cout << "CSon::void foo()" << endl;

	}

	int m_son;
};

int main(int argc, char* argv[])
{
	CFather f;
	CSon s;



	CFather* pFather = &f; //父类指针指向父类对象
	CSon* pSon = &s;       //子类指针指向子类对象

	//dynamic_cast能够在运行的时刻,检测出被转换的指针的类型
	pFather = dynamic_cast<CFather *>(pSon);//父类指针转换为子类,不推荐使用,避免开销






	return 0;
}

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

在这里插入图片描述

4.3 使用 dynamic_cast 对指针进行类型判断:

class Base {};
class Derived : public Base {};

Base* b1 = new Derived();
Derived* d1 = dynamic_cast<Derived*>(b1);
if (d1 != nullptr) {
    // b1 是 Derived 类型的。
}

需要注意的是,如果指向的基类指针并不真正指向派生类,或者目标类型与原始类型之间的类型转换无法完成,dynamic_cast会返回null指针或抛出std::bad_cast异常。因此,在使用dynamic_cast时需要非常小心,确保程序的健壮性和安全性。

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

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

相关文章

slamplay:用C++实现的SLAM工具集

0. 项目简介 slamplay 是一个功能强大的工具集合&#xff0c;可用于开始使用 C 来玩和试验 SLAM。这是一项正在进行的工作。它在单个 cmake 框架中安装并提供一些最重要的功能 后端框架&#xff08;g2o、gtsam、ceres、se-sync 等&#xff09;、 前端工具&#xff08;opencv、…

C++那些让我们偷懒的函数

欢迎来到Cefler的博客&#x1f601; &#x1f54c;博客主页&#xff1a;那个传说中的man的主页 &#x1f3e0;个人专栏&#xff1a;题目解析 &#x1f30e;推荐文章&#xff1a;题目大解析&#xff08;3&#xff09; 前言 让我们康康c为我们提供的懒人函数吧&#xff0c;后续会…

tcpdump(一)基础理论知识

一 抓包分析技术初探 说明&#xff1a; 本篇章跟tcp/ip的知识没有关系,只是讲解tcpdump工具背景补充&#xff1a; 抓包是做报文分析的第一步敬畏心&#xff1a; 隔行如隔山,不要想当然 ① 背景 ② 抓包技术名词 1、捋顺这些技术的来龙去脉甚至八卦;2、这样我们在后续课程…

Stable Signature - 为开源生成式AI 创建的图像 添加水印的新方法

文章目录 关于 Stable Signature 关于 Stable Signature 一种为开源生成式AI创建的图像添加水印的新方法 Stable Signature: A new method for watermarking images created by open source generative AI https://ai.meta.com/blog/stable-signature-watermarking-generativ…

Redis三种模式(主从复制,哨兵,集群)

Redis三种模式&#xff08;主从复制&#xff0c;哨兵&#xff0c;集群&#xff09; 一、主从复制1.1、主从复制概述1.2、 Redis主从复制流程1.3、 Redis主从复制作用1.4 、部署Redis 主从复制 二、Redis 哨兵模式2.1、哨兵模式的原理2.2、哨兵模式的作用2.3、哨兵的结构组成2.4…

【Spring知识点介绍 | 第二篇】什么是AOP

前言&#xff1a; 在本文中&#xff0c;我们将介绍AOP的基本概念、原理和核心组件。我们将探讨AOP的作用、优势以及如何在实际应用中使用AOP来提升软件开发的效率和质量。无论是新手还是资深开发人员&#xff0c;都将受益于对AOP的理解和应用。 希望本文能够帮助读者对AOP有一…

MineDojo - “我的世界” 基础上构建的 具身智能体仿真组件

关于 MineDojo Building Open-Ended Embodied Agents with Internet-Scale Knowledge github : https://github.com/MineDojo/MineDojo论文&#xff1a;MineDojo: Building Open-Ended Embodied Agents with Internet-Scale Knowledge https://arxiv.org/abs/2206.08853 Min…

Linux安装 spark 教程详解

目录 一 准备安装包 二 安装 scala 三 修改配置文件 1&#xff09;修改 workers 文件 2&#xff09;修改 spark-env.sh文件 四 进入 spark 交互式平台 一 准备安装包 可以自行去 spark 官网下载想要的版本 这里准备了 spark3.1.2的网盘资源 链接: https://pan.baidu.com…

stl 输入输出流

标准输入输出流 头文件 iostream 从标准输入读取流 cin >> 从标准输出写入流 cout << get 系列函数 get 无参数&#xff1a;cin.get() 从指定的输入流中提取一个字符&#xff08;包括空白字符&#xff09;&#xff0c;若读取成功&#xff0c;返回该字符的 ASC…

css--踩坑

1. 子元素的宽高不生效问题 设置flex布局后&#xff0c;子元素的宽高不生效问题。 如果希望子元素的宽高生效&#xff0c;解决方法&#xff0c;给子元素添加如下属性&#xff1a; flex-shrink: 0; flex-shrink: 0;2. 横向滚动&#xff08;子元素宽度不固定&#xff09; /* tab…

K8s学习笔记5

安装Kubernetes&#xff1a; 主机名IP地址角色master192.168.17.131master结点node1192.168.17.132nodenode2192.168.17.133node 设置主机名&#xff1a; [rootlocalhost ~]# hostnamectl set-hostname master[rootlocalhost ~]# hostnamectl set-hostname node1[rootlocalho…

尤雨溪:Vite的现状与未来展望

10 月 5 日 - 6 日&#xff0c;ViteConf 2023 在线举行&#xff0c;Vue 和 Vite 的创建者尤雨溪发表了题为《The State of Vite》 的演讲&#xff0c;他分享了 Vite 的现状与未来展望&#xff0c;本文就来看一看 Vite 现在怎么样了&#xff0c;以及未来的路将怎么走&#xff01…

港联证券:a股b股区别?

A股和B股是指中国大陆股市中的两类股票&#xff0c;两者在法律规定、税收、流转规划和出资者身份等多个方面都有较大不同。下面从多个视点分析A股B股的差异。 一、法律规定 在中国大陆股市中&#xff0c;A股是指在上海证券生意所和深圳证券生意所上市流转的股票&#xff0c;只…

IDEA对比两个文件的内容

提前说明&#xff1a;IDEA可以用对比功能对比两个文件的内容&#xff0c;适合对比大部分内容重复&#xff0c;少部分内容有变化的两个文件。 第一步&#xff1a;鼠标点击选中一个文件 第二步&#xff1a;按照Ctrl的同时&#xff0c;鼠标左键再点击选择另一个文件 第三步&#…

零基础如何自学网络安全,基于就业前景全方位讲解,包教包会

你是否对网络空间安全充满好奇&#xff1f;想要解开网络世界神秘的面纱&#xff1f;你是否对黑客技术着迷&#xff1f;而找不到合适的学习途径&#xff1f;你是否遭到过各种各样的网络攻击&#xff0c;却因知识的匮乏束手无策&#xff1f; 那么接下来将为你全面介绍&#xff0c…

【算法练习Day14】二叉树的最大深度二叉树的最小深度完全二叉树的节点个数

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 二叉树的最大深度二叉树的最…

插入排序/折半插入排序

插入排序/折半插入排序 插入排序 插入排序(英语&#xff1a;Insertion Sort)是一种简单直观的排序算法。它的工作原理是通过构建有序序列&#xff0c;对于未排序数据&#xff0c;在已排序序列中从后向前扫描&#xff0c;找到相应位置并插入。插入排序在实现上&#xff0c;通常…

IDEA中字符串怎么自动转义,双引号自动转义的小技巧

1.选中要填写的字符串 &#xff0c;按altenter 2.点击 Inject language or reference&#xff0c;选择JSON组件 3.再次按altenter&#xff0c;点击Edit JSON Fragment 4.在弹出的窗口编辑源字符串&#xff0c;代码中字符串就能自动转义了

ThreeJS-3D教学五-材质

我们在ThreeJS-3D教学二&#xff1a;基础形状展示中有简单介绍过一些常用的材质&#xff0c;这次我们举例来具体看下效果&#xff1a; 代码是这样的&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8">&…

4.绘制颜色点(点击)

愿你出走半生,归来仍是少年&#xff01; 在点击绘制点的基础上&#xff0c;通过片源着色器给每个点设置颜色。以原点为中心&#xff0c;在一象限的点为红色&#xff0c;三象限为绿色&#xff0c;其他象限为白色。 1.知识点 1.1.Uniform变量 向片源着色器传入的数据变量。 1.…