Team LiB
Previous Section Next Section

5.10. Evaluating Compound Expressions

5.10. 复合表达式的求值

An expression with two or more operators is a compound expression. In a compound expression, the way in which the operands are grouped to the operators may determine the result of the overall expression. If the operands group in one way, the result differs from what it would be if they grouped another way.

含有两个或更多操作符的表达式称为复合表达式。在复合表达式中,操作数和操作符的结合方式决定了整个表达式的值。表达式的结果会因为操作符和操作数的分组结合方式的不同而不同。

Precedence and associativity determine how the operands are grouped. That is, precedence and associativity determine which part of the expression is the operand for each of the operators in the expression. Programmers can override these rules by parenthesizing compound expressions to force a particular grouping.

操作数的分组结合方式取决于操作符的优先级和结合性。也就是说,优先级和结合性决定了表达式的哪个部分用作哪个操作符的操作数。如果程序员不想考虑这些规则,可以在复合表达式中使用圆括号强制实现某个特殊的分组。

Precedence specifies how the operands are grouped. It says nothing about the order in which the operands are evaluated. In most cases, operands may be evaluated in whatever order is convenient.

优先级规定的是操作数的结合方式,但并没有说明操作数的计算顺序。在大多数情况下,操作数一般以最方便的次序求解。

5.10.1. Precedence

5.10.1. 优先级

The value of an expression depends on how the subexpressions are grouped. For example, in the following expression, a purely left-to-right evaluation yields 20:

表达式的值取决于其子表达式如何分组。例如,下面的表达式,如果纯粹从左向右计算,结果为 20:

     6 + 3 * 4 / 2 + 2;

Other imaginable results include 9, 14, and 36. In C++, the result is 14.

想像中其他可能的结果包括 9、14 和 36。在 C++ 中,该表达式的值应为 14。

Multiplication and division have higher precedence than addition. Their operands are bound to the operator in preference to the operands to addition. Multiplication and division have the same precedence as each other. Operators also have associativity, which determines how operators at the same precedence level are grouped. The arithmetic operators are left associative, which means they group left to right. We now can see that our expression is equivalent to

乘法和除法的优先级高于加法操作,于是它们的操作数先于加法操作的操作数计算。但乘法和除法的优先级相同。当操作符的优先级相同时,由其结合性决定求解次序。算术操作具有左结合性,这意味着它们从左向右结合。因此上面表达式等效于:

     int temp = 3 * 4;           // 12
     int temp2 = temp / 2;       // 6
     int temp3 = temp2 + 6;      // 12
     int result = temp3 + 2;     // 14
Parentheses Override Precedence
圆括号凌驾于优先级之上

We can override precedence with parentheses. Parenthesized expressions are evaluated by treating each parenthesized subexpression as a unit and otherwise applying the normal precedence rules. For example, we can use parentheses on our initial expression to force the evaluation to result in any of the four possible values:

我们可使用圆括号推翻优先级的限制。使用圆括号的表达式将用圆括号括起来的子表达式视为独立单元先计算,其他部分则以普通的优先级规则处理。例如,下面的程序在前述表达式上添加圆括号,强行更改其操作次序,可能得到四种结果:

     // parentheses on this expression match default precedence/associativity
     cout << ((6 + ((3 * 4) / 2)) + 2) << endl; // prints 14
     // parentheses result in alternative groupings
     cout << (6 + 3) * (4 / 2 + 2) << endl;     // prints 36
     cout << ((6 + 3) * 4) / 2 + 2 << endl;     // prints 20
     cout << 6 + 3 * 4 / (2 + 2) << endl;       // prints 9

We have already seen examples where precedence rules affect the correctness of our programs. For example, consider the expression described in the "Advice" box on page 164:

我们已经通过前面的例子了解了优先级规则如何影响程序的正确性。例如,考虑第 5.5 节第二个建议框中描述的表达式:

     *iter++;

Precedence says that ++ has higher precedence than *. That means that iter++ is grouped first. The operand of *, therefore, is the result of applying the increment operator to iter. If we wanted to increment the value that iter denotes, we'd have to use parentheses to force our intention:

其中,++ 的优先级高于*操作符,这就意味着 iter++ 先结合。而操作符 * 的操作数是 iter 做了自增操作后的结果。如果我们希望对 iter 所指向的值做自增操作,则必须使用圆括号强制实现我们的目的:

     (*iter)++; // increment value to which iter refers and yield unincremented value

The parentheses specify that the operand of * is iter. The expression now uses *iter as the operand to ++.

圆括号指明操作符 * 的操作数是 iter,然后表达式以 *iter 作为 ++操作符的操作数。

As another example, recall the condition in the while on page 161:

另一个例子,回顾一下第 5.4.2 节中的 while 循环条件:

     while ((i = get_value()) != 42) {

The parentheses around the assignment were necessary to implement the desired operation, which was to assign to i the value returned from get_value and then test that value to see whether it was 42. Had we failed to parenthesize the assignment, the effect would be to test the return value to see whether it was 42. The true or false value of that test would then be assigned to i, meaning that i would either be 1 or 0.

赋值操作上的圆括号是必需的,这样才能实现预期的操作:将 get_value 的返回值赋给 i,然后检查刚才赋值的结果是否为42。如果赋值操作上没有加圆括号,结果将是先判断 get_value 的返回值是否为 42,然后将判断结果 truefalse 值赋给 i,这意味着 i 的值只能是 1 或 0。

5.10.2. Associativity

5.10.2. 结合性

Associativity specifies how to group operators at the same precedence level. We have also seen cases where associativity matters. As one example, the assignment operator is right associative. This fact allows concatenated assignments:

结合性规定了具有相同优先级的操作符如何分组。我们已经遇到过涉及结合性的例子。其中之一使用了赋值操作的右结合性,这个特性允许将多个赋值操作串接起来:

     ival = jval = kval = lval       // right associative
     (ival = (jval = (kval = lval))) // equivalent, parenthesized version

This expression first assigns lval to kval, then the result of that to jval, and finally the result of that to ival.

该表达式首先将 lval 赋给 kval ,然后将 kval 的值赋给 jval ,最后将 jval 的值再赋给 ival

The arithmetic operators, on the other hand, are left associative. The expression

另一方面,算术操作符为左结合。表达式

     ival * jval / kval * lval       // left associative
     (((ival * jval) / kval) * lval) // equivalent, parenthesized version

multiplies ival and jval, then divides that result by kval, and finally multiplies the result of the division by lval.

先对 ivaljval 做乘法操作,然后乘积除以 kval,最后再将其商与 lval 相乘。

Table 5.4 presents the full set of operators ordered by precedence. The table is organized into segments separated by double lines. Operators in each segment have the same precedence, and have higher precedence than operators in sub-sequent segments. For example, the prefix increment and dereference operators share the same precedence and have higher precedence than the arithmetic or relational operators. We have seen most of these operators, although a few will not be defined until later chapters.

表 5.4 按照优先级顺序列出了 C++ 的全部操作符。该表以双横线分割成不同的段,每段内各个操作符的优先级相同,且都高于后面各段中的操作符。例如,前自增操作符和解引用操作符的优先级相同,它们的优先级都比算术操作符或关系操作符高。此表中大部分操作符已经介绍过,而少数未介绍的操作符将在后续章节中学习。

Table 5.4. Operator Precedence
表 5.4. 操作符的优先级

Associativity
and Operator

操作符及其结合性

Function

功能

Use

用法

See
Page

参见页码

L

::

global scope(全局作用域)

:: name

p. 450

L

::

class scope(类作用域)

class :: name

p. 85

L

::

namespace scope(名字空间作用域)

namespace :: name

p. 78

L

.

member selectors(成员选择)

object . member

p. 25

L

->

member selectors(成员选择)

pointer -> member

p. 164

L

[]

subscript(下标)

variable [ expr ]

p. 113

L

()

function call(函数调用)

name (expr_list)

p. 25

L

()

type construction(类型构造)

type (expr_list)

p. 460

R

++

postfix increment(后自增操作)

lvalue++

p. 162

R

--

postfix decrement(后自减操作)

lvalue--

p. 162

R

typeid

type ID(类型 ID)

typeid (type)

p. 775

R

typeid

run-time type ID(运行时类型 ID)

typeid (expr)

p. 775

R

explicit cast(显式强制类型转换)

type conversion(类型转换)

cast_name <type>(expr)

p. 183

R

sizeof

size of object(对象的大小)

sizeof expr

p. 167

R

sizeof

size of type(类型的大小)

sizeof(type)

p. 167

R

++

prefix increment(前自增操作)

++ lvalue

p. 162

R

--

prefix decrement(前自减操作)

-- lvalue

p. 162

R

~

bitwise NOT(位求反)

~expr

p. 154

R

!

logical NOT(逻辑非)

!expr

p. 152

R

-

unary minus(一元负号)

-expr

p. 150

R

+

unary plus(一元正号)

+expr

p. 150

R

*

dereference(解引用)

*expr

p. 119

R

&

address-of(取地址)

&expr

p. 115

R

()

type conversion(类型转换)

(type) expr

p. 186

R

new

allocate object(创建对象)

new type

p. 174

R

delete

deallocate object(释放对象)

delete expr

p. 176

R

delete[]

deallocate array(释放数组)

delete[] expr

p. 137

L

->*

ptr to member select(指向成员操作的指针)

ptr ->* ptr_to_member

p. 783

L

.*

ptr to member select(指向成员操作的指针)

obj .*ptr_to_member

p. 783

L

*

multiply(乘法)

expr * expr

p. 149

L

/

divide(除法)

expr / expr

p. 149

L

%

modulo (remainder)(求模(求余))

expr % expr

p. 149

L

+

add(加法)

expr + expr

p. 149

L

-

subtract(减法)

expr - expr

p. 149

L

<<

bitwise shift left(位左移)

expr << expr

p. 154

L

>>

bitwise shift right(位右移)

expr >> expr

p. 154

L

<

less than(小于)

expr < expr

p. 152

L

<=

less than or equal(小于或等于)

expr <= expr

p. 152

L

>

greater than(大于)

expr > expr

p. 152

L

>=

greater than or equal(大于或等于)

expr >= expr

p. 152

L

==

equality(相等)

expr == expr

p. 152

L

!=

inequality(不等)

expr != expr

p. 152

L

&

bitwise AND(位与)

expr & expr

p. 154

L

^

bitwise XOR()

expr ^ expr

p. 154

L

|

bitwise OR(位异或)

expr | expr

p. 154

L

&&

logical AND(逻辑与)

expr && expr

p. 152

L

||

logical OR(逻辑或)

expr || expr

p. 152

R

?:

conditional(条件操作)

expr ? expr : expr

p. 165

R

=

assignment(赋值操作)

lvalue = expr

p. 159

R

*=, /=, %=,

compound assign(复合赋值操作)

lvalue += expr, etc.

p. 159

R

+=, -=,

   

p. 159

R

<<=, >>=,

   

p. 159

R

&=,|=, ^=

   

p. 159

R

throw

throw exception(抛出异常)

throw expr

p. 216

L

,

comma(逗号)

expr , expr

p. 168


Exercises Section 5.10.2

Exercise 5.25:

Using Table 5.4 (p. 170), parenthesize the following expressions to indicate the order in which the operands are grouped:

根据表 5.4 的内容,在下列表达式中添加圆括号说明其操作数分组的顺序(即计算顺序):

     (a)  ! ptr == ptr->next
     (b)  ch = buf[ bp++ ] != '\n'

Exercise 5.26:

The expressions in the previous exercise evaluate in an order that is likely to be surprising. Parenthesize these expressions to evaluate in an order you imagine is intended.

习题 5.25 中的表达式的计算次序与你的意图不同,给它们加上圆括号使其以你所希望的操作次序求解。

Exercise 5.27:

The following expression fails to compile due to operator precedence. Using Table 5.4 (p. 170), explain why it fails. How would you fix it?

由于操作符优先级的问题,下列表达式编译失败。请参照表 5.4 解释原因,应该如何改正?

     string s = "word";
     // add an 's' to the end, if the word doesn't already end in 's'
     string pl = s + s[s.size() - 1] == 's' ? "" : "s" ;

5.10.3. Order of Evaluation

5.10.3. 求值顺序

In Section 5.2 (p. 152) we saw that the && and || operators specify the order in which their operands are evaluated: In both cases the right-hand operand is evaluated if and only if doing so might affect the truth value of the overall expression. Because we can rely on this property, we can write code such as

第 5.2 节中,我们讨论了 &&|| 操作符计算其操作数的次序:当且仅当其右操作数确实影响了整个表达式的值时,才计算这两个操作符的右操作数。根据这个原则,可编写如下代码:

     // iter only dereferenced if it isn't at end
     while (iter != vec.end() && *iter != some_val)

The only other operators that guarantee the order in which operands are evaluated are the conditional (?:) and comma operators. In all other cases, the order is unspecified.

C++中,规定了操作数计算顺序的操作符还有条件(?:)和逗号操作符。除此之外,其他操作符并未指定其操作数的求值顺序。

For example, in the expression

例如,表达式

     f1() * f2();

we know that both f1 and f2 must be called before the multiplication can be done. After all, their results are what is multiplied. However, we have no way to know whether f1 will be called before f2 or vice versa.

在做乘法操作之前,必须调用 f1 函数和 f2 函数,毕竟其调用结果要相乘。然而,我们却无法知道到底是先调用 f1 还是先调用 f2

The order of operand evaluation often, perhaps even usually, doesn't matter. It can matter greatly, though, if the operands refer to and change the same objects.

其实,以什么次序求解操作数通常没有多大关系。只有当操作符的两个操作数涉及到同一个对象,并改变其值时,操作数的计算次序才会影响结果。

The order of operand evaluation matters if one subexpression changes the value of an operand used in another subexpression:

如果一个子表达式修改了另一个子表达式的操作数,则操作数的求解次序就变得相当重要:

     // oops! language does not define order of evaluation
     if (ia[index++] < ia[index])

The behavior of this expression is undefined. The problem is that the left- and right-hand operands to the < both use the variable index. However, the left-hand operand involves changing the value of that variable. Assuming index is zero, the compiler might evaluate this expression in one of the following two ways:

此表达式的行为没有明确定义。问题在于:< 操作符的左右操作数都使用了 index 变量,但是,左操作数更改了该变量的值。假设 index 初值为 0,编译器可以用下面两种方式之一求该表达式的值:

     if (ia[0] < ia[0]) // execution if rhs is evaluated first
     if (ia[0] < ia[1]) // execution if lhs is evaluated first

We can guess that the programmer intended that the left operand be evaluated, thereby incrementing index. If so, the comparison would be between ia[0] and ia[1]. The language, however, does not guarantee a left-to-right evaluation order. In fact, an expression like this is undefined. An implementation might evaluate the right-hand operand first, in which case ia[0] is compared to itself. Or the implementation might do something else entirely.

可以假设程序员希望先求左操作数的值,因此 index 的值加 1。如果是这样的话,比较 ia[0]ia[1] 的值。然而,C++ 语言不能确保从左到右的计算次序。事实上,这类表达式的行为没有明确定义。一种实现可能是先计算右操作数,于是 ia[0] 与自己做比较,要不然就是做完全不同的操作。

Advice: Managing Compound Expressions

建议:复合表达式的处理

Beginning C and C++ programmers often have difficulties understanding order of evaluation and the rules of precedence and associativity. Misunderstanding how expressions and operands are evaluated is a rich source of bugs. Moreover, the resulting bugs are difficult to find because reading the program does not reveal the error unless the programmer already understands the rules.

初学 C 和 C++ 的程序员一般很难理解求值顺序、优先级和结合性规则。误解表达式和操作数如何求解将导致大量的程序错误。此外,除非程序员已经完全理解了相关规则,否则这类错误很难发现,因为仅靠阅读程序是无法排除这些错误的。

Two rules of thumb can be helpful:

下面两个指导原则有助于处理复合表达式:

  1. When in doubt, parenthesize expressions to force the grouping that the logic of your program requires.

    如果有怀疑,则在表达式上按程序逻辑要求使用圆括号强制操作数的组合。

  2. If you change the value of an operand, don't use that operand elsewhere in the same statement. If you need to use the changed value, then break the expression up into separate statements in which the operand is changed in one statement and then used in a subsequent statement.

    如果要修改操作数的值,则不要在同一个语句的其他地方使用该操作数。如果必须使用改变的值,则把该表达式分割成两个独立语句:在一个语句中改变该操作数的值,再在下一个语句使用它。

An important exception to the second rule is that subexpressions that use the result of the subexpression that changes the operand are safe. For example, in *++iter the increment changes the value of iter, and the (changed) value of iter is then used as the operand to *. In this, and similar, expressions, order of evaluation of the operand isn't an issue. To evaluate the larger expression, the subexpression that changes the operand must first be evaluated. Such usage poses no problems and is quite common.

第二个规则有一个重要的例外:如果一个子表达式修改操作数的值,然后将该子表达式的结果用于另一个子表达式,这样则是安全的。例如,*++iter 表达式的自增操作修改了 iter 的值,然后将 iter(修改后)的值用作 * 操作符的操作数。对于这个表达式或其他类似的表达式,其操作数的计算次序无关紧要。而为了计算更复杂的表达式,改变操作数值的子表达式必须首先计算。这种方法很常用,不会产生什么问题。

Do not use an increment or decrement operator on the same object in more than two subexpressions of the same expression.

一个表达式里,不要在两个或更多的子表达式中对同一对象做自增或自减操作。

One safe and machine-independent way to rewrite the previous comparison of two array elements is

以一种安全而且独立于机器的方式重写上述比较两个数组元素的程序:

     if (ia[index] < ia[index + 1]) {
         // do whatever
     }
     ++index;

Now neither operand can affect the value of the other.

现在,两个操作数的值不会相互影响。

Exercises Section 5.10.3

Exercise 5.28:

With the exception of the logical AND and OR, the order of evaluation of the binary operators is left undefined to permit the compiler freedom to provide an optimal implementation. The trade-off is between an efficient implementation and a potential pitfall in the use of the language by the programmer. Do you consider that an acceptable trade-off? Why or why not?

除了逻辑与和逻辑或外,C++ 没有明确定义二元操作符的求解次序,编译器可自由地提供最佳的实现方式。只能在“实现效率”和程序语言使用中“潜在的缺陷”之间寻求平衡。你认为这可以接受吗?说出你的理由。

Exercise 5.29:

Given that ptr points to a class with an int member named ival, vec is a vector holding ints, and that ival, jval, and kval are also ints, explain the behavior of each of these expressions. Which, if any, are likely to be incorrect? Why? How might each be corrected?

假设 ptr 指向类类型对象,该类拥有一个名为 ivalint 型数据成员, vec 是保存 int 型元素的 vector 对象,而 ivaljvalkval 都是 int 型变量。请解释下列表达式的行为,并指出哪些(如果有的话)可能是不正确的,为什么?如何改正?

     (a) ptr->ival != 0            (b) ival != jval < kval
     (c) ptr != 0 && *ptr++        (d) ival++ && ival
     (e) vec[ival++] <= vec[ival]

Team LiB
Previous Section Next Section