5.10. Evaluating Compound ExpressionsAn 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.
5.10.1. PrecedenceThe 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 PrecedenceWe 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. AssociativityAssociativity 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.
5.10.3. Order of EvaluationIn 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 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.
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.
|