C++: Pointers and References
In this tutorial we're going to talk about two core concepts of C++ - references and pointers. Don't worry if you get a little bit confused by different concepts, this is the area in which people usually trip up. I would say this is where the largest proportion of people learning C++ just give up – do not give up, if you don't understand something then try reading it again, and if you're still having problems then post on the forums and we'll try to fill the gap in your knowledge.
So firstly, let's talk about pointers. Pointers are the more confusing of the two things we're going to be talking about in this tutorial, however in learning about pointers we'll cover everything we need to know to easily understand references. Before we actually get into talking about pointers themselves though, we need to learn about memory and two important operators...
When we talk about memory in computers, we're usually talking about RAM. RAM stands for Random Access Memory and is completely different to what you might refer to as "memory" on your hard drive. Whenever we create a normal variable in C++, the information is (usually) stored at a point in the computer's RAM. The RAM itself is organised a little bit like a long tabular list – it's nice to visualise as a table with two columns but many rows. Each row has a name which it can be identified by, and a value. The identification or "name" of each "cell" is called the memory address, and this address is usually a set of hexadecimal digits. If you don't know what hexadecimal is, it's a number system with a base of 16 – this most basically means that just like we, with a number system of base 10, can have a maximum of '9' in one column, hexadecimal can have a maximum of 15 in one column – numbers 10 to 15 are expressed in one column through the usage of the letters A to F. It's worth noting that hexadecimal is usually denoted by the '0x' prefix - so 0xF (hexadecimal) is 15 in our decimal system. With this knowledge, we can visualise the contents of our RAM to look something like the following:
In the example above I've just filled the different addresses with some random pieces of data – remember in a real section of RAM there will probably be a huge number of addresses. It's also important to note that depending how many bits (of space) a piece of data takes up, it may (and probably will) span across several addresses. For this reason, when reading data straight from a memory address (/memory addresses) it's important that you know what type/structure of data you are reading (so you know where to start and when to stop reading).
With this knowledge of how we store basic things in memory in mind, we should now proceed to learn about two extremely important operators which essentially underpin the whole concept of pointers. The first operator which we need to learn about is called the address-of or the reference operator. As the name would suggest, the address-of operator essentially gets the memory address of whatever it's applied to. It is represented in C++ by the ampersand, &, symbol, and so something like the following would output the memory address where the variable 'x' is stored in memory:
#include <iostream>
#include <string>
using namespace std;
int main()
{
string x = "Hi";
cout << "String 'x' is at the memory address: 0x" << &x;
}
As you can imagine, this is extremely powerful and useful. It becomes even more useful however when used in conjunction with the 'value pointed by' or dereference operator, represented in C++ by an asterisk, *, symbol. The dereference operator essentially does the opposite of the reference operator, it can get the value at a given memory address (for example getting '6' from 0x03 in the RAM visualization image I showed you earlier). While it might currently seem pointless to go back to what we had before, the value of 'x' itself, I can assure you that you will see how useful this is in just a minute. In this example, because 'x' is a string, the C++ compiler will know that if we write something like *&x, we want it to find a string value at the address we specified. So something like the following in which the two operators essentially just cancel themselves out to just leave the equivalent to 'x', works fine:
string x = "Hi"; *&x = "Hello"; //Change the value of 'x' cout << "String 'x' is: " << x;
The above works great, but the issue is that we're still calling 'x' by it's name directly, this means that in the above example we could remove all of the fancy operators we just learnt and it would still work fine. What makes the operators really useful is indirect addressing – changing/accessing a variable without using its name.
When trying to get around this issue without knowledge of pointers however, you can run into some issues. Let's say for example, you wanted to try and indirectly address 'x' by storing its memory address in an integer and then accessing the value of that. You might write something like the following:
string x = "Hi"; int address = &x; cout << "String 'x' is: " << *address;
There are a couple of issues with trying to solve the problem this way. The first is that the compiler will try to protect you -- it will see that you're trying to store the memory address of a string in an integer, and it will try and stop you because it knows that when you then try and access that data later, you might not know what type of data is stored at 'address' and hence won't know how to properly interpret the data stored there. The second is exactly what I just said the compiler is trying to protect you from – even if the initialisation of 'address' was successful – how does the compiler know how much data to read from the address you specified? It doesn't know what datatype/structure is stored at that address and so won't read it properly.
To prove that I didn't just lie with the second problem there, we can stop the compiler worrying about the whole 'string' and 'int' thing, by doing what is called a type cast on &x in the initialisation to make the compiler treat the hexadecimal address like an integer. Type casts should usually be avoided wherever possible, however we're going to use them here simply to demonstrate a point (something they're very good for). We can write the most basic type cast in C++ by writing the variable type in brackets next to the expression we want to treat like that type – so (int)&x will be what we want to intialise 'address' to in this case. You should be able to see that on trying to compile the code now, the error shifts to the cout line as it doesn't know what it's reading from the address.
The solution to all of these problems lies with the pointer variable type. It solves the problem of a memory address perhaps being bigger than the size of an integer (which would completely break our previous attempt at indirect addressing), it solves the problem of not knowing what type of data is at a certain address, and it solves the problem of you making errors in your code that you didn't mean to make. Pointers are essentially just really a really nice way of storing memory addresses which are associated with certain datatypes.
Pointers are created in C++ by writing an asterisk to the left of the datatype or the right of the variable name – it's very important to remember that the asterisk here is simply to declare a pointer and should not be confused with the dereference operator. Pointers have a datatype like any normal variable as to get over the issues we previously discussed – if a regular datatype is associated with a memory address, then we don't run into the issue of not knowing how to read that memory address. There are large arguments over if it is better to write the asterisk in pointer declaration next to the datatype or the variable name (if your interested, here is a good place to start), but I personally like writing it next to the variable name as it then implies that the variable being declared is, for example, an 'int' pointer named whatever, instead of implying that the variable being declared is called whatever and is a pointer which happens to point to an integer. It's all down to personal preference and some people like writing it in-between like int * whatever, however when I write pointers I will be writing the asterisk next to the type.
So if we try to create the indirect behavior which we attempted (and failed) to create earlier, using pointers this time, we could write something like this:
string x = "Hi"; string* string_pointer = &x; //Set our string_pointer to the address of 'x' cout << "String 'x' is: " << *string_pointer;
On compiling the code above, you should see that this time it works! Remember because the pointer itself simply holds a memory address, we have to use the dereference operator on it whenever we want to talk about the value that the address holds. Using the same technique we could change the value of 'x' using the pointer:
string x = "Hi";
string* string_pointer = &x; //Set our string_pointer to the address of 'x'
*string_pointer = "Hello"; //Set the value at string_pointer ('x') to "Hello"
cout << "String 'x' is: " << *string_pointer;
Brilliant, so we can now indirectly address variables! The big question peers, how is this useful?
As I alluded to earlier, pointers are useful in many ways, however one of the most common ways they can be used is so that functions can directly modify data from the main function. Usually if we pass parameters to functions we are actually creating a copy of the parameters and sending those to it, hence something like the following won't work as intended:
void SwapNumbers(int x, int y)
{
int temp;
temp = x;
x = y; //Set 'x' to 'y'
y = temp; //Set 'y' to what 'x' originally was
}
int main()
{
int x = 10;
int y = 20;
cout << x << " " << y;
SwapNumbers(x, y);
cout << x << " " << y;
}
However with pointers we can play with values outside of the function as much as we like. If you've ever wanted to return more than one thing from a function (as using return has been the only way to get a result from a function up until now), then pointers may be a good solution for you. Of course the more we let function mess with things, the higher chance things will start to go wrong (and if used incorrectly, pointers can be dangerous – remember you're messing directly with memory addresses!), however if used correctly they are extremely useful. The code snippet that we just talked about not working properly could be made to have it's intended purpose by making the function take pointers as parameters, like so:
void SwapNumbers(int* x, int* y) //The function takes pointers as parameters
{
int temp;
temp = *x; //Remember to use the dereference operator
*x = *y; //Set 'x' to 'y'
*y = temp; //Set 'y' to what 'x' originally was
}
int main()
{
int x = 10;
int y = 20;
cout << x << " " << y << endl;
SwapNumbers(&x, &y); //Pass in the memory addresses
cout << x << " " << y << endl
}
This is called passing by reference instead of passing by value (passing copies of the values) and it makes functions extremely powerful.
As a quick sidenote before we stop talking about pointers for a little bit and move onto references, if we wanted our original attempt at indirect addressing to work (even though it's much easier, cleaner, and safer in every way to use pointer type variables) we could actually typecast the integer we used to store the address as a string pointer ((string*)) and use the dereference operator on this to get the value of 'x':
string x = "Hi"; int address = (int)&x; cout << "String 'x' is: " << *((string*)address);
Although pointer type variables are simply another abstraction of the language and the same operations can be accomplished via methods like the above (if you understand the above method, you understand pointers) – please remember that the method above still does have some of the original issues that we outlined and so shouldn't be used in any production program.
With pointers done and dusted, let's talk a little bit about the younger brother of pointers – references. References, are basically just aliases (another way of referring) to variables – they are constant pointers. The advantage to using them over "regular pointers" is that they are constant – there is no worry about the reference changing what it points to half way through the program, and hence references are generally considered safer, although less powerful. The bottom line is that when references can be used instead of pointers, they should be used. Take our simple program that swaps numbers for example – since the pointers in the function parameters should always point towards the same 'x' and 'y' in the main function, references could (and hence probably should) be used in place of pointers. This may leave you thinking that pointers don't have their place – but trust me they do, being able to change where something points to is extremely powerful and will be something we utilise later in this tutorial series.
Similarly to pointers, references are declared in C++ using a special operator that is placed either after the datatype or before the variable name (or somewhere inbetween). Once again my preference is the put the symbol next to the datatype, and the symbol in this case is the ampersand symbol (&). Make sure you don't confuse the ampersand symbol in a reference declaration with the address-of operator which does an entirely different thing. As references act as an alias for a variable itself rather than containing a memory address, there is no need to use the dereference operator when you want to change the value of the variable the reference points to. Using this information, we could re-write our program which swaps the numbers in the main function around like so:
void SwapNumbers(int& x, int& y) //The function takes references as parameters
{
int temp;
temp = x; //No need to use the dereference operator
x = y; //Set 'x' to 'y'
y = temp; //Set 'y' to what 'x' originally was
}
int main()
{
int x = 10;
int y = 20;
cout << x << " " << y << endl;
SwapNumbers(x, y); //Pass in the variables we want to alias
cout << x << " " << y << endl
}
Back to C++

