C++中的类型转换

static_cast

static_cast可以在相关类型之间进行转换,如整型与浮点型,指针类型等。它执行的是编译时类型检查,不会做运行时的类型检查。它的工作原理是在编译时,编译器利用已知的类型信息来进行类型兼容性检查并执行转换。所以如果类型之间的转换是不安全的或不允许的,编译器在编译时会给出错误(比如基类指针向派生类进行转换)。

示例1:基本数据类型的转换

1
2
int i = 10;
float f = static_cast<float>(i); // 将int转换为float

示例2:指针类型的转换

用于将void指针转换为具体类型指针,或执行向上转换(派生类指针转换为基类指针)。

1
2
3
4
5
6
7
8
int i = 5;
void* ptr = &i;
int* intPtr = static_cast<int*>(ptr); // void指针转换为int指针

class Base {};
class Derived : public Base {};
Derived d;
Base* basePtr = static_cast<Base*>(&d); // 派生类指针转换为基类指针

错误示例:

1
2
int* i = 10;
float* floatPtr = static_cast<float*>(i); // 这种是错误的用法!!

static_cast不能在两个具体类型的指针之间进行转换。因为这两种类型在内存表示上是不同的,intfloat分别是整数和浮点数,在内存中的存储方式和大小不同,直接转换指针类型可能会导致未定义行为。

示例3: 引用转换

可以将对象的引用转换为另一种类型的引用。

1
2
double d = 3.14;
int& intRef = static_cast<int&>(d); // 将double引用转换为int引用

示例4:类型向上的安全类型转换

在类层次结构中,static_cast可以用于安全的向上转换(从派生类转换到基类)。

1
2
3
4
5
class Animal {};
class Dog : public Animal {};

Dog dog;
Animal& animal = static_cast<Animal&>(dog); // 将Dog类型引用转换为Animal类型引用

注意事项

  • static_cast不能用于向下转换(基类转换为派生类),这需要dynamic_cast
  • static_cast不执行运行时类型检查,因此在使用中需要保证转换的安全性。
  • 不应使用static_cast来移除变量的constvolatile属性,这应当使用const_cast
    通过这些定义、原理和用法,我们可以看到static_cast是一种在编译时检查类型安全的强大工具,它在C++编程中非常有用,特别是在处理基本数据类型转换和类型向上转换时。

为什么说static_cast是编译时类型检查?

如以下范例所示,编译器只会检查static_cast后的模板参数Derived*和传入的指针类型Base*是否存在继承关系,因此下面的范例在语法上是合法的。然而,p实际上是一个指向Base实例的指针,将其强行转换为Derived*,由于内存布局上的不同,很可能会出现问题。

1
2
3
Base* p = new Base;
Derived* d = static_cast<Derived*>(p); // 编译器允许
// 运行时出事(未定义行为)

dynamic_cast

语法如下:

1
dynamic_cast <newType> (expression)
  • newTypeexpression必须同时是指针类型或者引用类型。换句话说dynamic_cast只能转换指针类型和引用类型,其它类型(intdouble、数组、类、结构体等)都不行。
  • 对于指针,如果转换失败将返回NULL;对于引用,如果转换失败将抛出std::bad_cast异常。
  • dynamic_cast是C++中一个专门用于处理多态类型转换的类型转换操作符。它在运行时检查转换的安全性,主要用于类层次结构中的向下转换(从基类指针或引用转换到派生类指针或引用)和侧向转换(在同一继承层次结构的不同派生类之间的转换)。
  • 它的原理基于运行时类型信息(RTTI)。当使用dynamic_cast进行转换时,程序运行时会通过虚指针vptr找到虚函数表vtable,从而获取对象的实际类型。沿继承树遍历查找是否可以转换为目标类型,判断是否合法转换。

示例1:向下转换(基类转换为派生类)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class Base {
public:
virtual void print() { std::cout << "Base" << std::endl; }
};

class Derived : public Base {
public:
void print() override { std::cout << "Derived" << std::endl; }
};

Base* base = new Derived;
Derived* derived = dynamic_cast<Derived*>(base);
if (derived) { // 检查转换是否成功
derived->print(); // 输出 "Derived"
}

示例2:侧向转换(同一继承层次中的不同派生类之间的转换)

1
2
3
4
5
6
7
8
9
class Sibling : public Base {
void print() override { std::cout << "Sibling" << std::endl; }
};

Base* base = new Sibling;
Derived* derived = dynamic_cast<Derived*>(base);
if (!derived) { // 转换失败,derived 为 nullptr
std::cout << "Conversion failed" << std::endl;
}

注意事项

  • 为了使dynamic_cast在向下转型时正常工作,基类必须至少有一个虚函数。

    在 C++ 的典型实现里(比如 Itanium ABI,MSVC ABI),只有含有虚函数的类才会生成 虚函数表(vtable)。vtable里除了虚函数指针,还会关联一份 类型信息(type_info),这样dynamic_cast就能通过对象指针找到其真实类型。如果基类没有虚函数,那么对象布局中就没有 vptr(虚表指针),编译器无法在运行时确定其实际类型,自然也就不能安全地执行dynamic_cast

  • dynamic_cast在运行时检查类型,因此性能开销相对较大。
  • 不应该用dynamic_cast来转换不涉及多态类型的对象,应使用其他类型转换操作符如static_castreinterpret_cast
    通过以上定义、原理和用法,我们可以看到dynamic_cast是C++中安全处理多态类型转换的强大工具。尽管它在运行时有额外的性能开销,但它提供了类型安全性,防止了非法转换带来的未定义行为。

const_cast

const_cast用于修改类型的constvolatile属性。通常,它被用来去除对象的const属性,允许我们修改原本被声明为常量的对象。

示例1:修改const变量

1
2
3
4
5
6
7
const int value = 10;
const int* ptr = &value;

*const_cast<int*>(ptr) = 20; // 修改 const 变量
// 等同于
int* tempPtr = const_cast<int*>(ptr);
*tempPtr = 20; // 修改 const 变量

示例2:在一个const成员函数中调用非const成员函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Counter {
public:
Counter() : count(0) {}

void increment() {
++count;
}

void print() const {
// 错误尝试:在const成员函数中调用非const成员函数
// increment(); // 这会编译错误,因为increment()不是const

// 使用const_cast去除const限制以调用increment()
const_cast<Counter*>(this)->increment();

std::cout << "Count: " << count << std::endl;
}

private:
int count;
};

int main() {
Counter counter;
counter.print(); // 这里会输出 "Count: 1" 而不是 "Count: 0"
return 0;
}

在这个例子中,print()函数是一个const成员函数,它不能直接调用非const成员函数increment()。但是,我们可以使用 const_cast<Counter*>(this)->increment(); 来暂时去除this指针的const限定,允许我们调用increment()函数。

reinterpret_cast

reinterpret_cast是C++中一种强大且很不安全的类型转换操作符,它可以在几乎任何类型的指针之间进行转换,甚至可以在指针和足够大的整数类型之间进行转换。这种转换不会尝试保留对象的值,而是简单地重新解释位模式,很容易导致错误的结果。由于这种转换的不安全性,只有在确实必要且你非常清楚自己在做什么的情况下,才应该使用它。

示例1:指针和整数之间的转换

假设你正在编写一个需要将指针存储到一个无符号整数类型中的底层系统接口。在这种情况下,reinterpret_cast 是必要的。

1
2
3
4
5
6
7
void* ptr = ...;  // 某个指针
uintptr_t ptrValue = reinterpret_cast<uintptr_t>(ptr); // 将指针转换为整数

// 在系统中传递 ptrValue...

// 恢复原始指针
void* originalPtr = reinterpret_cast<void*>(ptrValue);

在这个例子中,reinterpret_cast被用于在指针和整数类型之间进行转换,这是其他类型的转换所不能完成的。

示例2:不同类型的指针之间的转换

在与硬件接口时,你可能需要将接收到的字节数据解释为特定类型的数据结构。例如,从网络接收一个字节流,并将其解释为某个结构体。

1
2
3
4
5
6
char networkData[] = ...;  // 从网络接收的字节数据
// 假设我们知道数据的布局符合 MyStruct 的布局
MyStruct* dataPtr = reinterpret_cast<MyStruct*>(networkData);

// 现在可以作为 MyStruct 来访问这些数据
int value = dataPtr->someField;

这里使用reinterpret_cast来将char*转换为MyStruct*是合理的,因为我们确信数据的布局与MyStruct是兼容的。

示例3:与外部代码或硬件接口时

有时候需要将 C++ 对象传递给一个只接受 void* 类型参数的 C 函数或硬件接口,然后再将其恢复回原来的类型。

1
2
3
4
5
6
7
8
9
10
MyClass obj;
// 将 C++ 对象传递给 C 函数
c_function(reinterpret_cast<void*>(&obj));

// 在 C 函数内部
void c_function(void* data) {
// 恢复原始对象
MyClass* objPtr = reinterpret_cast<MyClass*>(data);
// 现在可以访问 MyClass 的成员
}

在这种情况下,reinterpret_cast是必要的,因为我们需要在C++和C代码之间传递对象,而C语言没有类类型的概念。

示例4:类型擦除和恢复

在一些高级编程技巧中,例如类型擦除,reinterpret_cast 可以用于存储具体类型的信息,然后再恢复它。

1
2
3
4
5
6
7
std::vector<void*> objects;
MyClass obj;
// 存储对象,擦除类型信息
objects.push_back(reinterpret_cast<void*>(&obj));

// 恢复对象的原始类型
MyClass* originalObj = reinterpret_cast<MyClass*>(objects[0]);

在这个例子中,使用reinterpret_cast来擦除和恢复类型信息是必要的,因为我们想在同一容器中存储不同类型的对象。

在所有这些情况中,reinterpret_cast都是正确的选择,因为它能够完成其他类型转换所不能完成的任务。然而,需要强调的是,使用 reinterpret_cast必须非常小心,确保转换的合法性和安全性,以避免未定义行为。

参考

C++中的四种强制类型转换


C++中的类型转换
https://delta0406.github.io/2025/09/16/技术/语言/CPP/C-中的类型转换/
作者
执妄
发布于
2025年9月16日
许可协议