CS105: Introduction to Computer Programming: C++

C++: Pointers

Pointers are variables that (surprise!) point to other variables. A pointer is declared by putting a star (or '*') before the variable name. Here's an example of the declaration of a pointer to an int:

  int *intPtr;

That's just how we declare our pointer. Let's make the pointer actually point to something:

  int val = 42;
  int *intPtr = &val;

The ampersand (or '&') before val means "take the address of val". The pointer intPtr is assigned the address of val, which is another way of saying that intPtr now points to val.

If we print out the value of intPtr, we'll get the address of val. In order to get the data that intPtr actually points at, we have to dereference intPtr with the unary star operator:

  int val = 42;
  int *intPtr = &val;
  cout << "&val: " << &val << endl;       // displays 0x5634a2b7 on my machine
  cout << "intPtr: " << intPtr << endl;   // ..again, 0x5634a2b7 
  cout << "*intPtr: " << *intPtr << endl; // displays 42
Note that the star (or '*') operator has many uses. When used in a binary context, it can mean multiplication. When used in a variable declaration, it means "this variable is a pointer". When used in an expression as a unary (single-argument) operator, it can mean "dereference". With so many uses, it's easy to get confused.

Since intPtr points to val, any changes that we make to val will also show up when we dereference intPtr:

  int val = 42;
  int *intPtr = &val;
  cout << "val: " << val << endl;         // displays 42
  cout << "*intPtr: " << *intPtr << endl; // displays 42
  val = 999;
  cout << "val: " << val << endl;         // displays 999
  cout << "*intPtr: " << *intPtr << endl; // displays 999

You can declare a pointer to any type, not just ints:

  string str = "Stupid Flanders..";
  string *strPtr = &str;
  cout << "str: " << str << endl;         // "Stupid Flanders..."
  cout << "strPtr: " << strPtr << endl;   // 0x449a72bc on my machine
  cout << "*strPtr: " << *strPtr << endl; // "Stupid Flanders..."
  *strPtr = "Okily-dokily!";
  cout << "*strPtr: " << *strPtr << endl; // "Okily-dokily!"
  cout << "str: " << str << endl;         // "Okily-dokily!"

If you're dealing with a pointer to a class, you can access member functions and data using the -> operator, instead of the . operator:

  string str = "Stupid Flanders..";
  string *strPtr = &str;
  cout << "str.length(): " << str.length() << endl;
  cout << "strPtr->length(): " << strPtr->length() << endl;

Function pointers

You can also have pointers to functions, which allows you to store functions as variables and pass functions as arguments to other functions:

  int sum( int a, int b) {
    return a+b;
  }
  int diff( int a, int b) {
    return a-b;
  }
  int displayResult( int (*func)(int,int), int a, int b ) {
    cout << "result is " << func(a,b) << endl;
  }
  ...
  displayResult( sum, 399, 500 );
  displayResult( diff, 399, 500 );

Functors

In C++, you can use operator overloading to overload the "function call" operator of a class, resulting in what is sometimes called a functor. Functors are similar to function pointers, in that they allow you to pass around "functions" as variables. Since functors are actually classes, they can also have state.

  class SumInts {
  public:
    int operator()( int a, int b ) {
      return a + b;
    }
  };  
  int computeFunction( SumInts f, int a, int b ) {
    return f(a,b);   // using the overloaded function call operator
  }
  ...
  SumInts sum;
  cout << computeFunction(sum,30,40) << endl;

Pointers and arrays

In C++, you can treat pointers and arrays very similarily:

  int array[] = {3,1,4,1,5,9};
  int *arrayPtr = array;
  cout << "array[0]: " << array[0] << endl;        // displays "3"
  cout << "arrayPtr[0]: " << arrayPtr[0] << endl;  // displays "3"
  arrayPtr++;
  cout << "arrayPtr[0]: " << arrayPtr[0] << endl;  // displays "1"
  array++; // error: arrays are const
One way to think about arrays and pointers is that they can be used interchangably, but you can change what pointers point at, whereas arrays will always "point" to the same thing.

Pointers and const

Pointers can be declared const in three ways:

The variety of ways that you can use const can be confusing. One useful way to understand pointer declarations is to read them starting from the variable name alternating from right to left repeatedly. Here are some examples where this rule boils down to just reading declarations from right to left:

Pointers and dynamic allocation

Instead of making a pointer point at an existing object, we can also dynamically allocate memory for a pointer to point at:

  int *intPtr = new int(55);
  cout << *intPtr << endl;     // displays "55"
Calling new allocates memory and initializes the memory. For primitive types, the memory is initialized to 0. For classes, the default constructor is called to initialize any created objects. If you're using new to allocate an array, each element of the array is initialized in this manner.

Whenever you use the new operator to dynamically allocate memory, you are responsible for calling delete to de-allocate that memory. If you don't de-allocate everything you allocate, your program is said to "leak memory":

  int *intPtr;
  while( true ) {
    intPtr = new int(55);   // memory leak!
  }

You can use the delete operator to de-allocate memory:

  int *intPtr;
  while( true ) {
    intPtr = new int(55);
    delete intPtr;          // we've plugged the leak
  }

You can allocate arrays using square brackets with the new operator:

  int *intPtr = new int[1000];
  intPtr[55] = 2;
  cout << intPtr[55] << endl;
  delete[] intPtr;          // use delete[] instead of delete
Make sure to use delete[] when de-allocating arrays: if you use delete without brackets, the array will not be successfully de-allocated.