C++与C语言一样,有显式和隐式两种强制类型转换(Type Cast)
- 显式一般就是直接用小括号强制转换TYPE b = (TYPE)a;
- 隐式就是直接赋予类型不同的定义,如float b = 1; int a = b。
在实际使用中,C++基本采用以下四种常用的强制类型转换关键字:
- static_cast
- const_cast
- einterpret_cast
- dynamic_cast
下面参考标题相关文献具体说明:
0.应用场景
一般来说,我们需要类型转换的场景可以分为如下几种:
- 1.整型和浮点型相互转换。
这种转换往往是在数学计算的场景下。比如一个库函数导出的是double型数据,而我们使用该数据的函数的参数要求是整型,于是我们就需要对其进行转换。反之亦然。 - 2.整型和指针相互转换。
当我们试图根据某个成员变量的偏移位计算其在该对象内存空间位置时,就会需要将指针转换为整型进行计算。当计算出该变量的位置后(整型),就需要将其转换为指针类型。 - 3.整型和枚举类型相互转换。
这种转换往往发生在数学计算的场景下。枚举可以自动向整型变换,但反之不可,所以主要枚举主要还是表意,数学运算靠整型。 - 4.指针和无类型指针相互转换。
一个典型的场景是,win32编程中,线程函数的入参要求是个LPVOID型数据。而我们往往将类对象的指针传递进去,以方便我们调用封装在类中的相关函数和变量。即CreateThread时将指针转为void型,在线程函数中将void转为指针。 - 5.无关系类指针的相互转换。
这种场景并不多见。存在继承关系的类指针相互转换。多发生在多态等场景下。 - 6.转换不同长度的数据。
这是个转换截断的问题。
在测试如上场景时,难点源于两个方面:
-
编译器出错。这是因为语法规定这种使用不合法。所以编译器在编译代码时,认为该行为违法,终止之后的流程。
-
运行时出错。这是因为在语法上是合法的,但是运行时是不合理的。
1.static_cast
该运算符把expression转换为type-id类型,但没有运行时类型检查来保证转换的安全性,因此需要我们自行判断。它主要有如下几种用途:
- ①用于类层次结构中基类(父类)和派生类(子类)之间指针或引用的转换。
- 进行上行转换(把派生类的指针或引用转换成基类表示)是安全的;
- 进行下行转换(把基类指针或引用转换成派生类表示)时,由于没有动态类型检查,所以是不安全的。
- ②用于基本数据类型之间的转换,如把int转换成char,把int转换成enum。这种转换的安全性也要开发人员来保证。
- ③把空指针转换成目标类型的空指针。
- ④把任何类型的表达式转换成void类型。
注意:static_cast不能转换掉expression的const、volatile、或者__unaligned属性。
2.const_cast
const_cas主要作用是:修改类型的const或volatile属性。
const_cast<type_id> (expression)除了const 或volatile修饰之外, type_id和expression的类型是一样的。
- ①常量指针被转化成非常量的指针,并且仍然指向原来的对象;
- ②常量引用被转换成非常量的引用,并且仍然指向原来的对象;
- ③const_cast一般用于修改底指针。如const char *p形式。
3.einterpret_cast
reinterpret_cast (expression)中的type-id 必须是一个指针、引用、算术类型、函数指针或者成员指针。该操作符修改了操作数类型,但仅仅是重新解释了给出的对象的比特模型而没有进行二进制转换。
例如:
int *n= new int ;
double *d=reinterpret_cast<double*> (n);
在进行计算以后, d 包含无用值. 这是因为 reinterpret_cast 仅仅是复制 n 的比特位到 d, 没有进行必要的分析。
它可以把一个指针转换成一个整数,也可以把一个整数转换成一个指针(先把一个指针转换成一个整数,再把该整数转换成原类型的指针,还可以得到原先的指针值)。
reinterpret_cast是为了映射到一个完全不同类型的意思,这个关键词在我们需要把类型映射回原有类型时用到它。我们映射到的类型仅仅是为了故弄玄虚和其他目的,这是所有映射中最危险的。(这句话是C++编程思想中的原话)
static_cast和reinterpret_cast的区别主要在于多重继承,比如:
class A {
public:
int m_a;
};
class B {
public:
int m_b;
};
class C : public A, public B {};
那么对于以下代码:
C c;
printf(
"%p, %p, %p",
&c, reinterpret_cast<B*>(&c), static_cast <B*>(&c)
);
但是需要注意的是:
- 前两个的输出值是相同的
- 最后一个则会在原基础上偏移4个字节
这是因为static_cast计算了父子类指针转换的偏移量,并将之转换到正确的地址(c里面有m_a,m_b,转换为B*指针后指到m_b处),而reinterpret_cast却不会做这一层转换。
因此, 你需要谨慎使用 reinterpret_cast.
4.dynamic_cast
dynamic_cast能把一个基类对象指针(或引用)转换到继承类指针
dynamic_cast会根据基类指针是否真正指向继承类指针来做相应处理
- dynamic_cast是用来检查两者是否有继承关系。因此该运算符实际上只接受基于类对象的指针和引用的类转换
- 如果 type-id 是类指针类型,那么expression也必须对应的是一个指针,如果 type-id 是一个引用,那么 expression 也必须是一个引用。
- dynamic_cast运算符可以在执行期决定真正的类型。
- 如果 downcast 是安全的(也就说,如果基类指针或者引用确实指向一个派生类对象)这个运算符会传回适当转型过的指针。
- 如果 downcast 不安全,这个运算符会传回空指针(也就是说,基类指针或者引用没有指向一个派生类对象)。
- 用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。
- 在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;
- 在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。
注意:为使dynamic_cast能正常使用:
- 如果要用继承,那么一定要让析构函数是虚函数;
- 如果一个函数是虚函数,那么在子类中也要是虚函数。
具体原因见虚函数与dynamic的关系。
四种强制类型的具体操作,可以参考该文