Google
 

Thursday, September 2, 2010

C++ Primer, Fourth Edition:Reading notes:Chapter 4. Arrays and Pointers

  1. The language defines two lower-level compound typesarrays and pointersthat are similar to vectors and iterators. Like a vector, an array holds a collection of objects of some type. Unlike vectors, arrays are fixed size; once an array is created, new elements cannot be added. Like iterators, pointers can be used to navigate among and examine the elements in an array.
  2. Modern C++ programs should almost always use vectors and iterators in preference to the lower-level arrays and pointers. Well-designed programs use arrays and pointers only in the internals of class implementations where speed is essential.
  3. Arrays are data structures that are similar to library vectors but are built into the language. Like a vector, an array is a container of objects of a single data type. The individual objects are not named; rather, each one is accessed by its position in the array
  4. Arrays have significant drawbacks compared to vectors: They are fixed size, and they offer no help to the programmer in keeping track of how big a given array is. There is no size operation on arrays. Similarly, there is no push_back to automatically add elements. If the array size needs to change, then the programmer must allocate a new, larger array and copy the elements into that new space.
  5. BEWARE:Programs that rely on built-in arrays rather than using the standard vector are more error-prone and harder to debug
  6. Prior to the advent of the standard library, C++ programs made heavy use of arrays to hold collections of objects. Modern C++ programs should almost always use vectors instead of arrays. Arrays should be restricted to the internals of programs and used only where performance testing indicates that vectors cannot provide the necessary speed. However, there will be a large body of existing C++ code that relies on arrays for some time to come. Hence, all C++ programmers must know a bit about how arrays work.
  7. Section 4.1.  Arrays
    1. An array is a compound type (Section 2.5, p. 58) that consists of a type specifier, an identifier, and a dimension. The type specifier indicates what type the elements stored in the array will have. The dimension specifies how many elements the array will contain.
    2. 4.1.1. Defining and Initializing Arrays
      1. The dimension must be a constant expression (Section 2.7, p. 62) whose value is greater than or equal to one. A constant expression is any expression that involves only integral literal constants, enumerators (Section 2.7, p. 62), or const objects of integral type that are themselves initialized from constant expressions. A nonconst variable, or a const variable whose value is not known until run time, cannot be used to specify the dimension of an array.
      2. Explicitly Initializing Array Elements
        1. When we define an array, we can provide a comma-separated list of initializers for its elements.
        2. If we do not supply element initializers, then the elements are initialized in the same way that variables are initialized (Section 2.3.4, p. 50).
          1. Elements of an array of built-in type defined outside the body of a function are initialized to zero.
          2. Elements of an array of built-in type defined inside the body of a function are uninitialized.
          3. Regardless of where the array is defined, if it holds elements of a class type, then the elements are initialized by the default constructor for that class if it has one. If the class does not have a default constructor, then the elements must be explicitly initialized.
        3. NOTE:Unless we explicitly supply element initializers, the elements of a local array of built-in type are uninitialized. Using these elements for any purpose other than to assign a new value is undefined.
        4. An explicitly initialized array need not specify a dimension value. The compiler will infer the array size from the number of elements listed:
        5. If the dimension size is specified, the number of elements provided must not exceed that size. If the dimension size is greater than the number of listed elements, the initializers are used for the first elements. The remaining elements are initialized to zero if the elements are of built-in type or by running the default constructor if they are of class type:
      3. Character Arrays Are Special
        1. A character array can be initialized with either a list of comma-separated character literals enclosed in braces or a string literal.
        2. Note, however, that the two forms are not equivalent. Recall that a string literal (Section 2.2, p. 40) contains an additional terminating null character. When we create a character array from a string literal, the null is also inserted into the array:
      4. No Array Copy or Assignment
        1. Unlike a vector, it is not possible to initialize an array as a copy of another array. Nor is it legal to assign one array to another:
        2. BEWARE:Some compilers allow array assignment as a compiler extension. If you intend to run a given program on more than one compiler, it is usually a good idea to avoid using nonstandard compiler-specific features such as array assignment.
      5. Caution: Arrays Are Fixed Size
        1. Unlike the vector type, there is no push_back or other operation to add elements to the array. Once we define an array, we cannot add elements to it.
        2. If we must add elements to the array, then we must manage the memory ourselves. We have to ask the system for new storage to hold the larger array and copy the existing elements into that new storage. We'll see how to do so in Section 4.3.1 (p. 134).
    3. 4.1.2. Operations on Arrays
      1. Array elements, like vector elements, may be accessed using the subscript operator (Section 3.3.2, p. 94). Like the elements of a vector, the elements of an array are numbered beginning with 0. For an array of ten elements, the correct index values are 0 through 9, not 1 through 10.
      2. Checking Subscript Values
        1. As with both strings and vectors, the programmer must guarantee that the subscript value is in range that the array has an element at the index value.
        2. Nothing stops a programmer from stepping across an array boundary except attention to detail and thorough testing of the code. It is not inconceivable for a program to compile and execute and still be fatally wrong.
      3. BEWARE:By far, the most common causes of security problems are so-called "buffer overflow" bugs. These bugs occur when a subscript is not checked and reference is made to an element outside the bounds of an array or other similar data structure.
      // EXE-4.1.cpp : Defines the entry point for the console application.
      #include "stdafx.h"
      #include <iostream>
      #include <string>
      using namespace std;

      int get_size();
      int main()
      {
      /*
      Although staff_size is initialized with a literal constant, staff_size itself is a non-const object. Its value can be known only at run time, so it is illegal as an array dimension. Even though size is a const object, its value is not known until get_size is called at run time. Therefore, it may not be used as a dimension. On the other hand, the expression max_files + 1
      is a constant expression because max_files is a const variable. The expression can be and is evaluated at compile time to a value of 21.
      */

      // both buf_size and max_files are const
      const size_t buf_size = 512, max_files = 20;
      size_t staff_size = 27; // non-const
      const unsigned size = get_size(); // const value not known until run time
      char input_buffer[buf_size]; // ok: const variable
      string fileTable[max_files + 1]; // ok: constant expression
      //double salaries[staff_size]; // error: non const variable
      //int test_scores[get_size()]; // error: non const expression
      //int vals[size]; // error: size not known until run time

      const size_t array_size0 = 3;
      int ia0[array_size0] = {0, 1, 2};
      int ia1[] = {0, 1, 2}; // an array of dimension 3

      const size_t array_size = 5;
      // Equivalent to ia2 = {0, 1, 2, 0, 0}
      // ia2[3] and ia2[4] default initialized to 0
      int ia2[array_size] = {0, 1, 2};
      cout << "int ia2[array_size] = {0, 1, 2}; 's elements are(expected are: {0, 1, 2, 0, 0}): " <<endl;;
      size_t index = 0;
      for (index = 0; index != array_size; ++index)
      {
      cout << index << "th is " << ia2[index] << ";";
      }
      //here out of boundary's value show
      cout << index << "th is " << ia2[index] << ";";
      cout <<endl;
      // Equivalent to str_arr = {"hi", "bye", "", "", ""}
      // str_arr[2] through str_arr[4] default initialized to the empty string
      string str_arr[array_size] = {"hi", "bye"};
      cout << "string str_arr[array_size] = {\"hi\", \"bye\"};'s elements are (expected are:{\"hi\", \"bye\", \"\", \"\", \"\"}):" << endl;
      for (index = 0;index!= array_size ; ++ index)
      {
      cout << index << "th is " << str_arr[index] << ";";
      }
      //here out of boundary's value show
      cout << index << "th is " << str_arr[index] << ";";
      cout << endl;
      /*
      The dimension of ca1 is 3; the dimension of ca2 and ca3 is 4. It is important to remember the null-terminator when initializing an array of characters to a literal. For example, the following is a compile-time error:
      While the literal contains only six explicit characters, the required array size is sevensix to hold the literal and one for the null.
      */

      char ca1[] = {'C', '+', '+'}; // no null
      cout << "char ca1[] = {'C', '+', '+'}; 's element show: " << endl;
      for (index = 0; index != 3; ++index)
      {
      cout << index << "th is " << ca1[index] << ";" ;
      }
      cout << index << "th is " << ca1[index] << endl;

      char ca2[] = {'C', '+', '+', '\0'}; // explicit null
      cout << "char ca2[] = {'C', '+', '+', '\0'}; 's element show: " << endl;
      for (index = 0; index != 3; ++index)
      {
      cout << index << "th is " << ca2[index] << ";" ;
      }
      cout << index << "th is " << ca2[index] << endl;

      char ca3[] = "C++"; // null terminator added automatically
      cout << "char ca3[] = \"C++\"; 's element show: " << endl;
      for (index = 0; index != 4; ++index)
      {
      cout << index << "th is " << ca3[index] << ";" ;
      }
      cout << index << "th is " << ca3[index] << endl;
      //error C2117: 'ch3' : array bounds overflow
      //const char ch3[6] = "Daniel"; // error: Daniel is 7 elements

      int ia3[] = {0, 1, 2}; // ok: array of ints
      //error C2440: 'initializing' : cannot convert from 'int [3]' to 'int []'
      //int ia4[](ia3); // error: cannot initialize one array with another
      cout << "int ia3[] = {0, 1, 2} 's element show: " << endl;
      for (index = 0; index != 3; ++index)
      {
      cout << index << "th is " << ia3[index] << ";" ;
      }
      cout << index << "th is " << ia3[index] << endl;

      const size_t array_size1 = 3;
      int ia5[array_size1]; // ok: but elements are uninitialized!
      //error C2106: '=' : left operand must be l-value
      //ia5 = ia3; // error: cannot assign one array to another
      cout << "int ia5[array_size1]; 's element show: " << endl;
      for (index = 0; index !=array_size1; ++index)
      {
      cout << index << "th is " << ia5[index] << ";" ;
      }
      cout << index << "th is " << ia5[index] << endl;

      const size_t array_size2 = 10;
      int ia[array_size2]; // 10 ints, elements are uninitialized
      cout << "int ia[array_size2]; 's element show: " << endl;
      for (index = 0; index !=array_size2; ++index)
      {
      cout << index << "th is " << ia[index] << ";" ;
      }
      cout << index << "th is " << ia[index] << endl;

      // loop through array, assigning value of its index to each element
      for (size_t ix = 0; ix != array_size2; ++ix)
      ia[ix] = ix;
      cout << "int ia[array_size2]; 's initialized element show: " << endl;
      for (index = 0; index !=array_size2; ++index)
      {
      cout << index << "th is " << ia[index] << ";" ;
      }
      cout << index << "th is " << ia[index] << endl;

      //Here should use the size_t as size variable type
      const size_t array_size3 = 7;
      int ia6[] = { 0, 1, 2, 3, 4, 5, 6 };
      int ia7[array_size3]; // local array, elements uninitialized

      cout << "int ia6[] = { 0, 1, 2, 3, 4, 5, 6 }; 's initialized element show: " << endl;
      for (index = 0; index !=array_size3; ++index)
      {
      cout << index << "th is " << ia6[index] << ";" ;
      }
      cout << index << "th is " << ia6[index] << endl;

      // copy elements from ia1 into ia2
      size_t ix = 0;
      for (ix = 0; ix != array_size3; ++ix)
      {
      ia7[ix] = ia6[ix];
      }
      //ia6[ix] = 3;
      cout << "ia7[ix] = ia6[ix];; 's initialized element show: " << endl;
      for (index = 0; index !=array_size3; ++index)
      {
      cout << index << "th is " << ia7[index] << ";" ;
      }
      cout << index << "th is " << ia7[index] << endl;

      return 0;
      }

      int get_size()
      {
      const int size = 10;
      return size;
      }

  8. Section 4.2.  Introducing Pointers

    1. Just as we can traverse a vector either by using a subscript or an iterator, we can also traverse an array by using either a subscript or a pointer.
    2. A pointer is a compound type; a pointer points to an object of some other type. Pointers are iterators for arrays: A pointer can point to an element in an array. The dereference and increment operators, when applied to a pointer that points to an array element, have similar behavior as when applied to an iterator. When we dereference a pointer, we obtain the object to which the pointer points. When we increment a pointer, we advance the pointer to denote the next element in the array. Before we write programs using pointers, we need to know a bit more about them.
    3. 4.2.1. What Is a Pointer?

      1. For newcomers, pointers are often hard to understand. Debugging problems due to pointer errors bedevil even experienced programmers. However, pointers are an important part of most C programs and to a much lesser extent remain important in many C++ programs.
      2. Conceptually, pointers are simple: A pointer points at an object. Like an iterator, a pointer offers indirect access to the object to which it points. However, pointers are a much more general construct. Unlike iterators, pointers can be used to point at single objects. Iterators are used only to access elements in a container.
      3. Specifically, a pointer holds the address of another object:
          string s("hello world");
          string *sp = &s; // sp holds the address of s

      4. The second statement defines sp as a pointer to string and initializes sp to point to the string object named s. The * in *sp indicates that sp is a pointer. The & operator in &s is the address-of operator. It returns a value that when dereferenced yields the original object. The address-of operator may be applied only to an lvalue (Section 2.3.1, p. 45). Because a variable is an lvalue, we may take its address. Similarly, the subscript and dereference operators, when applied to a vector, string, or built-in array, yield lvalues. Because these operators yield lvalues, we may apply the address-of to the result of the subscript or dereference operator. Doing so gives us the address of a particular element.

    4. Advice: Avoid Pointers and Arrays

      1. Pointers and arrays are surprisingly error-prone. Part of the problem is conceptual: Pointers are used for low-level manipulations and it is easy to make bookkeeping mistakes. Other problems arise because of the syntax, particularly the declaration syntax used with pointers.
      2. Many useful programs can be written without needing to use arrays or pointers. Instead, modern C++ programs should use vectors and iterators to replace general arrays and strings to replace C-style array-based character strings.

    5. 4.2.2. Defining and Initializing Pointers

      1. Every pointer has an associated type. The type of a pointer determines the type of the objects to which the pointer may point. A pointer to int, for example, may only point to an object of type int.
      2. Defining Pointer Variables

        1. We use the * symbol in a declaration to indicate that an identifier is a pointer:
        2. TIPS: When attempting to understand pointer declarations, read them from right to left.
        3. The * can come anywhere in a list of objects of a given type:

      3. A Different Pointer Declaration Style

        1. The * symbol may be separated from its identifier by a space It is legal to write:
            string* ps; // legal but can be misleading

        2. We say that this definition can be misleading because it encourages the belief that string* is the type and any variable defined in the same definition is a pointer to string.

      4. Multiple Pointer Declarations Can Be Confusing

        1. There are two common styles for declaring multiple pointers of the same type. One style requires that a declaration introduce only a single name. In this style, the * is placed with the type to emphasize that the declaration is declaring a pointer
        2. The other style permits multiple declarations in a single statement but places the * adjacent to the identifier. This style emphasizes that the object is a pointer:
        3. TIPS:As with all questions of style, there is no single right way to declare pointers. The important thing is to choose a style and stick with it.
        4. In this book we use the second style and place the * with the pointer variable name.

      5. Possible Pointer Values

        1. A valid pointer has one of three states: It can hold the address of a specific object, it can point one past the end of an object, or it can be zero. A zero-valued pointer points to no object. An uninitialized pointer is invalid until it is assigned a value. The following definitions and assignments are all legal:
        2. 	int ival = 1024;
          int *pi = 0; // pi initialized to address no object
          int *pi2 = & ival; // pi2 initialized to address of ival
          int *pi3; // ok, but dangerous, pi3 is uninitialized
          pi = pi2; // pi and pi2 address the same object, e.g. ival
          pi2 = 0; // pi2 now addresses no object

      6. Avoid Uninitialized Pointers

        1. BEWARE:Uninitialized pointers are a common source of run-time errors.
        2. As with any other uninitialized variable, what happens when we use an uninitialized pointer is undefined. Using an uninitialized pointer almost always results in a run-time crash. However, the fact that the crash results from using an uninitialized pointer can be quite hard to track down.
        3. Under most compilers, if we use an uninitialized pointer the effect will be to use whatever bits are in the memory in which the pointer resides as if it were an address. Using an uninitialized pointer uses this supposed address to manipulate the underlying data at that supposed location. Doing so usually leads to a crash as soon as we attempt to dereference the uninitialized pointer.
        4. It is not possible to detect whether a pointer is uninitialized. There is no way to distinguish a valid address from an address formed from the bits that are in the memory in which the pointer was allocated. Our recommendation to initialize all variables is particularly important for pointers.
        5. Best Practise:If possible, do not define a pointer until the object to which it should point has been defined. That way, there is no need to define an uninitialized pointer.
        6. If you must define a pointer separately from pointing it at an object, then initialize the pointer to zero. The reason is that a zero-valued pointer can be tested and the program can detect that the pointer does not point to an object.

      7. Constraints on Initialization of and Assignment to Pointers

        1. There are only four kinds of values that may be used to initialize or assign to a pointer:

          1. A constant expression (Section 2.7, p. 62) with value 0 (e.g., a const integral object whose value is zero at compile time or a literal constant 0)
          2. An address of an object of an appropriate type
          3. The address one past the end of another object
          4. Another valid pointer of the same type

        2. It is illegal to assign an int to a pointer, even if the value of the int happens to be 0. It is okay to assign the literal 0 or a const whose value is known to be 0 at compile time:
            	int ival;
            int zero = 0;
            const int c_ival = 0;
            int *pi = ival; // error: pi initialized from int value of ival
            pi = zero; // error: pi assigned int value of zero
            pi = c_ival; // ok: c_ival is a const with compile-time value of 0
            pi = 0; // ok: directly initialize to literal constant 0

        3. In addition to using a literal 0 or a const with a compile-time value of 0, we can also use a facility that C++ inherits from C. The cstdlib header defines a preprocessor variable (Section 2.9.2, p. 69) named NULL, which is defined as 0. When we use a preprocessor variable in our code, it is automatically replaced by its value. Hence, initializing a pointer to NULL is equivalent to initializing it to 0:
        4. NOTE:Preprocessor variables are not defined in the std namespace and hence the name is NULL, not std::NULL.
        5. With two exceptions, which we cover in Sections 4.2.5 and 15.3, we may only initialize or assign a pointer from an address or another pointer that has the same type as the target pointer:
        6. The reason the types must match is that the type of the pointer is used to determine the type of the object that it addresses. Pointers are used to indirectly access an object. The operations that the pointer can perform are based on the type of the pointer: A pointer to int treats the underlying object as if it were an int. If that pointer actually addressed an object of some other type, such as double, then any operations performed by the pointer would be in error.

      8. void* Pointers

        1. The type void* is a special pointer type that can hold an address of any object:
            	double obj = 3.14;
            double *pd = &obj;
            // ok: void* can hold the address value of any data pointer type
            void *pv = &obj; // obj can be an object of any type
            pv = pd; // pd can be a pointer to any type

        2. A void* indicates that the associated value is an address but that the type of the object at that address is unknown.
        3. There are only a limited number of actions we can perform on a void* pointer: We can compare it to another pointer, we can pass or return it from a function, and we can assign it to another void* pointer. We cannot use the pointer to operate on the object it addresses. We'll see in Section 5.12.4 (p. 183) how we can retrieve the address stored in a void* pointer.


    6. 4.2.3. Operations on Pointers

      1. Pointers allow indirect manipulation of the object to which the pointer points. We can access the object by dereferencing the pointer. Dereferencing a pointer is similar to dereferencing an iterator (Section 3.4, p. 98). The * operator (the dereference operator) returns the object to which the pointer points:
      2. Dereference Yields an Lvalue

        1. The dereference operator returns the lvalue of the underlying object, so we can use it to change the value of the object to which the pointer points:
        2. We change the value of a pointer by assigning to it directly without dereferencing the pointer.

          1. sp = &s2;  // sp now points to s2
            cout << *sp << endl;

      3. Key Concept: Assigning TO or THROUGH a Pointer

        1. When first using pointers, the difference in whether an assignment is to the pointer or through the pointer to the value pointed to can be confusing. The important thing to keep in mind is that if the left-hand operand is dereferenced, then the value pointed to is changed. If there is no dereference, then the pointer itself is being changed. A picture can sometimes help:
        2. Key Concept- Assigning TO or THROUGH a Pointer

      4. Comparing Pointers and References

        1. While both references and pointers are used to indirectly access another value, there are two important differences between references and pointers. The first is that a reference always refers to an object: It is an error to define a reference without initializing it. The behavior of assignment is the second important difference: Assigning to a reference changes the object to which the reference is bound; it does not rebind the reference to another object. Once initialized, a reference always refers to the same underlying object.
        2. After the assignment, ival, the object addressed by pi remains unchanged. The assignment changes the value of pi, making it point to a different object.
        3. This assignment changes ival, the value referenced by ri, and not the reference itself. After the assignment, the two references still refer to their original objects, and the value of those objects is now the same as well.
            	int ival3 = 1024, ival4 = 2048;
            int *pi5 = &ival3, *pi6 = &ival4;
            pi5 = pi6; // pi5 now points to ival4
            int &ri = ival3, &ri2 = ival4;
            ri = ri2; // assigns ival4 to ival3

      5. Pointers to Pointers

        1. Pointers are themselves objects in memory. They, therefore, have addresses that we can store in a pointer:
          	int ival5 = 1024;
          int *pi7 = &ival5; // pi points to an int
          int **ppi = &pi7; // ppi points to a pointer to int
          int *pi8 = *ppi; // ppi points to a pointer
          cout << "The value of ival\n"
          << "direct value: " << ival5 << "\n"
          << "indirect value: " << *pi7 << "\n"
          << "doubly indirect value: " << **ppi << " or " << *pi8
          << endl;

    7. 4.2.4. Using Pointers to Access Array Elements

      1. Pointers and arrays are closely intertwined in C++. In particular, when we use the name of an array in an expression, that name is automatically converted into a pointer to the first element of the array:
      2. If we want to point to another element in the array, we could do so by using the subscript operator to locate the element and then applying the address-of operator to find its location:
          	int ia[] = {0,2,4,6,8};
          int *ip = ia; // ip points to ia[0]
          ip = &ia[4]; // ip points to last element in ia

      3. Pointer Arithmetic

        1. Rather than taking the address of the value returned by subscripting, we could use pointer arithmetic. Pointer arithmetic works the same way (and has the same constraints) as iterator arithmetic (Section 3.4.1, p. 100). Using pointer arithmetic, we can compute a pointer to an element by adding (or subtracting) an integral value to (or from) a pointer to another element in the array
        2. More generally, when we add (or subtract) an integral value to a pointer, the effect is to compute a new pointer. The new pointer points to the element as many elements as that integral value ahead of (or behind) the original pointer.
        3. NOTE:Pointer arithmetic is legal only if the original pointer and the newly calculated pointer address elements of the same array or an element one past the end of that array. If we have a pointer to an object, we can also compute a pointer that points just after that object by adding one to the pointer.
        4. We can also subtract two pointers as long as they point into the same array or to an element one past the end of the array:
            	int ia[] = {0,2,4,6,8};
            int *ip = ia; // ip points to ia[0]
            ip = &ia[4]; // ip points to last element in ia
            //ip = ia; // ok: ip points to ia[0]
            int *ip3 = ip + 4; // ok: ip2 points to ia[4], the last element in ia
            ptrdiff_t n = ip2 - ip; // ok: distance between the pointers



            1. The result of subtracting two pointers is a library type named ptrdiff_t. Like size_t, the ptrdiff_t type is a machine-specific type and is defined in the cstddef header. The size_t type is an unsigned type, whereas ptrdiff_t is a signed integral type
            2. It is always possible to add or subtract zero to a pointer, which leaves the pointer unchanged. More interestingly, given a pointer that has a value of zero, it is also legal to add zero to that pointer. The result is another zero-valued pointer. We can also subtract two pointers that have a value of zero. The result of subtracting two zero-valued pointers is zero.

          1. Interaction between Dereference and Pointer Arithmetic

            1. The result of adding an integral value to a pointer is itself a pointer. We can dereference the resulting pointer directly without first assigning it to another pointer:
            2. The parentheses are required due to the precedence of the addition and dereference operators. We'll learn more about precedence in Section 5.10.1 (p. 168). Simply put, precedence stipulates how operands are grouped in expressions with multiple operators. The dereference operator has a higher precedence than the addition operator.

          2. Subscripts and Pointers

            1. We have already seen that when we use an array name in an expression, we are actually using a pointer to the first element in the array. This fact has a number of implications, which we shall point out as they arise.
            2. One important implication is that when we subscript an array, we are really subscripting a pointer:
            3. When we write ia[0], that is an expression that uses the name of an array. When we subscript an array, we are really subscripting a pointer to an element in that array. We can use the subscript operator on any pointer, as long as that pointer points to an element in an array:
                	int ia2[] = {0,2,4,6,8};
                int i = ia2[0]; // ia points to the first element in ia
                int *p = &ia2[2]; // ok: p points to the element indexed by 2
                int j = p[1]; // ok: p[1] equivalent to *(p + 1),
                // p[1] is the same element as ia[3]
                int k = p[-2]; // ok: p[-2] is the same element as ia[0]

          3. Computing an Off-the-End Pointer

            1. When we use a vector, the end operation returns an iterator that refers just past the end of the vector. We often use this iterator as a sentinel to control loops that process the elements in the vector. Similarly, we can compute an off-the-end pointer value:
                	const size_t arr_size = 5;
                int arr[arr_size] = {1,2,3,4,5};
                int *p = arr; // ok: p points to arr[0]
                int *p2 = p + arr_size; // ok: p2 points one past the end of arr
                // use caution -- do not dereference!

            2. NOTE:It is legal to compute an address one past the end of an array or object. It is not legal to dereference a pointer that holds such an address. Nor is it legal to compute an address more than one past the end of an array or an address before the beginning of an array.

          4. Printing the Elements of an Array
              	const size_t arr_sz = 5;
              int int_arr[arr_sz] = { 0, 1, 2, 3, 4 };
              // pbegin points to first element, pend points just after the last
              for (int *pbegin = int_arr, *pend = int_arr + arr_sz;
              pbegin != pend; ++pbegin)
              cout << *pbegin << endl; // print the current element

          5. Pointers Are Iterators for Arrays

            1. This similarity is not a coincidence. In fact, the built-in array type has many of the properties of a library container, and pointers, when we use them in conjunction with arrays, are themselves iterators. We'll have much more to say about containers and iterators in Part II.

        5. 4.2.5. Pointers and the const Qualifier

          1. Pointers to const Objects

            1. The pointers we've seen so far can be used to change the value of the objects to which they point. But if we have a pointer to a const object, we do not want to allow that pointer to change the underlying, const value. The language enforces this property by requiring that pointers to const objects must take the constness of their target into account:
            2. We cannot use a void* pointer (Section 4.2.2, p. 119) to hold the address of a const object. Instead, we must use the type const void* to hold the address of a const object:
            3. A pointer to a const object can be assigned the address of a nonconst object, such as
            4. NOTE:We cannot use a pointer to const to change the underlying object. However, if the pointer addresses a nonconst object, it is possible that some other action will change the object to which the pointer points.
            5. The fact that values to which a const pointer points can be changed is subtle and can be confusing.
            6. It is important to remember that there is no guarantee that an object pointed to by a pointer to const won't change.
            7. TIPS:It may be helpful to think of pointers to const as "pointers that think they point to const."
            8. In real-world programs, pointers to const occur most often as formal parameters of functions. Defining a parameter as a pointer to const serves as a contract guaranteeing that the actual object being passed into the function will not be modified through that parameter.
                	const int universe = 42;
                const void *cpv = &universe; // ok: cpv is const
                //void *pv1 = &universe; // error: universe is const ;error C2440: 'initializing' : cannot convert from 'const int *' to 'void *'
                /*
                Although dval is not a const, any attempt to modify its value through cptr results in a compile-time error. When we declared cptr, we said that it would not change the value to which it points. The fact that it happens to point to a nonconst object is irrelevant.
                */

                double dval1 = 3.14; // dval is a double; its value can be changed
                cptr = &dval1; // ok: but can't change dval through cptr

                dval1 = 3.14159; // dval is not const
                //*cptr = 3.14159; // error: cptr is a pointer to const
                double *ptr = &dval1; // ok: ptr points at non-const double
                *ptr = 2.72; // ok: ptr is plain pointer
                cout << *cptr; // ok: prints 2.72

          2. const Pointers

            1. In addition to pointers to const, we can also have const pointersthat is, pointers whose own value we may not change:
            2. Reading this definition from right to left, we see that "curErr is a constant pointer to an object of type int." As with any const, we may not change the value of the pointer that is, we may not make it point to any other object. Any attempt to assign to a constant pointer even assigning the same value back to curErr is flagged as an error during compilation:
            3. As with any const, we must initialize a const pointer when we create it.
            4. The fact that a pointer is itself const says nothing about whether we can use the pointer to change the value to which it points. Whether we can change the value pointed to depends entirely on the type to which the pointer points.
            5. 	int errNumb = 0;
              int *const curErr = &errNumb; // curErr is a constant pointer
              //error C3892: 'curErr' : you cannot assign to a variable that is const
              //curErr = curErr; // error: curErr is const
              if (*curErr) {
              //errorHandler();
              *curErr = 0; // ok: reset value of the object to which curErr is bound
              }

          3. const Pointer to a const Object

            1. In this case, neither the value of the object addressed by pi_ptr nor the address itself can be changed. We can read its definition from right to left as "pi_ptr is a constant pointer to an object of type double defined as const."
              	const double pi10 = 3.14159;
              // pi_ptr is const and points to a const object
              const double *const pi_ptr = &pi10;

          4. Pointers and Typedefs

            1. The use of pointers in typedefs (Section 2.6, p. 61) often leads to surprising results. Here is a question almost everyone answers incorrectly at least once.
            2. The mistake is in thinking of a typedef as a textual expansion. When we declare a const pstring, the const modifies the type of pstring, which is a pointer. Therefore, this definition declares cstr to be a const pointer to string.
              	typedef string *pstring;
              const pstring cstr;
              const string *cstr; // wrong interpretation of const pstring cstr
              // cstr is a const pointer to string
              string *const cstr; // equivalent to const pstring cstr

          5. Advice: Understanding Complicated const Type Declarations

            1. Part of the problem in reading const declarations arises because the const can go either before or after the type:
            2. When writing const definitions using typedefs, the fact that the const can precede the type can lead to confusion as to the actual type being defined:
              	string const s1;   // s1 and s2 have same type,
              const string s2; // they're both strings that are const

              string s;
              typedef string *pstring;
              const pstring cstr1 = &s; // written this way the type is obscured
              pstring const cstr2 = &s; // all three decreations are the same type
              string *const cstr3 = &s; // they're all const pointers to string

            3. Putting the const after pstring and reading the declaration from right to left makes it clearer that cstr2 is a const pstring, which in turn is a const pointer to string.
            4. Unfortunately, most readers of C++ programs expect to see the const before the type. As a result, it is probably a good idea to put the const first, respecting common practice. But it can be helpful in understanding declarations to rewrite them to put the const after the type.

          // EXE-4.2.cpp : Defines the entry point for the console application.
          #include "stdafx.h"
          #include <iostream>
          #include <vector>
          #include <string>
          using namespace std;
          int main()
          {
          string s("hello world\n");
          string *sp = &s; // sp holds the address of s
          cout << *sp;
          *sp = "goodbye"; // contents of s now changed
          cout << *sp << endl;
          string s2 = "some value";
          sp = &s2; // sp now points to s2
          cout << *sp << endl;

          vector<int> *pvec; // pvec can point to a vector<int>
          int *ip1, *ip2; // ip1 and ip2 can point to an int
          string *pstring; // pstring can point to a string
          double *dp; // dp can point to a double
          double dp1, *dp2; // dp2 is a ponter, dp is an object: both type double

          string* ps; // legal but can be misleading

          string* ps1, ps2; // ps1 is a pointer to string, ps2 is a string

          string* ps3, *ps4; // both ps3 and ps4 are pointers to string

          int ival = 10241;
          int *pi = 0; // pi initialized to address no object
          int *pi2 = & ival; // pi2 initialized to address of ival
          int *pi3; // ok, but dangerous, pi3 is uninitialized
          pi = pi2; // pi and pi2 address the same object, e.g. ival
          pi2 = 0; // pi2 now addresses no object

          int ival1;
          int zero = 0;
          const int c_ival = 0;
          int *pi1 = 0;
          //int *pi1 = ival1; // error: pi initialized from int value of ival
          //pi1 = zero; // error: pi assigned int value of zero
          pi1 = c_ival; // ok: c_ival is a const with compile-time value of 0
          pi1 = 0; // ok: directly initialize to literal constant 0

          // cstdlib #defines NULL to 0
          int *pi4 = NULL; // ok: equivalent to int *pi = 0;

          double dval;
          double *pd = &dval; // ok: initializer is address of a double
          double *pd2 = pd; // ok: initializer is a pointer to double

          //int *pi5 = pd; // error: types of pi and pd differ
          //pi5 = &dval; // error: attempt to assign address of a double to int *

          double obj = 3.14;
          double *pd1 = &obj;
          // ok: void* can hold the address value of any data pointer type
          void *pv = &obj; // obj can be an object of any type
          pv = pd1; // pd can be a pointer to any type

          //string s("hello world");
          //string *sp = &s; // sp holds the address of s
          //cout <<*sp; // prints hello world

          int ival3 = 1024, ival4 = 2048;
          int *pi5 = &ival3, *pi6 = &ival4;
          pi5 = pi6; // pi now points to ival2
          int &ri = ival3, &ri2 = ival4;
          ri = ri2; // assigns ival2 to ival

          int ival5 = 1024;
          int *pi7 = &ival5; // pi points to an int
          int **ppi = &pi7; // ppi points to a pointer to int
          int *pi8 = *ppi; // ppi points to a pointer
          cout << "The value of ival\n"
          << "direct value: " << ival5 << "\n"
          << "indirect value: " << *pi7 << "\n"
          << "doubly indirect value: " << **ppi << " or " << *pi8
          << endl;

          int ia[] = {0,2,4,6,8};
          int *ip = ia; // ip points to ia[0]
          ip = &ia[4]; // ip points to last element in ia
          //ip = ia; // ok: ip points to ia[0]
          int *ip3 = ip + 4; // ok: ip2 points to ia[4], the last element in ia
          ptrdiff_t n = ip3 - ip; // ok: distance between the pointers

          int ia1[4];
          // error: ia has only 4 elements, ia + 10 is an invalid address
          int *ip4 = ia1 + 10; // ip4 is ilegal pointer, but will not cause compile error

          int last = *(ia + 4); // ok: initializes last to 8, the value of ia[4]
          last = *ia + 4; // ok: last = 4, equivalent to ia[0]+4

          int ia2[] = {0,2,4,6,8};
          int i = ia2[0]; // ia points to the first element in ia
          int *p = &ia2[2]; // ok: p points to the element indexed by 2
          int j = p[1]; // ok: p[1] equivalent to *(p + 1),
          // p[1] is the same element as ia[3]
          int k = p[-2]; // ok: p[-2] is the same element as ia[0]

          const size_t arr_size = 5;
          int arr[arr_size] = {1,2,3,4,5};
          int *p3 = arr; // ok: p points to arr[0]
          int *p4 = p3 + arr_size; // ok: p2 points one past the end of arr
          // use caution -- do not dereference!

          const size_t arr_sz = 5;
          int int_arr[arr_sz] = { 0, 1, 2, 3, 4 };
          // pbegin points to first element, pend points just after the last
          for (int *pbegin = int_arr, *pend = int_arr + arr_sz;
          pbegin != pend; ++pbegin)
          cout << *pbegin << endl; // print the current element

          const double *cptr1; // cptr may point to a double that is const
          //*cptr = 42; // error: *cptr might be const
          const double pi9 = 3.14;
          //double *ptr = &pi9; // error: ptr is a plain pointer
          //*cptr1 = &pi9; //error C2440: '=' : cannot convert from 'const double *' to 'const double'
          const double *cptr = &pi9; // ok: cptr is a pointer to const

          const int universe = 42;
          const void *cpv = &universe; // ok: cpv is const
          //void *pv1 = &universe; // error: universe is const ;error C2440: 'initializing' : cannot convert from 'const int *' to 'void *'
          /*
          Although dval is not a const, any attempt to modify its value through cptr results in a compile-time error. When we declared cptr, we said that it would not change the value to which it points. The fact that it happens to point to a nonconst object is irrelevant.
          */

          double dval1 = 3.14; // dval is a double; its value can be changed
          cptr = &dval1; // ok: but can't change dval through cptr

          dval1 = 3.14159; // dval is not const
          //*cptr = 3.14159; // error: cptr is a pointer to const
          double *ptr = &dval1; // ok: ptr points at non-const double
          *ptr = 2.72; // ok: ptr is plain pointer
          cout << *cptr; // ok: prints 2.72

          int errNumb = 0;
          int *const curErr = &errNumb; // curErr is a constant pointer
          //error C3892: 'curErr' : you cannot assign to a variable that is const
          //curErr = curErr; // error: curErr is const
          if (*curErr) {
          //errorHandler();
          *curErr = 0; // ok: reset value of the object to which curErr is bound
          }

          const double pi10 = 3.14159;
          // pi_ptr is const and points to a const object
          const double *const pi_ptr = &pi10;

          //typedef string *pstring;
          //const pstring cstr1; //error C2065: 'cstr1' : undeclared identifier
          const string *cstr2; // wrong interpretation of const pstring cstr
          // cstr is a const pointer to string
          //string *const cstr3; // equivalent to const pstring cstr //error C2734: 'cstr3' : const object must be initialized if not extern

          string const s11; // s1 and s2 have same type,
          const string s9; // they're both strings that are const

          string s10;
          //typedef string *pstring;
          //const pstring cstr4 = &s; // written this way the type is obscured //error C2065: 'cstr1' : undeclared identifier
          //pstring const cstr5 = &s10; // all three decreations are the same type //IntelliSense: const variable "cstr" requires an initializer
          string *const cstr6 = &s10; // they're all const pointers to string

          return 0;
          }

      4. Section 4.3.  C-Style Character Strings

        1. BEWARE:Although C++ supports C-style strings, they should not be used by C++ programs. C-style strings are a surprisingly rich source of bugs and are the root cause of many, many security problems
        2. In Section 2.2 (p. 40) we first used string literals and learned that the type of a string literal is array of constant characters. We can now be more explicit and note that the type of a string literal is an array of const char. A string literal is an instance of a more general construct that C++ inherits from C: C-style character strings. C-style strings are not actually a type in either C or C++. Instead, C-style strings are null-terminated arrays of characters
        3. Using C-style Strings

          1. C-style strings are manipulated through (const) char* pointers. One frequent usage pattern uses pointer arithmetic to traverse the C-style string. The traversal tests and increments the pointer until we reach the terminating null character:
            	const char *cp = "some value";
            while (*cp) {
            // do something to *cp
            ++cp;
            }

          2. BEWARE:This loop will fail if the array that cp addresses is not null-terminated. If this case, the loop is apt to read characters starting at cp until it encounters a null character somewhere in memory.

        4. C Library String Functions

          1. The Standard C library provides a set of functions, listed in Table 4.1, that operate on C-style strings. To use these functions, we must include the associated C header file
          2. #include <cstring> which is the C++ version of the string.h header from the C library.
          3. Table 4.1. C-Style Character String Functions
          4. Beware:These functions do no checking on their string parameters.
          5. The pointer(s) passed to these routines must be nonzero and each pointer must point to the initial character in a null-terminated array.
          6. These functions assume that the array to which they write is large enough to hold whatever characters the function generates. It is up to the programmer to ensure that the target string is big enough.
          7. When we compare library strings, we do so using the normal relational operators. We can use these operators to compare pointers to C-style strings, but the effect is quite different; what we're actually comparing is the pointer values, not the strings to which they point:
            if (cp1 < cp2) // compares addresses, not the values pointed to

          8. Assuming cp1 and cp2 point to elements in the same array (or one past that array), then the effect of this comparison is to compare the address in cp1 with the address in cp2. If the pointers do not address the same array, then the comparison is undefined.
          9. The strcmp function returns three possible values: 0 if the strings are equal; or a positive or negative value, depending on whether the first string is larger or smaller than the second.

        5. Never Forget About the Null-Terminator

          1. When using the C library string functions it is essential to remember the strings must be null-terminated:
            	char ca[] = {'C', '+', '+'}; // not null-terminated
            cout << strlen(ca) << endl; // disaster: ca isn't null-terminated

          2. In this case, ca is an array of characters but is not null-terminated. What happens is undefined. The strlen function assumes that it can rely on finding a null character at the end of its argument. The most likely effect of this call is that strlen will keep looking through the memory that follows wherever ca happens to reside until it encounters a null character. In any event, the return from strlen will not be the correct value.

        6. Caller Is Responsible for Size of a Destination String

          1. The array that we pass as the first argument to strcat and strcpy must be large enough to hold the generated string.

        7. When Using C-Style Strings, Use the strn Functions

          1. If you must use C-style strings, it is usually safer to use the strncat and strncpy functions instead of strcat and strcpy:
          2. The trick to using these versions is to properly calculate the value to control how many characters get copied. In particular, we must always remember to account for the null when copying or concatenating characters. We must allocate space for the null because that is the character that terminates largeStr after each call. Let's walk through these calls in detail:
          3. These operations are safer than the simpler versions that do not take a size argument as long as we calculate the size argument correctly. If we ask to copy or concatenate more characters than the size of the target array, we will still overrun that array. If the string we're copying from or concatenating is bigger than the requested size, then we'll inadvertently truncate the new version. Truncating is safer than overrunning the array, but it is still an error.

        8. Whenever Possible, Use Library strings

          1. None of these issues matter if we use C++ library strings:
            	string largeStr2 = cp1; // initialize large Str as a copy of cp1
            //cout << largeStr2 << endl;
            largeStr2 += " "; // add space at end of largeStr
            //cout << largeStr2 << endl;
            largeStr2 += cp2; // concatenate cp2 onto end of largeStr
            //cout << largeStr2 << endl;

          2. Now the library handles all memory management, and we need no longer worry if the size of either string changes.
          3. NOTE:For most applications, in addition to being safer, it is also more efficient to use library strings rather than C-style strings.

        9. 4.3.1. Dynamically Allocating Arrays

          1. A variable of array type has three important limitations: Its size is fixed, the size must be known at compile time, and the array exists only until the end of the block in which it was defined. Real-world programs usually cannot live with these restrictions they need a way to allocate an array dynamically at run time. Although all arrays have fixed size, the size of a dynamically allocated array need not be fixed at compile time. It can be (and usually is) determined at run time. Unlike an array variable, a dynamically allocated array continues to exist until it is explicitly freed by the program
          2. Every program has a pool of available memory it can use during program execution to hold dynamically allocated objects. This pool of available memory is referred to as the program's free store or heap. C programs use a pair of functions named malloc and free to allocate space from the free store. In C++ we use new and delete expressions.
          3. Defining a Dynamic Array

            1. When we define an array variable, we specify a type, a name, and a dimension. When we dynamically allocate an array, we specify the type and size but do not name the object. Instead, the new expression returns a pointer to the first element in the newly allocated array
              int *pia = new int[10]; // array of 10 uninitialized ints

            2. This new expression allocates an array of ten ints and returns a pointer to the first element in that array, which we use to initialize pia.
            3. A new expression takes a type and optionally an array dimension specified inside a bracket-pair. The dimension can be an arbitrarily complex expression. When we allocate an array, new returns a pointer to the first element in the array. Objects allocated on the free store are unnamed. We use objects on the heap only indirectly through their address.

          4. Initializing a Dynamically Allocated Array

            1. When we allocate an array of objects of a class type, then that type's default constructor (Section 2.3.4, p. 50) is used to initialize each element. If the array holds elements of built-in type, then the elements are uninitialized
            2. Alternatively, we can value-initialize (Section 3.3.1, p. 92) the elements by following the array size by an empty pair of parentheses
            3. The parentheses are effectively a request to the compiler to value-initialize the array, which in this case sets its elements to 0.
            4. NOTE:The elements of a dynamically allocated array can be initialized only to the default value of the element type. The elements cannot be initialized to separate values as can be done for elements of an array variable

          5. Dynamic Arrays of const Objects

            1. If we create an array of const objects of built-in type on the free store, we must initialize that array: The elements are const, there is no way to assign values to the elements. The only way to initialize the elements is to value-initialize the array
            2. It is possible to have a const array of elements of a class type that provides a default constructor:
            3. Of course, once the elements are created, they may not be changedwhich means that such arrays usually are not very useful.

          6. It Is Legal to Dynamically Allocate an Empty Array

            1. When we dynamically allocate an array, we often do so because we don't know the size of the array at compile time. We might write code such as : to figure out the size of the array and then allocate and process the array.
              	size_t n = get_size(); // get_size returns number of elements needed
              int* p = new int[n];
              for (int* q = p; q != p + n; ++q)
              /* process the array */ ;

            2. An interesting question is: What happens if get_size returns 0? The answer is that our code works fine. The language specifies that a call to new to create an array of size zero is legal. It is legal even though we could not create an array variable of size 0;When we use new to allocate an array of zero size, new returns a valid, nonzero pointer. This pointer will be distinct from any other pointer returned by new. The pointer cannot be dereferencedafter all, it points to no element. The pointer can be compared and so can be used in a loop such as the preceeding one. It is also legal to add (or subtract) zero to such a pointer and to subtract the pointer from itself, yielding zero

          7. Freeing Dynamic Memory

            1. When we allocate memory, we must eventually free it. Otherwise, memory is gradually used up and may be exhausted. When we no longer need the array, we must explicitly return its memory to the free store. We do so by applying the delete [] expression to a pointer that addresses the array we want to release:
              	delete [] p;

            2. deallocates the array pointed to by pia, returning the associated memory to the free store. The empty bracket pair between the delete keyword and the pointer is necessary: It indicates to the compiler that the pointer addresses an array of elements on the free store and not simply a single object
            3. BEWARE:If the empty bracket pair is omitted, it is an error, but an error that the compiler is unlikely to catch; the program may fail at run time.
            4. The least serious run-time consequence of omitting brackets when freeing an array is that too little memory will be freed, leading to a memory leak. On some systems and/or for some element types, more serious run-time problems are possible. It is essential to remember the bracket-pair when deleting pointers to arrays.

          8. Contrasting C-Style Strings and C++ Library strings

            1. The following two programs illustrate the differences in using C-style character strings versus using the C++ library string type. The string version is shorter, easier to understand, and less error-prone:

          9. Using Dynamically Allocated Arrays

            1. A common reason to allocate an array dynamically is if its dimension cannot be known at compile time. For example, char* pointers are often used to refer to multiple C-style strings during the execution of a program. The memory used to hold the various strings typically is allocated dynamically during program execution based on the length of the string to be stored. This technique is considerably safer than allocating a fixed-size array. Assuming we correctly calculate the size needed at run time, we no longer need to worry that a given string will overflow the fixed size of an array variable.
            2. Recall that strlen returns the length of the string not including the null. It is essential to remember to add 1 to the length returned from strlen to accommodate the trailing null.

        10. 4.3.2. Interfacing to Older Code

          1. Many C++ programs exist that predate the standard library and so do not yet use the string and vector types. Moreover, many C++ programs interface to existing C programs that cannot use the C++ library. Hence, it is not infrequent to encounter situations where a program written in modern C++ must interface to code that uses arrays and/or C-style character strings. The library offers facilities to make the interface easier to manage.
          2. Mixing Library strings and C-Style Strings

            1. More generally, because a C-style string has the same type as a string literal and is null-terminated in the same way, we can use a C-style string anywhere that a string literal can be used:

              1. We can initialize or assign to a string from a C-style string.
              2. We can use a C-style string as one of the two operands to the string addition or as the right-hand operand to the compound assignment operators.

            2. The reverse functionality is not provided: there is no direct way to use a library string when a C-style string is required.
            3. There is, however, a string member function named c_str that we can often use to accomplish what we want:
              char *str = st2; // compile-time type error
              char *str = st2.c_str(); // almost ok, but not quite
              const char *str = st2.c_str(); // ok

            4. The name c_str indicates that the function returns a C-style character string. Literally, it says, "Get me the C-style string representation"that is, a pointer to the beginning of a null-terminated character array that holds the same data as the characters in the string.
            5. NOTE:The array returned by c_str is not guaranteed to be valid indefinitely. Any subsequent use of st2 that might change the value of st2 can invalidate the array. If a program needs continuing access to the data, then the program must copy the array returned by c_str.

          3. Using an Array to Initialize a vector

            1. On page 112 we noted that it is not possible to initialize an array from another array. Instead, we have to create the array and then explicitly copy the elements from one array into the other. It turns out that we can use an array to initialize a vector, although the form of the initialization may seem strange at first. To initialize a vector from an array, we specify the address of the first element and one past the last element that we wish to use as initializers:

          // EXE-4.3.cpp : Defines the entry point for the console application.
          //

          #include "stdafx.h"
          #include <iostream>
          //#include <cstring>
          #include <vector>
          #include <string>
          using namespace std;
          int get_size();

          int main()
          {
          char ca1[] = {'C', '+', '+'}; // no null, not C-style string
          char ca2[] = {'C', '+', '+', '\0'}; // explicit null
          char ca3[] = "C++"; // null terminator added automatically
          const char *cp = "C++"; // null terminator added automatically
          char *cp1 = ca1; // points to first element of a array, but not C-style string
          char *cp2 = ca2; // points to first element of a null-terminated char array

          const char *cp0 = "some value";
          while (*cp0) {
          // do something to *cp
          cout << cp0 << endl;
          ++cp0;
          }

          if (cp1 < cp2) // compares addresses, not the values pointed to
          cout << "cp1 < cp2" << endl;
          else
          {
          cout << "cp1 < cp2;" << "cp1:" << cp1 << ";cp2:" << cp2 << endl;
          }
          cp1 = cp2;
          if(cp1 < ++cp2)
          {
          cout << "cp1 < cp2;" << "cp1:" << cp1 << ";cp2:" << cp2 << endl;
          }
          else
          {
          cout << "cp1 > cp2;" << "cp1:" << cp1 << ";cp2:" << cp2 << endl;
          }
          //To compare the strings, we must use strcmp and interpret the result:

          const char *cp3 = "A string example";
          const char *cp4 = "A different string";
          int i = strcmp(cp3, cp4); // i is positive
          cout << i <<endl;
          i = strcmp(cp4, cp3); // i is negative
          cout << i <<endl;
          i = strcmp(cp3, cp3); // i is zero
          cout << i <<endl;

          char ca[] = {'C', '+', '+'}; // not null-terminated
          cout << strlen(ca) << endl; // disaster: ca isn't null-terminated

          // Dangerous: What happens if we miscalculate the size of largeStr?
          char largeStr0[16 + 18 + 2]; // will hold cp1 a space and cp2
          cout << strlen(largeStr0) << ":" << largeStr0 << endl;
          strcpy(largeStr0, cp1); // copies cp1 into largeStr
          cout << strlen(largeStr0) << ":" << largeStr0 << endl;
          strcat(largeStr0, " "); // adds a space at end of largeStr
          cout << strlen(largeStr0) << ":" << largeStr0 << endl;
          strcat(largeStr0, cp2); // concatenates cp2 to largeStr
          // prints A string example A different string
          cout << strlen(largeStr0) << ":" << largeStr0 << endl;

          cout << "using strncpy..."<< endl;
          char largeStr1[16 + 18 + 2]; // to hold cp1 a space and cp2
          char *plargeStr = largeStr1;
          //why the string of largeStr is 56 char length.
          //
          cout << strlen(largeStr1) << ":" << largeStr1 << endl;
          strncpy(largeStr1, cp1, 17); // size to copy includes the null
          cout << strlen(largeStr1) << ":" << largeStr1 << endl;
          strncat(largeStr1, " ", 2); // pedantic, but a good habit
          cout << strlen(largeStr1) << ":" << largeStr1 << endl;
          strncat(largeStr1, cp2, 19); // adds at most 18 characters, plus a null
          cout << strlen(largeStr1) << ":" << largeStr1 << endl;


          string largeStr2 = cp1; // initialize large Str as a copy of cp1
          //cout << largeStr2 << endl;
          largeStr2 += " "; // add space at end of largeStr
          //cout << largeStr2 << endl;
          largeStr2 += cp2; // concatenate cp2 onto end of largeStr
          //cout << largeStr2 << endl;


          string *psa = new string[10]; // array of 10 empty strings
          int *pia = new int[10]; // array of 10 uninitialized ints
          cout << "int *pia = new int[10];" << endl;
          // Exception on runtime.

          for ( int * p = pia; p != pia + 10; ++p)
          {
          cout << *p << endl;
          }
          for ( int i = 0; i != 10; ++i)
          {
          cout << *pia++ << endl;
          }

          int *pia2 = new int[10] (); // array of 10 uninitialized ints
          cout << "int *pia2 = new int[10] ();" << endl;
          for (int *p = pia2 ; p != pia2 + 10; ++p)
          {
          cout << *p << endl;
          }
          for ( int i = 0; i != 10; ++i)
          {
          cout << *pia2++ << endl;
          }
          cout << "string *psa = new string[10];" << endl;
          for ( string *p = psa; p != psa + 10; ++p)
          {
          cout << *p << endl;
          }

          // error: uninitialized const array
          // actually no error on compile and runtime;
          const int *pci_bad = new const int[100];
          // ok: value-initialized const array
          const int *pci_ok = new const int[100]();

          // ok: array of 100 empty strings
          //In this case, the default constructor is used to initialize the elements of the array.
          const string *pcs = new const string[100];

          size_t n = get_size(); // get_size returns number of elements needed
          int* p = new int[n];
          for (int* q = p; q != p + n; ++q)
          {
          /* process the array */ ;
          cout << "... ... " << endl;
          }
          ///If the empty bracket pair is omitted, it is an error, but an error that the compiler is unlikely to catch; the program may fail at run time.
          // but actually I got no error here
          delete p;
          //delete [] p;



          //// C-style character string implementation
          //const char *pc = "a very long literal string";
          //const size_t len = strlen(pc +1); // space to
          ////allocate
          // // performance test on string allocation and copy
          // for (size_t ix = 0; ix != 1000000; ++ix) {
          // char *pc2 = new char[len + 1]; // allocate the space
          // strcpy(pc2, pc); // do the copy
          // if (strcmp(pc2, pc)) // use the new string
          // ; // do nothing
          // delete [] pc2; // free the memory
          // }
          // // string implementation
          // string str("a very long literal string");
          // // performance test on string allocation and copy
          // for (int ix = 0; ix != 1000000; ++ix) {
          // string str2 = str; // do the copy, automatically
          // //allocated
          // if (str != str2) // use the new string
          // ; // do nothing
          // }
          // // str2 is
          // //automatically freed

          string st2("Hello World"); // st3 holds Hello World
          //error C2440: 'initializing' : cannot convert from 'const char *' to 'char *'
          //char *str1 = st2; // compile-time type error
          //error C2440: 'initializing' : cannot convert from 'const char *' to 'char *'
          //char *str = st2.c_str(); // almost ok, but not quite
          const char *str = st2.c_str(); // ok
          cout << str << endl;

          const size_t arr_size = 6;
          int int_arr[arr_size] = {0, 1, 2, 3, 4, 5};
          // ivec has 6 elements: each a copy of the corresponding element in int_arr
          vector<int> ivec(int_arr, int_arr + arr_size);
          for (vector<int>::iterator ite = ivec.begin(); ite != ivec.end(); ite++)
          {
          cout << *ite << endl;
          }
          // copies 3 elements: int_arr[1], int_arr[2], int_arr[3]
          vector<int> ivec2(int_arr + 1, int_arr + 4);
          for (vector<int>::iterator ite = ivec2.begin(); ite != ivec2.end(); ite++)
          {
          cout << *ite << endl;
          }

          return 0;
          }

          int get_size()
          {
          // error to locate the array with the size
          //return 0xFFFFFFFE;
          //return 0xFFFF;
          return 3;
          }

      5. Section 4.4.  Multidimensioned Arrays


        1. NOTE: Strictly speaking, there are no multidimensioned arrays in C++. What is commonly referred to as a multidimensioned array is actually an array of arrays:
        2. Initializing the Elements of a Multidimensioned Array


          1. As with any array, we can initialize the elements by providing a bracketed list of initializers. Multidimensioned arrays may be initialized by specifying bracketed values for each row:

        3. Subscripting a Multidimensioned Array


          1. Indexing a multidimensioned array requires a subscript for each dimension.
          2. When we want to access a particular element of the array, we must supply both a row and column index. The row index specifies which of the inner arrays we intend to access. The column index selects an element from that inner array. Remembering this fact can help in calculating proper subscript values and in understanding how multidimensioned arrays are initialized.
          3. If an expression provides only a single index, then the result is the inner-array element at that row index. Thus, ia[2] fetches the array that is the last row in ia. It does not fetch any element from that array; it fetches the array itself.

        4. 4.4.1. Pointers and Multidimensioned Arrays


          1. As with any array, when we use the name of a multidimensioned array, it is automatically converted to a pointer to the first element in the array.
          2. NOTE:When defining a pointer to a multidimensioned array, it is essential to remember that what we refer to as a multidimensioned array is really an array of arrays.
          3. Because a multidimensioned array is really an array of arrays, the pointer type to which the array converts is a pointer to the first inner array. Although conceptually straightforward, the syntax for declaring such a pointer can be confusing:
          4. NOTE:The parentheses in this declaration are essential:
          5. 	int *ip1[4]; // array of pointers to int
            int (*ip2)[4]; // pointer to an array of 4 ints

          6. Typedefs Simplify Pointers to Multidimensioned Arrays


            1. Typedefs (Section 2.6, p. 61) can help make pointers to elements in multidimensioned arrays easier to write, read, and understand.


        // EXE-4.4.cpp : Defines the entry point for the console application.
        #include "stdafx.h"
        #include <iostream>
        #include <string>
        using namespace std;

        int main()
        {
        // array of size 3, each element is an array of ints of size 4
        int ia0[3][4];
        cout << "int ia0[3][4];" << endl;
        for (int i = 0; i != 3; ++i)
        {
        cout << i << " row :" ;
        for (int j = 0; j!= 4; ++j)
        {
        cout << " " << ia0[i][j];
        }
        cout << endl;
        }
        int ia1[3][4] = { /* 3 elements, each element is an array of size 4 */
        {0, 1, 2, 3} , /* initializers for row indexed by 0 */
        {4, 5, 6, 7} , /* initializers for row indexed by 1 */
        {8, 9, 10, 11} /* initializers for row indexed by 2 */
        };
        cout << "int ia1[3][4]:" << endl;
        for (int i = 0; i != 3; ++i)
        {
        cout << i << " row :" ;
        for (int j = 0; j!= 4; ++j)
        {
        cout << " " << ia1[i][j];
        }
        cout << endl;
        }
        // equivalent initialization without the optional nested braces for each row
        int ia2[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};
        cout << "int ia2[3][4] = {0,1,2,3,4,5,6,7,8,9,10,11};" << endl;
        for (int i = 0; i != 3; ++i)
        {
        cout << i << " row :" ;
        for (int j = 0; j!= 4; ++j)
        {
        cout << " " << ia2[i][j];
        }
        cout << endl;
        }
        // explicitly initialize only element 0 in each row
        //As is the case for single-dimension arrays, elements may be left out of the initializer list. We could initialize only the first element of each row as follows:
        int ia3[3][4] = {{ 0 } , { 4 } , { 8 } };
        cout << "int ia3[3][4] = {{ 0 } , { 4 } , { 8 } };" << endl;
        for (int i = 0; i != 3; ++i)
        {
        cout << i << " row :" ;
        for (int j = 0; j!= 4; ++j)
        {
        cout << " " << ia3[i][j];
        }
        cout << endl;
        }
        // explicitly initialize row 0
        //initializes the elements of the first row. The remaining elements are initialized to 0.
        int ia4[3][4] = {0, 3, 6, 9};
        cout << "int ia4[3][4] = {0, 3, 6, 9};" << endl;
        for (int i = 0; i != 3; ++i)
        {
        cout << i << " row :" ;
        for (int j = 0; j!= 4; ++j)
        {
        cout << " " << ia4[i][j];
        }
        cout << endl;
        }

        const size_t rowSize = 3;
        const size_t colSize = 4;
        int ia5 [rowSize][colSize]; // 12 uninitialized elements
        // for each row
        for (size_t i = 0; i != rowSize; ++i)
        // for each column within the row
        for (size_t j = 0; j != colSize; ++j)
        // initialize to its positional index
        ia5[i][j] = i * colSize + j;
        cout << "int ia5 [rowSize][colSize]; " << endl;
        for (int i = 0; i != rowSize; ++i)
        {
        cout << i << " row :" ;
        for (int j = 0; j!= colSize; ++j)
        {
        cout << " " << ia5[i][j];
        }
        cout << endl;
        }

        int ia6[3][4]; // array of size 3, each element is an array of ints of size 4
        int (*ip0)[4] = ia6; // ip points to an array of 4 ints
        ip0 = &ia6[2]; // ia[2] is an array of 4 ints

        int *ip1[4]; // array of pointers to int
        int (*ip2)[4]; // pointer to an array of 4 ints

        typedef int int_array[4];
        int_array *ip3 = ia2;

        cout << "int_array *ip3 = ia2;" << endl;
        int index(0);
        for (int_array *p = ia2; p != ia2 + 3; ++p)
        {
        cout << index << " row :" ;
        for (int *q = *p; q != *p + 4; ++q)
        {
        cout << " " << *q ;
        }
        cout << endl;
        }

        return 0;
        }