一、隐式类型转换
在C++中,某些类型之间有关联。如果两种类型有关联,那么程序需要其中一种类型的运算对象时,可以用另一种关联类型的对象或值来替代,则这两种类型是可以相互转换的。
如果类型转换是自动执行,无需程序员介入的,则被称为隐式转换(implicit conversion)。在以下情况中,编译器会自动进行类型转换:
(1)在大多数表达式中,比int类型小的整型值首先提升为较大的整数类型;
(2)在判断条件中,非布尔值转换成布尔类型;
(3)初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型;
(4)如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型;
(5)函数调用时也会发生类型转换。
算术转换的规则定义了一套类型转换的层次,其中运算符的运算对象将转换成最宽的类型。如果一个运算对象是无符号、另一个是有符号类型,而且其无符号类型不小于有符号类型时,有符号类型转换为无符号类型。如果无符号类型小于有符号类型时,此时若无符号类型的所有值都能存入该有符号类型,则无符号转换为有符号类型,若不能,则有符号类型转换为无符号类型。
除了算术转换外,还有以下几种隐式类型转换:
(1)数组转换为指针;
(2)指针转换:①常量整数值0或字面值nullptr能转换成任意指针类型;②指向任意非常量的指针能转换成void*;③指向任意对象的指针能转换成const void*;
(3)指针或算术类型转换成布尔类型;
(4)允许将指向非常量类型的指针或引用转换成指向相应常量类型的指针或引用;
(5)类类型可由编译器自动执行转换,但编译器每次只能执行一种类类型的转换。
二、显式类型转换
虽然有时不得不使用强制类型转换,但这种方法本质上是非常危险的。一个命名的强制类型转换形式为:cast-name(expression);
其中,type是转换的目标类型,expression是要转换的值。如果type是引用类型,则转换结果为左值。cast-name可以是以下4种:static_cast;dynamic_cast;const_cast;reinterpret_cast。
(1)static_cast:任何具有明确定义的类型转换,只要不包含底层const,都可以使用。其还可以找回存在于void*指针中的值,例如:
void *p = &d;
double *dp = static_cast<double*>(p);
需要static_cast进行强制类型转换的时候:
①void指针转换为其他类型指针;
②改变通常的标准转换;
③避免出现可能发生多种转换的歧义。
static_cast的主要用法有:
①用于类层次结构中基类和子类之间指针或引用的转换,进行上行转换(把子类指针或引用转换为基类表示)是安全的;进行下行转换(把基类指针或引用转换成子类表示)时,由于没有动态类型检查,所以是不安全的。
②用于基本数据类型之间的转换,如把int转换成char,或把int转换为enum等。这种转换的安全性要由开发人员保证。
③把void指针转换成目标类型的指针,其是不安全的。
④把任何类型的表达式转换成void类型。
(2)dynamic_cast的使用方式有3种:
①dynamic_cast<type*>(e);
②dynamic_cast<type&>(e);
③dynamic_cast<type&&>(e);
其中,type必须是一类类型,并且通常情况下该类型应该包含有虚函数。e的类型必须符合以下3种情况之一:
①e的类型是目标type的公有派生类;
②e的类型是目标type的公有基类;
③e的类型就是目标type的类型。
①指针类型的dynamic_cast,例如:
Base *bp;
Derived *dp = dynamic_cast<Derived*>(bp);
可以对一个空指针执行dynamic_cast,其结果是所需类型的空指针。
②引用类型的dynamic_cast,由于不存在空引用,所以当对引用类型转换失败时,程序会抛出std::bad_cast异常。例如:
const Base &b;
try {
const Derived &d = dynamic_cast<const Derived&>(b);
}
catch (bad_cast) {
}
③右值引用类型的dynamic_cast
(3)const_cast:其只能改变运算对象的底层const,例如:
const char *pc;
char *p = const_cast<char*>(pc);
该运算符用来修改类型的const或volatile属性。
一旦去掉某个对象的const性质,则编译器就不再阻止对该对象进行写操作。只有const_cast能改变表达式的常量属性,使用其他形式的强制类型转换改变常量属性都将引发编译器错误。同时,const_cast还不能改变表达式的类型。例如:
const char *cp;
static_cast<string>(cp); // 正确
const_cast<string>(cp); // 错误
(4)reinterpret_cast:其通常为运算对象的位模式提供较低层次上的重新解释。例如:
int *ip;
char *pc = reinterpret_cast<char*>(ip);
string str(pc); // 错误 pc的真实对象是一int而非字符
每次使用强制类型转换语句,都应该反复斟酌能否以其他方式实现相同的目标。就算实在无法避免,也应该尽量限制类型转换值的作用域,并且记录对相关类型的所有假定,这样可以减少错误发生的机会。
如果无法避免显式类型转换,则应仅使用static_cast或const_cast,尽量不要使用dynamic_cast,永远不要使用reinterpret_cast。
当我们在某处执行旧式的强制类型转换时,如果换成const_cast和static_cast也合法,则其行为与对应的命名转换一致。如果替换后不合法,则旧式强制类型转换执行与reinterpret_cast类似的功能。