Google
 

Monday, January 10, 2011

C++ Primer, Fourth Edition-Notes:Chapter 5. Expressions

  1. C++ provides a rich set of operators and defines what these operators do when applied to operands of built-in type. It also allows us to define meanings for the operators when applied to class types. This facility, known as operator overloading, is used by the library to define the operators that apply to the library types.
  2. In this chapter our focus is on the operators as defined in the language and applied to operands of built-in type. We will also look at some of the operators defined by the library.
  3. An expression is composed of one or more operands that are combined by operators.
  4. Every expression yields a result.
  5. The result of expressions that involve operators is determined by applying each operator to its operand(s). Except when noted otherwise, the result of an expression is an rvalue (Section 2.3.1, p. 45). We can read the result but cannot assign to it.
  6. NOTE:The meaning of an operator what operation is performed and the type of the result depends on the types of its operands.
  7. There are both unary operators and binary operators. Unary operators, such as address-of (&) and dereference (*), act on one operand. Binary operators, such as addition (+) and subtraction (-), act on two operands. There is also one ternary operator that takes three operands.
  8. Operators impose requirements on the type(s) of their operand(s). The language defines the type requirements for the operators when applied to built-in or compound types.
  9. The binary operators, when applied to operands of built-in or compound type, usually require that the operands be the same type, or types that can be converted to a common type.
  10. Understanding expressions with multiple operators requires understanding operator precedence, associativity, and the order of evaluation of the operands.
  11. Knowing how operands and operators are grouped is not always sufficient to determine the result. It may also be necessary to know in what order the operands to each operator are evaluated. Each operator controls what assumptions, if any, can be made as to the order in which the operands will be evaluatedthat is, whether we can assume that the left-hand operand is always evaluated before the right or not. Most operators do not guarantee a particular order of evaluation.
  12. Section 5.1.  Arithmetic Operators
    1. Table 5.1. Arithmetic Operators
    2. Unless noted otherwise, these operators may be applied to any of the arithmetic types (Section 2.1, p. 34), or any type that can be converted to an arithmetic type.
    3. The table groups the operators by their precedence the unary operators have the highest precedence, then the multiplication and division operators, and then the binary addition and subtraction operators. Operators of higher precedence group more tightly than do operators with lower precedence. These operators are all left associative, meaning that they group left to right when the precedence levels are the same.
    4. Caution: Overflow and Other Arithmetic Exceptions
      1. The result of evaluating some arithmetic expressions is undefined. Some expressions are undefined due to the nature of mathematicsfor example, division by zero. Others are undefined due to the nature of computerssuch as overflow, in which a value is computed that is too large for its type.
      2. On many systems, there is no compile-time or run-time warning when an overflow might occur. The actual value put into short_value varies across different machines.
    5. The % operator is known as the "remainder" or the "modulus" operator. It computes the remainder of dividing the left-hand operand by the right-hand operand. This operator can be applied only to operands of the integral types: bool, char, short, int, long, and their associated unsigned types:
    6. For both division (/) and modulus(%), when both operands are positive, the result is positive (or zero). If both operands are negative, the result of division is positive (or zero) and the result of modulus is negative (or zero). If only one operand is negative, then the value of the result is machine-dependent for both operators.
    7. When only one operand is negative, the sign and value of the result for the modulus operator can follow either the sign of the numerator or of the denominator. On a machine where modulus follows the sign of the numerator then the value of division truncates toward zero. If modulus matches the sign of the denominator, then the result of division truncates toward minus infinity.
      // EXE-5.1.cpp : Defines the entry point for the console application.
      #include "stdafx.h"
      #include <iostream>
      #include <string>
      using namespace std;
      int main()
      {
      cout << "5 + 10 * 20/2=" << 5 + 10 * 20/2 << endl;
      // max value if shorts are 8 bits
      short short_value = 32767;
      short ival = 1;
      // this calculation overflows
      short_value += ival;
      cout << "short_value: " << short_value << endl;

      cout << "ok: result is 3:" << 21 % 6 << endl; // ok: result is 3
      cout << "ok: result is 0:" << 21 % 7 << endl; // ok: result is 0
      cout << "ok: result is -5:" << -21 % -8 << endl; // ok: result is -5
      cout << "machine-dependent: result is 1 or -4:" << 21 % -5 << endl; // machine-dependent: result is 1 or -4
      cout << "ok: result is 3:" << 21 / 6 << endl; // ok: result is 3
      cout << "ok: result is 3:" << 21 / 7 << endl; // ok: result is 3
      cout << "ok: result is 2:" << -21 / -8 << endl; // ok: result is 2
      cout << "machine-dependent: result -4 or -5:" << 21 / -5 << endl; // machine-dependent: result -4 or -5
      return 0;
      }
      EXE-5.1

  13. Section 5.2.  Relational and Logical Operators

    1. The relational and logical operators take operands of arithmetic or pointer type and return values of type bool.
    2. Table 5.2. Relational and Logical Operators
    3. Logical AND and OR Operators

      1. The logical operators treat their operands as conditions (Section 1.4.1, p. 12). The operand is evaluated; if the result is zero the condition is false, otherwise it is true. The overall result of the AND operator is TRue if and only if both its operands evaluate to TRue. The logical OR (||) operator evaluates to true if either of its operands evaluates to true.
      2. NOTE:The logical AND and OR operators always evaluate their left operand before the right. The right operand is evaluated only if the left operand does not determine the result. This evaluation strategy is often referred to as "short-circuit evaluation."

    4. Logical NOT Operator

      1. The logical NOT operator (!) treats its operand as a condition. It yields a result that has the opposite truth value from its operand. If the operand evaluates as nonzero, then ! returns false.

    5. The Relational Operators Do Not Chain Together

      1. The relational operators (<, <=, >, <=) are left associative. The fact that they are left associative is rarely of any use because the relational operators return bool results. If we do chain these operators together, the result is likely to be surprising:
        // oops! this condition does not determine if the 3 values are unequal
        if (i < j < k) { /* ... */ }

      2. As written, this expression will evaluate as true if k is greater than one!

    6. Equality Tests and the bool Literals

      1. As we'll see in Section 5.12.2 (p. 180) a bool can be converted to any arithmetic typethe bool value false converts to zero and true converts to one.
      2. BEWARE:Because bool converts to one, is almost never right to write an equality test that tests against the bool literal TRue:
      // EXE-5.2.cpp : Defines the entry point for the console application.

      #include "stdafx.h"
      #include <string>
      #include <iostream>
      using namespace std;

      int main()
      {
      string s("Expressions in C++ are composed...");
      string::iterator it = s.begin();
      // convert first word in s to uppercase
      while (it != s.end() && !isspace(*it)) {
      *it = toupper(*it); // toupper covered in section 3.2.4 (p. 88)
      ++it;
      }
      cout << s << endl;

      int i(5),j(2),k(2);
      //int i(0),j(0),k(0);
      cout << "i = " << i << endl
      << "j = " << j << endl
      << "k = " << k << endl;
      // oops! this condition does not determine if the 3 values are unequal
      // As written, this expression will evaluate as true if k is greater than one!
      if (i < j < k)
      {
      cout << "if (i < j < k) success" << endl;
      }
      else
      {
      cout << "if (i < j < k) failed" << endl;
      }

      return 0;
      }

  14. Section 5.3.  The Bitwise Operators

    1. The bitwise operators take operands of integral type. These operators treat their integral operands as a collection of bits, providing operations to test and set individual bits. In addition, these operators may be applied to bitset (Section 3.5, p. 101) operands with the behavior as described here for integral operands.
    2. The type of an integer manipulated by the bitwise operators can be either signed or unsigned. If the value is negative, then the way that the "sign bit" is handled in a number of the bitwise operations is machine-dependent. It is, therefore, likely to differ across implementations; programs that work under one implementation may fail under another.
    3. Table 5.3. Bitwise Operators
    4. Best Practice:Because there are no guarantees for how the sign bit is handled, we strongly recommend using an unsigned type when using an integral value with the bitwise operators.
    5. The <<, >> operators are the bitwise shift operators. These operators use their right-hand operand to indicate by how many bits to shift. They yield a value that is a copy of the left-hand operand with the bits shifted as directed by the right-hand operand. The bits are shifted left (<<) or right (>>), discarding the bits that are shifted off the end.
    6. The left shift operator (<<) inserts 0-valued bits in from the right. The right shift operator (>>) inserts 0-valued bits in from the left if the operand is unsigned. If the operand is signed, it can either insert copies of the sign bit or insert 0-valued bits; which one it uses is implementation defined. The right-hand operand must not be negative and must be a value that is strictly less than the number of bits in the left-hand operand. Otherwise, the effect of the operation is undefined.
    7. The bitwise AND operator (&) takes two integral operands. For each bit position, the result is 1 if both operands contain 1; otherwise, the result is 0.
    8. BEWARE:It is a common error to confuse the bitwise AND operator (&) with the logical AND operator (&&) (Section 5.2, p. 152). Similarly, it is common to confuse the bitwise OR operator (|) and the logical OR operator(||).
    9. The bitwise XOR (exclusive or) operator (^) also takes two integral operands. For each bit position, the result is 1 if either but not both operands contain 1; otherwise, the result is 0.
    10. The bitwise OR (inclusive or) operator (|) takes two integral operands. For each bit position, the result is 1 if either or both operands contain 1; otherwise, the result is 0.
    11. 5.3.1. Using bitset Objects or Integral Values

      1. Best Practice:In general, the library bitset operations are more direct, easier to read, easier to write, and more likely to be used correctly. Moreover, the size of a bitset is not limited by the number of bits in an unsigned. Ordinarily bitset should be used in preference to lower-level direct bit manipulation of integral values.
      2. We said that the bitset class was easier to use than the lower-level bitwise operations on integral values.

    12. 5.3.2. Using the Shift Operators for IO

      1. The IO library redefines the bitwise >> and << operators to do input and output. Even though many programmers never need to use the bitwise operators directly, most programs do make extensive use of the overloaded versions of these operators for IO. When we use an overloaded operator, it has the same precedence and associativity as is defined for the built-in version of the operator. Therefore, programmers need to understand the precedence and associativity of these operators even if they never use them with their built-in meaning as the shift operators.The IO Operators Are Left Associative
      2. Like the other binary operators, the shift operators are left associative. These operators group from left to right, which accounts for the fact that we can concatenate input and output operations into a single statement:
      3. The shift operators have midlevel precedence: lower precedence than the arithmetic operators but higher than the relational, assignment, or conditional operators. These relative precedence levels affect how we write IO expressions involving operands that use operators with lower precedence. We often need to use parentheses to force the right grouping
      // EXE-5.3.cpp : Defines the entry point for the console application.
      #include "stdafx.h"
      #include <iostream>
      #include <iomanip>
      #include <bitset>
      using namespace std;

      int main()
      {
      unsigned char bits0 = 227;
      cout << "unsigned char bits0 = 227; bits0=" << static_cast<unsigned int>(bits0) << endl;
      cout << setbase(16);
      cout << "cout << setbase(16); bits0=" << static_cast<unsigned int>(bits0) << endl;
      bits0 = ~bits0;
      cout << "bits0 = ~bits0; bits0=" << static_cast<unsigned int>(bits0) << endl;

      unsigned char bits1 = 200;
      cout << "unsigned char bits1 = 1; bits1=" << static_cast<unsigned int>(bits1) <<endl;
      bits1 <<= 1; // left shift
      cout << "bits1 << 1; bits1=" << static_cast<unsigned int>(bits1) <<endl;
      bits1 <<= 2; // left shift
      cout << "bits1 << 2; bits1=" << static_cast<unsigned int>(bits1) <<endl;
      bits1 >>= 3; // right shift
      cout << "bits1 >> 3; bits1=" << static_cast<unsigned int>(bits1) <<endl;
      bits1 >>= 10; // right shift
      cout << "bits1 >> 10; bits1=" << static_cast<unsigned int>(bits1) <<endl;
      bits1 >>= -2; // right shift
      cout << "bits1 >> -2; bits1=" << static_cast<unsigned int>(bits1) <<endl;

      unsigned char b1 = 0145;

      unsigned char b2 = 0257;

      unsigned char result = b1 & b2;

      result = b1 ^ b2;

      result = b1 | b2;

      bitset<30> bitset_quiz1; // bitset solution
      cout << "bitset<30> bitset_quiz1; bitset_quiz1 = " << bitset_quiz1 << endl;
      unsigned long int_quiz1 = 0; // simulated collection of bits
      cout << "unsigned long int_quiz1 = 0; int_quiz1 = " << int_quiz1 << endl;

      bitset_quiz1.set(27); // indicate student number 27 passed
      cout << "bitset_quiz1.set(27);bitset_quiz1 = " << bitset_quiz1.to_ulong() << endl;
      //1UL << 27; // generate a value with only bit number 27 set
      int_quiz1 |= 1UL<<27; // indicate student number 27 passed
      cout << "int_quiz1 |= 1UL<<27; int_quiz1 = " << int_quiz1 << endl;

      // following assignment is equivalent to int_quiz1 |= 1UL << 27;
      int_quiz1 = int_quiz1 | 1UL << 27;
      cout << "int_quiz1 = int_quiz1 | 1UL << 27; int_quiz1 = " << endl;

      bitset_quiz1.reset(27); // student number 27 failed
      cout << "bitset_quiz1.reset(27); bitset_quiz1 = " << bitset_quiz1.to_ulong() << endl;
      int_quiz1 &= ~(1UL<<27); // student number 27 failed
      cout << "int_quiz1 &= ~(1UL<<27); int_quiz1 = " << int_quiz1 << endl;
      bool status;
      status = bitset_quiz1[27]; // how did student number 27 do?
      cout << "status = bitset_quiz1[27]; status = " << status << endl;
      status = int_quiz1 & (1UL<<27); // how did student number 27 do?
      cout << "status = int_quiz1 & (1UL<<27); status = " << status << endl;

      cout << "hi" << " there" << endl;
      ( (cout << "hi") << " there" ) << endl;

      cout << 42 + 10; // ok, + has higher precedence, so the sum is printed
      cout << (10 < 42); // ok: parentheses force intended grouping; prints 1
      //cout << 10 < 42; // error: attempt to compare cout to 42! (cout << 10) < 42;
      return 0;
      }

  15. Section 5.4.  Assignment Operators

    1. The left-hand operand of an assignment operator must be a nonconst lvalue.
    2. Array names are nonmodifiable lvalues: An array cannot be the target of an assignment. Both the subscript and dereference operators return lvalues.
    3. The result of an assignment is the left-hand operand; the type of the result is the type of the left-hand operand.
    4. The value assigned to the left-hand operand ordinarily is the value that is in the right-hand operand. However, assignments where the types of the left and right operands differ may require conversions that might change the value being assigned. In such cases, the value stored in the left-hand operand might differ from the value of the right-hand operand
    5. 5.4.1. Assignment Is Right Associative

      1. Unlike the other binary operators, the assignment operators are right associative. We group an expression with multiple assignment operators from right to left.

    6. 5.4.2. Assignment Has Low Precedence

      1. Inside a condition is another common place where assignment is used as a part of a larger expression.
      2. Beware of Confusing Equality and Assignment Operators

    7. 5.4.3. Compound Assignment Operators

      1. There is one important difference: When we use the compound assignment, the left-hand operand is evaluated only once. If we write the similar longer version, that operand is evaluated twice: once as the right-hand operand and again as the left. In many, perhaps most, contexts this difference is immaterial aside from possible performance consequences.
        +=   -=   *=   /=   %=   // arithmetic operators
        <<= >>= &= ^= |= // bitwise operators
      // EXE-5.4.cpp : Defines the entry point for the console application.
      //

      #include "stdafx.h"
      #include <iostream>
      #include <string>

      using std::cin;
      using std::cout;
      using std::endl;
      using std::string;

      int main(int argc, _TCHAR* argv[])
      {
      int k, j, ival;
      const int ci = 1; // ok: initialization not assignment
      //1024 = ival; // error: literals are rvalues
      //i + j = ival; // error: arithmetic expressions are rvalues
      //ci = ival; // error: can't write to ci

      int ia[10];
      ia[0] = 0; // ok: subscript is an lvalue
      cout << ia[0] << endl;
      *ia = 1; // ok: dereference also is an lvalue
      cout << ia[0] <<endl;

      ival = 0; // result: type int value 0
      cout << ival << endl;

      ival = 3.14159; // result: type int value 3
      cout << ival << endl;

      int jval;
      ival = jval = 0; // ok: each assigned 0

      int *pval;
      //ival = pval = 0; // error: cannot assign the value of a pointer to an int
      string s1, s2;
      s1 = s2 = "OK"; // ok: "OK" converted to string
      cout << s1 << endl;
      cout << s2 << endl;

      /*
      int i = get_value(); // get_value returns an int
      while (i != 42) {
      // do something ...
      i = get_value(); }

      int i;
      while ((i = get_value()) != 42) {
      // do something ...
      }

      */


      cin >> k;
      return 0;
      }


  16. Section 5.5.  Increment and Decrement Operators

    1. The increment (++) and decrement (--) operators provide a convenient notational shorthand for adding or subtracting 1 from an object. There are two forms of these operators: prefix and postfix.
    2. The prefix decrement operates similarly, except that it decrements its operand. The postfix versions of these operators increment (or decrement) the operand but yield a copy of the original, unchanged value as its result
    3. Because the prefix version returns the incremented value, it returns the object itself as an lvalue. The postfix versions return an rvalue.
    4. Advice: Use Postfix Operators Only When Necessary

      1. Readers from a C background might be surprised that we use the prefix increment in the programs we've written. The reason is simple: The prefix version does less 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 ints and pointers, the compiler can optimize away this extra work. For more complex iterator types, this extra work potentially could be more costly. By habitually favoring the use of the prefix versions, we do not have to worry if the performance difference matters.

    5. Postfix Operators Return the Unincremented Value
    6. Combining Dereference and Increment in a Single Expression

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

    7. Advice: Brevity Can Be a Virtue

      1. It is worthwhile to study examples of such code until their meanings are immediately clear. Most C++ programs use succinct expressions rather than more verbose equivalents. Therefore, C++ programmers must be comfortable with such usages. Moreover, once these expressions are familiar, you will find them less error-prone.
      // EXE-5.5.cpp : Defines the entry point for the console application.
      //

      #include "stdafx.h"
      #include <string>
      #include <iostream>
      #include <vector>
      using namespace std;
      using std::cin;
      using std::cout;
      using std::endl;
      using std::vector;
      int main(int argc, _TCHAR* argv[])
      {

      int i = 0, j;
      j = ++i; // j = 1, i = 1: prefix yields incremented value
      cout << j << endl;
      j = i++; // j = 1, i = 2: postfix yields unincremented value
      cout << j << endl;

      vector<int> ivec; // empty vector
      int cnt = 10;
      // add elements 10...1 to ivec
      while (cnt > 0)
      ivec.push_back(cnt--); // int postfix decrement

      vector<int>::iterator iter = ivec.begin();
      for (; iter != ivec.end(); iter++)
      {
      cout << *iter << endl;
      }

      vector<int>::iterator iter2 = ivec.begin();
      while(iter2!=ivec.end())
      {
      cout << *iter2 << endl;
      ++iter2;
      }

      iter = ivec.begin();
      // prints 10 9 8 ... 1
      while (iter != ivec.end())
      cout << *iter++ << endl; // iterator postfix increment

      cin >> i;
      return 0;
      }



  17. Section 5.6.  The Arrow Operator

    1. The arrow operator (->) provides a synonym for expressions involving the dot and dereference operators.
      // EXE-5.6.cpp : Defines the entry point for the console application.
      //

      #include "stdafx.h"
      #include <iostream>
      #include <string>
      #include <vector>
      using namespace std;

      int main(int argc, _TCHAR* argv[])
      {
      /*
      //item1.same_isbn(item2); // run the same_isbn member of item1

      Sales_item *sp = &item1;
      (*sp).same_isbn(item2); // run same_isbn on object to which sp points


      // run the same_isbn member of sp then dereference the result!
      *sp.same_isbn(item2); // error: sp has no member named same_isbn


      *(sp.same_isbn(item2)); // equivalent to *sp.same_isbn(item2);


      (*p).foo; // dereference p to get an object and fetch its member named foo
      p->foo; // equivalent way to fetch the foo from the object to which p points


      sp->same_isbn(item2); // equivalent to (*sp).same_isbn(item2)

      */


      vector<string> vect, *p;
      p = &vect;

      int i = 20;

      while(--i!= 0)
      {
      p->push_back("*");
      }

      vector<string>::iterator iter = vect.begin();
      while(iter != vect.end())
      {
      cout << *iter++ << endl;
      }

      cin >> i;
      return 0;
      }



  18. Section 5.7.  The Conditional Operator

    1. The conditional operator is the only ternary operator in C++.
    2. cond ? expr1 : expr2;
    3. Avoid Deep Nesting of the Conditional Operator
    4. Using a Conditional Operator in an Output Expression
      // EXE-5.7.cpp : Defines the entry point for the console application.
      //

      #include "stdafx.h"
      #include <iostream>
      #include <string>

      using namespace std;

      int _tmain(int argc, _TCHAR* argv[])
      {

      int i = 10, j = 20, k = 30;
      // if i > j then maxVal = i else maxVal = j
      int maxVal = i > j ? i : j;
      cout << maxVal << endl;

      int max = i > j
      ? i > k ? i : k
      : j > k ? j : k;
      cout << max << endl;

      max = i;
      if (j > max)
      max = j;
      if (k > max)
      max = k;

      cout << (i < j ? i : j) << endl; // ok: prints larger of i and j
      cout << ((i < j) ? i : j )<< endl; // prints 1 or 0!
      //cout << i < j ? i : j; // error: compares cout to int

      cout << (i < j) << endl; // prints 1 or 0
      //cout << "false" << endl;
      cout << (cout ? i : j ) << endl; // test cout and then evaluate i or j
      // depending on whether cout evaluates to true or false

      cin >> i;

      return 0;
      }

  19. Section 5.8.  The sizeof Operator

    1. The sizeof operator returns a value of type size_t (Section 3.5.2, p. 104) that is the size, in bytes (Section 2.1, p. 35), of an object or type name. The result of sizeof expression is a compile-time constant.
    2. Evaluating sizeof expr does not evaluate the expression. In particular, in sizeof *p, the pointer p may hold an invalid address, because p is not dereferenced.
    3. The result of applying sizeof depends in part on the type involved:

      1. sizeof char or an expression of type char is guaranteed to be 1
      2. sizeof a reference type returns the size of the memory necessary to contain an object of the referenced type
      3. sizeof a pointer returns the size needed hold a pointer; to obtain the size of the object to which the pointer points, the pointer must be dereferenced
      4. sizeof an array is equivalent to taking the sizeof the element type times the number of elements in the array

    4. Because sizeof returns the size of the entire array, we can determine the number of elements by dividing the sizeof the array by the sizeof an element:
        // sizeof(ia)/sizeof(*ia) returns the number of elements in ia
        int sz = sizeof(ia)/sizeof(*ia);
      // EXE-5.8.cpp : Defines the entry point for the console application.
      //

      #include "stdafx.h"
      #include <iostream>
      #include <string>
      #include <vector>

      using namespace std;

      int main(int argc, _TCHAR* argv[])
      {
      /*sizeof (type name);
      sizeof (expr);
      sizeof expr;*/


      cout << sizeof (int) << endl;

      char a = 'a' ,*p;
      cout << sizeof 'a' << sizeof(a) << sizeof p << sizeof *p << endl;

      char b[100];
      vector<char> vect;
      cout << sizeof b << sizeof vect << endl;

      int i = 2000;
      while(i-- != 0)
      vect.push_back(a);
      cout << sizeof vect << endl;


      int c[10101];
      cout << "The size of the int array C is " << sizeof(c)/sizeof(int) <<endl;
      //Sales_item item, *p;
      //// three ways to obtain size required to hold an object of type Sales_item
      //sizeof(Sales_item); // size required to hold an object of type Sales_item
      //sizeof item; // size of item's type, e.g., sizeof(Sales_item)
      //sizeof *p; // size of type to which p points, e.g., sizeof(Sales_item)

      cin >> i;

      return 0;
      }

  20. Section 5.9.  Comma Operator

    1. A comma expression is a series of expressions separated by commas. The expressions are evaluated from left to right. The result of a comma expression is the value of the rightmost expression. The result is an lvalue if the rightmost operand is an lvalue.
      // EXE-5.9.cpp : Defines the entry point for the console application.
      //

      #include "stdafx.h"
      #include <iostream>
      #include <string>
      #include <vector>
      using namespace std;

      int main(int argc, _TCHAR* argv[])
      {
      vector<int> ivec;
      vector<int>::size_type size = 100;
      ivec.resize(size);
      int cnt = ivec.size();
      // add elements from size... 1 to ivec
      for(vector<int>::size_type ix = 0;
      ix != ivec.size(); ++ix, --cnt)
      ivec[ix] = cnt;

      vector<int>::iterator iter = ivec.begin();
      while(iter != ivec.end())
      {
      cout << *iter++ << endl;
      }

      int i;
      cin >> i;

      return 0;
      }

  21. Section 5.10.  Evaluating Compound Expressions

    1. 5.10.1. Precedence

      1. 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.
      2. 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.
      3. 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.
      4. Parentheses Override Precedence

        1. Precedence says that ++ has higher precedence than *.

    2. 5.10.2. Associativity

      1. Associativity specifies how to group operators at the same precedence level.
      2. the assignment operator is right associative.
      3. Operators in each segment have the same precedence, and have higher precedence than operators in sub-sequent segments.






























































































































































































































































































































































        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


    3. 5.10.3. Order of Evaluation

      1. 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.
      2. The order of operand evaluation matters if one subexpression changes the value of an operand used in another subexpression









        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.


      3. Do not use an increment or decrement operator on the same object in more than two subexpressions of the same expression.
        // EXE-5.10.cpp : Defines the entry point for the console application.
        //

        #include "stdafx.h"
        #include <iostream>
        #include <string>

        using namespace std;

        int main(int argc, _TCHAR* argv[])
        {
        int temp = 3 * 4; // 12
        cout << temp << endl;
        int temp2 = temp / 2; // 6
        cout << temp2 << endl;
        int temp3 = temp2 + 6; // 12
        cout << temp3 << endl;
        int result = temp3 + 2; // 14
        cout << result << endl;

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

        int ival, jval, kval, lval = 3;
        cout << (ival = jval = kval = lval) << endl; // right associative
        cout << (ival = (jval = (kval = lval))) << endl; // equivalent, parenthesized version

        cout << ival * jval / kval * lval << endl; // left associative
        cout << (((ival * jval) / kval) * lval) << endl; // equivalent, parenthesized version


        // oops! language does not define order of evaluation
        int ia[10];
        int j = 10;
        while ( j-- != 0)
        {
        ia[j] = j % 2 ;
        }
        for (int index = 0;index < 10 ; )
        {
        //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.

        if (ia[index++] < ia[index])
        {
        cout << "smaller"<< index << ":" << ia[index] << endl;
        }
        else
        {
        cout << "bigger" << index << ":" << ia[index] << endl;
        }
        }
        //if (ia[0] < ia[0]) // execution if rhs is evaluated first
        //if (ia[0] < ia[1]) // execution if lhs is evaluated first
        cout << "rewritting the code ...." << endl;
        for (int index = 0;index < 10 ; ++index)
        {
        if (ia[index] < ia[index + 1])
        {
        cout << "smaller"<< index << ":" << ia[index] << endl;
        }
        else
        {
        cout << "bigger" << index << ":" << ia[index] << endl;
        }

        }
        int i;
        cin >> i;
        return 0;
        }


  22. Section 5.11.  The new and delete Expressions

    1. When we define a variable, we specify a type and a name. When we dynamically allocate an object, we specify a type but do not name the object. Instead, the new expression returns a pointer to the newly allocated object; we use that pointer to access the object
    2. This new expression allocates one object of type int from the free store and returns the address of that object.
    3. Initializing Dynamically Allocated Objects

      1. Dynamically allocated objects may be initialized, in much the same way as we initialize variables:
      2. We must use the direct-initialization syntax (Section 2.3.3, p. 48) to initialize dynamically allocated objects. When an initializer is present, the new expression allocates the required memory and initializes that memory using the given initializer(s).

    4. Default Initialization of Dynamically Allocated Objects

      1. If we do not explicitly state an initializer, then a dynamically allocated object is initialized in the same way as is a variable that is defined inside a function. (Section 2.3.4, p. 50) If the object is of class type, it is initialized using the default constructor for the type; if it is of built-in type, it is uninitialized.
      2. As usual, it is undefined to use the value associated with an uninitialized object in any way other than to assign a good value to it.
      3. Just as we (almost) always initialize the objects we define as variables, it is (almost) always a good idea to initialize dynamically allocated objects.
      4. We indicate that we want to value-initialize the newly allocated object by following the type name by a pair of empty parentheses. The empty parentheses signal that we want initialization but are not supplying a specific initial value.
      5. The () syntax for value initialization must follow a type name, not a variable. As we'll see in Section 7.4 (p. 251)

    5. Memory Exhaustion

      1. Although modern machines tend to have huge memory capacity, it is always possible that the free store will be exhausted. If the program uses all of available memory, then it is possible for a new expression to fail. If the new expression cannot acquire the requested memory, it throws an exception named bad_alloc. We'll look at how exceptions are thrown in Section 6.13 (p. 215).

    6. Destroying Dynamically Allocated Objects

      1. When our use of the object is complete, we must explicitly return the object's memory to the free store.
      2. We do so by applying the delete expression to a pointer that addresses the object we want to release.
          delete pi;

      3. It is illegal to apply delete to a pointer that addresses memory that was not allocated by new.The effect of deleting a pointer that addresses memory that was not allocated by new is undefined.

    7. delete of a Zero-Valued Pointer

      1. It is legal to delete a pointer whose value is zero; doing so has no effect
      2. The language guarantees that deleting a pointer that is equal to zero is safe.

    8. Resetting the Value of a Pointer after a delete

      1. After deleting a pointer, the pointer becomes what is referred to as a dangling pointer. A dangling pointer is one that refers to memory that once held an object but does so no longer. A dangling pointer can be the source of program errors that are difficult to detect.
      2. Setting the pointer to 0 after the object it refers to has been deleted makes it clear that the pointer points to no object.

    9. Dynamic Allocation and Deallocation of const Objects

      1. It is legal to dynamically create const objects
             // allocate and initialize a const object
        const int *pci = new const int(1024);

      2. Like any const, a dynamically created const must be initialized when it is created and once initialized cannot be changed. The value returned from this new expression is a pointer to const int. Like the address of any other const object, the return from a new that allocates a const object may only be assigned to a pointer to const.
      3. A const dynamic object of a class type that defines a default constructor may be initialized implicitly
      4. Objects of built-in type or of a class type that does not provide a default constructor must be explicitly initialized









        Caution: Managing Dynamic Memory Is Error-Prone


        The following three common program errors are associated with dynamic memory allocation:






        1. Failing to delete a pointer to dynamically allocated memory, thus preventing the memory from being returned to the free store. Failure to delete dynamically allocated memory is spoken of as a "memory leak." Testing for memory leaks is difficult because they often do not appear until the application is run for a test period long enough to actually exhaust memory.




        2. Reading or writing to the object after it has been deleted. This error can sometimes be detected by setting the pointer to 0 after deleting the object to which the pointer had pointed.




        3. Applying a delete expression to the same memory location twice. This error can happen when two pointers address the same dynamically allocated object. If delete is applied to one of the pointers, then the object's memory is returned to the free store. If we subsequently delete the second pointer, then the free store may be corrupted.


        These kinds of errors in manipulating dynamically allocated memory are considerably easier to make than they are to track down and fix.


    10. Deleting a const Object

      1. Although the value of a const object cannot be modified, the object itself can be destroyed. As with any other dynamic object, a const dynamic object is freed by deleting a pointer that points to it
      // EXE-5.11.cpp : Defines the entry point for the console application.
      //

      #include "stdafx.h"
      #include <iostream>
      #include <string>

      using namespace std;

      int main(int argc, _TCHAR* argv[])
      {
      int ii(5); // named, uninitialized int variable
      int *pi = new int; // pi points to dynamically allocated, unnamed, uninitialized int
      cout << "*pi Address is:" << pi << " value is:" << *pi << endl;
      cout << "pi Address is:" << &pi << " value is:" << pi << endl;
      cout << "i Address is:" << &ii << " value is:" << ii << endl;

      int i(1024); // value of i is 1024
      cout << "i's value:" << i << endl;
      int *pii = new int(1024); // object to which pi points is 1024
      cout << "*pi's value:" << *pii << endl;
      string s(10, '9'); // value of s is "9999999999"
      cout << "s's value:" << s << endl;
      string *ps = new string(10, '9'); // *ps is "9999999999"
      cout << "*ps's value:" << *ps << endl;

      string *pss = new string; // initialized to empty string
      cout << "automatically initialized string:" << *pss << endl;
      int *piii = new int; // pi points to an uninitialized int
      cout << "automatically initialized int:" << *piii << endl;

      //We can also value-initialize (Section 3.3.1, p. 92) a dynamically allocated object:
      string *ps1 = new string(); // initialized to empty string
      cout << "value-initialize string:"<< *ps1 << endl;
      int *pi1 = new int(); // pi points to an int value-initialized to 0
      cout << "value-initialize int:" << *pi1 << endl;
      //cls *pc1 = new cls(); // pc points to a value-initialized object of type cls

      int *pi2 = new int; // pi points to an uninitialized int
      cout << "uninitialized int:" << *pi2 << endl;
      int *pi3 = new int(); // pi points to an int value-initialized to 0
      cout << "value-initialized int:" << *pi3 << endl;

      //The following are examples of safe and unsafe delete expressions:
      int i3;
      int *pi4 = &i3;
      string str = "dwarves";
      double *pd = new double(33);
      //delete str; // error: str is not a dynamic object
      // Error at compile time : error C2440: 'delete' : cannot convert from 'std::string' to 'void *'
      //delete pi4; // error: pi refers to a local
      //// exception at runtime:
      //Windows has triggered a breakpoint in EXE-5.11.exe.
      // This may be due to a corruption of the heap, which indicates a bug in EXE-5.11.exe or any of the DLLs it has loaded.
      // This may also be due to the user pressing F12 while EXE-5.11.exe has focus.
      // The output window may have more diagnostic information.
      cout << "&pd=" << &pd << " pd=" << pd << " *pd=" << *pd << endl;
      delete pd; // ok
      cout << "&pd=" << &pd << " pd=" << pd << " *pd=" << *pd << endl;
      pd = 0;

      int *ip5 = 0;
      cout << "&ip5=" << "&ip5" << " ip5=" << ip5 << " *ip5=" << "*ip5" << endl;
      delete ip5; // ok: always ok to delete a pointer that is equal to 0
      cout << "&ip5=" << "&ip5" << " ip5=" << ip5 << " *ip5=" << "*ip5"<< endl;
      ip5 = 0;

      // allocate and initialize a const object
      const int *pci = new const int(1024);
      cout << "*pci=" << *pci << endl;
      delete pci;
      pci = 0;

      // allocate default initialized const empty string
      const string *pcs = new const string(8,'!');
      cout << "*pcs=" << *pcs << endl;
      delete pcs;
      pcs = 0;

      int *pi6 = new int(666);
      int *pi7 = pi6;

      cout << "*pi6=" << *pi6 << endl;
      cout << "*pi7=" << *pi7 << endl;

      delete pi6;
      cout << "pi6=" << pi6 << endl;
      cout << "pi7=" << pi7 << endl;

      ////delete pi7;
      //cout << "pi6=" << pi6 << endl;
      //cout << "pi7=" << pi7 << endl;

      pi6 = 0;
      cout << "pi6=" << pi6 << endl;
      cout << "pi7=" << pi7 << endl;

      pi7 = 0;
      cout << "pi6=" << pi6 << endl;
      cout << "pi7=" << pi7 << endl;

      cin >> i;

      return 0;
      }

  23. Section 5.12.  Type Conversions

    1. C++ defines a set of conversions to transform the operands to a common type before performing the arithmetic.
    2. These conversions are carried out automatically by the compiler without programmer interventionand sometimes without programmer knowledge. For that reason, they are referred to as implicit type conversions.
    3. The built-in conversions among the arithmetic types are defined to preserve precision, if possible. Most often, if an expression has both integral and floating-point values, the integer is converted to floating-point.
    4. To understand implicit conversions, we need to know when they occur and what conversions are possible.
    5. 5.12.1. When Implicit Type Conversions Occur

      1. The compiler applies conversions for both built-in and class type objects as necessary.

        1. In expressions with operands of mixed types, the types are converted to a common type:
        2. An expression used as a condition is converted to bool:
        3. An expression used to initialize or assign to a variable is converted to the type of the variable:

    6. 5.12.2. The Arithmetic Conversions

      1. The language defines a set of conversions among the built-in types. Among these, the most common are the arithmetic conversions, which ensure that the two operands of a binary operator, such as an arithmetic or logical operator, are converted to a common type before the operator is evaluated. That common type is also the result type of the expression.
      2. The rules define a hierarchy of type conversions in which operands are converted to the widest type in the expression. The conversion rules are defined so as to preserve the precision of the values involved in a multi-type expression.
      3. The simplest kinds of conversion are integral promotions.
      4. Conversions between Signed and Unsigned Types

        1. When an unsigned value is involved in an expression, the conversion rules are defined to preserve the value of the operands.

      5. Understanding the Arithmetic Conversions

    7. 5.12.3. Other Implicit Conversions

      1. Pointer Conversions

        1. The exceptions when an array is not converted to a pointer are: as the operand of the address-of (&) operator or of sizeof, or when using the array to initialize a reference to the array
        2. There are two other pointer conversions: A pointer to any data type can be converted to a void*, and a constant integral value of 0 can be converted to any pointer type

      2. Conversions to bool

        1. Arithmetic and pointer values can be converted to bool. If the pointer or arithmetic value is zero, then the bool is false; any other value converts to TRue

      3. Arithmetic Type and bool Conversions

        1. Arithmetic objects can be converted to bool and bool objects can be converted to int. When an arithmetic type is converted to bool, zero converts as false and any other value converts as TRue. When a bool is converted to an arithmetic type, true becomes one and false becomes zero

      4. Conversions and Enumeration Types

        1. Objects of an enumeration type (Section 2.7, p. 62) or an enumerator can be automatically converted to an integral type.
        2. The type to which an enum object or enumerator is promoted is machine-defined and depends on the value of the largest enumerator. Regardless of that value, an enum or enumerator is always promoted at least to int. If the largest enumerator does not fit in an int, then the promotion is to the smallest type larger than int (unsigned int, long or unsigned long) that can hold the enumerator value.

      5. Conversion to const

        1. A nonconst object can be converted to a const object, which happens when we use a nonconst object to initialize a reference to const object. We can also convert the address of a nonconst object (or convert a nonconst pointer) to a pointer to the related const type

      6. Conversions Defined by the Library Types

        1. Class types can define conversions that the compiler will apply automatically.
          string s;
          while (cin >> s)

        2. The condition in the while expects a value of type bool, but it is given a value of type istream. That istream value is converted to bool. The effect of converting an istream to bool is to test the state of the stream. If the last attempt to read from cin succeeded, then the state of the stream will cause the conversion to bool to be truethe while test will succeed. If the last attempt failedsay because we hit end-of-filethen the conversion to bool will yield false and the while condition will fail

    8. 5.12.4. Explicit Conversions

      1. An explicit conversion is spoken of as a cast and is supported by the following set of named cast operators: static_cast, dynamic_cast, const_cast, and reinterpret_cast.
      2. Although necessary at times, casts are inherently dangerous constructs.

    9. 5.12.5. When Casts Might Be Useful

      1. One reason to perform an explicit cast is to override the usual standard conversions.
      2. Another reason for an explicit cast is to select a specific conversion when more than one conversion is possible.

    10. 5.12.6. Named Casts

      1. dynamic_cast

        1. A dynamic_cast supports the run-time identification of objects addressed either by a pointer or reference.

      2. const_cast

        1. A const_cast, as its name implies, casts away the constness of its expression.
        2. Only a const_cast can be used to cast away constness. Using any of the other three forms of cast in this case would result in a compile-time error. Similarly, it is a compile-time error to use the const_cast notation to perform any type conversion other than adding or removing const

      3. static_cast

        1. Any type conversion that the compiler performs implicitly can be explicitly requested by using a static_cast
        2. Such casts are useful when assigning a larger arithmetic type to a smaller type. The cast informs both the reader of the program and the compiler that we are aware of and are not concerned about the potential loss of precision. Compilers often generate a warning for assignments of a larger arithmetic type to a smaller type. When we provide the explicit cast, the warning message is turned off.
        3. A static_cast is also useful to perform a conversion that the compiler will not generate automatically.

      4. reinterpret_cast

        1. A reinterpret_cast generally performs a low-level reinterpretation of the bit pattern of its operands.
        2. A reinterpret_cast is inherently machine-dependent. Safely using reinterpret_cast requires completely understanding the types involved as well as the details of how the compiler implements the cast
        3. The use of pc to initialize str is a good example of why explicit casts are dangerous.









          Advice: Avoid Casts


          By using a cast, the programmer turns off or dampens normal type-checking (Section 2.3, p. 44). We strongly recommend that programmers avoid casts and believe that most well-formed C++ programs can be written without relying on casts.


          This advice is particularly important regarding use of reinterpret_casts. Such casts are always hazardous. Similarly, use of const_cast almost always indicates a design flaw. Properly designed systems should not need to cast away const. The other casts, static_cast and dynamic_cast, have their uses but should be needed infrequently. Every time you write a cast, you should think hard about whether you can achieve the same result in a different way. If the cast is unavoidable, errors can be mitigated by limiting the scope in which the cast value is used and by documenting all assumptions about the types involved.


    11. 5.12.7. Old-Style Casts

      1. Prior to the introduction of named cast operators, an explicit cast was performed by enclosing a type in parentheses
      2. The effect of this cast is the same as using the reinterpret_cast notation. However, the visibility of this cast is considerably less, making it even more difficult to track down the rogue cast
      3. Standard C++ introduced the named cast operators to make casts more visible and to give the programmer a more finely tuned tool to use when casts are necessary
      4. example, nonpointer static_casts and const_casts tend to be safer than reinterpret_casts. As a result, the programmer (as well as readers and tools operating on the program) can clearly identify the potential risk level of each explicit cast in code.
      5. Although the old-style cast notation is supported by Standard C++, we recommend it be used only when writing code to be compiled either under the C language or pre-Standard C++.
      6. The old-style cast notation takes one of the following two forms:
          type (expr); // Function-style cast notation
          (type) expr; // C-language-style cast notation

      7. Depending on the types involved, an old-style cast has the same behavior as a const_cast, a static_cast, ora reinterpret_cast. When used where a static_cast or a const_cast would be legal, an old-style cast does the same conversion as the respective named cast. If neither is legal, then an old-style cast performs a reinterpret_cast.
      8. The old-style cast notation remains supported for backward compatibility with programs written under pre-Standard C++ and to maintain compatibility with the C language.
      // EXE-5.12.cpp : Defines the entry point for the console application.
      //

      #include "stdafx.h"
      #include <iostream>
      #include <string>
      #include <vector>

      using namespace std;

      int main(int argc, _TCHAR* argv[])
      {
      int ival0;
      ival0 = 3.541 + 3; // typically compiles with a warning
      cout << ival0 << endl;

      int ival1(0);
      double dval0(0.0);
      cout << (ival1 >= dval0 ) << endl; // ival converted to double

      int ival2(0);
      if (ival2){}; // ival converted to bool
      while (cin){
      cout << "BREAK" << endl;
      break;
      } // cin converted to bool

      int ival3 = 3.14; // 3.14 converted to int
      int *ip;
      ip = 0; // the int 0 converted to a null pointer of type int *

      bool flag(false); char cval('a');
      short sval(3); unsigned short usval(3);
      int ival(4); unsigned int uival(6);
      long lval(-100000000000000000); unsigned long ulval(10000000000000000000);
      float fval(3.1415926); double dval(2.1111111111111111111);
      cout << "3.14159L + 'a' =" << 3.14159L + 'a' << endl; // promote 'a' to int, then convert to long double
      cout << "dval + ival=" << dval + ival << endl; // ival converted to double
      cout << "dval + fval=" << dval + fval << endl; // fval converted to double
      cout << "ival = dval=" << (ival = dval )<< endl ; // dval converted (by truncation) to int
      cout << "flag = dval=" << (flag = dval )<< endl ; // if dval is 0, then flag is false, otherwise true
      cout << "cval + fval=" << cval + fval << endl ; // cval promoted to int, that int converted to float
      cout << "sval + cval=" << sval + cval << endl ; // sval and cval promoted to int
      cout << "cval + lval=" << cval + lval << endl ; // cval converted to long
      cout << "ival + ulval=" << ival + ulval << endl ; // ival converted to unsigned long
      cout << "usval + ival=" << usval + ival << endl ; // promotion depends on size of unsigned short and int
      cout << "uival + lval=" << uival + lval << endl ; // conversion depends on size of unsigned int and long

      int ia[10]; // array of 10 ints
      int* ip6 = ia; // convert ia to pointer to first element

      bool b = true;
      int ival6 = b; // ival == 1
      cout << "int ival6 = b is " << ival6 << endl;
      double pi = 3.14;
      bool b2 = pi; // b2 is true
      cout << "bool b2 = pi is " << b2 << endl;
      pi = false; // pi == 0
      cout << "pi = false is " << pi << endl;

      // point2d is 2, point2w is 3, point3d is 3, point3w is 4
      enum Points { point2d = 2, point2w,
      point3d = 3, point3w };
      const size_t array_size = 1024;
      // ok: pt2w promoted to int
      int chunk_size = array_size * point2w;
      cout << "int chunk_size = array_size * point2w is " << chunk_size << endl;
      int array_3d = array_size * point3d;
      cout << "int array_3d = array_size * point3d is " << array_3d << endl;
      int array_3w = array_size * point3w;
      cout << "int array_3w = array_size * point3w is " << array_3w << endl;

      int i(3);
      cout << "int i(3) i is " << i << " &i is " << &i << endl;
      const int ci = 0 ;
      const int &j =i; // ok: convert non-const to reference to const int
      cout << "const int &j = i j is " << j << " &j is " << &j << endl;
      const int *p = &ci; // ok: convert address of non-const to address of a const
      cout << "const int *p = &ci &p is " << &p << " *p=" << *p << endl;
      cout << "p=" << p << endl;
      cout << "&ci=" << &ci << endl;

      /*string s;
      while (cin >> s)
      {
      cout << s << endl;
      }*/


      double dval7(2.003);
      int ival7(2);
      ival7 *= dval7; // ival = ival * dval
      ival7 *= static_cast<int>(dval7); // converts dval to int

      //const char *pc_str;
      //char *pc = string_copy(const_cast<char*>(pc_str));

      double d = 97.0;
      // cast specified to indicate that the conversion is intentional
      char ch = static_cast<char>(d);
      cout << ch << endl;

      void* dp0 = &d; // ok: address of any data object can be stored in a void*
      //cout << "void* dp0 = &d dp0 is " << dp0 << "*dp0 is " << (*dp0) << endl;
      // ok: converts void* back to the original pointer type
      double *dp1 = static_cast<double*>(dp0);
      cout << "*dp1 is " << *dp1 << endl;

      int z = 'a';
      int *ip8 = &z;
      char *pc = reinterpret_cast<char*>(ip8);
      cout << "*pc is " << *pc << endl;
      string str(pc);
      cout << "str is " << str << endl;
      if(1)
      {
      int ival(8); double dval(8.0);
      ival += int (dval); // static_cast: converts double to int
      const char* pc_str;
      //string_copy((char*)pc_str); // const_cast: casts away const
      int *ip;
      char *pc = (char*)ip; // reinterpret_cast: treats int* as char*
      }



      int jj;
      cin >> jj;

      return 0;
      }

No comments: