文章目录
在C++语言中,某些类型之间有关联。如果两种类型有关联,那么当程序需要其中一种类型的运算对象的时候,可以使用另一种关联类型的对象或值来代替。换句话说,如果两种类型可以相互转换,那么他们就是关联的。
举个例子:
int ival = 3.14+3;//结果是6
加法的两个运算对象的类型不一致:3.14是double,3是int。C++语言不会直接把两个不同类型的值进行相加,而是先根据类型转换规则设法将两个对象的类型统一后,再求值。上述的类型转换是自动进行的,不需要程序员的介入,因此这被称作隐式转换。
算术类型之间的隐式转换被设计得尽可能避免损失精度。很多时候,如果表达式当中既有整数类型又有浮点数类型的运算对象,整型会转换成浮点型。在上面的例子中,3换成3.0,然后执行浮点数加法,所得的结果是浮点数类型的,但由于初始化过程的对象的类型无法进行改变,所以最后又把结果转换为int传给ival。
总结一小下,何时发生隐式类型转换:
- 在大多数表达式中,比int类型小的整型值首先提升到较大的整数类型。
- 在条件中,非布尔值转换为布尔类型。
- 初始化过程中,初始值转换成变量的类型。
- 在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
- 如果算术运算或关系运算的运算对象有多种类型,需要换算成同一种类型。
- 函数调用的时候有时也会发生类型转换
算术转换
算术转换的含义是把一种算术运算类型转换成另一种算术类型。算术转换定义了一套类型转换的层次,其中运算符的运算对象将转换成最宽的类型。例如,刚刚的int和double,double所占的位数是8字节,int占的是4字节,所以转换为double类型。
整型提升
整型提升负责把小整数类型转化为较大的整数类型,对于bool、char、signed char、unsigned char 、short 、unsigned short等类型来说,只要他们所有可能的值都能存在int内部,它们就都会提升到int类型;否则提升到unsigned int类型。
较大的char类型(wchar_r 、char16_t 、char32_t)都会提升到int 、unsigned int 、long 、unsigned long 、long long、unsigned long long当中可以满足的较小的一种类型。
无符号类型运算对象
首先如果是运算符号两边的类型不一致,这些运算对象将转换成同一种类型。但是如果某个运算对象的类型是无符号类型,那么转换的结果就要依赖于机器中各个整数类型的相对大小了。
和往常一样,先进行整型提升。如果类型匹配,无需要进行下一步的转换,如果两个运算对象要么是带符号的和不带符号的,则小类型转换为大类型。
如果一个运算对象是无符号类型、另外-一个运算对象是带符号类型,而且其中的无符号类型不小于带符号类型,那么带符号的运算对象转换成无符号的。例如,假设两个类型分别是unsigned int和int, 则int类型的运算对象转换成unsigned int类型。需要注意的是,如果int型的值恰好为负值,其结果将以2.1.2节(第32页)介绍的方法转换,并带来该节描述的所有副作用。
剩下的一一种情况是带符号类型大于无符号类型,此时转换的结果依赖于机器。如果无符号类型的所有值都能存在该带符号类型中,则无符号类型的运算对象转换成带符号类型。如果不能,那么带符号类型的运算对象转换成无符号类型。例如,如果两个运算对象的类型分别是long和unsigned int,并且int和long的大小相同,则long类型的运算对象转换成unsigned int 类型;如果long类型占用的空间比int 更多,则unsigned int类型的运算对象转换成long类型。
PS:所以对于这么复杂的类型转换,个人建议是在使用的时候就要想好他生成的是什么类型。
理解算术转换
这里举一些例子,给大家理解理解:
3.14L +‘a’//1
double + int //2
double + float//3
int = double//4
bool = double//5
char + float//6
short + char//7
char + long//8
int +unsigned long//9
unsigned short + int//10
unsigned int +int//11
- 'a’提升为int类型,然后该int类型转换为long double类型再进行相加。(提升和转换不是一种东西,提升是电脑默认进行的处理,转化是程序分析两个数据的类型差异给出的处理)。
- int类型转化为double类型,再进行相加。
- float类型转换为double类型,再进行相加。
- double转换为int类型,然后进行赋值。
- 当且仅当double为0,bool才为false,否则为true。
- char提升到int,然后int转换为float,再进行相加。
- short和char都提升为int,再进行相加。
- char提升到int,再转换为long。
- int转换为unsigned long再进行相加。
- 根据二者所占的空间大小进行提升。
- 根据二至所占的空间大小进行转换。
其他隐式类型转换
除了算术转换之外还有几种隐式类型转换,介绍如下:
数组转化成指针
int ia[10];
int *p=ia;//ia转换为指向数组首元素的指针
值得注意的是:
当数组被当作decltype关键字的参数,或者作为取地址符&,sizeof以及typeid等运算符的运算对象的时候,上述的转换不会发生,或者是用一个引用来初始化数组上述的转换也不会发生。即int (&p)[10]=arr的时候,arr并不是指针,代表的是一个数组。
转换为布尔类型
存在一种从算术类型或指针类型向布尔类型自动转换的机制。如果指针或者算术值为0,转换结果为flase,否则为true。
转换成常量
允许将指向非常量类型的指针转换成指向相应的常量类型的指针,对于引用也是这样子。
例子:
int i;
const int& j = i;//正确,将非常量表达式转化为常量表达式。
const int* p = &i;//正确,将非常量表达式转化为常量表达式。
int& r = j ,*q = p;//错误,不允许将常量表达式转化为非常量表达式。或者这么理解,他试图删除底层const。
类类型定义的转换
这边举个例子,用处其实很少。
string s;
while(cin>>s)//实际上就是把cin输入的string类型值转换为布尔值。
显式转换
有时候我们希望显式的将对象强制转换为另外一种类型。例如,如果想在下面的代码中执行浮点数除法:
int i,j;
double slope = i / j;
就需要使用某种方法将i和j转化为double类型,这种方式叫做强制类型转换。
PS:虽然有时候不得不使用强制类型转换,但这种做法其实是十分危险的。
命名的强制类型转换
一个命名的强制类型转换具有如下的格式:
cast-name<type>(expression);
其中type是转换的目标类型,而expression是要转换的值。如果type是引用类型,则结果就是左值。cast-name是static_cast 、dynamic_cast 、const_cast 、reinterpret_cast当中的一种。
dynamic_cast
对于这个,我们将在之后进行更多的详细介绍,现在先不表,只需要知道其支持运行时的类型识别。主要用于虚函数当中。
static_cast
任何具有明确定义的类型转换,且不涉及到==底层const(指针指向的对象是不是常量)==的时候,都可以使用static_cast。例如:刚刚的除法:
double slope =static_cast<double> (i) / j;
这就是将int的i转化为double类型的C++模式。
基本等效于隐式转换的一种类型转换符号,可以使用于需要明确隐式转换的地方。
const_cast
const_cast只能改变运算对象的底层const。
const char *p;//底层const,形容的是指针指向的值不变。
char *p = const_char<char>(pc);
对于将常量对象转换为非常量对象的行为,我们把这个叫做"去掉const性质"。一旦我们去掉了某个对象的const的性质,编译器就不再阻止我们对该对象进行写操作。
const常用于函数重载的上下文当中,到时候也会进行详细介绍。
reinterpret_cast
reinterpret_cast通常作为运算对象的位模式提供较低层次上的重新解释,其实实际上也就是不管能不能进行,不管进行完出的是什么错误,就直接进行的一个操作。建议是不要使用,它实际上就是我们以前使用的强转。
旧式的强制类型转换
在早期的C++版本,显式的进行强制类型转换包含两种形式:
type(expr);
(type)expr;
根据所涉及的领域不同,旧式的强制类型转换分别具有const_cast 、static_cast 、reinterpret_cast相似的行为。当我们在某处执行旧式的强制类型转换的时候,如果换成const_cast和static_cast也合法,其行为与对应的命名转换一致。如果替换后不合法,则旧式的强制类型转换执行与reinterpret_cast有类似的功能。
这里的建议是尽量转换习惯,改成新标准下的转换。
常用的就是static_cast和const_cast。