目录
目标
在C或C++中,可以使用 “()” 来强制转换一个对象的类型。
在C++中,也有很多后缀为_cast
的强制转换运算符,例如static_cast
、dynamic_cast
等。
本篇的目标是,结合官方文档和自己的实验:
- 学习他们的基本含义
- 实验尝试他们的基本用法
- 实验比较他们行为的区别
static_cast
定义
static_cast <type-id> ( expression )
Converts an
expression
to the type oftype-id
, based only on the types that are present in theexpression
.
In standard C++, no run-time type check is made to help ensure the safety of the conversion.
In general, you are certain of the data types involved in the conversion.
将expression
转换为type-id
的类型。此转换仅基于expression
本身的类型。
在标准的C++中,并没有运行时的检查来确保转换的安全性。
通常情况下,你应该对参与转换的数据类型是确定的。
基本用法1:数值型的转换
In general you use
static_cast
when you want to convert numeric data types such as enums to ints or ints to floats.
一般情况下,你可以使用static_cast
来对数值型的数据类型进行转换。例如:将“枚举型”转换为“整数型”,将“整数型”转换为“浮点数型”。
#include <iostream>
enum Fruit{
apple,
banana,
};
int main()
{
Fruit f = Fruit::banana;
int x = static_cast<int>(f); //将“枚举”转换为“整数型”
std::cout << "x: " << x << std::endl;
int y = 3;
float z = static_cast<float>(y); //将“整数型”转换为“浮点数型”
std::cout << "z: " << z << std::endl;
}
输出:
x: 1
z: 3
基本用法2:将父类指针转换为子类指针
The
static_cast
operator can be used for operations such as converting a pointer to a base class to a pointer to a derived class. Such conversions are not always safe.
static_cast
可以用来将一个父类指针转换为子类指针。但这样的转换并不总是安全的。
#include <iostream>
class Base {
};
class Child : public Base
{
int data; //子类专有的数据
public:
Child()
{
data = 7;//在构造函数中设定为7
}
void TestFunction() //子类专有的函数
{
std::cout << "data: " << data << std::endl;
}
};
int main()
{
Base* pB = new Child(); //虽然“pB”的类型是父类指针,但实际指向的对象是完整的子类对象。(此行代码任何情况下都正确且没有Warning)
Child* pC = static_cast<Child*>(pB); //将父类指针转换为子类指针。这里是正确的,因为从逻辑上可确保pB指向的是完整的子类对象
pC->TestFunction();
}
输出:
data: 7
注意:本质上不安全,需要程序员自己保证正确性
正如官方文档中多次提到的:
not always safe.
It is left to the programmer to verify that the results of astatic_cast
conversion are safe.
static_cast
并不总是安全的,其安全性是程序员自己保证的。
例如对于上例,当转换时指针所指向的对象并不是一个完整的子类对象时,则此种转换是危险的:
int main()
{
Base* pB = new Base(); //pB指向了一个基类对象
Child* pC = static_cast<Child*>(pB); //将父类指针转换为子类指针。这里是错误的,因为从逻辑上可知pB指向的不是子类对象
pC->TestFunction();
}
在我的机器上,运行的结果是输出:
data: -33686067
不仅对于父类指针转换为子类指针,对于数值型的转换也需要考虑转换是否符合预期。
例如,将32位的int
转换为8位的char
,就需要考虑8位的char
有可能容纳不下32位的int
,此时会被截断:
#include <iostream>
int main()
{
int Int1 = 77; //77在ASCII码中对应“M”
int Int2 = 256 + 81; //81在ASCII码中对应“Q”
char Char1 = static_cast<char>(Int1);
std::cout << "Char1: " << Char1 << std::endl;
char Char2 = static_cast<char>(Int2);
std::cout << "Char2: " << Char2 << std::endl;
}
输出:
Char1: M
Char2: Q
另外,也不难猜到,将“浮点数”转换为“整数”也有小数部分被舍去的问题:
#include <iostream>
int main()
{
float a = 3.7;
int b = static_cast<int>(a);
std::cout << b << std::endl;
}
输出:
3
这些都需要程序员自己注意。
dynamic_cast
定义
dynamic_cast < type-id > ( expression )
Converts the operand
expression
to an object of typetype-id
.
Thetype-id
must be a pointer or a reference to a previously defined class type or a “pointer to void”. The type ofexpression
must be a pointer iftype-id
is a pointer, or an l-value iftype-id
is a reference.
将 expression
转换为type-id
类型的对象。
type-id
必须是一个已经定义的类(或void)的指针或引用。如果type-id
是指针,则expression
也必须是个指针,如果type-id
是引用,则expression
必须是一个“左值”。
这个操作符中的dynamic
意思为 “动态的” 即 “运行时的” ,与之相对的概念是static
即 “静态的” 或者说 “编译时的”。
由于程序运行时在某种程度上是“不可预测的”(至少在C++中是这样),所以在C++中一个父类型的指针,所指向的对象的具体类型是无法在编译时期进行准确判断的。因此static_cast
并不能保证转换的安全性。
然而,dynamic_cast
使用了运行时类型信息 (RTTI),所以可以在运行时进行准确的判断,使得之前用static_cast
的转换变得安全。
基本用法:安全地对类的指针进行转换
例如一个父类Base
有两个子类ChildX
和ChildY
。创建一个实际类型为ChildY
的对象并让一个父类指针指向它,然后用dynamic_cast
尝试将其转换为ChildX
和ChildY
类型,观察效果:
#include <iostream>
class Base
{
public:
virtual int testFunc() {
return 1; }
};
class ChildX : public Base
{
public:
virtual int testFunc() override {
return 2; }
};
class ChildY : public Base
{
public:
virtual int testFunc() override {
return 3; }
};
int main()
{
Base* pB = new ChildY();
ChildX* pX = dynamic_cast<ChildX*>(pB);
std::cout << "pX:" << ((pX == nullptr) ? "转换失败" : "转换成功") << std::endl;
ChildY* pY = dynamic_cast<ChildY*>(pB);
std::cout << "pY:" << ((pY == nullptr) ? "转换失败" : "转换成功") << std::endl;
}
输出:
pX:转换失败
pY:转换成功
可以看到,它只会转变为正确的类型,对于不符合的类型dynamic_cast
将返回nullptr
。
通过这种方式,可以在运行时判断一个指针所指向对象具体的类型。
注意:只针对于指针或引用,不能对数值类型转换
dynamic_cast
操作的对象必须是——已经定义的类(或void)的指针或引用。
否则,将通不过编译:
reinterpret_cast
定义
reinterpret_cast < type-id > ( expression )
Allows any pointer to be converted into any other pointer type. Also allows any integral type to be converted into any pointer type and vice versa.
Misuse of thereinterpret_cast
operator can easily be unsafe. Unless the desired conversion is inherently low-level, you should use one of the other cast operators.
允许任何类型指针转换为其他类型指针。允许任何数值型的值转换为指针类型,反之亦然。
对reinterpret_cast
的滥用很容易导致不安全的行为。除非你想进行的转换必须在底层,否则你应该使用其他的转换操作符。
特点:朴实无华的底层转换
使用reinterpret_cast
可以对任何类型的指针进行转换,比如将float
转换为int
:
#include <iostream>
int main()
{
//浮点数的指针:
float* a = new float(9.625f);
//转换为整数的指针
int* b = reinterpret_cast<int*>(a);
//整数的值:
std::cout << *b << std::endl;
}
如果是使用static_cast
将一个float
转换为int
,则很显然转换后应该是9
。然而此处对于reinterpret_cast
的运行结果是:
1092222976
看起来这个数像是个“乱码”,然而它有实际的意义:它是9.625
这个浮点数的内存数据,被解释成int
得到的结果。
如果感兴趣,可以看下面对上述说法的证明:
首先,先搞明白值为9.625
的float
,其在内存中的二进制数据是什么样的。
这里我参考了《IEEE 754浮点数标准详解》。
将9.625
拆分为二进制的形式:
9.625 = 8 + 1 + 0.5 + 0.125 = 2 3 + 2 0 + 2 − 1 + 2 − 3 = 1 × 2 3 + 0 × 2 2 + 0 × 2 1 + 1 × 2 0 + 1 × 2 − 1 + 0 × 2 − 2 1 × 2 − 3 \begin{aligned} 9.625 & = 8+1+0.5+0.125 \\ & =2^3+2^0+2^{-1}+2^{-3}\\ & = 1\times 2^3+0\times 2^2+0\times2^1+1\times2^0+1\times2^{-1}+0\times 2^{-2}1\times2^{-3}\\ \end{aligned} 9.625=8+1+0.5+0.125=23+20+2−1+2−3=1×23+0×22+0×21+1×20+1×2−1+0×2−21×2−3
用二进制表示的话,就是:
1001.101 1001.101 1001.101
规范化(将小数点移动到最左边的1的右边)后,就是:
1.001101 × 2 3 1.001101\times2^3 1.001101×23
对于IEEE 754标准的32位浮点数:
- 第1位是符号位,这里是正值,所以是
0
- 之后的8位指明指数偏移,这里是
+3
,所以是127+3 = 130
,即二进制的10000010
- 剩下的23位是数值部分,这里是
1.001101
,去掉小数点前的1之后是001101
,补齐23位后是00110100000000000000000
综上所述,9.625
的float
在内存中的二进制数据是:
0
10000010
00110100000000000000000
通过一些二进制转换为10进制的小工具,可以能计算出:
01000001000110100000000000000000
的10进制数值就是1092222976
const_cast
定义
const_cast <type-id> (expression)
Removes the
const
,volatile
, and__unaligned
attribute(s) from a class.
将一个对象的 const
、volatile
、和 __unaligned
修饰符去掉。
对于volatile
、和 __unaligned
我还没有使用经验所以暂时不讨论。
不过对于const
来说,似乎就是将const
这个修饰去掉。然而实际上情况并不只是这么简单,接下来结合实际代码讨论:
基本用法:去掉const的修饰使得编译通过
例如,下面的代码(改动自官方返范例)是通不过编译的,有两处错误:
必须使用const_cast
才能使得这两处编译通过,代码如下:
#include <iostream>
class TestClass
{
int data;
public:
void SetData(int* x)
{
data = *x;
}
void PrintData() const
{
std::cout << "改动前:" << data << std::endl;
const_cast<TestClass*>(this)->data--; //使用const_cast去掉const修饰
std::cout << "改动后:" << data << std::endl;
}
};
int main()
{
const int x = 7; //常量整数
TestClass c;
c.SetData(const_cast<int*>(&x)); //使用const_cast去掉const修饰
c.PrintData();
}
我能想象到的一种使用情况是:
你需要调用一个接口,此接口的参数不带const
,而你手上想给它的参数恰巧是const
。你没法改变此接口的定义,因此只能通过const_cast
修饰符保证此参数可以通过编译。
注意:不能直接改变一个变量的常量状态
You cannot use the
const_cast
operator to directly override a constant variable’s constant status.
你不能使用const_cast
来直接修改一个变量的常量状态。
这个问题也在《C++标准转换运算符const_cast - Ider - 博客园》这篇文章中有所讨论。
例如,下面的代码虽然可以通过编译:
#include <iostream>
int main()
{
const int x = 7; //常量整数
int* pX = const_cast<int*>(&x); //使用const_cast去掉const修饰
*pX = 3; //修改其值
std::cout << x << std::endl; //观察是否发生改变(应该没有)
}
但是从输出可以看出并没有修改成功:
7
看来,一个常量,没法通过const_cast
进行修改。
强制转换运算符“()”
定义
unary-expression ( type-name ) cast-expression
A type cast provides a method for explicit conversion of the type of an object in a specific situation.
在特定情况下,将一个类型的对象“显式”地转换为另一种类型。
编译器也在“隐式”地做一些转换,不过有时会发出警告,例如:
Explicit type casts are constrained by the same rules that determine the effects of implicit conversions
“显式”地转换将遵循“隐式”地转换相同的规则约束。
合法类型强制转换
《类型强制转换的转换 | Microsoft Docs》指出“合法类型强制转换”:
与“_cast”转换符的区别
我没有找到此方面明确的官方文档,不过我对我上面的例子都做了测试,将他们替换为()
,发现:
static_cast
的实例都可以换为()
并得到相同的结果。dynamic_cast
不行,看来要使用运行时类型信息 (RTTI)就必须要使用dynamic_cast
reinterpret_cast
的实例可以替换为()
并得到相同的结果。const_cast
的实例“部分”通过编译并得到相同结果:成员函数的const修饰符,若将const_cast
替换为()
会通不过编译。
我目前对其的理解是:
()
会首先尝试static_cast
的行为,如果失败则会表现得像reinterpret_cast
。牵扯到const
时则会表现得像const_cast
。不过一定不能替代dynamic_cast
的作用。- 虽然
()
很容易通过编译,但是其行为是不好预测的。因此,最好还是明确地使用具体的_cast
转换符,这样有助于对程序的行为进行预测,降低错误。
总结
首先,()
尽量不使用,而是使用具体的_cast
const_cast
主要用来去掉const
修饰
下面重点是static_cast
、dynamic_cast
、reinterpret_cast
三者的区别,我的总结如下:
不过以上的总结主要还是这些转换的基本含义和基本用法,实际项目中的情况可能会相对复杂,到时候还是推荐构筑一些实验来明确了解其行为。