Pointers and How to Use Them Pointers and How to Use Them with Classes

1  Pointers

1.1  Addresses (11.1 in Savitch)

Memory looks like a very long CD rack. Every slot in the rack consists of one byte (a set of 8 bits). Every byte has a number called its address.

The address of a variable is the address of the first byte in the variable.

1.2  How to get your grubby hands on an address: the & operator

The following code should be self explanatory:

	int A = 5;
	
	cout << "The value of A is "   << A  << endl;
	cout << "The address of A is " << &A << endl;

The output of this code would be 5 - the value of A, and a hex number that is the memory address of A.

In summary: &A is the address of A.

1.3  Pointers - variables that store addresses (11.1 in Savitch)

A code fragment:

	int  A, *Aptr;     //Aptr is a pointer to an int type variable
	
	A    = 5;
	Aptr = &A;         //Stores the address of A in Aptr
	
	cout << "A is at location " << Aptr << endl;
	
	cout << "Aptr points to an int with value " << *Aptr << endl;

Notes:

  1. In the above code, the *Aptr in the int declaration tells the computer that Aptr stores addresses of ints rather than ints.
  2. Aptr = &A is the way to actually get an address in Aptr.
  3. The *Aptr in the last cout line is the value of the variable that Aptr points to.

The output of the code fragment will be:

A is at location < a hex address >

Aptr points to an int with value 5.

In summary:

1.4  Working with the value of what is pointed to: the * operator

As seen above, *Aptr is the value of the variable that Aptr points to. Also one can do this:

	int  A, *Aptr;
	
	Aptr  = &A;        //Aptr gets the address of A
	*Aptr = 5;         //5 is placed in the variable Aptr points to
	
	cout << A << endl;

5 will be printed.

The line *Aptr = 5 puts the 5 in the variable that Aptr points to, that is A.

In summary: For all practical purposes, after Aptr = &A; *Aptr is the same as A.

1.5  You do it

    1. Set up a float variable x and a pointer-to-float called george.
    2. Then use george to put 8.34 in x.
    3. Finally use george to print out x.

    1. What would this mean:

      int A, *Aptr, **whatIsThis;

    2. Using the declaration above describe what will be printed:

      					A          = 6;
      					Apt        = &A;
      					whatIsThis = &Aptr;
      					
      					cout << A             << endl
      					     << Aptr          << endl
      					     << *Aptr         << endl
      					     << whatIsThis    << endl
      					     << *whatIsThis   << endl
      					     << **whatIsThis  << endl;
      				

1.6  Pointers to char and string arrays (11.1 in Savitch)

	char name [10] = "Harvey";

makes memory look like this:

name H a r v e y 0
0 1 2 3 4 5 6 7 8 9

But this is also possible:

	char name [] = "Harvey";

which makes memory look like this:

name H a r v e y 0
0 1 2 3 4 5 6

This gets kind of tedious if there are many names. But C++ lets us do this:

	char *names [3] = {"Harvey", "Myrtle", "Smedley"};
	
	cout << names [0] << endl
	     << names [1] << endl
	     << names [2] << endl;

The output is:

Harvey
Myrtle
Smedley

  1. char *names [3] sets up an array of 3 pointers to char.
  2. names [0] points to Harvey,
  3. names [1] points to Myrtle, and
  4. names [2] points to Smedley.

Here is another piece of code:

	char name [] = "Harvey";
	
	char *position = &name [2];
	
	cout << name     << endl;
	cout << position << endl;

What gets printed? This:

Harvey
rvey

Here's what goes on:

  1. In the first cout, name is considered by the computer to be a pointer to the array slot name [0]. The computer prints the characters it sees from this address to the first null character it finds, which occurs at the end of Harvey.
  2. In the second cout, the same logic is used: the computer prints the characters it sees from the address in positon to the first null character.

In summary:

1.7  You do it

  1. What gets printed?

    			int nums [5] = {9,4,2,7,5};
    			int *numPtrs [5];
    			
    			numPtrs [0] = &nums [4];
    			numPtrs [1] = &nums [1];
    			numPtrs [2] = &nums [3];
    			numPtrs [3] = &nums [0];
    			numPtrs [4] = &nums [2];
    			
    			cout << numPtrs [0]  << endl;
    			cout << *numPtrs [1] << endl;
    			cout << *numPtrs [2] << endl;
    			
    		

  2. Write a function that takes as inputs a string in a char array and a character number, and prints out all characters in the string from the given character number to the right. Use a pointer, not a loop.

  3. Write a program to use pointer arrays to store the card suits (spade, heart, diamond, club) and also to store card values (Ace, two, three, four, five, six, seven, eight, nine, ten, Jack, Queen, King). Have the program prompt the user to pick a card and then have the program produce a card at random.

1.8  Pointer arithmetic

A piece of code:

	char name [] = "Harvey";
	
	char *namePtr, *otherPtr;
	
	namePtr  = name;
	otherPtr = name + 3;
	
	cout << name     << endl;
	cout << namePtr  << endl;
	cout << otherPtr << endl << endl;
	
	for (int offset = 0; offset < 6; offset++)
		{
		otherPtr = name + offset;
		
	    cout << otherPtr << endl;
	    }

The output is

Harvey
Harvey
vey
Harvey
arvey
rvey
vey
ey
y

What is happening here?

  1. namePtr = name sets namePtr to the address of name [0].
  2. otherPtr = name + 3 sets otherPtr to the address of name [3].
  3. cout << namePtr << endl; prints characters from name [0] right to the null character.
  4. cout << otherPtr << endl; prints characters from name [3] right to the null character.
  5. In the loop, as offset takes on the values 0, 1, 2, 3, 4, 5, otherPtr points to name [0], name [1], name [2], name [3], name [4], name [5]. Each time cout is encountered, characters are printed from the address held by otherPtr right to the null character.

A code fragment:

	int x[7] = {1,2,3,4,5,6,7};
	int *xPtr;
	
	xPtr = x + 3;
	
	cout << *xPtr       << endl;
	cout << *(xPtr + 2) << endl;

The output is

4
6

Note here that x + 3 advances the address in xPtr by 3 int length (12 bytes on most systems) and not by 3 bytes.

In summary: If ptr points to slot n of an arry, then ptr + 3 points to slot n + 3 of the array.

1.9  You do it

  1. Start with

    			int x [10] = {2,1,5,7,8,4,3,6,9};
    			
    			int *ptr = &x [4];
    		

    Write C++ statements using the pointer prt to

    1. Print out x [7].
    2. Put a 10 in x[6].
    3. Add the contents of x [5] and x [8] and store the result in x [7].

  2. Start with

    			char A [10] = "Mortimer";
    			
    			char *ptr = &A [2];
    		

    Write C++ statements that use ptr to

    1. Change the o to e and the i to a.
    2. Print rtimer.
    3. Print imer.

1.10  How pointers can act like arrays

A code fragment:

	int x [5] = {2,4,6,8,10};
	
	int *xPtr;
	
	xPtr = &x [2];
	
	cout << xPtr [0] << endl;
	cout << xPtr [1] << endl;
	cout << xPtr [2] << endl;

Here is what is printed:

6
8
10

What goes on here?

  1. xPtr [0] is the same as *(xPtr + 0). xPtr is the address of x [2]. so xPtr + 0 is also the address of x [2]. Now *(xPtr + 0) is the content of x [2].
  2. xPtr [1] is the same as *(xPtr + 1). xPtr is the address of x [2]. so xPtr + 1 is the address of x [3]. Now *(xPtr + 1) is the content of x [3].
  3. xPtr [2] is the same as *(xPtr + 2). xPtr is the address of x [2]. so xPtr + 2 is the address of x [4]. Now *(xPtr + 2) is the content of x [4].

In summary: If Aptr is a pointer, then Aptr [n] is the same as *(Aptr + n).

1.11  You do it

  1. In this code:

    			int *ptr, A [5] = {6,8,3,2,1};
    			
    			ptr = &A [1];
    			
    			cout << ptr [3] << endl
    			     << ptr [2] << endl
    			     << ptr [0] << endl;
    		

    What is printed?

  2. Given this code:

    			char name [] = "George Washington";
    			
    			char *one, *two;
    			
    			one = name;
    			two = &name [7];
    		

    What further lines must be added to get output like this:

    G
    e
    o
    r
    g
    e
    W
    a
    s
    h
    i
    n
    g
    t
    o
    n

1.12  Stack memory and free store memory (11.2 in Savitch)

Stack memory (or the stack) is the area of memory where the computer stores values for variables that have been set up in declarations, stores function calls, and stores the arguments of function calls. Stack memory is limited in size and is largely out of the programmer's control. (Actually, you can use operating system calls to change the amount of stack memory, but how you do this will vary from operating system to operating system.)

Free store is the part of memory that is available for the program to stake out as its own. Another name for free store is heap memory (or the heap).

1.13  How to get memory from free store: new (11.2 in Savitch)

These lines take int storage (4 bytes on most compilers) and put 5 in that storage space:

	int *x = NULL;    //Set up x, a pointer to int, initialize it to NULL.
	
	x = new int (5);  //Reserve space for an int in free store, 
	                  //  put the address of the first byte in the pointer x,
	                  //  and store 5 int the space x points to.
	              

These lines set up an array of 5 ints in free store:

	int *x = NULL;    //Set up x, a pointer to int, initialize it to NULL.
	
	x = new int [5];  //Set up an array of 5 ints in free store.
	
	if (x != NULL)    //If the new failed (free store space not available)
	                  //  then new will return NULL to x.   Otherwise, store
	                  //  values in the int array.
	   {
	    x [0] = 6;
	    x [1] = 4;
	    x [2] = 5'
	    }

Finally, these lines set up a string in free store:

    char *str = NULL;
    int  length = strlen ("George");
    
    str = new char [length + 1]   //One extra space for the null 
                                  //   char at the end of the 
                                  //   string.
    if (str!= NULL)
        {
        strcpy (str, "George");
        cout << str;
        }

1.14  Cleaning up after yourself with delete (11.2 in Savitch)

If you have reserved memory with new, then this memory is not automatically released when your program ends. The memory you have reserved is released only when you release it with the delete operator.

If x is a pointer to a single int, then you can release the 4 bytes of memory with delete x; If str is a pointer to a character array, then you can release the memory with delete []str;

This short program reserves memory for a string, stores the string, prints it, and then releases the memory.

	#include <iostream.h>
	#include <string.h>
	
	int main ()
	   {
	   char *str = NULL;
	   
	   int length = strlen ("George");
	   
	   str = new char [length + 1];
	   
	   if (str != NULL)
	      {
	      strcpy (str, "George");
	      
	      cout << str;
	      }
	      
	   delete []str;         //Release the memory reserved in the new above.
	   
	   str = NULL;           //Re-initialize str for saftey's sake.
	   
	   return 0;
	   }

1.15  How to crash the computer - Dangling pointers

Here are several code fragments that will result in an almost certain system crash:

  1.     int *x;
        
        delete x;
    

    Here x doesn't point to anything, so the delete will fail catastrophically. This is the reason for initializing x to NULL. delete x does nothing if x is equal to NULL.

  2.      int *x;
         
         x = new int (5);
         
         cout << x;
         
         delete x;
         
         x = NULL;
         
         (*x) = 4;
    

    After the delete line, the storage that x points to no longer exists.

  3.     int *x;
        
        x = new int (5);
        
        cout << x;
        
        delete x;
        
        delete x;
    

    We can't release the storage that x points to twice.

2  Pointers and classes (11.3 in Savitch)

2.1  Pointers, Copy Constructors, and Overloaded =

If a class has pointer data in it, then you have to be careful when one object of the class is copied or created from another.

The problem is that, when you copy a pointer of one object into another object, the copy points to the same block of memory that belongs to the first object. New memory is not automatically created for the second object.

So, whenever a class includes a pointer as a data member, the class should have

  1. A copy constructor to make a new object that is a copy of the original object.
  2. An operator= function to overload = to make it work properly. (Overloading = means to redefine = so that it works for our objects and not just for the built-in C++ data types.)

If either the copy constructor of the operator= are missing, then lots of obscure problems may arise.

2.2  A List class

Suppose we want to make a class to represent a list of ints in such a way that the list is no longer than it has to be. In fact, we would like it to figure out the amount of storage on the fly.

2.3  The class definition

Such a class might be defined like this:

class intList
   {
   public:
      intList ();                               //Default constructor
      intList (int aList [], int howMany);     
      intList (intList&);                       //Copy constructor
      
      ~intList ();                              //Destructor
      
      void   operator= (intList&);              //Overloads =
      
      void   inputNumbers ();
      void   print        ();
      double average      ();
      
   private:
      int *theList;                //Pointer to point to a dynamic array.     
      int size;                    //Size of the dynamic array.
   };

Notes:

  1. We have three different constructors. The purposes are:

  2. Note the function, ~intList. This indicates a function that is called automatically when an intList object is about to go out of existence. How can an object go out of existence?

    Such a function is called a destructor. Note: You can't call the destructor yourself. The computer will call it.

2.4  A close look at the constructors

Here are the first two constructors:

intList::intList ()          //Default constructor
   {
   size    = 0;
   theList = NULL;
   }
   
intList::intList (int aList [],    //Incoming list of numbers
                  int howMany)     //How many numbers are incoming
   {
   size = howMany;                 //Copy over the list size
   
   theList = new int [size];       //Create a dynamic array of the right 
                                   //   size.  theList points to the 0 
                                   //   slot of the array.
   
   for (int n = 0; n < size; n++)  //Copy the numbers over to the dynamic 
      theList [n] = aList [n];     //   array.
   }

Notes:

  1. The default constructor simply initializes the list to empty and the pointer to NULL (actually a 0 value). This is important to do, because when we delete the pointer later, the computer knows how to delete a NULL pointer, but a non-NULL pointer that points to nothing will cause a crash.
  2. The line theList = new int [size]; makes an array with size losts in free memory. theList will point to the 0 slot of this array.
  3. Locations in the theList pointer can be referred to by ordinary array notation. theList [0] will be the 0 slot of the memory allocated, etc. This is what is going on in theList [n] = aList [n];

2.5  Copy constructors

A copy constructor is a constructor that makes the object as a copy of another object of the same type. In this case the copy constructor for the intList class makes a new intList object as a copy of a given intList object.

Why should we care about copy constructors? Any class that has pointers in its data must have a copy constructor to enable proper copying. If it doesn't, then the pointer of the new object will end up pointing to the dynamic memory of another object, a bad state of affairs.

But the copy constructor is not called if you use = . The copy constructor is called when an object is returned as the return type of a function. The copy constructor is also called when an object is used as a by-value argument in a function.

Here is the copy constructor for the intList class:

intList::intList (intList& aList)        //The argument is an object of 
                                         //  the same type, to be copied.
   {
   size = aList.size;                    //Copy over the size of the list.
         
   theList = new int [size];            //Make an array of the proper size.
   
   for (int n = 0; n < size; n++)       //Copy over the numbers.
      theList [n] = aList.theList [n];
   }

Notes:

  1. aList is another object of type intList. The intent is to create a copy of this object.
  2. How can we get away with size = aList.size;? Isn't size private data? Yes, it is, but this means that it is accessible inside of functions of the class, and inaccessible outside of functions of the class. The copy constructor is a function of the class.
  3. After the copy constructor is done, the aList object and the new object have theList pointers that point to different segments of memory that hold separate copies of the numbers in the list.

2.6  Overloading =

This function will look like:

void intList::operator= (intList& aList)
   {
   size = aList.size;                    //Copy over the size of the list.
         
   theList = new int [size];            //Make an array of the proper size.
   
   for (int n = 0; n < size; n++)       //Copy over the numbers.
      theList [n] = aList.theList [n];
   }

2.7  Destructors

In the class definition for intList, the prototype ~intList (); indicates a destructor: a function that is called by the computer (and not by the program) when any intList object goes out of existence.

What needs to be done in a destructor? Any cleaning up, especially releasing back to free memory any memory that the program grabbed.

The intList destructor might look like this:

intList::~intList ()
   {
   delete []theList;
   }

The effect of this destructor is to release the memory attached to the pointer theList. Note that it is important that theList is first initialized to NULL. In this case delete will work all right even if no memory gets attached to theList. Otherwise a crash will result.

2.8  All of the code for the intList class

The intList.h file:

#ifndef INTLIST_H
#define INTLIST_H

class intList
   {
   public:
      intList ();                               //Default constructor
      intList (int aList [], int howMany);     
      intList (intList&);                       //Copy constructor
      
      ~intList ();                              //Destructor
      
      void   operator=    (intList&);           //Overloads =
      
      void   inputNumbers ();
      void   print        ();
      double average      ();
      
   private:
      int *theList;                //Pointer to point to a dynamic array.     
      int size;                    //Size of the dynamic array.
   };

#endif

The intList.cpp file:

#include <iostream>
#include <iomanip>

#include "intList.h"

const int MAXSIZE = 10000;

intList::intList ()          //Default constructor
   {
   size    = 0;
   theList = NULL;
   }
   
intList::intList (int aList [],    //Incoming list of numbers
                  int howMany)     //How many numbers are incoming
   {
   size = howMany;                 //Copy over the list size
   
   theList = new int [size];       //Create a dynamic array of the right 
                                   //   size.   theList points to the 0 
                                   //   slot of the array.
   
   for (int n = 0; n < size; n++)  //Copy the numbers over to the dynamic 
      theList [n] = aList [n];     //   array.
   }

intList::intList (intList& aList)        //The argument is an object of the 
                                         //  same type, to be copied.
   {
   size = aList.size;                    //Copy over the size of the list.
         
   theList = new int [size];            //Make an array of the proper size.
   
   for (int n = 0; n < size; n++)       //Copy over the numbers.
      theList [n] = aList.theList [n];
   }

intList::~intList ()         //Destructor
   {
   delete []theList;
   }

void intList::operator= (intList& aList)
   {
   size = aList.size;                    //Copy over the size of the list.
         
   theList = new int [size];            //Make an array of the proper size.
   
   for (int n = 0; n < size; n++)       //Copy over the numbers.
      theList [n] = aList.theList [n];
   }

void intList::inputNumbers ()
   {
   cout << "Enter numbers.  Enter -1 to stop." << endl << endl;
   
   //Enter the numbers into a temporary array first.
   
   int size = 0;
   
   int tempList [MAXSIZE];
   
   cin >> tempList [size];
   
   while  (tempList [size] != -1)
      {
      size++;
      
      cin >> tempList [size];
      }
      
   //Then create a dynamic array of just the right size.
   
   theList = new int [size];
   
   //And copy the numbers into the dynamic array.
   
   for (int n = 0; n < size; n++)
      theList [n] = tempList [n];
   }
   
void intList::print ()
   {
   for (int n = 0; n < size; n++)
      cout << setw (10) << theList [n] << endl;
   }
   
double intList::average ()
   {
   int sum = 0;
   
   for (int n = 0; n < size; n++)
      sum += theList [n];
      
   return double (sum) / double (size);
   }


File translated from TEX by TTH, version 2.25.
On 11 Oct 2002, 16:21.