"Do not mistake the pointing finger for the moon."   -ZEN SAYING
Pointers are one of the most powerful features of C++, and the book has very nice text on them. The basic vibe is this: A variable such as an int consumes storage. You access that storage by using the variable name. A pointer is a variable that points to the storage of a variable. Sometimes we call this the "address" of a variable. To access the storage, you need to "dereference" the pointer. An example (in pex1.cpp):
#include <iostream>
using namespace std;
int main()
{
int i;
int *i_ptr;
i = 5; // Set the value of i
i_ptr = &i; // Set i_ptr to be the address of i
cout << "i is: " << i << endl;
cout << "&i is: " << &i << endl;
cout << "i_ptr is: " << i_ptr << endl;
cout << "*i_ptr is: " << *i_ptr << endl;
*i_ptr = 25;
cout << endl;
cout << "i is: " << i << endl;
cout << "*i_ptr is: " << *i_ptr << endl;
return 0;
}
|
You'll note, you access the address of i with &i, and you dereference the pointer i_ptr with *i_ptr. Here is the output of this when run on my office desktop (different machines will print different pointer values):
UNIX> g++ pex1.cpp UNIX> g++ -o pex1 pex1.cpp UNIX> pex1 i is: 5 &i is: 0xbf928b8c i_ptr is: 0xbf928b8c *i_ptr is: 5 i is: 25 *i_ptr is: 25 UNIX>The value of i_ptr is not overly important, except that it is the same as the address of i. Since both i and *i_ptr access the same storage, changing one of them changes the other. This is reflected in the last four lines of the program -- as you can see, when we change *i_ptr, i is changed as well.
#include <iostream>
using namespace std;
void a(int *i_ptr)
{
*i_ptr = 12;
}
int main()
{
int i;
int *i_ptr;
i = 5;
i_ptr = &i;
cout << "Before calling a(): i is: " << i << endl;
a(i_ptr);
cout << "After calling a(): i is: " << i << endl;
return 0;
}
|
Here's it running: you'll see that indeed i is modified by the call to a():
UNIX> g++ -o pex2 pex2.cpp UNIX> pex2 Before calling a(): i is: 5 After calling a(): i is: 12 UNIX>
One of the biggest uses of pointers is to in effect return two things from a function. For example, the function below determines both the square and the cube of a number: (In pex3.cpp),
#include <iostream>
using namespace std;
void square_and_cube(int n, int *n_squared, int *n_cubed)
{
*n_squared = n * n;
*n_cubed = n * n * n;
}
int main()
{
int n, sq, cube;
cout << "Enter n: ";
cin >> n;
square_and_cube(n, &sq, &cube);
cout << n << " squared is " << sq << endl;
cout << n << " cubed is " << cube << endl;
return 0;
}
|
Here's it running: you'll see that indeed i is modified by the call to a( ):
UNIX> g++ -o pex3 pex3.cpp UNIX> pex3 Enter n: 5 5 squared is 25 5 cubed is 125 UNIX>
For example, in the program below, even though the procedure a( ) changes i, that does not modify the variable i in main(). This is because i has been "passed by value." (In pex4.cpp).
#include <iostream>
using namespace std;
void a(int i)
{
i = 7;
cout << "Inside a(). i = " << i << " and &i = " << &i << endl;
}
int main()
{
int i;
i = 5;
cout << "Before calling a(). i = " << i << " and &i = " << &i << endl;
a(i);
cout << "After calling a(). i = " << i << " and &i = " << &i << endl;
return 0;
}
|
Here's it running. I've printed out the addresses of i in both a() and main() -- you can see that they are different. That is because they point to different storage -- i in a() is different from i in main(). That is why changing i in a() does not modify i in main():
UNIX> g++ -o pex4 pex4.cpp UNIX> pex4 Before calling a(). i = 5 and &i = 0xbffff74c Inside a(). i = 7 and &i = 0xbffff730 After calling a(). i = 5 and &i = 0xbffff74c UNIX>
Now, the same thing is true when you pass a pointer as an argument to a procedure -- a copy is made of the pointer. However, when you deference the copy, that will change whatever the pointer points to. For example, look at the following program: (In pex5.cpp).
#include <iostream>
using namespace std;
void a(int *p1, int *p2)
{
cout << endl << "Inside a()" << endl << endl ;
cout << "p1 = " << p1 << " and &p1 = " << &p1 << " and *p1 = " << *p1 << endl;
cout << "p2 = " << p2 << " and &p2 = " << &p2 << " and *p2 = " << *p2 << endl;
p1 = p2;
*p1 = 3;
*p2 = 4;
cout << endl << "Inside a() - after setting p1 to p2" << endl << endl ;
cout << "p1 = " << p1 << " and &p1 = " << &p1 << " and *p1 = " << *p1 << endl;
cout << "p2 = " << p2 << " and &p2 = " << &p2 << " and *p2 = " << *p2 << endl;
}
int main()
{
int i, j, *ptr1, *ptr2;
ptr1 = &i;
ptr2 = &j;
i = 5;
j = 6;
cout << "Before calling a()" << endl << endl ;
cout << "i = " << i << " and &i = " << &i << endl;
cout << "j = " << j << " and &j = " << &j << endl;
cout << endl;
cout << "ptr1 = " << ptr1 << " and &ptr1 = " << &ptr1 << " and *ptr1 = " << *ptr1 << endl;
cout << "ptr2 = " << ptr2 << " and &ptr2 = " << &ptr2 << " and *ptr2 = " << *ptr2 << endl;
a(ptr1, ptr2);
cout << endl << "After calling a()" << endl << endl ;
cout << "i = " << i << " and &i = " << &i << endl;
cout << "j = " << j << " and &j = " << &j << endl;
cout << endl;
cout << "ptr1 = " << ptr1 << " and &ptr1 = " << &ptr1 << " and *ptr1 = " << *ptr1 << endl;
cout << "ptr2 = " << ptr2 << " and &ptr2 = " << &ptr2 << " and *ptr2 = " << *ptr2 << endl;
return 0;
}
|
Here's it running. Study this carefully:
UNIX> g++ -o pex5 pex5.cpp UNIX> pex5 Before calling a() i = 5 and &i = 0xbffff73c j = 6 and &j = 0xbffff738 ptr1 = 0xbffff73c and &ptr1 = 0xbffff734 and *ptr1 = 5 ptr2 = 0xbffff738 and &ptr2 = 0xbffff730 and *ptr2 = 6 Inside a() p1 = 0xbffff73c and &p1 = 0xbffff720 and *p1 = 5 p2 = 0xbffff738 and &p2 = 0xbffff724 and *p2 = 6 Inside a() - after setting p1 to p2 p1 = 0xbffff738 and &p1 = 0xbffff720 and *p1 = 4 p2 = 0xbffff738 and &p2 = 0xbffff724 and *p2 = 4 After calling a() i = 5 and &i = 0xbffff73c j = 4 and &j = 0xbffff738 ptr1 = 0xbffff73c and &ptr1 = 0xbffff734 and *ptr1 = 5 ptr2 = 0xbffff738 and &ptr2 = 0xbffff730 and *ptr2 = 4 UNIX>
Ok -- ptr1 equals the address of i and ptr2 equals the address of j. When we pass them to a(), copies are made -- p1 is set to ptr1 and p2 is set to ptr2. This is reflected in the output in the beginning of a(), where both ptr1 and p1 equal 0xbffff73c -- the address of i. You'll note, though, that ptr1 and p1 have different addresses (0xbffff734 vs. 0xbffff720). That is because p1 is a copy of ptr1
When we modify p1 by setting it equal to p2, you'll see that both of them have values of 0xbffff738 -- the address of j. So they both modify j by setting it to three, then 4. When a() finishes, i is unchanged, but j is 4. Moreover, you'll notice that ptr1 had not been modified by the call to a(). That is because arguments are passed by value -- even pointers!
      Example:  const int COUNT = 101;   // COUNT cannot be modified
        const char name[COUNT] = "J. Wallace Mayo"; // elements of
name cannot be modified
#include <iostream>
using namespace std;
int main()
{
int array[10];
int *a_ptr;
a_ptr = array;
array[0] = 15;
cout << "array[0]: " << array[0] << endl;
cout << "*a_ptr: " << *a_ptr << endl;
return 0;
}
|
When it runs, you can see that dereferencing a_ptr is the same as accessing a[0].
UNIX> g++ -o pex6 pex6.cpp UNIX> pex6 array[0]: 15 *a_ptr: 15 UNIX>
In fact, you can use standard array accessing syntax (square brackets) on the pointer, just like you do with an array. You can see this in pex7.cpp:
#include <iostream>
using namespace std;
int main()
{
int array[10];
int *a_ptr;
int i;
a_ptr = array;
for (i = 0; i < 10; i++) a_ptr[i] = 30 + i;
for (i = 0; i < 10; i++) {
cout << "i: " << i << " - array[" << i << "]: " << array[i]
<< " - a_ptr[" << i << "]: " << a_ptr[i] << endl;
}
return 0;
}
|
As you can see, you can treat a_ptr just like array.
UNIX> g++ -o pex7 pex7.cpp UNIX> pex7 i: 0 - array[0]: 30 - a_ptr[0]: 30 i: 1 - array[1]: 31 - a_ptr[1]: 31 i: 2 - array[2]: 32 - a_ptr[2]: 32 i: 3 - array[3]: 33 - a_ptr[3]: 33 i: 4 - array[4]: 34 - a_ptr[4]: 34 i: 5 - array[5]: 35 - a_ptr[5]: 35 i: 6 - array[6]: 36 - a_ptr[6]: 36 i: 7 - array[7]: 37 - a_ptr[7]: 37 i: 8 - array[8]: 38 - a_ptr[8]: 38 i: 9 - array[9]: 39 - a_ptr[9]: 39 UNIX>
The reason why this is important is that when you pass an array to a function as an argument, you are passing a pointer to its first element. This means that if you change the contents of the array inside the function, that will change the contents of the original array. This is because only the pointer to the contents is copied in the function call. It is important to understand this.
So look at pex8.cpp:
#include <iostream>
using namespace std;
void initialize_array(int *a)
{
int i;
for (i = 0; i < 10; i++) a[i] = 40+i;
}
void display_array(int *a)
{
int *p; //traveling pointer
cout << "Inside display_array: ";
for (p = a; p < a + 10; p++)
cout << *p << " ";
cout << endl << endl;
}
int main()
{
int array[10];
int i;
initialize_array(array);
display_array(array);
for (i = 0; i < 10; i++) {
cout << "i: " << i << " - array[" << i << "]: "
<< array[i] << endl;
}
return 0;
}
|
When you run it, you'll see that initialize_array() changes the contents of array. That is because a is a pointer to the first element of array. Also, study the pointer arithmetic in display_array().
UNIX> g++ -o pex8 pex8.cpp UNIX> pex8 Inside display_array: 40 41 42 43 44 45 46 47 48 49 i: 0 - array[0]: 40 i: 1 - array[1]: 41 i: 2 - array[2]: 42 i: 3 - array[3]: 43 i: 4 - array[4]: 44 i: 5 - array[5]: 45 i: 6 - array[6]: 46 i: 7 - array[7]: 47 i: 8 - array[8]: 48 i: 9 - array[9]: 49 UNIX>