什么是显示转换
有时我们希望显式地将对象转换为另外一种类型。例如,如果想在下面的代码中执行浮点数除法:
int i,j;
double slope = i/j;
就要使用某种方法将i/j
的结果显示地转换为double。这种方法称作强制类型转换。
C风格的强制类型转换
C风格地进行强制类型转换包含两种形式:
type(expr);
(type)type;
C风格的强制类型转换从表现形式上来说不那么清楚明了,一旦出现问题,追踪起来比较困难。
C++风格的强制类型转换
C++风格的强制类型转换具有如下形式:
cast-name<type>(expression);
cast-name
可以是static_cast
、dynamic_cast
、const_cast
和reinterpret_cast
中的一种。
static_cast
static_cast
会在编译期间对类型转换做安全检查,防止不必要的错误。几个主要的用法如下:
-
基础类型间的转换,如
int
转为char
,float转为double。因为static_cast
明确告诉编译器是在知情情况下发生的类型转换,所以对可能的精度损失不会告警。把精度大的值转换为精度小的值,static_cast
会做截断处理。int64_t n1 = 0x12345678; int32_t n2 = n1; // warning: conversion from 'int64_t' {aka 'long long int'} to 'int32_t' {aka 'int'} may change value int32_t n3 = static_cast<int32_t>(n1); // no warning
-
把任意类型转换为void类型
int64_t num=64; static_cast<void>(num);
-
void*
指针和目标类型指针的相互转换char strArr[11] = "helloworld"; void *pVoid = static_cast<void *>(strArr); char* ptrStr=static_cast<char*>(pVoid);
-
用于基类和子类间的转换,但没有运行时类型检查保证转换安全,其中,子类转基类是安全的,但是父类转子类是不安全的
class B {}; class D : public B {}; void f(B* pb, D* pd) { D* pd2 = static_cast<D*>(pb); // Not safe, D can have fields // and methods that are not in B. B* pb2 = static_cast<B*>(pd); // Safe conversion, D always // contains all of B. }
-
static_cast
不能进行无关类型(如非父类和子类)指针之间的转换int64_t n1 = 0x12345678; // error: invalid static_cast from type 'int64_t*' {aka 'long long int*'} to type 'char*' char* ptr = static_cast<char*>(&n1);
const_cast
const_cast转换符是用来移除变量的const或volatile限定符。对于const变量,我们无法直接改变它的值,这个时候const_cast
可以派上用场
void printAddr(int32_t* ptr)
{
std::cout<<std::hex<<ptr<<std::oct<<std::endl;
}
int main()
{
int32_t num=100;
const int32_t* ptrNum=#
//printAddr(ptrNum); invalid conversion from 'const int32_t*' {aka 'const int*'} to 'int32_t*'
printAddr(const_cast<int32_t*>(ptrNum));
}
reinterpret_cast
reinterpret
字面意识是重新解释,reinterpret_cast
可以理解为重新解释数值,它可以实现
-
任意类型指针转换为其他无关类型的指针
struct Data { int32_t num; char ch; }; int main() { Data data{0x12345678,'A'}; int32_t* ptr=reinterpret_cast<int32_t*>(&data); std::cout<<std::hex<<*ptr<<std::endl; // 0x12345678 }
-
整形转换为任意类型指针
uint64_t addr = 0x12345678FFFFFFFF; char* ptr=reinterpret_cast<char*>(addr); std::cout<<std::hex<<static_cast<void*>(ptr)<<std::endl; // 0x12345678FFFFFFFF
-
任意类型指针转换为足够大的整形
char ch = 'A'; //uint32_t lAddr=reinterpret_cast<uint32_t>(&ch); error: cast from 'char*' to 'uint32_t' {aka 'unsigned int'} loses precision uint64_t lAddr = reinterpret_cast<uint64_t>(&ch); std::cout << std::hex << "lAddr : " << lAddr << " , ch addr : " << static_cast<void *>(&ch) << std::endl; // llNum : 0x65fe17 , ch addr : 0x65fe17
reinterpret_cast
不考虑转换数值类型之间是否是相关的,所以存在相当大的不安全性,如下例子整形指针转换为函数指针最终导致崩溃
typedef void(*pFunc)(int);
int main()
{
int32_t num=100;
pFunc pf=reinterpret_cast<pFunc>(&num);
pf(3);
}
dynamic_cast
dynamic_cast
用于将基类的指针或引用安全地转换成派生类的指针或引用(基类至少要包含一个虚函数),该转换特别适用于我们想使用基类对象的指针或引用执行某个派生类操作且该操作不是虚函数的情况。如果dynamic_cast
转换失败了,如果转换目标是指针类型则返回空指针,如果是引用类型则会抛出一个bad_alloc
异常
class CAnimal
{
public:
virtual void eat()
{
}
virtual ~CAnimal()
{
}
};
class CDog:public CAnimal
{
public:
virtual void eat() override
{
}
void bark()
{
std::cout<<"bark"<<std::endl;
}
virtual ~CDog()
{
}
};
int main()
{
CAnimal* animal=new CDog;
CDog* dog=dynamic_cast<CDog*>(animal);
if (dog)
{
dog->bark();
}
delete animal;
}
PS:通过运行时类型识别(run-time type identification,RTTI
),程序能够通过基类的指针或引用来检索其所指对象的实际类型。 dynamic_cast
是C++
中RTTI
的一种实现,另一个是typdid
。对于带虚函数的类,在运行时执行RTTI
操作符,返回动态类型信息;对于其他类型,在编译时执行RTTI
,返回静态类型信息。
总结
C++
支持4种强制类型转换:static_cast
、dynamic_cast
、const_cast
和reinterpret_cast
static_cast
会在编译期间对类型转换做安全检查,多用于基础类型的转换,是工作中常用的一种显示转换dynamic_cast
提供了RTTI
的功能,支持将带虚函数的基类指针或引用安全地转换为派生类的指针或引用const_cast
用于移除变量的const或volatile限定符reinterpret_cast
支持任意指针类型间的相互转换,功能强大但也比较危险,使用时要谨慎
参考资料
[^1]https://docs.microsoft.com/en-us/previous-versions/visualstudio/visual-studio-2012/5f6c9f8h(v=vs.110)
[^2]https://www.ibm.com/docs/en/xl-c-and-cpp-aix/16.1?topic=operators-cast-expressions