一.C语言的类型转换
在 C 语言里,当赋值运算符两边类型不一致、形参和实参类型不匹配、或者函数返回值与接收变量类型不一样时,就会触发类型转换。
C 语言的类型转换分为两种:隐式类型转换与显式类型转换。
- 隐式类型转换:由编译器在编译期间自动完成,可以转换就执行转换,无法转换则直接编译报错。
- 显式类型转换:需要程序员手动编写代码完成转换。
来看代码:
cpp
#include <stdio.h>
void Test ()
{
int i = 1;
// 隐式类型转换:int自动转为double
double d = i;
printf("%d, %.2f\n" , i, d);
int* p = &i;
// 显式强制类型转换,把指针地址转为整型
//注意:指针转int存在平台风险,64位下建议用uintptr_t
int address = (int) p;
printf("%p, %d\n" , p, address);
}
int main(void)
{
Test();
return 0;
}
C语言强转缺陷:转换的可视性较差,所有转换都写成同一种形式,很难定位出错的类型转换,不利于跟踪错误。
二.为什么C++需要四种类型转换
C 风格的转换格式很简单,但是有不少缺点的:
- 隐式类型转化有些情况下可能会出问题:比如数据精度丢失。
- 显式类型转换将所有情况混合在一起,代码不够清晰。
因此 C++ 提出了自己的类型转化风格,注意因为 C++ 要兼容 C 语言,所以 C++ 中还可以使用 C 语言的转化风格。
三.C++ 强制类型转换
标准 C++ 为了加强类型转换的可视性,引入了四种命名的强制类型转换操作符:
- static_cast(普通静态类型转换)
- reinterpret_cast(二进制重新解释转换)
- const_cast(修改常量属性)
- dynamic_cast(多态安全向下转换,运行校验)
1.static_cast
static_cast 适用于非多态类型之间的静态转换。凡是编译器能够自动隐式完成的类型转换,都可以使用 static_cast 显式写出;但它不支持两个毫无关联的类型相互转换。
cpp
#include <iostream>
using namespace std;
int main()
{
double d = 12.34;
// 把double转为int,属于相近类型转换,static_cast合法
int a = static_cast<int>(d);
cout << a << endl;
return 0;
}

double 向 int 转换属于合法的数值类型转换,static_cast 在编译阶段完成转换,直接舍弃小数部分,输出 12。static_cast 不支持无关类型(如指针与 int)的转换。
2.reinterpret_cast
reinterpret_cast 会直接对数据的二进制位重新解读,适合把一种完全不同的类型转换成另一种类型,不做类型安全性检查。
**注意:**这种转换仅仅改变类型解释方式,不会修改内存里的二进制内容,风险很高。
cpp
#include <iostream>
using namespace std;
int main()
{
double d = 12.34;
int a = static_cast<int>(d);
cout << a << endl;
// static_cast不支持整型向指针转换,编译报错
//int *p = static_cast<int*>(a);
//强行把整型数值解释成指针地址
int *p = reinterpret_cast<int*>(a);
return 0;
}

整型与指针是无关类型,static_cast 不支持转换。reinterpret_cast 直接将 a 中的数值解读成指针地址,仅改变类型视角,不改动二进制数据。该转换语法可行,但得到的是无效地址,不要对指针解引用,风险较大。
3.const_cast
const_cast 主要用来移除或者增加变量的 const 属性,最常见用法是去掉 const 限定。
**注意:**它不是真正消除常量本身的只读属性,只是修改指针 / 引用的类型;如果原本是常量,强行修改会出现未定义行为。
cpp
#include <iostream>
using namespace std;
void Test()
{
const int a = 2;
//去掉const属性,用普通指针接收常量的地址
int* p = const_cast<int*>(&a);
*p = 3;
cout << a << endl;
}
int main()
{
Test();
return 0;
}

普通指针无法接收 const 变量的地址,借助 const_cast 去除指针的 const 限制。但 a 本身是常量,通过指针修改常量属于未定义行为,运行结果不确定。const_cast 仅能增减 const 属性,不支持其他类型转换。
补充实用场景:它更适合本来不是常量,只是被 const 指针接收的情况,这种修改是安全的。如果你想要,我可以写一段安全使用 const_cast 的示例。
4.dynamic_cast
dynamic_cast 用来完成父类指针或引用向子类指针或引用的动态转换。
- 向上转型:子类指针 / 引用转为父类指针 / 引用,依靠赋值兼容规则,不需要强制转换。
- 向下转型:父类指针 / 引用转为子类指针 / 引用,使用 dynamic_cast 可以实现安全转换。
注意: dynamic_cast 只能用在包含虚函数的父类中。 转换时它会先做运行时检查,如果转换合法就正常转换;转换失败,指针会返回空指针 0。
cpp
#include <iostream>
using namespace std;
class A
{
public:
virtual void f() {}
};
class B : public A
{
};
void fun(A* pa)
{
B* pb1 = static_cast<B*>(pa);
B* pb2 = dynamic_cast<B*>(pa);
cout << "pb1:" << pb1 << endl;
cout << "pb2:" << pb2 << endl;
}
int main()
{
A a;
B b;
fun(&a);
fun(&b);
return 0;
}

- A 类中写了虚函数,这是 dynamic_cast 能够正常使用的前提。
- 第一次调用 fun (&a),传进去的是父类对象地址。 static_cast 不会在运行时校验对象真实类型,直接强制转为 B 类指针,拿到的指针是无效的; dynamic_cast 运行时检测出原始对象是父类,向下转换行不通,直接返回空指针。
- 第二次调用 fun (&b),传入子类对象地址。 两种转换都能成功得到子类指针。
- static_cast 只在编译期间做简单检查,向下转型存在安全隐患;dynamic_cast 依靠运行时类型判断保证安全,多用于多态代码。
补充:强制类型转换会跳过常规的类型检查。写代码时,优先想想有没有不用强制转换的方案。实在不得不使用强制转换,尽量缩小它的作用范围,减少 bug 出现的可能性。
四.RTTI(了解)
RTTI:Run-time Type identification 的缩写,即运行时类型识别,用来在程序运行阶段判断对象的真实类型。
C++ 通过下面几个方式支持 RTTI:
- typeid 运算符:返回一个 type_info 对象,用来查询表达式的实际类型。
- dynamic_cast 运算符:前面讲过,用于多态下安全向下转型,能转就转,不能转返回空指针。
- decltype:在编译阶段推导表达式的类型,主要配合模板和泛型编程使用。
补充:RTTI 需要依赖虚函数表来记录类型信息,所以想要用 RTTI 判断类型,前提是类里面包含虚函数
通俗理解就是:typeid 负责 "看是什么类型",dynamic_cast 负责 "能不能安全转",decltype 则偏编译期推导,三者应用场景不太一样。