4.1.2 优先级与结合律
复合表达式(compound expression)是指含有两个或多个运算符的表达式。求复合表达式的值需要首先将运算符和运算对象合理地组合在一起,优先级与结合律决定了运算对象组合的方式。也就是说,它们决定了表达式中每个运算符对应的运算对象来自表达式的哪一部分。表达式中的括号无视上述规则,程序员可以使用括号将表达式的某个局部括起来使其得到优先运算。
一般来说,表达式最终的值依赖于其子表达式的组合方式。高优先级运算符的运算对象要比低优先级运算符的运算对象更为紧密地组合在一起。如果优先级相同,则其组合规则由结合律确定。例如,乘法和除法的优先级相同且都高于加法的优先级。因此,乘法和除法的运算对象会首先组合在一起,然后才能轮到加法和减法的运算对象。算术运算符满足左结合律,意味着如果运算符的优先级相同,将按照从左向右的顺序组合运算对象:
根据运算符的优先级,表达式3+4*5的值是23,不是35。
根据运算符的结合律,表达式20-15-3的值是2,不是8。
举一个稍微复杂一点的例子,如果完全按照从左向右的顺序求值,下面的表达式将得到20:
- 6 + 3 * 4 / 2 + 2
也有一些人会计算得到9、14或者36,然而在C++(www.cppentry.com)语言中真实的计算结果应该是14。这是因为这条表达式事实上与下述表达式等价:
- // 这条表达式中的括号符合默认的优先级和结合律
- ((6 + ((3 * 4) / 2)) + 2)
括号无视普通的组合规则,表达式中括号括起来的部分被当成一个单元来求值,然后再与其他部分一起按照优先级组合。例如,对上面这条表达式按照不同方式加上括号就能得到4种不同的结果:
- // 不同的括号组合导致不同的组合结果
- cout << (6 + 3) * (4 / 2 + 2) << endl; // 输出36
- cout << ((6 + 3) * 4) / 2 + 2 << endl; // 输出20
- cout << 6 + 3 * 4 / (2 + 2) << endl; // 输出9
优先级与结合律有何影响
由前面的例子可以看出,优先级会影响程序的正确性,这一点在3.5.3节(第120页)介绍的解引用和指针运算中也有所体现:
- int ia[] = {0,2,4,6,8}; // 含有5个整数的数组
- int last = *(ia + 4); // 把last初始化成8,也就是ia[4]的值
- last = *ia + 4; // last = 4,等价于ia[0] + 4
如果想访问ia+4位置的元素,那么加法运算两端的括号必不可少。一旦去掉这对括号,*ia就会首先组合在一起,然后4再与*ia的值相加。
结合律对表达式产生影响的一个典型示例是输入输出运算,4.8节(第155页)将要介绍IO相关的运算符满足左结合律。这一规则意味着我们可以把几个IO运算组合在一条表达式当中:
- cin >> v1 >> v2; // 先读入v1,再读入v2
(4.12节(第166页)罗列出了全部的运算符,并用双横线将它们分割成若干组。同一组内的运算符优先级相同,组的位置越靠前组内的运算符优先级越高。例如,前置递增运算符和解引用运算符的优先级相同并且都比算术运算符的优先级高。表中同样列出了每个运算符在哪一页有详细的描述,有些运算符之前已经使用过了,大多数运算符的细节将在本章剩余部分逐一介绍,还有几个运算符将在后面的内容中提及。
4.1.2节练习
练习4.1:表达式5+10*20/2的求值结果是多少?
练习4.2:根据4.12节中的标,在下述表达式的合理位置添加括号,使得添加括号后运算对象的组合顺序与添加括号前一致。
(a) *vec.begin() (b) *vec.begin() + 1