4.7条件运算符
- 条件运算符(?:)允许我们把简单的if else逻辑嵌入到单个表达式当中,条件运算符按照如下形式使用:
- cond ? expr1 : expr2;其中cond是判断条件的表达式,而expr1和expr2是两个类型相同或可能转换为某个公共类型的表达式。条件运算符的执行过程是:首先求cond的值,如果条件为真对expr1求值并返回该值,否则对expr2求值并返回该值。举个例子,我们可以使用条件运算符判断成绩是否合格:
- string finalgrade = (grade<60) ?"fail" : "pass”;
- 条件部分判断成绩是否小于60。如果小于,表达式的结果是"fail",否则结果是"pass"。有点类似于逻辑与运算符和逻辑或运算符(&&和||),条件运算符只对expr1和expr2中的一个求值。
- 当条件运算符的两个表达式都是左值或者能转换成同一种左值类型时,运算的结果是左值;否则运算的结果是右值。
嵌套条件运算符
- 允许在条件运算符的内部嵌套另外一个条件运算符。也就是说,条件表达式可以作为另外一个条件运算符的cond或expr
- 举个例子,使用一对嵌套的条件运算符可以将成绩分成三档:优秀(highpass)>合格(pass)和不合格(fail):
- finalgrade=(grade>90)?"highpassn:(grade<60)?“fail":"pass";
- 第一个条件检查成绩是否在90分以上,如果是,执行符号?后面的表达式,得到"highpass";如果否,执行符号:后面的分支。这个分支本身又是一个条件表达式,它检查成绩是否在60分以下,如果是,得到"fail";否则得到"pass"。
- 条件运算符满足右结合律,意味着运算对象(一般)按照从右向左的顺序组合。因此在上面的代码中,靠右边的条件运算(比较成绩是否小于60)构成了靠左边的条件运算的:分支。
- 随着条件运算嵌套层数的增加,代码的可读性急剧下降.因此,条件运算的嵌套最好别超过两到三层
4.8位运算符
- 位运算符作用于整数类型的运算对象,并把运算对象看成是二进制位的集合。位运算符提供检查和设置二进制位的功能,如17.2节(第640页)将要介绍的,一种名为bitset的标准库类型也可以表示任意大小的二进制位集合,所以位运算符同样能用于bitset类型.
- 一般来说,如果运算对象是''小整型”,则它的值会被自动提升(参见4.11.1节,第142页)成较大的整数类型。运算对象可以是带符号的,也可以是无符号的。如果运算对象是带符号的且它的值为负,那么位运算符如何处理运算对象的“符号位”依赖于机器。而且,此时的左移操作可能会改变符号位的值,因此是一种未定义的行为。
- 关于符号位如何处理没有明确的规定,所以强烈建议仅将位运算符用于处理无符号类型。
移位运算符
- 之前在处理输入和输出操作时,我们已经使用过标准IO库定义的<<运算符和>>运算符的重载版本。这两种运算符的内置含义是对其运算对象执行基于二进制位的移动操作,首先令左侧运算对象的内容按照右侧运算对象的要求移动指定位数,然后将经过移动的(可能还进行了提升)左侧运算对象的拷贝作为求值结果。其中,右侧的运算对象一定不能为负,而且值必须严格小于结果的位数,否则就会产生未定义的行为。二进制位或者向左移(?)或者向右移(?),移出边界之外的位就被舍弃掉了:
- 左移运算符(<<)在右侧插入值为0的二进制位。右移运算符(>>)的行为则依赖于其左侧运算对象的类型:如果该运算对象是无符号类型,在左侧插入值为0的二进制位:
- 如果该运算对象是带符号类型,在左侧插入符号位的副本或值为0的二进制位,如何选择要视具体环境而定。
位求反运算符
- 位求反运算符( ~) 将运算对象逐位求反后生成一个新值,将 1 置为0、将 0 置为1:
- char类型的运算对象首先提升成int类型,提升时运算对象原来的位保持不变,往高位(highorderposition)添加0即可。因此在本例中,首先将bits提升成int类型,增加24个高位0,随后将提升后的值逐位求反。
位与、位或、位异或运算符
- 与(&)、或(|)、异或(^)运算符在两个运算对象上逐位执行相应的逻辑操作:
- 对于位与运算符(&)来说,如果两个运算对象的对应位置都是1则运算结果中该位为1,否则为0。对于位或运算符(|)来说,如果两个运算对象的对应位置至少有一个为1则运算结果中该位为1,否则为0。对于位异或运算符(^)来说,如果两个运算对象的对应位置有且只有一个为1则运算结果中该位为1,否则为0
移位运算符(又叫10运算符)满足左结合律
- 尽管很多程序员从未直接用过位运算符,但是几乎所有人都用过它们的重载版本来进行10操作。重载运算符的优先级和结合律都与它的内置版本一样,因此即使程序员用不到移位运算符的内置含义,也仍然有必要理解其优先级和结合律。
- 因为移位运算符满足左结合律,所以表达式
4.9 sizeof运算符
- sizeof运算符返回一条表达式或一个类型名字所占的字节数。sizeof运算符满足右结合律,其所得的值是一个size_t类型(参见3.5.2节,第 103页)的常量表达式(参 见2.4.4节,第 58页)。运算符的运算对象有两种形式:
- sizeof (type)
- sizeof expr
- 这些例子中最有趣的一个是sizeof *p。首先,因为sizeof满足右结合律并且与*运算符的优先级一样,所以表达式按照从右向左的顺序组合。也就是说,它等价于sizeof(*p)
- 其次,因为sizeof不会实际求运算对象的值,所以即使p是一个无效(即未初始化)的指针(参见2.3.2节,第47页)也不会有什么影响。在sizeof的运算对象中解引用一个无效指针仍然是一种安全的行为,因为指针实际上并没有被真正使用。
- sizeof不需要真的解引用指针也能知道它所指对象的类型。新标准允许我们使用作用域运算符来获取类成员的大小。通常情况下只有通过类的对象才能访问到类的成员,但是sizeof运算符无须我们提供一个具体的对象,因为要想知道类成员的大小无须真的获取该成员。
- sizeof运算符的结果部分地依赖于其作用的类型:
- 对char或者类型为char的表达式执行sizeof运算,结果得1。
- 对引用类型执行sizeof运算得到被引用对象所占空间的大小。
- 对指针执行sizeof运算得到指针本身所占空间的大小。
- 对解引用指针执行sizeof运算得到指针指向的对象所占空间的大小,指针不需有效。
- 对数组执行sizeof运算得到整个数组所占空间的大小,等价于对数组中所有的元素各执行一次sizeof运算并将所得结果求和。注意,sizeof运算不会把数组转换成指针来处理。
- 对string对象或vector对象执行sizeof运算只返回该类型固定部分的大小,不会计算对象中的元素占用了多少空间。
- 因为执行sizeof运算能得到整个数组的大小,所以可以用数组的大小除以单个元素的大小得到数组中元素的个数:
4.10逗号运算符
- 逗号运算符含有两个运算对象,按照从左向右的顺序依次求值。和逻辑与、逻辑或以及条件运算符一样,逗号运算符也规定了运算对象求值的顺序。
- 对于逗号运算符来说,首先对左侧的表达式求值,然后将求值结果丢弃掉。逗号运算符真正的结果是右侧表达式的值。如果右侧运算对象是左值,那么最终的求值结果也是左值。
- 逗号运算符经常被用在for循环当中:
4.11类型转换
- 在C++语言中,某些类型之间有关联。如果两种类型有关联,那么当程序需要其中一种类型的运算对象时,可以用另一种关联类型的对象或值来替代。换句话说,如果两种类
- 型可以相互转换(conversion),那么它们就是关联的。举个例子,考虑下面这条表达式,它的目的是将ival初始化为6:
- int ival = 3.541 + 3; / / 编译器可能会警告该运算损失了精度
- 加法的两个运算对象类型不同:3.541的类型是double,3的类型是int。C++语言不会直接将两个不同类型的值相加,而是先根据类型转换规则设法将运算对象的类型统一后再求值。上述的类型转换是自动执行的,无须程序员的介入,有时甚至不需要程序员了解。因此,它们被称作隐式转换(implicitconversion)。算术类型之间的隐式转换被设计得尽可能避免损失精度。很多时候,如果表达式中既有整数类型的运算对象也有浮点数类型的运算对象,整型会转换成浮点型。在上面的例子中,3转换成double类型,然后执行浮点数加法,所得结果的类型是double。接下来就要完成初始化的任务了。在初始化过程中,因为被初始化的对象的类型无法改变,所以初始值被转换成该对象的类型。仍以这个例子说明,加法运算得到的double类型的结果转换成int类型的值,这个值被用来初始化ival。由double向int转换时忽略掉了小数部分,上面的表达式中,数值6被赋给了ival。
何时发生隐式类型转换
- 在下面这些情况下,编译器会自动地转换运算对象的类型:
- 在大多数表达式中,比int类型小的整型值首先提升为较大的整数类型。
- 在条件中,非布尔值转换成布尔类型。
- 初始化过程中,初始值转换成变量的类型;在赋值语句中,右侧运算对象转换成左侧运算对象的类型。
- 如果算术运算或关系运算的运算对象有多种类型,需要转换成同一种类型。
- 如第6章将要介绍的,函数调用时也会发生类型转换。
4.11.1算术转换
- 算术转换(arithmeticconversion)的含义是把一种算术类型转换成另外一种算术类型,这一点在2.1.2节(第32页)中已有介绍。算术转换的规则定义了一套类型转换的层次,其中运算符的运算对象将转换成最宽的类型。例如,如果一个运算对象的类型是long double,那么不论另外一个运算对象的类型是什么都会转换成long double。还有一种更普遍的情况,当表达式中既有浮点类型也有整数类型时,整数值将转换成相应的浮点类型。
整型提升
- 整型提升 ,负责把小整数类型转换成较大的整数类型。对于bool、char、signedchar、unsignedchar、short和unsignedshort等类型来说,只要它们所有可能的值都能存在int里,它们就会提升成int类型;否则,提升成unsigned int类型。就如我们所熟知的,布尔值false提升成0、true提升成1。较大的char类型(wchar_t、charl6_t、char32_t)提升成int、unsignedint、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类型的运算对象转换成unsignedint类型;如果long类型占用的空间比int更多,则unsigned int类型的运算对象转换成long类型。
理解算术转换
- 要想理解算术转换,办法之一就是研究大量的例子:
- 在第一个加法运算中,小写字母,a,是char型的字符常量,它其实能表示一个数字值(参见2.1.1节,第30页)。到底这个数字值是多少完全依赖于机器上的字符集,在我们的环境中,a,对应的数字值是97。当把,a,和一个longdouble类型的数相加时,char类型的值首先提升成int类型,然后int类型的值再转换成longdouble类型。最终我们把这个转换后的值与那个字面值相加。最后的两个含有无符号类型值的表达式也比较有趣,它们的结果依赖于机器
4.11.2其他隐式类型转换
- 除了算术转换之外还有几种隐式类型转换,包括如下几种。
- 数组转换成指针:在大多数用到数组的表达式中,数组自动转换成指向数组首元素的指针:
- 当数组被用作decltype关键字的参数,或者作为取地址符(&)、sizeof及typeid(第19.2.2节,732页将介绍)等运算符的运算对象时,上述转换不会发生。同样的,如果用一个引用来初始化数组(参见3.5.1节,第102页),上述转换也不会发生。我们将在6.7节(第221页)看到,当在表达式中使用函数类型时会发生类似的指针转换。
- 指针的转换:C++还规定了几种其他的指针转换方式,包括常量整数值0或者字面值nullptr能转换成任意指针类型;指向任意非常量的指针能转换成void*;指向任意对象的指针能转换成const void*.15.2.2节(第530页)将要介绍,在有继承关系的型间还有另外一种指针转换的方式。
4.11.3显式转换
- 有时我们希望显式地将对象强制转换成另外一种类型。例如,如果想在下面的代码中执行浮点数除法:
- int i,j; double slope = i/j;
- 就要使用某种方法将i和/或j显式地转换成double,这种方法称作强制类型转换(cast)
- 然有时不得不使用强制类型转换,但这种方法本质上是非常危险的
命名的强制类型转换
- 一个命名的强制类型转换具有如下形式:
- cast-name<type> (expression); 其中,type是转换的目标类型而expression是要转换的值。如果是引用类型 ,则结果是左值 。 cast-name是static cast、dynamic cast、const cast和
reinterpret_cast中的一种。dynamic_cast支持运行时类型识别,我们将在19.2节(第730页)其做更详细的介绍。cast-name指定了执行的是哪种转换。
reinterpret__cast
- reinterpret_cast通常为运算对象的位模式提供较低层次上的重新解释。举个例子,假设有如下的转换
- reinterpret_cast本质上依赖于机器。要想安全地使用reinterpret_cast必须对涉及的类型和编译器实现转换的过程都非常了解;
4 . 1 2 运算符优先级表