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
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;
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 );
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;
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
const.constPointers can be declared const in three ways:
const, which means that you
can't make the pointer point at something else.
int val1 = 42; int * const intPtr = &val1; *intPtr = -1; // okay int val2 = 999; intPtr = &val2; // error!
const,
which means that you can't change the data that the pointer points to.
int val1 = 42; const int * intPtr = &val1; *intPtr = -1; // error! int val2 = 999; intPtr = &val2; // okay
int val1 = 42; const int * const intPtr = &val1; *intPtr = -1; // error! int val2 = 999; intPtr = &val2; // error!
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:
int * intPtr -- the variable intPtr is a pointer to
an int.const int * intPtr -- the variable intPtr is a
pointer to an int that is constant. Since the int is constant, we
can't change it. We can change intPtr, however.int * const intPtr -- the variable intPtr is a
constant pointer to an int. Since intPtr is constant, we can't change
it. However, we can change the int.const int * const intPtr -- the variable intPtr is a
constant pointer to an int that is constant. Everything is constant.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"
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
delete[] when
de-allocating arrays: if you use delete without brackets,
the array will not be successfully de-allocated.