文章目录

  • 一、C语言的类型转换
  • 二、C++ 的类型转换
  • 三、C++ 强制类型转换
    • 1、static_cast
    • 2、reinterpret_cast
    • 3、const_cast
    • 4、dynamic_cast
  • 四、RTTI

一、C语言的类型转换

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

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

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

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

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

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进行不相关类型之间的转换,编译器会直接报错。

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适用于不相关类型之间的转换,即重新解释一种类型的含义。

void test2(){int a = 12;//正确写法double b = static_cast<double>(a);cout << b << endl;//这里使用static_cast会报错,应该使用reinterpret_castint* pa = reinterpret_cast<int*>(a);cout << pa << endl;}

3、const_cast

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

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

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

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

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。
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 是一种运行时类型识别机制。