Pointers are a mechanism to refer to objects in memory indirectly. They are often used with arrays, which are collections of same size objects.
A pointer is the location of something. It is not the thing itself. It tells you where the thing is. Think of the address on an envelope. You are not really interested in the address, but the building it represents. By putting a different address on the envelope, you send it to a different building. We talked about how variables are a chunk of memory and have a name, content, location and type. This is also true of pointers.
They are variables. While they have different types, all pointers are the same size. They are big enough to hold a machine address. This is usually 32 bits. The content of a pointer is the location of another variable. It is the memory address where the variable starts in memory.
Perhaps a better analogy than house addresses is phone numbers. A phone number is assigned to you. If you move, many times you keep the same phone number, even though it connects to a different phone. The phone switch knows that when it receives that number it should now ring the phone in the new house instead of the old house.
Pointers are often used the same way. A single variable in your program can point at different times to different other variables. You can make a function that uses a piece of data and give it a different datum each time.
The type of a pointer is 'pointer to something', where 'something' is some other type. So, you can have 'pointer to float' and 'pointer to int' and these are different types, just as float and int are different. To make a pointer variable, you put a * before its name.
int * ipointer; // pointer to integer
float * fpointer; // pointer to float
By themselves, these aren't much use. Above, I said the contents of a pointer was the address of something else. So, given these other definitions:
int itarget;
float ftarget;
We can point the pointers at them;
ipointer = &itarget;The unary ampersand operator ( not to be confused with the binary one) retrieves the location of the variable that follows it. After we run the example program, we get this output,
fpointer = &ftarget;
contents of ipointer is 0x0066FDF0
contents of fpointer is 0x0066FDE4
contents of cpointer is c¦¦¦_²f // this is different because char pointers are special in C++
The size of ipointer is 4
The size of itarget is 4
The size of fpointer is 4
The size of ftarget is 8
The size of cpointer is 4
The size of ctarget is 1
Note that, while all the pointers are the same size, the things they point at are different size.
Now that I have a pointer, what do I do with it? I am rarely interested in its contents directly as they are memory addresses. Usually, I want the contents of the thing it points to. The process of getting at the thing pointed to is called dereferencing. To do this in C++, we use the unary * operator.
cout << "The value in itarget is " << itarget << " using the pointer it is " << *ipointer << endl;
If we add these lines to the example code, we get
The value in itarget is 42 using the pointer it is 42Which is what we would expect.
The value in ftarget is 2.4 using the pointer it is 2.4
The value in ctarget is c using the pointer it is c
In the terms we have been using, a pointer is used to hold the location
of another variable. The contents property of the pointer contains the
location property of the other variable. So in the code below,
int a=5, *ptr;
ptr = &a;
The ampersand extracts the location from the a variable and puts it
in as the contents of the ptr variable.
Lets look at some concrete examples. Here are the properties of a and ptr
Name | Type | Location | Contents |
---|---|---|---|
a | int | 22222 | 5 |
ptr | int * | 33333 | 22222 |
So the pointer variable now contains the address of the a variable.
We can use this to get and set the contents of a.
There is an operator
that is used to retrieve the contents of the thing pointed at.
For example, if we print ptr like this
cout << ptr << endl;
We get the value 22222 printed. But we are not really interested in
this value. We are interested in the entry in the table above that has
a location value of 22222. We use the star * operator to do this,
cout << *ptr << endl;
would print 5.
One way to view this is that the * operator searches this table and looks for the row where the location field matches the contents of the pointer variable. Another way is to see it as just getting the contents indirectly. * ptr works in two steps. It first gets the contents of pointer, which is 22222. Then it uses that number as a memory address and retrieves whatever is stored there. In this case, that is 5. This is because the location value is the actual machine address where the variables contents are stored.
Now let's look at the relationship between arrays and pointers. An array
van be seen as a collection of individual variables. For example,
int foo[3];
This is three variables collected together. They are named foo[0].
foo[1] and foo[2]. These can be used anywhere an integer can be used. Another
analogy is to a carton of eggs. The individual eggs can be used in any
recipe that calls for eggs. But the carton cannot. Consider a function
like this.
int
bar(int a) {
cout << a << endl;
} // bar
so a function call like
b = bar(foo[1]);
is legal but
b = bar(foo);
is not.
Now this variable
int *ptr;
Let's point this at the first element in the array
ptr = &foo[0];
now we have two ways to refer to the same memory location. Both *ptr
and foo[0] refer to the same thing. We can use them interchangeably.
*ptr = 8; foo[0] = 8;
x = *ptr; x = foo[0];
In C++, this two ways of getting to a memory address can be used mostly
the same. So we can say
ptr[0] = 8; *foo = 8;
Notice that we have used the name of the array as a pointer. This is
generally true and is more than a notation. The name of an array is a pointer
to the array. So instead of saying
ptr = &foo[0];
we could have said
ptr = foo;
The contents of ptr is the same as foo. And the contents of foo is
the address of the first element of foo. Now consider what would happen
if we added one to the contents of ptr. Since all elements of an array
are physically right next to each other, adding one to an address gives
us the next address. So, if ptr points to the first element of foo, ptr
+ 1, points to the second. And ptr + 2 points to the third. Now if we put
a * in front like this,
*ptr + 1
this would get the value stored at that location. But not quite. Due
to precedence rules, * is done before +, so this actually gets the value
stored at ptr and adds one to that. Not quite what we want. So we use parenthesis
to sort it out,
*(ptr+1)
This adds one to ptr, and retrieves the value stored there. This is exactly that same as ptr[0]. In fact, that is what happened when the compiler sees ptr[1].
This operation is called pointer arithmetic and is not quite the same as regular arithmetic. We can actually see that here. An integer is actually at least 2 bytes and more often 4 bytes long. Adding 1 to a pointer (memory address) would normally get us the address of the next byte. We want the address of the next integer. The compiler knows this and translates the arithmetic to it actually adds 1 times the size of the thing being pointed to. So ptr + 1 is really ptr + 4. You never have to worry about this translation. It happens automatically.
We can now pass arrays into functions. If we write the function to take
a pointer to an array, we can pass the array into the function.
void
nyarf(int * p) {
cout << p[0] << endl;
cout << *p << endl;
} // nyarf
We can call this function like this,
nyarf(foo);
This works since the type of foo, by itself, is pointer to int. This
is because the name of an array is a pointer to the base type. We could
also call this as
nyarf(ptr);
since ptr points at foo.