Section 5.10. Evaluating Compound Expressions


5.10. Evaluating Compound Expressions

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

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:

      6 + 3 * 4 / 2 + 2; 

Other imaginable results include 9, 14, and 36. In C++, the result is 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:

      *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)++; // 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 ++.

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

      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.

5.10.2. Associativity

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.

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.

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.

Table 5.4. Operator Precedence

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

typeid (type)

p. 775

R

typeid

run-time type 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:

      (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.

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?

      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

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

      // 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.

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.

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:

      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.

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.

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.


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?

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?

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




C++ Primer
C Primer Plus (5th Edition)
ISBN: 0672326965
EAN: 2147483647
Year: 2006
Pages: 223
Authors: Stephen Prata

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net