Important takeaways

  • declaring pointers: type *name
    • string *city;
    • int *age;
    • float *area;
  • assigning a memory address to a pointer: &variable or new type (dynamic memory)
    • city = &myCity;
    • age = new int;
    • area = new float;
  • accessing the memory that a pointer points to (dereferencing the pointer): *name
    • cout << *city << endl;
    • *age = 10;
    • sqFootage = *area;
  • aways delete memory allocated using new when finished using it:
    • delete age;
  • dynamic arrays:
    • use memory allocated from the heap
    • the size can be determined at run time
    • use new type[size] to allocate
    • use delete[] to deallocate
    • multi-dimensional arrays are similar; see examples below

Contents

Pointers

Up until now, we have used variables to store a value, like a number or string. If we have two variables and we want them to be set to the same value, we have to assign that value to each variable independently. If we change the value of one variable after the assignment, the other variable's value is left unchanged. For example, consider the following lines of code:

This code creates two int variables: x and y. We set the value of x to 10. We then copy that value to y in line 2. At the completion of line 2, the value 10 is stored in both x and y. On line 3, we update the value of x to be 20. y remains unchanged and still stores the value 10. Line 4 causes 20, 10 to be printed to the console.

What if we want x and y to be linked together so that if we update one, we update both? We can do that using pointers. Specifically, pointers allow us to store the address of a memory location. So we can store a value in a regular variable, like 10 in the integer variable x, then create a pointer to an integer variable named y, and finally assign the address of x to y (this is what links them). We can then use special syntax to access the value pointed to by y. Here's an example:

Note that we use the asterisk two ways. First, in the declaration of a pointer, we need an asterisks before the variable name. Second, we can dereference the pointer, i.e., specify the value that the pointer points to, by also placing an asterisks before the variable name. We often refer to dereferencing as "following" the pointer. To get the address of any variable, we use an ampersand prior to the variable name.

Here's what the memory looks like:

VariableMem.
Addr.
Binary
value
Decimal
value
int x10000000010
200000000
300000000
400001010
int *y5000000001
600000000
700000000
800000000
900000000
1000000000
1100000000
1200000001

In this table, x is given memory address 1. Suppose we are on a system where it takes up a total of 32 bits, or four bytes, starting at address 1. If our memory is set up in one byte slots, then we need four slots (addresses 1&ndah;4). When we assign 10 to x, that is stored as a 32 bit number spread across the four slots (I'm showing the decimal number just so it's easy to read). When we create the pointer to an int called y, on a 64-bit operating system we need to assign a total of eight slots, because an address can require up to 64-bits. When we assign the address of x—1—to y, we store that address as a binary number spread across the eight slots. As an aside, the type of a variable tells C++ how many memory slots it needs to look at for that variable. For a pointer, the type of pointer tells C++ how many memory slots the thing the pointer points to takes up. So an int pointer points to four slots of memory.

(Back to top)

Dynamic memory

While pointers allow us to link two variables, we use them mostly with dynamic memory. First, lets talk about how a program sits in memory. Each program consists of four segments of memory: the compiled program code, global constants, a segment called the stack for local variables and function parameters, and finally, a segment called the heap from which dynamic memory can be allocated.

The stack segment is of limited size and because of that, if your program stores lots of data in memory, it is beneficial to allocate memory from dynamic memory. For right now, let's not worry so much about why, and worry more about how. The first thing is, we need to make use of pointers. So, if we want to allocate space for an integer, the first thing we need is an integer pointer:

Now we need to assign this pointer the address of a block of memory allocated from the heap. To do that, we will use the new keyword, like this:

This says, "Create a new int pointer called x, allocate memory for an int in the heap, and assign the address of that memory to x". We can assign a value to that memory by dereferencing the pointer and setting the value:

Dynamic memory is great, but it is important that for every piece of memory we allocate, we also deallocate it. That is, we need to free the memory so that another process may use it. We do that using the delete keyword. It is important to do so only after you are finished using the memory. Here's a complete example of allocating, setting, and then deallocating dynamic memory:

(Back to top)

Dynamic arrays

In the long run, static arrays (which we learned about earlier) will hold us back since we have to know their size at compile time. Dynamic arrays do not have this same constraint, and they should be easy now that we know about pointers!

Dynamic arrays are arrays that we allocate memory for in the heap using the new keyword. The process is only slightly different than what we did with static arrays.

Dynamic 1D arrays

Let's start with 1D arrays. Here's an example:

First of all, our array myArray is an int pointer—just like before! But the memory it points to isn't just for an int, it's for three ints. We say this by using square brackets after the type when we allocate memory with the new keyword: new int[3];.

Next, we don't need a * to dereference our array—the square brackets are doing that for us automatically. So, myArray[0] will access the first thing in the array, just like with a static array.

Finally, we cannot use the regular delete keyword to deallocate our array. Rather, we need to use a special version: delete[]. And that's it!

So we said that dynamic arrays are nice because we can get the size from the user, right? Here's a demonstration of allocating space for an array based on what the user requests:

This program reads in the size of the array from the user, creates a dynamic array of that size, then prompts the user to enter the value that should be stored at each index. Running this gives the following (the user's input is in orange):

$ ./arrays
How many entries should the array be? 4
Please enter a number to store at index 0: 948
Please enter a number to store at index 1: 32
Please enter a number to store at index 2: 39
Please enter a number to store at index 3: 22342

You can pass dynamic 1D arrays to functions just like how we learned to pass static 1D arrays here.

Dynamic multi-dimensional arrays

Declaring and initialize dynamic multi-dimensional arrays is similar to 1D arrays, except that each dimension needs to be handled separately. In addition, passing dynamic multi-dimensional arrays is not the same as their static counter parts! The example below demonstrates how they compare.

(Back to top)