The chapter begins with a recap of an earlier lesson: there are three kinds of C++ data types. All data types are either simple, structured, or pointer data types.
Students usually have difficulty with the concept of pointers. A pointer is a variable used to store an address.
Pointers are declared using the format
int * ptr;
where ptr points to type int. Yes, kids, this is yet another use of the asterisk. This notation is flexible. Your text explains several features of it:
To assign an address of a variable called pooh to a pointer variable called ptr, use the format
ptr type = &pooh;
This assumes that ptr is a pointer, and that is was declared to point to the type of variable that pooh is. (A careless programmer could get in trouble here.) This also introduces the use of the ampersand, the address of operator. The line above assigns the address of the pooh variable to the ptr
To find the value stored in an address using the indirection operator. The what? Well, the indirection operator is the asterisk again. This time, it is used to indirectly access the contents of a variable. In the example so far, ptr points to the variable pooh. We use the indirection operator to read the value at the location that ptr points to, and assign it to the variable piglet:
int piglet; piglet = *ptr;
ptr = &pooh; piglet = *ptr;
means the same as
piglet = pooh;
Pointers can be used to pass the address of variables to functions. The function call:
interchange( &x, &y);
passes the addresses of two variables to a function called interchange. That function might be defined as
void interchange(int * u, int * v)
where u and v both point to type int. Note that the variables in the called function are different from the ones in the calling function. Since the variables are local to each function, the two sets could even have had the same names, and they would still be different variables. (Yes, you can take an aspirin now. I know I did.)
The text goes on to describe pointers to classes. If we can declare a variable that is an instance of a class (or a struct), we can also declare a variable that is a pointer to such a variable.
Assume a struct called studentType. We can create an object of this type and a pointer to that type as well:
Once again, note the three critical steps: we declared a variable, we declared a pointer to the variable's type, and we assigned the variable's address to the pointer.
The notation gets a little odd when we use the pointer to make a change to a member of the struct:
The parentheses in the example above are necessary. The text explains that the dot operator has a higher precedence than the dereferencing operator (the asterisk), so we must use parentheses in this notation. Once again, in English: we need the computer to read that line and understand that we mean "the struct at the address that studentPtr points to", and its member variable called gpa will now hold 3.9. If we had left off the parentheses, the computer would have thought we meant the gpa member of studentPtr, which does not exist.
To avoid this problem, we can use a different notation: studentPtr -> gpa = 3.9;
Pointers, like other variables, do not have values until values are assigned. It is allowed to assign either 0 or NULL to a pointer variable to initialize it.
The discussion so far has not told us why we would want to use pointers instead of the variables that they point to. The text reveals a new secret: you can create a pointer that points to a dynamic variable. A dynamic variable has a type, but does not have a name. It can be created and deleted when we are done with it. When it is created, a previously declared pointer should be assigned to it, which is what gives us access to the variable. Without the pointer, we have no access to the variable. Example:
This code uses the new operator to dynamically create an int variable, and to assign a pointer to it called p. To assign a value to this unnamed int, we use the *p notation:
To create an array dynamically, we could have written:
Be aware that the new operator can fail if there is not enough memory available for use. It can create a bad_alloc (bad allocation command) exception, which will either be handled by the system or will crash the program.
Dynamic variables can be deleted from memory by using the delete operator and the pointer linked to the variable. Example:
Be aware that you are not really removing the stored data from memory. You are stating that the memory used by the dynamic variable(s) is now available for other uses.
You can do integer math with pointers. Example:
To cause ptrIntArray to point to the next element, we could increment it: ptrIntArray++. Since ptrIntArray points to an int, adding one to it actually increments the address it holds by four bytes, since ints take up four bytes of memory space. Incrementing a pointer by an integer actually increments the address in holds by the value of the integer times the number of bytes it takes to hold the type it points to.
Be careful when doing math with pointers, since you can easily reassign the pointer to a memory location that you did not intend. This takes us into an area in which it is the programmer's responsibility to make sure the program does what is intended, not just what it is told.
As noted above, you can create a dynamic array, which can only be accessed by the pointer you assign to it. You can reassign the pointer to various elements of the array by incrementing, decrementing, adding integer values, or subtracting integer values. There is, however, another way.
Using the name of the pointer like the name of the array, you can use subscripts to access each element of the array. In the example above, ptrIntArray points to the first element of a dynamic array. So does ptrIntArray. We could store a number (e.g. 5) in the second element with this:
This is very much like what you have done with arrays previously, but there is a difference as well. If a standard array is declared with a type and a name, that name is like a pointer to the array and can be used with subscripts as we normally do. (And, as you have just learned, you can use a pointer to the array the same way.)
However, the name of the array cannot be incremented or decremented. It is considered a constant, and must always point to the first element of the array. The text calls it a constant pointer. A regular pointer is a variable, and it can be made to point to any element of the array by math operations on it.
One further oddity: since the name of an array is like a pointer to the array, you do not use the address of operator with it when you assign a pointer to it.
The text turns briefly to creating an array sized by the user. This is not possible with the array lessons from earlier chapters, but dynamic arrays allow you to ask the user for a size, store it in an int, and use the new command to create an array of the requested size. Example:
int usersize; // creates an int to store the user's requested size
In the example above, we created an array of integers. It could just as easily been an array of any other data type.
The text introduces two new phrases: shallow copy and deep copy. (Silly names, but easy to understand.)
Pointers can be passed by reference or by value to functions. In the definition of the function, you either use an ampersand (to make it a reference) or don't (to make it a value). Examples:
void function1 (int* &ptr) // the ampersand tells the function to receive a reference to a pointer
void function2 (int* ptr) // no ampersand tells the function to receive the value of the pointer