【C++】类型转换

2023-10-17 08:14:39 浏览数 (1)

一、C语言的类型转换

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

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

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

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

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

代码语言:javascript复制
void test1()
{
	int i = 1;

	// 隐式类型转换
	double d = i;
	printf("%d, %.2fn", i, d);

	int* p = &i;
	// 显示的强制类型转换
	int address = (int)p;
	printf("%x, %dn", 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;
}

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;
}

拓展:volatile 关键字的用途

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

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

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

这个例子其实也可以反映出为什么 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;
}

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;
}

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

四、RTTI

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

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

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

0 人点赞