Neil's C++ Stuff



Pointers & References Tutorial

I recommend you read this in the following order:

  1. Reference Variables
  2. Pointer Variables

C++ Reference Variables

When declaring a reference variable you must also make it refer to something at the same time. To declare on you simply make a variable of the type you are going to be referring to, make up your own name, and put an ampersand (&) in front of it:

int &ref;

That creates a reference variable called ref of type integer. Of course this doesn't do anything because you can't assign references to other variables at any other time than declaration. So to make ref refer to something we have to do it in the declaration:

int &ref = x;

This is all assuming we have a variable called x and that it is also an integer (int). But after doing this, anything we do to ref will effect x. Try this source code (cut and paste).

/* -example-source-1------------------------------------------------- */

#include <iostream.h>

void main()
{
  int x = 10;         // create integer variable called x
  int &ref = x;       // make a reference variable that refers to x

  cout << "x is " << x << " and ref is " << ref << endl;

  cout << "Now we change ref to equal 25 ... " << endl;
  ref = 25;
  cout << "And now x is " << x << " and ref is " << ref << endl;
}

/* ------------------------------------------------------------------ */

What I have explained so far with reference variables is actually fairly simple then. Just remember these rules:

If you don't understand to the point I have gotten right now then ask questions. Because this is the easy part... it gets, um difficult from here (in comparison).

Using Reference Variables in Functions

The previous section was an intro into reference variables. But to be quite honest if you use them like that then they're not really necessary. Reference variables come into shine when it comes to functions.

The point of reference variables and functions is that you can pass a variable as a parameter and have the variable changed in the function. Like in the following code snippet.

/* -example-source-2------------------------------------------------- */

#include <iostream.h>

void times2(int &x);  // function prototype

void main()
{
  int var;            // declare var as integer variable
  var = 10;           // put value of 10 in var
  cout << "var is " << var << endl;

  times2(var);        // call 'times2()' with var as parameter
  cout << "var is now " << var << endl;
}

void times2(int &x)
{
  x = x * 2;
}

/* ------------------------------------------------------------------ */

In the above example, whatever we did to x in the function 'times2()' would effect the actual variable we passed to the function. In this case we multiplied x by 2. We passed var to times2(). So x becomes a reference to var when we called it. Now we make x equal itself times 2. And since we passed var to the function it multiplies THAT by two.

But, you're probably thinking (maybe), you could just have times2() return a value. However in returning you can only get that one value from the function. With references you could get multiple values, like in the following.

/* -example-source-3------------------------------------------------- */

#include <iostream.h>

void times2(int &v1, int &v2);  // function prototype

void main()
{
  int x,y;
  x = 10;
  y = 15;
  cout << "x is " << x << " and y is " << y << endl;

  times2(x,y);

  cout << "x is now " << x << " and y is now " << y << endl;
}

void times2(int &v1, int &v2)
{
  v1 = v1 * 2;
  v2 = v2 * 2;
}

/* ------------------------------------------------------------------ */

Mara Hong tried the above program but took out all the ampersands to see the difference between passing by reference and passing by value. I thought I'd post that below as well. Because you can see that when you don't pass by reference, any modifications made to the parameters (v1 and v2 in this example) will not effect what you pass to the function (x and y for this case). Why? Because without passing by reference you're simply telling v1 and v2 to contain values. Anyway, try out the source, you'll see. (I added an extra line too, if you noticed):

/* -example-source-4-------------------------------------(Mara-Hong)- */

#include <iostream.h>

void times2(int v1, int v2);  // function prototype

void main()
{
  int x,y;
  x = 10;
  y = 15;
  cout << "x is " << x << " and y is " << y << endl;

  times2(x,y);

  cout << "x is now " << x << " and y is now " << y << endl;
}

void times2(int v1, int v2)
{
  v1 = v1 * 2;
  v2 = v2 * 2;
  cout << "v1 is " << v1 << " and v2 is " << v2 << endl;
}

/* ------------------------------------------------------------------ */

C++ Pointer Variables

Pointers are remarkably similar to reference variables. In fact reference variables are the same thing, just easier to use, but less dynamic. Pointers are the number one cause of pain with C and C++ programmers. You can do so much with them and at the same time you can fall into a deep pile of shit if you screw up.

What are POINTERS?

Pointers are data types. They store memory addresses. That's about it. Doesn't sound too complicated and its not but when you get into heavy use of them, little screw ups can lead to heavy crashes! In this document I will only deal with pointers to simple data types (integers and characters mostly).

Memory Addresses

Before I get into how to use pointers, you're going to need a basic understanding of memory addresses. Now these can differ when going to different platforms so I'll try to keep this pretty generic.

A memory address is a location within your computers memory. Its where something is. And each one of your variables has a memory address of its own. Which means that at that address in memory is the value of that variable. Say for instance you have a single integer named x and it has a value of 654. Let's pretend that x's memory address is "0x5FA70" (hexidecimal). Well, at the location "0x5FA70" in your computers memory is the number 654. Still, don't see it? Okay, well let's just say for the sake of example that your computer's memory looks like this:

memory addressvalue
0x5FA62...
0x5FA66...
0x5FA70654
0x5FA74...
0x5FA78...

See, now at the memory address "0x5FA70" is the number 654. I put "..." in the other boxes because we don't know what's at those memory locations. On a nerdy note, I wrote the memory addresses in hexidecimal because its easier to represent them that way. Also, the addresses in this depiction increase in increments of 4. That is because the size (in bytes) of an integer is usually four (unless you're working with a crummy ol' 16-bit compiler). Actually you can try out the following program to see how big (in bytes) your typical integer is with your compiler:

/* -example-[size of typical integer]-------------------------------- */

#include <iostream.h>

int main()
{
  int x;

  cout << "The size of integers on this compiler is " << sizeof(x)
       << " bytes." << endl;

  return 0;
}

/* ------------------------------------------------------------------ */

After compiling and running the above program you'll know the default size of integers on your compiler. So for instance if it told you that they were only two bytes long then you could change my example listing of memory to increase by two rather than four. So instead of going "0x5FA62", "0x5FA66", ... it would go "0x5FA62", "0x5FA64", "0x5FA66" ...

Remember, every single stinking variable has a memory address. It has to keep whatever you put into it somewhere. So when we stick that 654 into x it puts it at that memory address. The memory address is the place where that variable keeps its value. I suppose you might be able to say its like how everyone on the internet has an email address. Hmm, better yet. Since a memory address is a location ... if you were a variable then the place you were sitting/standing would be your memory address. And what you are is the value that is put there.

Getting a Variable's Address

At some point we'll need to know the address of a particular variable (get to WHY later). To do this we place an ampersand in front of the variable. When we do that, it is interpreted as the memory address of that variable rather than its value. Its like when/if you're a variable, pushing you aside to see where you're standing rather than to look at what/who you are. So in the case of &x we would see "0x5FA70" rather than 654. And how does this work, you ask?

Let's say we wanted to output the memory address of x. Run this short program:

/* -example-[memory address of 'x']---------------------------------- */

#include <iostream.h>

int main()
{
  int x = 654;

  cout << "The value of 'x' is " << x << ", its memory address is "
       << &x << endl;

  return 0;
}

/* ------------------------------------------------------------------ */

So by putting that ampersand in front of x we're telling the compiler, "we don't care what the hell it has, we want to know where it is!" You'll get varying results from this program, for example when I compiled it through DJGPP I did get "0x5FA70".

By this point you should have a very basic understanding of memory addresses. Enough so you kind of know what they are, and what's in them.

More Information

Memory addresses may stay basically constant on one machine. For example, on your computer you may always have usable address space at "0x5FA70". The values at memory addresses, however, are not fixed. When you run your program the variable x may very well be at that address. However, when you're program completes and exits (or crashes), the address becomes available again. The next time you run your program x could possibly be a memory address megabytes away!

Did you ever declare a variable and then view its contents before actually putting anything there? Well, try it out and you'll probably find that there's garbage data there. Garbage as in useless! When a process or program ends or at least stops using a part of memory, it does not automatically get cleared. This means that when another program comes along to use the same space, there will generally be crap there. That's why its a good idea to remember to initialize your variables before using them and never assume that the value will be zero. Some compilers will automatically generate code to reset variables when you declare them - do not depend on this under any circumstance.

An Analogy

So how about an analogy for this. Let's take a few houses. Each house is a memory address, the value in the house is the person that lives there. Note that people will move, but the houses always stay there. It might look something like this (relating to an earlier example above):

C++ Pointers House Analogy: the value 654 at address 0x5FA70

Think of "647" as the person that lives at address "0x5FA70". x could be the car that gets you to see that nice person, "647". If you put an ampersand(&) in front of x you are looking for the house address and you could care less about who's inside.


Contact

Comments or questions? Email me at webmaster@neilstuff.com.