4-Expressions

来源:互联网 发布:博客互踩软件 编辑:程序博客网 时间:2024/04/27 12:37

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

  • An expression is composed of one or more operands and yields a result when it is evaluated.

4.1. Fundamentals

4.1.1. Basic Concepts

  • Unary operators act on one operand. Binary operators act on two operands. Ternary operator takes three operands. One operator, function call, that takes an unlimited number of operands.

Operand Conversions

  • When evaluating an expression, operands are often converted from one type to another. For example, the binary operators usually expect operands with the same type. These operators can be used on operands with differing types so long as the operands can be converted(§ 2.1.2, p. 35) to a common type.
  • Small integral type operands(e.g., bool, char, short, etc.) are generally promoted to a larger integral type, typically int.

Lvalues and Rvalues

  • Every expression in C++ is either an rvalue or an lvalue. lvalues could stand on the left-hand side of an assignment whereas rvalues could not.
  • An lvalue expression yields an object or a function. When we use an object as an rvalue, we use the object’s value(its contents). When we use an object as an lvalue, we use the object’s identity(its location in memory).
  • With one exception in § 13.6(p. 531), we can use an lvalue when an rvalue is required, but we cannot use an rvalue when an lvalue is required. When we use an lvalue in place of an rvalue, the object’s contents(its value) are used.
    1. Assignment requires a nonconst lvalue as its left-hand operand and yields its left-hand operand as an lvalue.
    2. The address-of operator requires an lvalue operand and returns a pointer to its operand as an rvalue.
    3. The built-in dereference and subscript operators and the iterator dereference and string and vector subscript operators all yield lvalues.
    4. The built-in and iterator increment and decrement operators require lvalue operands and the prefix versions also yield lvalues.
  • When we apply decltype to an expression(other than a variable), the result is a reference type if the expression yields an lvalue. Assume p is an int*: decltype(p) is int&; decltype(&p) is int*.

4.1.2. Precedence and Associativity

  • An expression with two or more operators is a compound expression. Precedence and associativity determine which parts of the expression are the operands for each of the operators in the expression.
  • Operands of operators with higher precedence group more tightly than operands of operators at lower precedence. Associativity determines how to group operands with the same precedence.
  • For example, multiplication and division have the same precedence as each other, but they have higher precedence than addition and subtraction. The arithmetic operators are left associative: operators at the same precedence group left to right.
    • Because of precedence, the expression 3+4*5 is 23, not 35.
    • Because of associativity, the expression 20-15-3 is 2, not 8.

Parentheses Override Precedence and Associativity

  • Programmers can override these rules by parenthesizing compound expressions to force a particular grouping. Parenthesized expressions are evaluated by treating each parenthesized subexpression as a unit and otherwise applying the normal precedence rules.

When Precedence and Associativity Matter

int arr[] = {0,2,4,6,8};int last = *(arr + 4);  // last = arr[4], i.e., last = 8last = *arr + 4;        // last = arr[0] + 4, i.e., last = 4

  • Table 4.12(Section 4.12) lists all the operators organized into segments separated by double lines. Operators in each segment have the same precedence, and have higher precedence than operators in subsequent segments.

Exercises Section 4.1.2

Exercise 4.1

What is the value returned by 5 + 10 * 20/2?

  • 5 + 10 * 10 = 5 + 100 = 105

Exercise 4.2

Using Table 4.12 (p. 166), parenthesize the following expressions to indicate the order in which the operands are grouped:
(a) * vec.begin()
(b) * vec.begin() + 1

  • *(vec.begin())
  • (*(vec.begin())) + 1

4.1.3. Order of Evaluation

  • int i = f1() * f2();
    f1 and f2 must be called before the multiplication can be done. We have no way of knowing whether f1 will be called before f2 or vice versa.
  • For operators that do not specify evaluation order, it is an error for an expression to refer to and change the same object. Expressions that do so have undefined behavior. E.g.: << makes no guarantees about when or how its operands are evaluated, the following expression is undefined:
int i = 0;cout << i << " " << ++i << endl; // undefined
  • There are 4 operators that guarantee the order in which operands are evaluated. The logical AND(&&) operator guarantees that its left-hand operand is evaluated first and the right-hand operand is evaluated only if the left-hand operand is true. Other 3 are: logical OR (||) operator(§ 4.3, p. 141), conditional (? :) operator(§ 4.7, p. 151), and comma(,) operator (§ 4.10, p. 157).

Order of Evaluation, Precedence, and Associativity

  • Order of operand evaluation is independent of precedence and associativity.
    f() + g() * h() + j()
    1. Precedence guarantees that the results of g() and h() are multiplied.
    2. Associativity guarantees that the result of f() is added to the product of g() and h() and that the result of that addition is added to the value of j().
    3. There are no guarantees as to the order in which these functions are called.
  • If f, g, h, and j are independent functions that do not affect the state of the same objects or perform IO, then the order in which the functions are called is irrelevant. If any of these functions do affect the same object, then the expression has undefined behavior.

Exercises Section 4.1.3

Exercise 4.3

Order of evaluation for most of the binary operators is left undefined to give the compiler opportunities for optimization. This strategy presents a trade-off between efficient code generation and potential pitfalls in the use of the language by the programmer. Do you consider that an acceptable trade-off? Why or why not?

  • Yes. Result oriented.

Advice: Managing Compound Expressions

  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 expression.
    • Exception to the second rule occurs when the subexpression that changes the operand is itself the operand of another subexpression. For example, *++iter, the increment changes the value of iter. The changed value of iter is the operand to the dereference operator. In this expression, order of evaluation isn’t an issue. The increment (i.e., the subexpression that changes the operand) must be evaluated before the dereference can be evaluated. Such usage poses no problems and is quite common.

4.2. Arithmetic Operators

  • Table 4.1 and the operator tables in subsequent sections groups the operators by their precedence.
  • Unless noted otherwise, the arithmetic operators may be applied to any of the arithmetic types(§ 2.1.1, p. 32) or to any type that can be converted to an arithmetic type. The operands and results of these operators are rvalues. Operands of small integral types are promoted to a larger integral type, and all operands may be converted to a common type as part of evaluating these operators.
  • The unary plus operator and the addition and subtraction operators may also be applied to pointers. When applied to a pointer or arithmetic value, unary plus returns a (possibly promoted) copy of the value of its operand.
  • The unary minus operator returns the result of negating a (possibly promoted) copy of the value of its operand.
bool b = true;  // b is 1(promotes to int)bool b2 = -b;       // b2 = -1, not 0, so is true.
  • bool values should not be used for computation. For most operators, operands of type bool are promoted to int.

Caution: Overflow and Other Arithmetic Exceptions

  • Overflow happens when a value is computed that is outside the range of values that the type can represent.
short short_value = 32767;  // max value if shorts are 16 bitsshort_value += 1;           // this calculation overflowscout << "short_value: " << short_value << endl;  // undefined
  • Division between integers returns an integer. If the quotient contains a fractional part, it is truncated toward zero:
int ival1 = 21/6;   // ival1 is 3; result is truncated; remainder is discardedint ival2 = 21/7;   // ival2 is 3; no remainder; result is an integral value
  • The % operator computes the remainder that results from dividing the left-hand operand by the right-hand operand. The operands to % must have integral type:
int ival = 42;double dval = 3.14;ival % 12;  // ok: result is 6ival % dval;    // error: floating-point operand
  • In division, a nonzero quotient is positive if the operands have the same sign and negative otherwise. New standard requires the quotient to be rounded toward zero (i.e., truncated).
  • If m and n are integers and n is nonzero, then: (m/n)*n + m%n = m. If m%n is nonzero, it has the same sign as m. Except for the case where -m overflows, (-m)/n and m/(-n) = -(m/n), m%(-n) = m%n, (-m)%n = -(m%n).
21 % 6;     /* result is 3 */       21 / 6;     /* result is 3 */21 % 7;     /* result is 0 */       21 / 7;     /* result is 3 */-21 % -8;   /* result is -5 */      -21 / -8;       /* result is 2 */21 % -5;        /* result is 1 */       21 / -5;        /* result is -4 */

Exercises Section 4.2

Exercise 4.4

Parenthesize the following expression to show how it is evaluated. Test your answer by compiling the expression (without parentheses) and printing its result.
12 / 3 * 4 + 5 * 15 + 24 % 4 / 2

  • (((12 / 3) * 4) + (5 * 15) + ((24 % 4) / 2)) = 16 + 75 + 0 = 91

Exercise 4.5

Determine the result of the following expressions.
(a) -30 * 3 + 21 / 5
(b) -30 + 3 * 21 / 5
(c) 30 / 3 * 21 % 5
(d) -30 / 3 * 21 % 4

  • -90 + 4 = -86
  • -30 + 12 = -18
  • 10 * 21 % 5 = 210 % 5 = 0
  • -10 * 21 % 4 = -210 % 4 = -2

Exercise 4.6

Write an expression to determine whether an int value is even or odd.

int num = value;cout << ((num % 2) ? “Odd\n” : “Even\n”);

Exercise 4.7

What does overflow mean? Show three expressions that will overflow.

  • Exceed the scope.
char ch = 10000;short num1 = 10000000;int num2 = 1000000000000000000000000;

4.3. Logical and Relational Operators

  • The relational operators take operands of arithmetic or pointer type; the logical operators take operands of any type that can be converted to bool. These operators all return values of type bool. Arithmetic and pointer operand(s) with a value of zero are false; all other values are true. The operands to these operators are rvalues and the result is an rvalue.

Logical AND and OR Operators

  • The result of the logical AND operator is true if and only if both its operands evaluate to true. The logical OR (||) operator evaluates as true if either of its operands evaluates as true.
  • The logical AND and OR operators always evaluate their left operand before the right. Short-circuit evaluation:
    • The right side of an && is evaluated if and only if the left side is true.
    • The right side of an || is evaluated if and only if the left side is false.

Logical NOT Operator

  • The logical NOT operator(!) returns the inverse of the truth value of its operand.

The Relational Operators

  • The relational operators(<, <=, >, <=) return bool values and they are left associative.
  • Because the relational operators return bools, the result of chaining these operators together is surprising. The bool result of i < j is the left-hand operand of the second less-than operator.
// Compare k to the bool result of (i < j), true if k is greater than 1if (i < j < k)// ok: condition is true if i is smaller than j and j is smaller than kif (i < j && j < k)

Equality Tests and the bool Literals

  • Use an arithmetic or pointer object as a condition to test the truth value.
if (val)    { /* ... */ }   // true if val is any nonzero valueif (!val)   { /* ... */ }   // true if val is zero
  • if(val == true) { /* ... */ } // true only if val is equal to 1!
    If val is not a bool, then true is converted to the type of val before the == operator is applied. That is, when val is not a bool, it is as if we had written
    if (val == 1) { /* ... */ }
  • The boolean literals true and false should be used only to compare to an object of type bool.

Exercises Section 4.3

Exercise 4.8

Explain when operands are evaluated in the logical AND, logical OR, and equality operators.

  • The right side of an && is evaluated if and only if the left side is true.
  • The right side of an || is evaluated if and only if the left side is false.
  • equality operators: undefined.

Exercise 4.9

Explain the behavior of the condition in the following if:

const char *cp = "Hello World";if (cp && *cp)
  • Because cp is not NULL pointer, so left side is true; and *cp = ‘H’, not ‘\0’, is also true. So this expression is true.

Exercise 4.10

Write the condition for a while loop that would read ints from the standard input and stop when the value read is equal to 42.

int num = 0;while(cin >> num && num != 42){}

Exercise 4.11

Write an expression that tests four values, a, b, c, and d, and ensures that a is greater than b, which is greater than c, which is greater than d.

if(a > b && b > c && c > d)

Exercise 4.12

Assuming i, j, and k are all ints, explain what i != j < k means.

  • i != (j < k), that is, i != 0/1

4.4. Assignment Operators

  • The left-hand operand of an assignment operator must be a modifiable lvalue.
int i = 0, j = 0, k = 0;    // initializations, not assignmentconst int ci = i;   // initialization, not assignmenti + j = k;          // error: arithmetic expressions are rvaluesci = k;         // error: ci is a const (nonmodifiable) lvalue
  • The result of an assignment is its left-hand operand, which is an lvalue. The type of the result is the type of the left-hand operand. If the types of the left and right operands differ, the right-hand operand is converted to the type of the left.
k = 12345;      // result: type int, value 0k = 3.14159;        // result: type int, value 3
  • C++11: Use a braced initializer list(§ 2.2.1, p. 43) on the right-hand side.
int num = 0;num = {3.1};//warning: narrowing conversion of ‘3.1e+0’ from ‘double’ to ‘int’ inside { }vector<int> vi; // initially emptyvi = {0,1,2,3,4,5,6,7,8,9}; // vi now has ten elements, values 0 through 9
  • If the left-hand operand is of a built-in type, the initializer list may contain at most one value, and that value must not require a narrowing conversion(§ 2.2.1, p. 43). Right???
  • For class types, the class defines its own version of an assignment operator that can take an initializer list. This operator replaces the elements of the left-hand side with the elements in the list on the right-hand side.
  • If the initializer list is empty, the compiler generates a value-initialized(§ 3.3.1, p. 98) temporary and assigns that value to the left-hand operand.

Assignment Is Right Associative

int ival, jval;ival = jval = 0; // ok: each assigned 0
  • Because assignment is right associative, the right-most assignment, jval = 0, is the right-hand operand of the left-most assignment operator. Because assignment returns its left-hand operand, the result of the right-most assignment(i.e., jval) is assigned to ival.
  • Each object in a multiple assignment must have the same type as its right-hand neighbor or a type to which that neighbor can be converted(§ 4.11, p. 159):
int num, *ptr;num = ptr = 0;  // error: invalid conversion from ‘int*’ to ‘int’string s1, s2;s1 = s2 = "OK"; // string literal "OK" converted to string

Assignment Has Low Precedence

  • Assignment has lower precedence than the relational operators, parentheses are usually needed around assignments in conditions.
int num;while ((num = get_value()) != 42) {// do something ...}
  • Without the parentheses, the operands to != would be the value returned from get_value and 42. The true or false result of that test would be assigned to num.

Beware of Confusing Equality and Assignment Operators

  • if (i = j)
    The condition assigns the value of j to i and then tests the result of the assignment. If j is nonzero, the condition will be true.

Compound Assignment Operators

+= -= *= /= %= // arithmetic operators<<= >>= &= ^= |= // bitwise operators; see § 4.8 (p. 152)
  • Each compound operator is equivalent to a = a op b; with the exception that, when we use the compound assignment, the left-hand operand is evaluated only once. If we use an ordinary assignment, that operand is evaluated twice: once in the expression on the right-hand side and again as the operand on the left hand.

Exercises Section 4.4

Exercise 4.13

What are the values of i and d after each assignment?

int i;double d;(a) d = i = 3.5;(b) i = d = 3.5;
  • i = 3, d= 3.0
  • d = 3.5, i = 3

Exercise 4.14

Explain what happens in each of the if tests:

if (42 = i) // ...if (i = 42) // ...
  • error: lvalue required as left operand of assignment
  • make i equal to 42 and return 42 as condition, so i true.

Exercise 4.15

The following assignment is illegal. Why? How would you correct it?

double dval;int ival;int *pi;dval = ival = pi = 0;
  • error: invalid conversion from ‘int*’ to ‘int’

Exercise 4.16

Although the following are legal, they probably do not behave as the programmer expects. Why? Rewrite the expressions as you think they should be.
(a) if (p = getPtr() != 0)
(b) if (i = 1024)

  • if ((p = getPtr()) != 0)
  • if(i == 1024)

4.5. Increment and Decrement Operators

  • The prefix operators increments(or decrements) its operand and yields the changed object as its result. The postfix operators increment(or decrement) the operand but yield a copy of the original, unchanged value as its result.
int i = 0, j;j = ++i;    // j = 1, i = 1: prefix yields the incremented valuej = i++;    // j = 1, i = 2: postfix yields the unincremented value
  • These operators require lvalue operands. The prefix operators return the object itself as an lvalue. The postfix operators return a copy of the object’s original value as an rvalue.

Advice: Use Postfix Operators only When Necessary

  • The prefix version avoids unnecessary work. It increments the value and returns the incremented version.
  • The postfix operator must store the original value so that it can return the unincremented value as its result. For complicated iterator types, this extra work potentially might be costly.

Combining Dereference and Increment in a Single Expression

  • The precedence of postfix increment(++) is higher than the dereference() operator, *iter++ is equivalent to (iter++).

Remember That Operands Can Be Evaluated in Any Order

  • Most operators give no guarantee as to the order in which operands will be evaluated. The cases where it does matter are when one subexpression changes the value of an operand that is used in another subexpression.
while (iter != s.end() && !isspace(*iter))    *iter = toupper(*iter++);  // error: assignment is undefined
  • Both the left- and right-hand operands to ‘=’ use iter and the right-hand operand changes iter. The assignment is therefore undefined. The compiler might evaluate this expression as either
*iter = toupper(*iter);         // execution if left-hand side is evaluated first*(iter + 1) = toupper(*iter);   // execution if right-hand side is evaluated first

or some other way.

Exercises Section 4.5

Exercise 4.17

Explain the difference between prefix and postfix increment.

  • Prefix: increments its operand and yields the changed object as its result.
  • Postfix: increment the operand but yield a copy of the original, unchanged value as its result.

Exercise 4.18

What would happen if the while loop on page 148 that prints the elements from a vector used the prefix increment operator?

  • It will print from the second element and dereference v.end() at last.

Exercise 4.19

Given that ptr points to an int, that vec is a vector, and that ival is an int, explain the behavior of each of these expressions. Which, if any, are likely to be incorrect? Why? How might each be corrected?
(a) ptr != 0 && *ptr++
(b) ival++ && ival
(c) vec[ival++] <= vec[ival]

  • Check ptr is not a nullptr and check the pointer value.
  • Check ival and ival+1 whether equal zero.
  • Incorrect. It is an undefined behavior.

Correct: vec[ival] <= vec[ival + 1]

4.6. The Member Access Operators

    1. dot operator fetches a member from an object of class type;
    2. arrow is defined so that ptr->mem is a synonym for (*ptr).mem:
  • Because dereference has a lower precedence than dot, we must parenthesize the dereference subexpression.
  • The arrow operator requires a pointer operand and yields an lvalue. The dot operator yields an lvalue if the object from which the member is fetched is an lvalue; otherwise the result is an rvalue.

Exercises Section 4.6

Exercise 4.20

Assuming that iter is a vector::iterator, indicate which, if any, of the following expressions are legal. Explain the behavior of the legal expressions and why those that aren’t legal are in error.
(a) *iter++;
(b) (*iter)++;
(c) *iter.empty()
(d) iter->empty();
(e) ++*iter;
(f) iter++->empty();

  • Return *iter, then ++iter.
  • Illegal, *iter is a string, cannot increment value.
  • Illegal, iter should use ‘->’ to indicate whether empty.
  • Indicate the iter’ value whether empty.
  • Illegal, string have not increment.
  • Return iter->empty(), then ++iter.

4.7. The Conditional Operator

  • Conditional operator form:
    cond ? expr1 : expr2;
    cond is an expression that is used as a condition and expr1 and expr2 are expressions of the same type(or types that can be converted to a common type).

  • This operator executes by evaluating cond. If the condition is true, then expr1 is evaluated; otherwise, expr2 is evaluated. Only one of expr1 or expr2 is evaluated.

  • The result of the conditional operator is an lvalue if both expressions are lvalues or if they convert to a common lvalue type. Otherwise the result is an rvalue.

Nesting Conditional Operations

  • The conditional operator can be used as the cond or as one or both of the exprs of another conditional expression.
    final_grade = (grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass";
  • The conditional operator is right associative, meaning that the operands group right to left. Associativity accounts for the fact that the right-hand conditional(the one that compares grade to 60) forms the : branch of the left-hand conditional expression.

Using a Conditional Operator in an Output Expression

  • Because the conditional operator has low precedence, when we embed a conditional expression in a larger expression, we usually must parenthesize the conditional subexpression.
int grade = 90;cout << ((grade < 60) ? "fail\n" : "pass\n");               // Print:passcout << (cout << (grade < 60) ? "fail\n" : "pass\n");       // Print:0failcout << cout << ‘#’ << (grade < 60) ? "fail\n" : "pass\n";  // Print:0x601088#0// error: no match for ‘operator<’ (operand types are ‘std::basic_ostream<char>’ and ‘int’)cout << grade < 60 ? "fail\n" : "pass\n";               // error: compares cout to 60
  • The second expression uses the comparison between grade and 60 as the operand to the second <<. The value 1 or 0 is printed. The second << returns cout, which is tested as the condition for the conditional operator. The second expression is equivalent to
cout << (grade < 60);               // prints 1 or 0cout << (cout ? "fail" : "pass");   // test cout and then print one of the two literals
  • The last expression is an error because it is equivalent to
cout << grade;              // less-than has lower precedence than shiftcout < 60 ? "fail" : "pass";        // compare cout to 60!

Exercises Section 4.7

Exercise 4.21

Write a program to use a conditional operator to find the elements in a vector that have odd value and double the value of each such element.

int length = vec.size();for(int index = 0; index < length; ++index){    vec[index] = ((vec[index] % 2 != 0) ? vec[index] * 2 : vec[index]);}

Exercise 4.22

Extend the program that assigned high pass, pass, and fail grades to also assign low pass for grades between 60 and 75 inclusive. Write two versions: One version that uses only conditional operators; the other should use one or more if statements. Which version do you think is easier to understand and why?

#include <iostream>using std::cout;using std::cin;int main(){    int grade;    while(cin >> grade)    {        cout << ((grade > 90) ? "high pass" :                 ((grade < 60) ? "fail" :                  ((grade > 75) ? "pass" : "low pass")));        cout << '\t';        if(grade > 90)        {            cout << "high pass";        }        else if(grade < 60)        {            cout << "fail";        }        else if(grade > 75)        {            cout << "pass";        }        else        {            cout << "low pass";        }        cout << '\n';    }    return 0;}

Exercise 4.23

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

string s = "word";string pl = s + s[s.size() - 1] == 's' ? "" : "s";
  • It equivalent to:
    `string pl = (((s + s[s.size() - 1]) == ‘s’) ? “” : “s”);
    // error: no match for ‘operator==’ (operand types are ‘std::basic_string’ and ‘char’)
  • FIX:
    string pl = s + ((s[s.size() - 1] == 's') ? "" : "s");

Exercise 4.24

Our program that distinguished between high pass, pass, and fail depended on the fact that the conditional operator is right associative. Describe how that operator would be evaluated if the operator were left associative.
final_grade = (grade > 90) ? “high pass” : (grade < 60) ? “fail” : “pass”;

// Right associative:cout << ((grade > 90) ? "high pass" : (grade < 60) ? "fail" : "pass");// Left associative.// error: operands to ?: have different types ‘const char*’ and ‘bool’cout << (((grade > 90) ? "high pass" : (grade < 60)) ? "fail" : "pass");

4.8. The Bitwise Operators

  • The bitwise operators take operands of integral type that they use as a collection of bits.
  • If an operand is a “small integer,” its value is first promoted(§ 4.11.1, p.160) to a larger integral type. The operand(s) can be either signed or unsigned.

  • If the operand is signed and its value is negative, then the way that the “sign bit” is handled in a number of the bitwise operations is machine dependent. Doing a left shift that changes the value of the sign bit is undefined. Because there are no guarantees for how the sign bit is handled, we recommend using unsigned types with the bitwise operators.

Bitwise Shift Operators

  • << and >> yield a value that is a copy of the(possibly promoted) left-hand operand with the bits shifted as directed by the right-hand operand. The right-hand operand must not be negative and must be a value that is strictly less than the number of bits in the result. Otherwise, the operation is undefined. Bits that are shifted off the end are discarded.

  • The left-shift operator inserts 0-valued bits on the right.
  • The behavior of the right-shift operator depends on the type of the left-hand operand: 1. unsigned: the operator inserts 0-valued bits on the left;
    1. signed: the result is implementation defined, either copies of the sign bit or 0-valued bits are inserted on the left.

Bitwise NOT Operator

  • The bitwise NOT operator(~) generates a new value with the bits of its operand inverted.

  • char is first promoted to int. Promoting a char to int leaves the value unchanged but adds 0 bits to the high order positions.

Bitwise AND, OR, and XOR Operators

  • The AND(&), OR(|), and XOR(^) operators generate new values with the bit pattern composed from its two operands:

  • AND: 1 if both operands contain 1; otherwise, the result is 0.
  • OR: 1 if either or both operands contain 1; otherwise, the result is 0.
  • XOR: 1 if either but not both operands contain 1; otherwise, the result is 0.

Using Bitwise Operators

  • 1UL << 27
    1UL has a 1 in the low-order bit and at least 31 zero bits. This shifts the 1 bit left 27 positions inserting 0 bits behind it.

Shift Operators(aka IO Operators) Are Left Associative

  • Shift operators are left associative.
    cout << "hi" << " there" << endl;
    executes as
    ((cout << "hi") << " there") << endl;
  • The shift operators have midlevel precedence: lower than the arithmetic operators but higher than the relational, assignment, and conditional operators. We must use parentheses to force the correct grouping of operators with lower precedence.
cout << 42 + 10;        // ok: + has higher precedence, so the sum is printedcout << (10 < 42);  // ok: parentheses force intended grouping; prints 1cout << 10 < 42;        // error: attempt to compare cout to 42!

Exercises Section 4.8

Exercise 4.25

What is the value of ~’q’ << 6 on a machine with 32-bit ints and 8 bit chars, that uses Latin-1 character set in which ‘q’ has the bit pattern 01110001?

  • -7296.
[‘q’]2      =   0000 0000   0000 0000   0000 0000   0111 0001[~‘q’]2        =   1111 1111   1111 1111   1111 1111   1000 1110[~‘q’ <<6]2    =   1111 1111   1111 1111   1110 0011   1000 0000

Exercise 4.26

In our grading example in this section, what would happen if we used unsigned int as the type for quiz1?

  • The C++ standard does not specify the size of integral types in bytes, but it specifies minimum ranges they must be able to hold. Minimum mange of unsigned int is 0 to 65535. Thus if unsigned int adopted, the result is undefined.

Exercise 4.27

What is the result of each of these expressions?
unsigned long ul1 = 3, ul2 = 7;
(a) ul1 & ul2
(b) ul1 | ul2
(c) ul1 && ul2
(d) ul1 || ul2

  • 3
  • 7
  • 1(true)
  • 1(true)

4.9. The sizeof Operator

  • The sizeof operator returns the size, in bytes, of an expression or a type name. The operator is right associative. The result of sizeof is a constant expression(§2.4.4, p.65) of type size_t(§ 3.5.2, p. 116). The operator takes one of two forms:
sizeof (type)sizeof expr
  • In the second form, sizeof returns the size of the type returned by the given expression. The sizeof operator does not evaluate its operand.
Sales_data data, *p;sizeof(Sales_data); // size required to hold an object of type Sales_datasizeof data;            // size of data's type, i.e., sizeof(Sales_data)sizeof p;               // size of a pointersizeof *p;          // size of the type to which p points, i.e., sizeof(Sales_data)sizeof data.revenue;    // size of the type of Sales_data's revenue membersizeof Sales_data::revenue; // C++11 alternative way to get the size of revenue
  • sizeof *p.
    Because sizeof is right associative and has the same precedence as *, this expression groups right to left, equivalent to sizeof(*p). Because sizeof does not evaluate its operand, it doesn’t matter that p is an invalid(i.e., uninitialized) pointer(§ 2.3.2, p.52). Dereferencing an invalid pointer as the operand to sizeof is safe because the pointer is not actually used.
  • C++11: We can use the scope operator to ask for the size of a member of a class type.
Obj’s type sizeof(Obj) return char/an expression of type char 1. reference the size of an object of the referenced type. pointer the size needed hold a pointer. dereferenced pointer the size of an object of the type to which the pointer points; the pointer need not be valid. array the size of the entire array. It is equivalent to taking the sizeof the element type times the number of elements in the array, sosizeof(array)/sizeof(*array)returns the number of elements in ia. sizeof does not convert the array to a pointer. string/vector the size of the fixed part of these types, not return the size used by the object’s elements.

Exercises Section 4.9

Exercise 4.28

Write a program to print the size of each of the built-in types.

#include <iostream>using std::cout;using std::endl;int main(){    // void type    cout << "void: nullptr_t\t" << sizeof(std::nullptr_t) << " bytes" << endl << endl;    // boolean type    cout << "bool:\t\t" << sizeof(bool) << " bytes" << endl << endl;    // charactor type    cout << "char:\t\t" << sizeof(char) << " bytes" << endl;    cout << "wchar_t:\t" << sizeof(wchar_t) << " bytes" << endl;    cout << "char16_t:\t" << sizeof(char16_t) << " bytes" << endl;    cout << "char32_t:\t" << sizeof(char32_t) << " bytes" << endl << endl;    // integers type    cout << "short:\t\t" << sizeof(short) << " bytes" << endl;    cout << "int:\t\t" << sizeof(int) << " bytes" << endl;    cout << "long:\t\t" << sizeof(long) << " bytes" << endl;    cout << "long long:\t" << sizeof(long long) << " bytes" << endl << endl;    // floating point type    cout << "float:\t\t" << sizeof(float) << " bytes" << endl;    cout << "double:\t\t" << sizeof(double) << " bytes" << endl;    cout << "long double:\t" << sizeof(long double) << " bytes" << endl << endl;    // Fixed width integers    cout << "int8_t:\t\t" << sizeof(int8_t) << " bytes" << endl;    cout << "uint8_t:\t" << sizeof(uint8_t) << " bytes" << endl;    cout << "int16_t:\t" << sizeof(int16_t) << " bytes" << endl;    cout << "uint16_t:\t" << sizeof(uint16_t) << " bytes" << endl;    cout << "int32_t:\t" << sizeof(int32_t) << " bytes" << endl;    cout << "uint32_t:\t" << sizeof(uint32_t) << " bytes" << endl;    cout << "int64_t:\t" << sizeof(int64_t) << " bytes" << endl;    cout << "uint64_t:\t" << sizeof(uint64_t) << " bytes" << endl;    return 0;}/*void: nullptr_t 8 bytesbool:       1 byteschar:       1 byteswchar_t:    4 byteschar16_t:   2 byteschar32_t:   4 bytesshort:      2 bytesint:        4 byteslong:       8 byteslong long:  8 bytesfloat:      4 bytesdouble:     8 byteslong double:    16 bytesint8_t:     1 bytesuint8_t:    1 bytesint16_t:    2 bytesuint16_t:   2 bytesint32_t:    4 bytesuint32_t:   4 bytesint64_t:    8 bytesuint64_t:   8 bytes*/

Exercise 4.29

Predict the output of the following code and explain your reasoning. Now run the program. Is the output what you expected? If not, figure out why.

int x[10];int *p = x;cout << sizeof(x)/sizeof(*x) << endl;cout << sizeof(p)/sizeof(*p) << endl;
  • 10
    2

Exercise 4.30

Using Table 4.12(p. 166), parenthesize the following expressions to match the default evaluation:
(a) sizeof x + y
(b) sizeof p->mem[i]
(c) sizeof a < b
(d) sizeof f()

  • (sizeof x)+y
  • sizeof(p‐>mem[i])
  • sizeof(a) < b
  • If f() returns void, this statement is undefined, otherwise it returns the size of return type.

4.10. Comma Operator(,)

  • The comma operator takes two operands, which it evaluates from left to right. The left-hand expression is evaluated and its result is discarded. The result of a comma expression is the value of its right-hand expression. The result is an lvalue if the right-hand operand is an lvalue.
  • One common use for the comma operator is in a for loop:
vector<int>::size_type cnt = ivec.size();// assign values from size... 1 to the elements in ivecfor(vector<int>::size_type ix = 0; ix != ivec.size(); ++ix, --cnt)    ivec[ix] = cnt;

Exercises Section 4.10

Exercise 4.31

The program in this section used the prefix increment and decrement operators. Explain why we used prefix and not postfix. What changes would have to be made to use the postfix versions? Rewrite the program using postfix operators.

for(vector<int>::size_type ix = 0; ix != ivec.size(); ix++, cnt--)    ivec[ix] = cnt;

Exercise 4.32

Explain the following loop.

constexpr int size = 5;int ia[size] = {1,2,3,4,5};for (int *ptr = ia, ix = 0; ix != size && ptr != ia+size; ++ix, ++ptr){ /* ... */ }
  • ptr and ix have the same function. The former use a pointer, and the latter use the index of array. we use the loop to through the array.﴾just choose one from ptr and ix)

Exercise 4.33

Using Table 4.12(p. 166) explain what the following expression does:
some_Value ? ++x, ++y : --x, --y

#include <iostream>using std::cout;int main(){    int x = 1, y = 9;    cout << (true ? ++x, ++y : --x, --y) << '\n';    cout << x << '\t' << y << '\n';    cout << (false ? ++x, ++y : --x, --y) << '\n';    cout << x << '\t' << y << '\n';    return 0;}/*92   981   8*/
  • Equivalent to: (some_Value ? ++x, ++y :--x), --y
    If true, return y; else, return –y.

4.11. Type Conversions

int ival = 3.541 + 3;
  • The implicit conversions among the arithmetic types are defined to preserve precision, if possible. If an expression has both integral and floating point operands, the integer is converted to floating-point. In this case, 3 is converted to double, floating-point addition is done, and the result is a double.
  • In an initialization, the type of the object we are initializing dominates. The initializer is converted to the object’s type. In this case, the double result of the addition is converted to int and used to initialize ival. Converting a double to an int truncates the double’s value, discarding the decimal portion. In this expression, the value 6 is assigned to ival.

When Implicit Conversions Occur

  • The compiler automatically converts operands in the following circumstances:
    1. Values of integral types smaller than int are first promoted to an appropriate larger integral type.
    2. In conditions: nonbool expressions are converted to bool.
    3. In initializations: the initializer is converted to the type of the variable.
    4. In assignments: the right-hand operand is converted to the type of the left-hand.
    5. In arithmetic and relational expressions with operands of mixed types: the types are converted to a common type.
    6. Chapter 6: conversions also happen during function calls.

4.11.1. The Arithmetic Conversions

  • The arithmetic conversions convert one arithmetic type to another. The rules define a hierarchy of type conversions in which operands to an operator are converted to the widest type. For example, if one is long double, then the other is converted to long double regardless of what the second type is.
  • When mixing floating-point and integral values, the integral value is converted to an appropriate floating-point type.

Integral Promotions

  • The integral promotions convert the small integral types to a larger integral type.
  • The types bool, char, signed char, unsigned char, short, and unsigned short are promoted to int if all possible values of that type fit in an int. Otherwise, the value is promoted to unsigned int.
  • The larger char types(wchar_t, char16_t, and char32_t) are promoted to the smallest type of int, unsigned int, long, unsigned long, long long, or unsigned long long in which all possible values of that character type fit.

Operands of Unsigned Type

  • If the operands of an operator have differing types, those operands are converted to a common type. If any operand is an unsigned type, the type to which the operands are converted depends on the relative sizes of the integral types on the machine.
  • Integral promotions happen first.
    1. If the resulting type(s) match: no further conversion is needed.
    2. If both operands have the same signedness: the operand with the smaller type is converted to the larger type.
    3. If the signedness differs and the type of the unsigned operand is the same as or larger than that of the signed operand: the signed operand is converted to unsigned. Given an unsigned int and an int, the int is converted to unsigned int.(Negative will be very large.)
    4. If the signed operand has a larger type than the unsigned operand:
      -1- If all values in the unsigned type fit in the larger type: the unsigned operand is converted to the signed type.
      -2- If the values don’t fit: the signed operand is converted to the unsigned type.
      Given the operands are long and unsigned int:
      -1- long type has more bits: unsigned int will be converted to long.
      -2- int and long have the same size: long will be converted to unsigned int.

Understanding the Arithmetic Conversions

bool flag;char cval;short sval;unsigned short usval;int ival;unsigned int uival;long lval;unsigned long ulval;float fval;double dval;3.14159L + 'a'; // 'a' promoted to int, then that int converted to long doubledval + ival;        // ival converted to doubledval + fval;        // fval converted to doubleival = dval;        // dval converted(by truncation) to intflag = dval;        // if dval is 0, then flag is false, otherwise truecval + fval;        // cval promoted to int, then that int converted to floatsval + cval;        // sval and cval promoted to intcval + lval;        // cval converted to longival + ulval;       // ival converted to unsigned longusval + ival;       // promotion depends on the size of unsigned short and intuival + lval;       // conversion depends on the size of unsigned int and long

Exercises Section 4.11.1

Exercise 4.34

Given the variable definitions in this section, explain what conversions take place in the following expressions:
(a) if (fval)
(b) dval = fval + ival;
(c) dval + ival * cval;
Remember that you may need to consider the associativity of the operators.

  • float to bool: if fval = 0, then false; otherwise true.
  • ival from int to float, then result convert from float to double.
  • cval convert from char to int, then result convert from int to double.

Exercise 4.35

Given the following definitions,

char cval;int ival;unsigned int ui;float fval;double dval;

identify the implicit type conversions, if any, taking place:
(a) cval = ‘a’ + 3;
(b) fval = ui - ival * 1.0;
(c) dval = ui * fval;
(d) cval = ival + fval + dval;

  • ‘a’: char -> int; result: int -> char
  • ival: int -> double; ui: unsigned int -> double; result: double -> float
  • ui: unsigned int -> float; result: float -> double
  • ival: int -> float; (ival + fval): float -> double; result: double -> char

4.11.2. Other Implicit Conversions

Array to Pointer Conversions.

  • When use an array, the array is automatically converted to a pointer to the first element in that array.
  • This conversion is not performed when:
    1. An array is used with decltype or as the operand of the address-of (&), sizeof, or typeid(§ 19.2.2 (p. 826)) operators.
    2. Initialize a reference to an array(§ 3.5.1, p. 114).

Pointer Conversions

  • A constant integral value of 0 and the literal nullptr can be converted to any pointer type.
  • A pointer to any nonconst type can be converted to void*.
  • A pointer to any type can be converted to a const void*.
  • §15.2.2 (p. 597): an additional pointer conversion that applies to types related by inheritance.

Conversions to bool

  • There is an automatic conversion from arithmetic or pointer types to bool. If the pointer or arithmetic value is zero, the conversion yields false; any other value yields true.

Conversion to const

  • We can convert a pointer to a nonconst type to a pointer to the corresponding const type, and similarly for references. If T is a type, we can convert a pointer or a reference to T into a pointer or reference to const T, respectively. The reverse conversion(removing a low-level const) does not exist.

Conversions Defined by Class Types

  • Class types can define conversions that the compiler will apply automatically. The compiler will apply only one class-type conversion at a time. §7.5.4 (p. 295)
  • We use a class-type conversion when we use a C-style character string where a library string is expected(§ 3.5.5, p. 124) and when we read from an istream in a condition.
string s, t = "a value";        // character string literal converted to type stringwhile (cin >> s)            // while condition converts cin to bool
  • The condition(cin >> s) reads cin and yields cin as its result. Conditions expect a value of type bool, but this condition tests a value of type istream. The IO library defines a conversion from istream to bool. The resulting bool value depends on the state of the stream. If the last read succeeded, then the conversion yields true. If the last attempt failed, then the conversion to bool yields false.

4.11.3. Explicit Conversions

  • We use a cast to request an explicit conversion.

Named Casts

  • A named cast has the following form
    cast-name<type>(expression);
    cast-name: static_cast, dynamic_cast(run-time type identification §19.2 (p.825)), const_cast, and reinterpret_cast.
    type: target type of the conversion. If type is a reference, then the result is an lvalue.
    expression: value to be cast.

static_cast

  • Any type conversion except involving low-level const can be requested using a static_cast.
  • Compilers often generate a warning for assignments of a larger arithmetic type to a smaller type. When we use static_cast, the warning message is turned off.
  • A static_cast can perform a conversion that the compiler will not generate automatically. For example, we can use static_cast to retrieve a pointer value that was stored in a void* pointer(§ 2.3.2, p. 56):
#include <iostream>using std::cout;int main(){    double num1 = 3.14;    void *ptr1 = &num1;    //double *ptr2 = ptr1;  // error: invalid conversion from ‘void*’ to ‘double*’    double *ptr3 = static_cast<double *>(ptr1); //ok    int *ptr4 = static_cast<int *>(ptr1);       //ok    cout << "num1 = 3.14\ndouble num1:\t" << &num1 << '\t' << num1 << '\n';    cout << "void *ptr1:\t" << ptr1 << '\n';    //cout << *ptr1;  // error: ‘void*’ is not a pointer-to-object type    cout << "double *ptr3:\t" << ptr3 << '\t' << *ptr3 << '\n';    cout << "int *ptr4:\t" << ptr4 << '\t' << *ptr4 << '\n';    num1 = 6.28;    cout << "num1 = 6.28\ndouble num1:\t" << &num1 << '\t' << num1 << '\n';    cout << "void *ptr1:\t" << ptr1 << '\n';    cout << "double *ptr3:\t" << ptr3 << '\t' << *ptr3 << '\n';    cout << "int *ptr4:\t" << ptr4 << '\t' << *ptr4 << '\n';    *ptr3 = 9.42;    cout << "*ptr3 = 9.42\ndouble num1:\t" << &num1 << '\t' << num1 << '\n';    cout << "void *ptr1:\t" << ptr1 << '\n';    cout << "double *ptr3:\t" << ptr3 << '\t' << *ptr3 << '\n';    cout << "int *ptr4:\t" << ptr4 << '\t' << *ptr4 << '\n';    *ptr4 = 12.56;    cout << "*ptr4 = 12.56\ndouble num1:\t" << &num1 << '\t' << num1 << '\n';    cout << "void *ptr1:\t" << ptr1 << '\n';    cout << "double *ptr3:\t" << ptr3 << '\t' << *ptr3 << '\n';    cout << "int *ptr4:\t" << ptr4 << '\t' << *ptr4 << '\n';    return 0;}/*Output:num1 = 3.14double num1:    0x7ffd0f9c6f30  3.14void *ptr1: 0x7ffd0f9c6f30double *ptr3:   0x7ffd0f9c6f30  3.14int *ptr4:  0x7ffd0f9c6f30  1374389535num1 = 6.28double num1:    0x7ffd0f9c6f30  6.28void *ptr1: 0x7ffd0f9c6f30double *ptr3:   0x7ffd0f9c6f30  6.28int *ptr4:  0x7ffd0f9c6f30  1374389535*ptr3 = 9.42double num1:    0x7ffd0f9c6f30  9.42void *ptr1: 0x7ffd0f9c6f30double *ptr3:   0x7ffd0f9c6f30  9.42int *ptr4:  0x7ffd0f9c6f30  1030792151*ptr4 = 12.56double num1:    0x7ffd0f9c6f30  9.42void *ptr1: 0x7ffd0f9c6f30double *ptr3:   0x7ffd0f9c6f30  9.42int *ptr4:  0x7ffd0f9c6f30  12*/
  • When we store a pointer in a void* and then use a static_cast to cast the pointer back to its original type, we are guaranteed that the pointer value is preserved. The result of the cast will be equal to the original address value. We must be certain that the type to which we cast the pointer is the actual type of that pointer; if the types do not match, the result is undefined.

const_cast

  • A const_cast changes a low-level(§ 2.4.3, p. 63) const in its operand. A cast that converts a const object to a nonconst type casts away the const. Once we have cast away the const of an object, the compiler will no longer prevent us from writing to that object. If the object was originally not a const, using a cast to obtain write access is legal; otherwise, write to a const object is undefined.
  • Trying to change whether an expression is const with any of the other forms of named cast is a compile-time error. We cannot use a const_cast to change the type of an expression.
int num1 = 1;const int *ptr1 = &num1;int *ptr2 = ptr1;//error: invalid conversion from ‘const int*’ to ‘int*’// error: invalid static_cast from type ‘const int*’ to type ‘int*’int *ptr2 = static_cast<int *>(ptr1);int *ptr2 = const_cast<int *>(ptr1);  // okconst char *str = "gaoxiangnumber1";string str1 = static_cast<string>(str); //okstring str2 = const_cast<string>(str);  // error

reinterpret_cast

  • A reinterpret_cast performs a low-level reinterpretation of the bit pattern of its operands.
#include <iostream>using std::cout;int main(){    int num1 = 1;    int *ptr1 = &num1;    char *ptr2 = reinterpret_cast<char *>(ptr1);    cout << &num1 << '\t' << num1 << '\n';    cout << ptr1 << '\t' << *ptr1 << '\n';    cout << ptr2 << '\t' << *ptr2 << '\n';    *ptr2 = 'A';    cout << &num1 << '\t' << num1 << '\n';    cout << ptr1 << '\t' << *ptr1 << '\n';    cout << ptr2 << '\t' << *ptr2 << '\n';    return 0;}/*Output:0x7ffc6e8e905c  10x7ffc6e8e905c  1   0x7ffc6e8e905c  650x7ffc6e8e905c  65A   A*/
  • We must never forget that the actual object addressed by ptr2 is an int, not a character. Any use of ptr2 that assumes it’s a character pointer is likely to fail at run time. For example: string str(ptr2); is likely to result in bizarre run-time behavior.

Old-Style Casts

  • An explicit cast took one of the following two forms:
type (expr); // function-style cast notation(type) expr; // C-language-style cast notation

Advice: Avoid Casts

  • We recommend avoid casts, particularly applicable to reinterpret_casts.
  • A const_cast is useful in the context of overloaded functions(§6.4 (p. 232)).
  • static_cast and dynamic_cast should be needed infrequently.
  • Depending on the types involved, an old-style cast has the same behavior as a const_cast, a static_cast, or a reinterpret_cast. When we use an old-style cast where a static_cast or a const_cast would be legal, the old-style cast does the same conversion as the respective named cast. If neither cast is legal, then an old-style cast performs a reinterpret_cast. For example:
int *ptr1;char *ptr2 = (char *)ptr1;

has the same effect as using a reinterpret_cast.

Exercises Section 4.11.3

Exercise 4.36

Assuming i is an int and d is a double, write the expression i *= d so that it does integral, rather than floating-point, multiplication.

i *= static_cast<int>(d);

Exercise 4.37

Rewrite each of the following old-style casts to use a named cast:

int i;double d;const string *ps;char *pc;void *pv;(a) pv = (void*)ps;(b) i = int(*pc);(c) pv = &d;(d) pc = (char*) pv;
pv = static_cast<void*>(const_cast<string*>(ps)); i = static_cast<int>(*pc); pv = static_cast<void*>(&d); pc = reinterpret_cast<char*>(pv);

Exercise 4.38

Explain the following expression:
double slope = static_cast<double>(j/i);

  • j/i is an int﴾by truncation﴿, then converted to double and assigned to slope.

4.12. Operator Precedence Table

Chapter Summary

  • Each operator has a precedence level and associativity.
    1. Precedence determines how operators are grouped in a compound expression.
    2. Associativity determines how operators at the same precedence level are grouped.

Please indicate the source: http://blog.csdn.net/gaoxiangnumber1

Welcome to my github: https://github.com/gaoxiangnumber1

0 0
原创粉丝点击