type cast
C++中的类型转换, 四种cast: static_cast、dynamic_cast、const_cast和reinterpret_cast, 以及其他转换方法
# 隐式转换
在不同类型之间进行赋值,其根据类型不同得到不同的结果,如下为基本的转换规则:
非布尔->布尔:0->false; 非0->true
布尔->非布尔:false->0; true->1
浮点->整数:近似处理,保留浮点数小数点之前的部分
整数->浮点数:小数部分记为0,如果整数所占空间超过浮点类型容量,精度会产生损失
给无符号类型一个超过其表示范围的值时,结果是初始值对无符号类型表示数值的总数取模以后的余数。例如unsigned char(8 bit) 可以表示0-255,如果赋一个区间以外的值,则实际结果为该值对256取模所得的余数。unsigned char x = -1; // x = 255
当赋给带符号类型一个超过其表示范围的值时,结果是未定义的。此时程序可能继续工作、可能崩溃,也可能产生垃圾数据(未定义)
无符号数转换为更大的数据类型时, 只需简单地在开头添加0,这种运算称为0扩展
将有符号数转换为更大的数据类型需要执行符号扩展,规则是将当前数符号位扩展至所需要的位数
当数据类型转换时,同时需要在不同数据大小,以及无符号和有符号之间转换时,C语言标准要求先进行数据大小的转换,之后再进行无符号和有符号之间的转换。C语言中的强制类型转换保持二进制位值不变,只是改变解释位的方式。
将一个大的数据类型转换为小的数据类型时,不管是无符号数还是有符号数都是简单地进行位截断
进行整数的算术运算时,当结果变量的位数不足以存放实际实际结果的位数时,运算的结果就会因截断而产生溢出
#include <iostream> #include <limits> using namespace std; int main() { bool bValue = true; char cValue = numeric_limits<char>::max(); short sValue = numeric_limits<short>::max(); int iValue = numeric_limits<int>::max(); long lValue = numeric_limits<long>::max(); long long llValue = numeric_limits<long long>::max(); float fValue = numeric_limits<float>::max(); double dValue = numeric_limits<double>::max(); long double ldValue = numeric_limits<long double>::max(); //测试开始 cout << (bValue=cValue) << " " << (bValue=sValue) << " " << (bValue=iValue) <<endl; cout << (bValue=lValue) << " " << (bValue=llValue) << " " << (bValue=fValue) <<endl; cout << (bValue=dValue) << " " << (bValue=ldValue) << endl; }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21以上输出为全1。
# 整型提升
负责把小整数装换成较大的整数类型,
对于bool、char、signed char、unsigned char、short、unisned short等类型,只要他们所有可能的值都能存在int类型中,就会提升为int,否则提升为unsigned int。
wchar_t、char16_t、char32_6提升成int、unsigned int、long、unsigned long、long long、unsigned long long中最小的一种,前提是能容纳原始类型所有可能的值
无符号与有符号之间的转换有几种情况:
- 如果无符号类型不小于带符号类型,则有符号类型转换成无符号类型,如int需要转换成unsigned int。如果有符号是负数,则取模运算
- 如果带符号大于无符号且带符号类型可以容纳所有无符号值,则转换成带符号,否则转换成无符号。比如long和unsigned int,如果int和long大小相同,则long类型转换成unsigned int。
# 显式转换
# static_cast
static_cast< new_type >(expression)
相当于传统的C语言里的强制转换: 把expression转换为new_type类型
compile time检查,用于非多态的转换,可以转换指针及其他
没有运行时类型检查来保证转换的安全性
用法
- 用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
- 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
- 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
- 用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。
- 把空指针转换成目标类型的空指针。
- 把任何类型的表达式转换成void类型。
static_cast不能转换掉expression的const、volatile、或者__unaligned属性
char a = 'a';
int b = static_cast<int>(a);//正确,将char型数据转换成int型数据
double *c = new double;
void *d = static_cast<void*>(c);//正确,将double指针转换成void指针
int e = 10;
const int f = static_cast<const int>(e);//正确,将int型数据转换成const int型数据
const int g = 20;
int *h = static_cast<int*>(&g);//编译错误,static_cast不能转换掉g的const属性
class Base
{};
class Derived : public Base
{}
Base* pB = new Base();
if(Derived* pD = static_cast<Derived*>(pB))
{} //下行转换是不安全的(坚决抵制这种方法)
Derived* pD = new Derived();
if(Base* pB = static_cast<Base*>(pD))
{} //上行转换是安全的
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
# dynamic_cast
dynamic_cast 是C++对多态支持的强制类型转换。 所以class 里面必须有 virtual function; 通过运行时类型识别(RTTI),程序能够使用基类的指针或引用来检索这些指针或引用所指对象的实际派生类型。
与其他强制类型转换不同,dynamic_cast涉及运行时类型检查(RTTI)。 如果绑定到引用或指针的对象不是目标类型的对象,则 dynamic_cast 失败。有两种情况:
- to pointer : 如果转换到指针类型的dynamic_cast失败,则dynamic_cast的结果是 0 值;
- to reference : 如果转换到引用类型的dynamic_cast失败,则抛出一个bad_cast类型的异常。
dynamic_cast<type*>(expr) //type必须是一个类类型且必须是一个有效的指针
dynamic_cast<type&>(expr) //type必须是一个类类型且必须是一个左值
dynamic_cast<type&&>(expr) //type必须是一个类类型且必须是一个右值
2
3
expr的类型必须符合以下三个条件中的任何一个:
- expr的类型是目标类型type的公有派生类
- expr的类型是目标type的共有基类
- expr的类型就是目标type的类型。
用法
- dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换(cross cast)。
- 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
- 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
- dynamic_cast是唯一无法由旧式语法执行的动作,也是唯一可能耗费重大运行成本的转型动作。
if(Derived *dp = dynamic_cast<Derived *>(bp)){
//使用dp指向的Derived对象
}
else{
//使用bp指向的Base对象
}
void f(const Base &b){
try{
const Derived &d = dynamic_cast<const Base &>(b);
//使用b引用的Derived对象
}
catch(std::bad_cast){
//处理类型转换失败的情况
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# reinterpret_cast
仅仅重新解释类型,但没有进行二进制的转换:
转换的类型必须是一个指针、引用、算术类型、函数指针或者成员指针。
在比特位级别上进行转换。它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,在把该整数转换成原类型的指针,还可以得到原先的指针值)。
最普通的用途就是在函数指针类型之间进行转换。
很难保证移植性。
用法可以总结如下:
- 去const属性用const_cast。
- 基本类型转换用static_cast。
- 多态类之间的类型转换用daynamic_cast。
- 不同类型的指针类型转换用reinterpret_cast。
通常为运算对象的位模式提供较低层次上的重新解释。即要求编译器将两种无关联的类型作转换。
- reinterpret_cast本质上依赖于机器。要想安全地使用reinterpret_cast必须对涉及的类型和编译器实现转换的过程都非常了解。
- 较危险, 慎用
# const_cast
这个操作符可以暂时去掉变量const属性或者volatile属性的转换符,这样就可以更改const变量了。比如下面代码
string str = "hello";
char *_const = str.substr(0,3).c_str();//c_str()返回const char*类型,直接赋值给char *显然出错,这句话编译不能通过
char *_non_const = const_cast<char *> (str.substr(0,3).c_str()); //将const属性移除,可以通过编译了
2
3
- const_cast, const_cast 只能改变运算对象的low_level const
- 一旦我们去掉了某个对象的const性质,编译器就不再阻止我们对该对象进行写操作了。
- 如果对象本身不是一个常量,使用强制类型转换获得写权限是合法的行为。然而如果对象是一个常量,再使用const_cast执行写操作就会产生未定义的后果。
# C语言中的强制类型转换
在引入命名的强制类型转换操作符之前,显式强制转换用圆括号将类型括起来实现:
char *pc = (char*) ip;
效果与使用 reinterpret_cast 符号相同,但这种强制转换的可视性比较差,难以跟踪错误的转换。
标准 C++ 为了加强类型转换的可视性,引入命名的强制转换操作符,为程序员在必须使用强制转换时提供了更好的工具。例如,非指针的 static_cast 和const_cast 要比 reinterpret_cast 更安全。 结果使程序员(以及读者和操纵程序的工具)可清楚地辨别代码中每个显式的强制转换潜在的风险级别。
虽然标准 C++ 仍然支持旧式强制转换符号,但是我们建议,只有在 C 语言或标准 C++ 之前的编译器上编写代码时,才使用这种语法。 旧式强制转换符号有下列两种形式:
type (expr); // Function-style cast notation
(type) expr; // C-language-style cast notation
2
旧式强制转换依赖于所涉及的数据类型,具有与 const_cast、 static_cast 和 reinterpret_cast 一样的行为。 在合法使用 static_cast 或 const_cast 的地方,旧式强制转换提供了与各自对应的命名强制转换一样的功能。 如果这两种强制转换均不合法,则旧式强制转换执行 reinterpret_cast 功能。 例如,我们可用旧式符号重写上一节的强制转换:
int ival; double dval;
ival += int (dval); // static_cast: converts double to int
const char* pc_str;
string_copy((char*)pc_str); // const_cast: casts away const
int *ip;
char *pc = (char*)ip; // reinterpret_cast: treats int as char
2
3
4
5
6
支持旧式强制转换符号是为了对“在标准 C++ 之前编写的程序”保持向后兼容性,并保持与 C 语言的兼容性。
# Summary
reinterpret_cast可以转换任意一个32bit整数,包括所有的指针和整数。可以把任何整数转成指针,也可以把任何指针转成整数,以及把指针转化为任意类型的指针,威力最为强大!但不能将非32bit的实例转成指针。总之,只要是32bit的东东,怎么转都行!
static_cast和dynamic_cast可以执行指针到指针的转换,或实例本身到实例本身的转换,但不能在实例和指针之间转换。
static_cast只能提供编译时的类型安全, 而dynamic_cast可以提供运行时类型安全。举个例子:
class a;
class b:a;
class c;
2
3
上面三个类a是基类,b继承a,c和ab没有关系。
有一个函数
void function(a &a);
现在有一个对象是b的实例b,一个c的实例c。
function(static_cast<a&>(b)可以通过而 function(static_cast<a&>(c))不能通过编译,因为在编译的时候编译器已经知道c和a的类型不符,因此static_cast可以保证安全。
下面我们骗一下编译器,先把c转成类型a
b& ref_b = reinterpret_cast<b&>c;
然后function(static_cast<a&>(ref_b))就通过了!因为从编译器的角度来看,在编译时并不能知道ref_b实际上是c!
function(dynamic_cast<a&>(ref_b))编译时也能过,但在运行时失败了,因为dynamic_cast在运行时会检查ref_b的实际类型,这样就骗不过去了。
在应用多态编程时,当我们无法确定传过来的对象的实际类型时使用dynamic_cast,如果能保证对象的实际类型,用 static_cast 就可以了。
- dynamic_cast:动态类型转换
- static_cast:静态类型转换
- reinterpret_cast:重新解释类型转换
- const_cast:常量类型转换
一些常规操作:
- dynamic_cast 一般用在父类和子类指针或应用的互相转化;
- static_cast 一般是普通数据类型(如int m =static_cast
(3.14)); - reinterpret_cast 很像c的一般暴力类型转换操作
- const_cast 是把cosnt或volatile属性去掉
# static_cast 一些将会导致错误的情况。
# 泛型(Generic Types)
float f = 12.3;
float *pf = &f;
// static cast<>
// 成功编译, n = 12
int n = static_cast<int>(f);
// 错误,指向的类型是无关的(译注:即指针变量pf是float类型,现在要被转换为int类型)
int* pn = static_cast<int*>(pf);
//成功编译
void* pv = static_cast<void*>(pf);
int* pn2 = static_cast<int*>(pv); //成功编译, 但是 *pn2是无意义的内存(rubbish)
// reinterpret_cast<>
int i = reinterpret_cast<int>(f); //错误,编译器知道你应该调用static_cast<>
int* pi = reinterpret_cast<int*>(pf); //成功编译, 但是 *pi 实际上是无意义的内存,和 *pn2一样
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
简而言之,static_cast<> 将尝试转换,举例来说,如float-到-integer,而reinterpret_cast<>将改变编译器的意图重新将对象作为另一类型来处理
# 指针类型(Pointer Types)``
指针转换有点复杂,后面其情况都会使用下面的类:
class CBaseX
{
public:
int x;
CBaseX() { x = 10; }
void foo() { printf("CBaseX::foo() x =%d/n", x); }
};
class CBaseY
{
public:
int y;
int* py;
CBaseY() { y = 20; py = &y; }
void bar() { printf("CBaseY::bar() y =%d, *py =%d/n", y, *py); }
};
class CDerived : public CBaseX, public CBaseY
{
public:
int z;
};
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# 情况1:两个无关的类之间的转换
// Convert between CBaseX* and CBaseY*
// CBaseX* 和 CBaseY*之间的转换
CBaseX* pX = new CBaseX(); // Error, types pointed to are unrelated
CBaseY* pY1 = static_cast<CBaseY*>(pX); // Compile OK, but pY2 is not CBaseX
CBaseY* pY2 = reinterpret_cast<CBaseY*>(pX); // System crash!!!
// pY2->bar();
2
3
4
5
6
7
正如我们在泛型例子中所认识到的,如果你尝试转换一个对象到另一个无关的类static_cast<>将失败,而reinterpret_cast<>就总是成功“欺骗”编译器:那个对象就是那个无关类。但是在运行的时候由于编译器对被转换的对象会发生错误的解释, 程序会crash掉.
# 情况2:转换到相关的类
CDerived* pD = new CDerived();
printf("CDerived* pD = %x/n", (int)pD);
// static_cast<> CDerived* -> CBaseY* -> CDerived*
CBaseY* pY1 = pD; //成功编译,隐式static_cast<>转换
printf("CBaseY* pY1 = %x/n", (int)pY1);
CDerived* pD1 = static_cast<CDerived*>(pY1);// 成功编译, 现在 pD1 = pD
printf("CDerived* pD1 = %x/n", (int)pD1);
// reinterpret_cast
CBaseY* pY2 = reinterpret_cast<CBaseY*>(pD);// 成功编译, 但是 pY2 不是 CBaseY*
printf("CBaseY* pY2 = %x/n", (int)pY2);
// 无关的 static_cast<>
CBaseY* pY3 = new CBaseY();
printf("CBaseY* pY3 = %x/n", (int)pY3);
CDerived* pD3 = static_cast<CDerived*>(pY3);// 成功编译,尽管 pY3 只是一个 "新 CBaseY()"
printf("CDerived* pD3 = %x/n", (int)pD3);
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
---------------------- output ---------------------------
CDerived* pD = 392fb8
CBaseY* pY1 = 392fbc
CDerived* pD1 = 392fb8
CBaseY* pY2 = 392fb8
CBaseY* pY3 = 390ff0
CDerived* pD3 = 390fec
注意:在将CDerived用隐式 static_cast<>转换到CBaseY(第5行)时,结果是(指向)CDerived*(的指针向后) 偏移了4(个字节)(译注:4为int类型在内存中所占字节数)。为了知道static_cast<> 实际如何,我们不得不要来看一下CDerived的内存布局。
CDerived的内存布局包括两个对象,CBaseX 和 CBaseY,编译器也知道这一点。因此,当将CDerived* 转换到 CBaseY时,它给指针添加4个字节,同时当你将CBaseY转换到CDerived*时,它给指针减去4。然而,甚至它即便不是一个CDerived你也可以这样做。
当然,这个问题只在如果你做了多继承时发生。在你将CDerived转换 到 CBaseX时static_cast<> 和 reinterpret_cast<>是没有区别的。
# 情况3:void之间的向前和向后转换
因为任何指针可以被转换到void,而void*可以被向后转换到任何指针(对于static_cast<> 和 reinterpret_cast<>转换都可以这样做),如果没有小心处理的话错误可能发生。
CDerived* pD = new CDerived();
printf("CDerived* pD = %x/n", (int)pD);
CBaseY* pY = pD; // 成功编译, pY = pD + 4
printf("CBaseY* pY = %x/n", (int)pY);
void* pV1 = pY; //成功编译, pV1 = pY
printf("void* pV1 = %x/n", (int)pV1);
// pD2 = pY, 但是我们预期 pD2 = pY - 4
CDerived* pD2 = static_cast<CDerived*>(pV1);
printf("CDerived* pD2 = %x/n", (int)pD2);
// pD2->bar();// 系统崩溃
2
3
4
5
6
7
8
9
10
---------------------- 输出 ---------------------------
CDerived* pD = 392fb8
CBaseY* pY = 392fbc
void* pV1 = 392fbcd
CDerived* pD2 = 392fbc
一旦我们已经转换指针为void,我们就不能轻易将其转换回原类。在上面的例子中,从一个void 返回CDerived的唯一方法是将其转换为CBaseY然后再转换为CDerived。 但是如果我们不能确定它是CBaseY* 还是 CDerived*,这时我们不得不用dynamic_cast<> 或typeid[2]。
注释:
dynamic_cast<>, 从另一方面来说,可以防止一个泛型CBaseY* 被转换到CDerived*。
dynamic_cast<>, 需要类成为多态,即包括“虚”函数,并因此而不能成为void*。