前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++】类型转换

【C++】类型转换

作者头像
野猪佩奇`
发布2023-10-17 08:14:39
1880
发布2023-10-17 08:14:39
举报

一、C语言的类型转换

在C语言中,如下场景会发生类型转换:

  • 赋值运算符左右两侧类型不相同。
  • 形参与实参类型不匹配。
  • 返回值类型与接收返回值类型不一致。

C语言中一共有两种形式的类型转换:

  1. 隐式类型转化:编译器在编译阶段自动进行,能转就转,不能转就编译失败。
  2. 显式类型转化:需要用户自己手动进行类型转换。

隐式类型转换适用于相似类型之间的转换,比如 char、int、double 这类整形家族之间的互转;而强制类型转换适用于不相关类型的转换,比如 int 和 int*。

代码语言:javascript
复制
void test1()
{
	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风格的转换格式很简单,但是存在一些缺点:

  • 隐式类型转化在有些情况下可能会出问题:比如数据精度丢失。
  • 所有的显示类型转换形式都是以一种相同形式书写,代码不够清晰,发生错误时也难以辨别跟踪。

基于C风格类型转换存在的一些缺点,C++ 提出了自己的类型转化风格,具体来说引入了四种命名的强制类型转换操作符,它们加强了类型转换的可视性static_cast、reinterpret_cast、const_cast、dynamic_cast

注意:由于 C++ 要兼容C语言,所以 C++ 中仍然可以使用C语言的转化风格。


三、C++ 强制类型转换

1、static_cast

static_cast 适用于隐式类型转换的场景,即适用于相似类型之间的转换;如果我们使用 tatic_cast进行不相关类型之间的转换,编译器会直接报错。

代码语言:javascript
复制
void test2()
{
	int a = 12;
	//正确写法
	double b = static_cast<double>(a);
	cout << b << endl;

	//错误写法
	int* pa = static_cast<int*>(a);
	cout << pa << endl;
}
image-20230526172559155
image-20230526172559155

2、reinterpret_cast

reinterpret_cast适用于不相关类型之间的转换,即重新解释一种类型的含义。

代码语言:javascript
复制
void test2()
{
	int a = 12;
	//正确写法
	double b = static_cast<double>(a);
	cout << b << endl;

	//这里使用static_cast会报错,应该使用reinterpret_cast
	int* pa = reinterpret_cast<int*>(a);
	cout << pa << endl;
}

3、const_cast

const_cast适用于 const 类型和非 const 类型之间的转化,即删除变量的 cons t属性,从而方便赋值。

代码语言:javascript
复制
void test3()
{
	const int a = 2;
	int* p = const_cast<int*>(&a);
	*p = 3;
	cout << a << endl;
}
image-20230526173029034
image-20230526173029034

拓展:volatile 关键字的用途

相信有的同学看到上面的输出结果会有疑惑:这里我们将 a 变量的地址通过 const_cast 转换之后赋值给指针变量 p,然后通过 p 将变量 a 的值修改为3;通过监视窗口我们也观察到内存中变量 a 的值确实变成了3,但是为什么终端打印出来的值是2呢?

这其实是因为变量 a 在定义时被 const 修饰,而编译器认为 a 的值不会被修改,所以编译器会将 a 的值放入一个寄存器中,以后每次使用 a 都直接从该寄存器中读取,而不再从内存中读取;这就导致了我们虽然通过指针变量 p 修改了内存中 a 的值,但寄存器中保存的仍然是 a 修改之前的值,所以打印出来的是 2。

要解决这个问题也很简单,我们在定义常变量 a 时使用 volatile 关键字进行修饰即可;volatile 关键字的作用是保持内存可见性,即每次都从内存中读取变量的值

image-20230526174203431
image-20230526174203431

这个例子其实也可以反映出为什么 C++ 要设计出 const_cast 强制类型转换操作符来用于 const 类型和非 const 类型之前的转换 – 它从侧面提醒了程序员使用 const_cast 时要注意使用当前普通变量对程序其他位置常变量值的修改。

4、dynamic_cast

前面在学习继承时,我们提到过由于子类中包含父类,所以 子类对象/子类对象的指针/子类对象的引用 赋值给 父类对象/父类对象的指针/父类对象的引用 的过程是天然的,中间没有类型转换,也不会产生临时对象,我们称这个过程为切片/向上转型

向下转型则是指将 父类对象/父类对象的指针/父类对象的引用 赋值给 子类对象/子类对象的指针/子类对象的引用,由于父类中并没有子类,所以向上转型是不安全的,很有可能发生越界访问。如下:

代码语言:javascript
复制
class A
{
public:
	virtual void f() {}
	int _a = 1;
};

class B : public A
{
public:
	void f() {}
	int _b = 2;
};

void fun(A* pa)
{
	//当pa指向的是B类时,这里就是B类型转为B类型,不会发生越界
	//当当pa指向的是A类时,这里就是A类型转为B类型,可能会发生越界
	B* pb = (B*)pa;
	cout << pb->_b << endl;
}
image-20230526180341450
image-20230526180341450

dynamic_cast 的作用就是将一个父类对象的指针/引用转换为子类对象的指针或引用 (向下转型)。需要注意的是:

  • dynamic_cast只能用于父类含有虚函数的类。
  • dynamic_cast会检查是否能转换成功,能则进行转换,不能则返回0。
代码语言:javascript
复制
void fun(A* pa)
{
	//当pa指向的是B类时,这里就是B类型转为B类型,不会发生越界
	//当当pa指向的是A类时,这里就是A类型转为B类型,可能会发生越界
	B* pb = dynamic_cast<B*>(pa);
	cout << pb->_b << endl;
}
image-20230526180713880
image-20230526180713880

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

四、RTTI

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

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

注意:C++ 中的 auto 并不属于 RTTI,auto 是一种变量类型推导机制,它能够根据变量的初始化表达式自动推导出变量的类型,属于编译时识别;而 RTTI 是一种运行时类型识别机制。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2023-05-30,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、C语言的类型转换
  • 二、C++ 的类型转换
  • 三、C++ 强制类型转换
    • 1、static_cast
      • 2、reinterpret_cast
        • 3、const_cast
          • 4、dynamic_cast
          • 四、RTTI
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档