[C++]C++Primer Chapter 4
表达式
4.1 基础
4.1.1 基本概念
左值和右值
在C++语言中,二者的区别就没那么简单了。一个左值表达式的求值结果是一个对象或者一个函数,然而以常量对象为代表的某些左值实际上不能作为赋值语句的左侧运算对象。此外,虽然某些表达式的求值结果是对象,但它们是右值而非左值。可以做一个简单的归纳:当一个对象被用作右值的时候,用的是对象的值(内容);当对象被用作左值的时候,用的是对象的身份(在内存中的位置)。
4.1.2 优先级与结合律
4.1.3 求值顺序
优先级规定了运算对象的组合方式,但是没有说明运算对象按照什么顺序求值。在大多数情况下,不会明确指定求值的顺序。对于如下的表达式
我们知道f1和f2一定会在执行乘法之前被调用,因为毕竟相乘的是这两个函数的返回值。但是我们无法知道到底f1在f2之前调用还是f2在f1之前调用。
有4种运算符明确规定了运算对象的求值顺序:
1.逻辑与(&&)运算符,它规定先求左侧运算对象的值,只有当左侧运算对象的值为真时才继续求右侧运算对象的值。
2.逻辑或(||)运算符(参见4.3节,第126页)、
3.条件(?:)运算符(参见4.7节,第134页)
4.逗号(,)运算符(参见4.10节,第140页)。
4.2 算术运算符
C++11新标准则规定商一律向0取整(即直接切除小数部分)。
4.3 逻辑和关系运算符
4.4 赋值运算符
赋值运算返回的是其左侧的运算对象
赋值运算满足右结合律
4.5 递增和递减运算符
递增和递减运算符有两种形式:前置版本和后置版本。到目前为止,本书使用的都是前置版本,这种形式的运算符首先将运算对象加1(或减1),然后将改变后的对象作为求值结果。后置版本也会将运算对象加1(或减1),但是求值结果是运算对象改变之前那个值的副本
这两种运算符必须作用于左值运算对象。前置版本将对象本身作为左值返回,后置版本则将对象原始值的副本作为右值返回。
建议:除非必须,否则不用递增递减运算符的后置版本
运算对象可按任意顺序求值
大多数运算符都没有规定运算对象的求值顺序(参见4.1.3节,第123页),这在一般情况下不会有什么影响。然而,如果一条子表达式改变了某个运算对象的值,另一条子表达式又要使用该值的话,运算对象的求值顺序就很关键了。因为递增运算符和递减运算符会改变运算对象的值,所以要提防在复合表达式中错用这两个运算符。
4.6 成员访问运算符
4.7 条件运算符
其中cond是判断条件的表达式,而expr1和expr2是两个类型相同或可能转换为某个公共类型的表达式。条件运算符的执行过程是:首先求cond的值,如果条件为真对expr1求值并返回该值,否则对expr2求值并返回该值。
当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值;否则运算的结果是右值。
4.8 位运算符、
位运算符作用于整数类型的运算对象或者bitset类型
关于符号位如何处理没有明确的规定,所以强烈建议仅将位运算符用于处理无符号类型。
如果运算对象是“小整型”,则它的值会被自动提升(参见4.11.1节,第142页)成较大的整数类型。
移位运算符
左移运算符(<<)在右侧插入值为0的二进制位。右移运算符(>>)的行为则依赖于其左侧运算对象的类型:如果该运算对象是无符号类型,在左侧插入值为0的二进制位;如果该运算对象是带符号类型,在左侧插入符号位的副本或值为0的二进制位,如何选择要视具体环境而定。
4.9 sizeof运算符
sizeof运算符返回一条表达式或一个类型名字所占的字节数。sizeof运算符满足右结合律,其所得的值是一个size_t类型(参见3.5.2节,第103页)的常量表达式(参见2.4.4节,第58页)。
sizeof并不实际计算其运算对象的值。因为sizeof不会实际求运算对象的值,所以即使p是一个无效(即未初始化)的指针(参见2.3.2节,第47页)也不会有什么影响。在sizeof的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。sizeof不需要真的解引用指针也能知道它所指对象的类型。
sizeof运算符的结果部分地依赖于其作用的类型:
· 对char或者类型为char的表达式执行sizeof运算,结果得1。
· 对引用类型执行sizeof运算得到被引用对象所占空间的大小。
· 对指针执行sizeof运算得到指针本身所占空间的大小。
· 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需有效。
· 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理。
· 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。
4.10 逗号运算符
逗号运算符(comma operator)含有两个运算对象,按照从左向右的顺序依次求值。和逻辑与、逻辑或以及条件运算符一样,逗号运算符也规定了运算对象求值的顺序。
对于逗号运算符来说,首先对左侧的表达式求值,然后将求值结果丢弃掉。逗号运算符真正的结果是右侧表达式的值。如果右侧运算对象是左值,那么最终的求值结果也是左值。
4.11 类型转换
算术类型之间的隐式转换被设计得尽可能避免损失精度。
何时发生隐式类型转换
在大多数表达式中,比int类型小的整型值首先提升为较大的整数类型。
在条件中,非布尔值转换成布尔类型
初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型。
4.11.1 算术转换
整型提升
整型提升(integral promotion)负责把小整数类型转换成较大的整数类型。对于bool、char、signed char、unsigned char、short和unsigned short等类型来说,只要它们所有可能的值都能存在int里,它们就会提升成int类型;否则,提升成unsigned int类型。就如我们所熟知的,布尔值false提升成0、true提升成1。
较大的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型的值恰好为负值,其结果将以2.1.2节(第32页)介绍的方法转换,并带来该节描述的所有副作用。
剩下的一种情况是带符号类型大于无符号类型,此时转换的结果依赖于机器。
如果无符号类型的所有值都能存在该带符号类型中,则无符号类型的运算对象转换成带符号类型。
如果不能,那么带符号类型的运算对象转换成无符号类型。例如,如果两个运算对象的类型分别是long和unsigned int,并且int和long的大小相同,则long类型的运算对象转换成unsigned int类型;如果long类型占用的空间比int更多,则unsigned int类型的运算对象转换成long类型。
4.11.2 其他隐式类型转换
数组转换成指针
在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针
当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof及typeid(第19.2.2节,732页将介绍)等运算符的运算对象时,上述转换不会发生。
4.11.3 显式转换
命名的强制类型转换
ype是转换的目标类型而expression是要转换的值。如果type是引用类型,则结果是左值。cast-name是static_cast、dynamic_cast、const_cast和reinterpret_cast中的一种。
static_cast
任何具有明确定义的类型转换,只要不包含底层const,都可以使用static_cast。
用处:
1.当需要把一个较大的算术类型赋值给较小的类型时,static_cast非常有用。此时,强制类型转换告诉程序的读者和编译器:我们知道并且不在乎潜在的精度损失。
2.static_cast对于编译器无法自动执行的类型转换也非常有用。例如,我们可以使用static_cast找回存在于void*指针(参见2.3.2节,第50页)中的值
const_cast
用处:
const_cast常常用于有函数重载的上下文中,关于函数重载将在6.4节(第208页)进行详细介绍。
reinterpret_cast
reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。