C++类型转换

目录

一、C语言中的类型转换

二、C++强制类型转换

2.1 static_cast 

2.2 reinterpret_cast 

2.3 const_cast

2.4 dynamic_cast 

三、RTTI


一、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语言的转换格式非常简单,但是其有不少的缺点:

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

下面举一个C语言编写顺序表中的坑:

在insert函数中,我们在 i 位置进行插入数据。

//在i位置插入数据
void Insert(int* arr, size_t& arr_size, size_t i, int val)
{
	int end = arr_size - 1;
	while (end >= i)
	{
		arr[end + 1] = arr[end];
		end--;
	}
	arr[i] = val;
	arr_size++;
}

void test01()
{
	//大小为10的int数组
	int* arr = (int*)malloc(sizeof(int) * 10);
	size_t arr_size = 5;
	//初始化数组  
	arr[0] = 1,arr[1] = 2,arr[2] = 3,arr[3] = 4,arr[4] = 5;

	//插入数据
	Insert(arr, arr_size, 0, 70);
    //打印数组
	for (int i = 0; i < arr_size; i++)
		printf("%d ", arr[i]);
}

当我们运行代码,程序便出现了运行超时,即死循环。

原因

        因为当我们在0位置插入数据的时候,end-- 移动数据后会等于-1,当 end== -1时,进行end >= i 判断时,因为 i 是size_t 类型,所以end会隐式类型转化为 size_t 类型,而size_t 类型的 -1 为全1,是无符号数的最大值,则会进行死循环。

类似这样的坑在C语言类型中经常出现。

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

二、C++强制类型转换

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

2.1 static_cast 

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

2.2 reinterpret_cast 

这是一种比较底层的类型转换方式,它可以将一个指针或引用类型转换为任意其他指针或引用类型, 甚至完全不相关的类型。这种转换主要用于特殊场景,比如处理底层的二进制数据或进行某些底层结构的转换。但是,由于它的风险较高,使用时需要谨慎。

 

2.3 const_cast

const_cast最常用的用途就是删除变量的const 属性,方便赋值。
const 变量称之为常变量,其本质还是存放在栈上,虽然其被const 修饰,但是还是可以通过获取其地址对其进行修改。
接下来我们来看看一段程序,并分析其运行结果:

  原因分析:

        a被const修饰,则编译器会认为其不会被修改,则会将其加载至寄存器中,当我们使用指针p获取a的地址时,则会去内存中取出a的地址,赋值给a。当我们解引用修改a地址处的值时,a内存中的值就会被修改;而让我们直接打印a的值时,这是一种读操作,编译器会直接回去寄存器中取出a的值,而不是内存中,则打印出2;当我们使用p解引用则会去内存中取出a的值,则会打印3。

接下来我们使用监视窗口来看看:

        当我们使用监视窗口时,会重新创建一个进程,该进程在获取数据时则会去内存中读取,则读取到的a值是3.

我们可以使用 volatile关键字 修改const int a 让编译器不进行优化。

其实也可以使用(int*)进行强制转换。

此种const情况,使用reinterpret_cast也不行,必须使用const_cast。

2.4 dynamic_cast 

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

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

注意:

  1. dynamic_cast只能用于父类含有虚函数的类

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

在C++中,天然支持子类向父类的转换(不需要转换,赋值兼容规则)。

首先我们要明确,父类是无论如何不能转为子类的。

而指针和引用是要允许进行转换的,因为父类指针可以指向父类也可以指向子类

具体看下面这个例子,pa 指针可能指向父类也可能指向子类。

class A
{
public:
    virtual void f(){}
	int _a = 0;
};

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

// pa 可能指向父类也可能指向子类
void fun(A* pa)
{
    //函数体……
}

void test06()
{
	A aa;
	B bb;
	fun(&aa);
	fun(&bb);
}

当 pa 指向父类时,我们将其强行转化为子类的指针,该指针则会指向非对象的空间,出现越界的风险。

 如果 pa 指向子类对象,我们是无法访问到子类的成员的。

 此时我们进行要将pa转换为子类指针才能进行访问:

 通过以上例子的分析,我们得出两个痛点:

  1. 当指针指向父类对象时,我们不能将其转换为子类指针,要不然会出现越界。
  2. 当指针指向子类对象时,我们要将其转换为子类指针,才能访问到子类的成员。

而dynamic_cast关键字就解决了以上两个痛点:

dynamic_cast的作用是将父类对象的指针(引用)向子类对象指针(引用)进行转换;如果当前指针指向父类,则其不能将转换为子类指针,如果当前指针指向子类对象,则其可以转换为子类指针。

接着,我们就可以使用dynamic_cast关键字将以上代码进行改写了。

 

简而言之,dynamic_cast就是能帮我们区分当前指针指向父类还是子类。
注意:
        
         dynamic_cast 只能用于 父类 +虚函数的结构。即多态类型,没有虚函数编译直接会报错。
而强制类型转换是对虚函数是没有要求,可以直接进行转换的。 
C++中的强制类型转换兼容了C语言中的隐式类型转换和强制类型转换,C++规范了类型转换,static_cast(隐式类型转换)、reinterpret_cast(强制类型转换)、const_cast(去const属性)、dynamic_cast(继承关系的转换)
C语言中的类型转换主要依赖于强制类型转换操作符(如(int)、(double)等),这种转换是基于编译器的原始方式,没有类型检查,容易出现潜在的错误。而C++的四种强制类型转换方式提供了更严格的类型检查和错误处理机制,减少了类型转换的潜在风险。

三、RTTI

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

C++通过以下方式来支持RTTI:

  1. typeid运算符      ——  (获取对象类型字符串)

  2. dynamic_cast运算符     —— (继承关系) 

  3. decltype  ——  (推导对象类型用于定义另一个对象)

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Brant_zero2022

素材免费分享不求打赏,只求关注

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值