Skip to content

强制转换(类型转换)

在 C++ 中,在合适的条件下,我们可以把一种类型强制转换成其它类型。

C 语言风格的强制转换

C 语言风格的强制转换有以下两种写法:

  • (type)expression
  • type(expression)
cpp
int a = 10;
double b = (double)a;
double c = double(a);

同时,C++ 中存在隐式转换。

cpp
int a = 10;
double b = a;

强制转换运算符

除了 C 语言风格的写法,C++ 中还提供了一些运算符来进行强制转换。

const_cast

从某个类删除 constvolatile__unaligned 属性,一般用于移除 const 限定。

语法:const_cast<type-id>(expression)

例如以下代码:

cpp
class Person {};

const Person* p = new Person();
Person* p2 = p;

其中第 4 行会报错。这时我们就可以使用 const_cast 解决这个问题。

cpp
class Person {};

const Person* p = new Person();
Person* p2 = const_cast<Person*>(p);

当然我们也可以使用 C 语言风格:

cpp
class Person {};

const Person* p = new Person();
Person* p2 = (Person*)p;

这两种方式在汇编上没有任何区别。

dynamic_cast

将操作数 expression 转换为 type-id 类型的对象,一般用于多态类型的转换,有运行时安全监测。

语法:dynamic_cast<type-id>(expression)

cpp
/**
 * Copyright 2023 <Hardworking Bee>
 */

#include <iostream>

using std::cout;

class Person {
 public:
  virtual ~Person() {}
};

class Student : public Person {};

class Car {};

int main() {
  Person* p1 = new Person();
  Person* p2 = new Student();
  cout << "p1: " << p1 << "\n";
  cout << "p2: " << p2 << "\n";
  // Student* s1 = p1;  // error
  Student* s2 = dynamic_cast<Student*>(p1);  // 不安全的转换,s2 为 nullptr
  Student* s3 = dynamic_cast<Student*>(p2);
  cout << "s2: " << s2 << "\n";  // 00000000
  cout << "s3: " << s3 << "\n";

  Car* c1 = (Car*)p1;                // 不安全的转换,c1 为 p1 的地址
  Car* c2 = dynamic_cast<Car*>(p2);  // 不安全的转换,c2 为 nullptr
  cout << "c1: " << c1 << "\n";
  cout << "c2: " << c2 << "\n";  // 00000000

  return 0;
}

dynamic_cast 与 C 语言风格的转换区别是:dynamic_cast 有运行时安全监测,如果转换是不安全的,会返回 nullptr;而 C 语言风格不会有任何的安全检测。

static_cast

仅根据表达式中存在的类型,将 expression 转换为 type-id 类型。

语法:static_cast<type-id>(expression)

  • dynamic_cast 相比,缺乏运行时安全检测;
  • 不是同一继承体系的,无法转换(不能交叉转换);
  • 通常用于基本数据类型的转换、非 const 转换成 const
cpp
/**
 * Copyright 2023 <Hardworking Bee>
 */

#include <iostream>

using std::cout;

class Person {
 public:
  virtual ~Person() {}
};

class Student : public Person {};

class Car {};

int main() {
  Person* p1 = new Person();
  const Person* p2 = static_cast<const Person*>(p1);
  // 等价于
  // const Person* p2 = p1;

  // Car* c1 = static_cast<Car*>(p1);  // error

  int a = 10;
  double b = static_cast<double>(a);
  // 等价于
  // double b = (double)a; double b = a;

  return 0;
}

reinterpret_cast

属于比较底层的强制转换,没有任何类型检查和格式转换,仅仅是简单的二进制数据拷贝。

语法:reinterpret_cast<type-id>(expression)

让我们看以下例子:

cpp
int a = 10;
double b = a;
cout << a << '\n';  // 10
cout << b << '\n';  // 10

double c = reinterpret_cast<double&>(a);
cout << c << '\n';  // 随机值

可能你会以为第 2 行的语句与第 6 行的语句相同,但事实并非如此。这是因为整型与浮点数在计算机中存储方式是不同的,处理相关方面的汇编语句也有所区别。

变量 a 的赋值语句对应的汇编:

asm
mov     dword ptr [a],0Ah

内存布局:

int-memory

变量 b 的赋值语句对应的汇编:

asm
cvtsi2sd     xmm0,dword ptr [a]
movsd        mmword ptr [b],xmm0

内存布局:

double-memory

变量 c 的赋值语句对应的汇编:

asm
movsd     xmm0,mmword ptr [a]
movsd     mmword ptr [c],xmm0

内存布局:

int-double-memory

由于变量 a 是整型,只占 4 个字节,变量 c 是双精度浮点型,占用 8 个字节,而 reinterpret_cast 只是进行简单的二进制数据拷贝,这里的拷贝将会把 a 的数据拷贝到 c 的低位的 4 个字节上,而高位的 4 个字节则是未知,所以最终造成 c 的值是一个随机值。

需要注意的区别是:变量 b 的赋值语句实际上对 a 进行了隐式类型转换,而变量 c 的赋值仅仅是简单的二进制数据拷贝,不会做类型转换。

reinterpret_cast 一般在以下两个场景使用。

将任何指针转换为任何其他指针类型(可以交叉转换)

cpp
class Person {
 public:
  virtual ~Person() {}
};

class Student : public Person {};

class Car {};

Person* p1 = new Person();
Person* p2 = new Student();
Student* s1 = reinterpret_cast<Student*>(p1);
Student* s2 = reinterpret_cast<Student*>(p2);
Car* c1 = reinterpret_cast<Car*>(p1);

将任何整数类型转换为任何指针类型以及反向转换

cpp
int* p1 = reinterpret_cast<int*>(0x12345678);
int a = reinterpret_cast<int>(p1);

References