隐式转换

何时发生隐式类型转换

在表达式中,比int类型小的整型会首先被提升为较大的整数类型。

在条件中,非布尔值转换成布尔值。

初始化过程中,初始值转换成变量的类型。

在赋值语句中,右侧对象转换成左侧对象的类型。

如果算术运算符或关系运算符的对象有多种类型,需要转换成同一种类型。

在函数调用时也将发生类型转换。

算术转换

算术转换是将一种算术类型转换成另一种算术类型。

算术转换的规则定义了一套类型的转换层次,其中运算符的运算对象将转换成最宽的类型。

整型提升

整型提升负责把小整数类型转换成较大的整数类型。

对于bool、char、singned char、unsigned char、short、unsigned short等类型来说,只要它们所有可能的值能存放在int中,它们就会被提升成int类型,否则提升成unsigned int类型。

较大的char类型(wchar_t、char16_t、char32_t)提升成int、unsigned int、long、unsigned long、long long和unsigned long long中最小的一种类型,前提是转换后的类型能够容纳原来类型的所有值。

无符号类型的运算对象

如果某个运算符的运算对象类型不一致,这些运算对象将转换成同一种类型。但是如果某个运算对象的类型是无符号类型,那么转换的结果将要依赖于机器中各个整数类型的相对大小。

像平常一样,首先执行整型提升。如果结果的类型匹配,那么无须进一步转换。如果两个提升后的运算对象的类型要么都是带符号的、要么都是无符号的,则小类型的运算对象转换成较大的类型。

如果一个运算对象是无符号类型、另外一个运算对象是带符号类型的,而且其中的无符号类型不小于带符号类型,那么带符号的运算对象将转换成无符号的。假如,假设两个类型分别是unsigned int和int,则int类型的运算对象转换成unsigned int类型,如果int类型是负值,转换将带来副作用。

若带符号类型大于无符号类型,此时转换的结果依赖于机器。如果无符号类型的所有值都能存在该带符号类型中,则无符号类型的运算对象转换成带符号类型。如果不能,那么带符号类型的运算对象转换成无符号类型:例如,如果两个运算对象的类型分别是long和unsigned int,并且int和long的大小相同,则long类型的运算对象转换成unsigned int类型;如果long类型占用的空间比int更多,则unsigned int类型的运算对象转换成long类型。

3.1415926 + 'a';  //'a'提升为int,然后该int提升转换成long double
dval + ival;  //ival转换成double
dval + fval;  //fval转换成double
ival = dval;   //dval转换成int
flag = dval;  //如果dval是0,则flag是false,否则flag是true
cval + fval;  //cval提升成int,然后该int转换成float
sval + cval;  //sval和cval都提升成int
cval + lval;   //cval转换成long
ival + ulval   //ival转换成unsigned long
usval + ival; //根据unsigned short和int所占空间的大小进行提升
uival + lval; //根据unsigned int和long所占空间的大小进行转换

其他隐式类型转换

数组转换成指针

int ia[10];
int *ip = ia;

当数组被用作decltype关键字的参数,或者作为取地址符,sizeof以及typeid等运算符的运算对象时,上述转换不会发生。

如果用引用来初始化数组,上述转换也不会发生。

指针的转换

整数值0或者字面值nullptr能转换成任意指针类型;

指向任意非常量的指针能转换成void*;

指向任意对象的指针能转换成const void*。

算术类型或指针类型可以转换成bool类型,如果指针或算术类型的值为0,转换结果为false,否则转换结果为true。

允许将指向非常量类型的指针转换成指向响应常量类型的指针,对于引用也是一样:

int i;
const int &j = i;  //非常量转换成const int的引用
const int *p = &i;      //非常量的地址转换成const的地址
int &r = j, *q = p;      //错误,不允许将const转换成非常量
显示转换

c风格的转换的格式很简单(TYPE)EXPRESSION,但是c风格的类型转换有不少的缺点,有的时候用c风格的转换是不合适的,因为它可以在任意类型之间转换,比如你可以把一个指向const对象的指针转换成指向非const对象的指针,把一个指向基类对象的指针转换成指向一个派生类对象的指针,这两种转换之间的差别是巨大的,但是传统的c语言风格的类型转换没有区分这些。还有一个缺点就是,c风格的转换不容易查找,他由一个括号加上一个标识符组成,而这样的东西在c++程序里一大堆。所以c++为了克服这些缺点,引进了4新的类型转换操作符,他们是:static_cas、const_cast、dynamic_cast、reinterpret_cast。

一个命名的强制类型转换具有如下的形式:

cast-name<type>(expression)

type是转换的目标类型,而expression是要转换的值。

static_cast

任何具有明确定义的类型转换,只要不包含底层const都可以使用static_cast。

//进行强制类型转换,以便执行浮点数除法

double slope = static_cast<double>(j) / i;

当需要把较大的算术类型赋值给较小的类型时,static_cast非常有用。一般来说,如果编译器发现一个较大的算术类型试图赋值给较小的类型,就会发出警告信息,但是当执行了显示类型转换后,警告信息就会被关闭。

static_cast对于编译器无法自动执行的类型转换也非常有用,例如,我们可以使用static_cast找回存在于void*指针中的值:

void *p = &d;     // 正确,任何非常量对象的地址都能存入到void*
double *dp = static_cast<double*>(p);       //转换成初始类型

必须确保转换后所得的类型就是指针所指的类型。类型一旦不符,将产生未定义的后果。

const_cast

const_cast只能该改变运算对象的底层const:

const char *pc;
char*p = const_cast<char*>(pc);

一旦去掉了某个对象的const性质,编译器就不再阻止对该对象进行操作。

只有const_cast能改变表达式的常量属性,使用其他形式的命名强制类型转换改变表达式的常量属性都将引发编译器的错误。同样的,也不能用const_cast改变表达式类型:

const char *cp;
char *q = static_cast<char*>(cp);  //错误,static_cast不能转换掉const性质
static_cast<string>(cp);    //正确,字符串字面值转换成string类型
const_cast<string>(cp);           //c错误,const_cast只改变常量属性

reinterpret_cast

reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。

const char *pc;
char*p = const_cast<char*>(pc);

pc所指的真实对象是一个int而非字符,如果把pc当成普通的字符指针使用就可能在运行时发生错误,例如:

string str(pc);

可能导致异常的运行时行为。

使用reinterpret_cast是非常危险的,用pc初始化str的例子就很好地证明了这一点。其中的关键问题是类型改变了,但是编译器并没有给出警告信息或者是错误的提示信息。当我们用一个int的地址初始化pc时,由于显示地声称这种转换合法,所以编译器不会发生任何警告和错误信息,接下来使用pc时就会认定它的值是char*类型,编译器没法知道它实际存放的是指向int的指针。

dynamic_cast

该操作符用于运行时检查该转换是否类型安全,但只在多态类型时合法,即该类至少具有一个虚拟方法。dynamic_cast与static_cast具有相同的基本语法,dynamic_cast主要用于类层次间的上行转换和下行转换,还可以用于类之间的交叉转换。在类层次间进行上行转换时,dynamic_cast和static_cast的效果是一样的;在进行下行转换时,dynamic_cast具有类型检查的功能,比static_cast更安全。