Pointers

This lesson provides a thorough introduction to pointers in C++, covering their definition, usage, and the distinction between pointers and references
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
3D art showing a female blacksmith character
Ryan McCombe
Ryan McCombe
Updated

Previously, we introduced reference variables. These were a way to, indirectly, have two variables pointing to the same location in memory.

Working with memory addresses is often considered quite dangerous - it can introduce some nasty bugs if not managed correctly.

Because of this, references had two restrictions, designed to ward off the most common cause of those bugs:

  1. References must be initialized with a value
  2. References cannot be reassigned

However, sometimes our use case requires us to do one or both of these things. For this reason, we have pointers.

The Address Of Operator: &

We can get the address of where a variable is stored in memory by using the & operator. This is referred to as the address-of operator.

#include <iostream>
using namespace std;

int main(){
  int x{1};

  // Log out the location of x in memory
  cout << &x;
}

The above program will output a memory address, which may look something like the following:

0x7ffe88a53e88

Doesn’t & mean reference?

Pointers and references are generally considered the most confusing topics learners encounter in their C++ journey, and the use of the same syntax to mean different things is one of the reasons why.

We’ve already seen & being used to denote a reference. For example, a reference to an int is an int&.

Now we’re seeing & being used for a different purpose. Confusingly, the & syntax has a different meaning depending on where it appears in our code.

When we use & next to a data type, we are saying we want to work with a reference to that data type:

int x { 5 };

// Create a reference to x
int& ReferenceToX { x };

When we use & with a value that has an identifiable memory location, such as the name of a variable, & is an operator. It is going to operate on that variable - specifically, it will get its memory address

int x { 5 };

// Find out where x is stored in memory
&x;
Test your Knowledge

The Address Of Operator

How can we find the memory address used by the isAlive variable?

bool isAlive { true };

Pointers

What gets returned from the & operator is referred to as a pointer. It points to a location in memory. A pointer is a value like any other - it can be stored in variables, stored as members of a class, handed off to functions, and more.

A pointer type includes the underlying type (ie, the type of data being pointed at), and a * suffix.

For example:

  • A pointer to an int is an int*
  • A pointer to a bool is a bool*
  • A pointer to a Character (a user-defined type) is a Character*

For example, if we wanted a variable called MyPointer to store a pointer to an int, we could declare it like this:

int* MyPointer;

If we use the address of operator, & on a variable containing an int, we will get an int* - a pointer to an int

We can store that like any other variable:

int x { 1 };
int* MyPointer { &x };

And we can pass them to functions:

void HandlePointer(int* Pointer) {
  // ...
}

int main() {
  int x{42};
  HandlePointer(&x);
}
Test your Knowledge

Creating Pointers

How can we create a variable called FloatPointer to store a pointer to a float?

Dereferencing Pointers

With references, we could just work with the reference as if it were the base data type. We could use a reference to an int (an int&) as if it were an int.

We can't do that with pointers. To access the value that a pointer points to, we first need to visit the memory address the pointer is pointing at, and get the value stored there. This is referred to as dereferencing. the pointer and it is done using the * operator.

The * operator returns an object of the underlying type. For example, dereferencing an int* (a pointer to an int) will return an int (a simple int value)

#include <iostream>
using namespace std;

void HandlePointer(int* Pointer){
  int Dereferenced{*Pointer};
  cout << "Dereferenced: " << Dereferenced;
}

int main(){
  int x{42};
  HandlePointer(&x);
}
Dereferenced: 42

More confusion: doesn’t * mean something else?

Just like & is used to mean different things, so too is *.

When we use * next to a data type, we are saying we want to work with a pointer to that data type:

// A pointer to an int
int* PointerToInt;

When we use * with a variable that is a pointer, or an expression that returns a pointer, the * then acts as an operator. Specifically, it is dereferencing that pointer, returning its underlying value:

// Dereference a pointer, returning the
// underlying value - an int, in this case
int x { *PointerToInt };

Operator Precedence

Previously, we covered how operators have different precedence. When we have multiple operators acting in a single expression, precedence controls what happens first.

For example, in an expression like 1 + 2 * 3, multiplication happens first.

Precedence rules apply to non-arithmetic operators too, such as the dereferencing operator.

For example, the following code will not compile:

int main() {
  int x{42};
  int* Pointer(&x);
  *Pointer++;
}

This is because the incrementing ++ operator has higher precedence than the dereferencing operator *, so it happens first. It’s equivalent to this:

int main() {
  int x{42};
  int* Pointer(&x);
  *(Pointer++);  
}

The dereferencing operator has fairly low precedence in general, so when using it, we will often need to introduce brackets to ensure it happens first:

int main() {
  int x{42};
  int* Pointer(&x);
  (*Pointer)++;  
}

What's the precedence of the other operators?

It's not important to know the precedence of all the operators. Even among professional developers, very few will have committed that to memory, because it's so easy to look up when it's needed.

The precedence of all operators in C++ is given here: https://en.cppreference.com/w/cpp/language/operator_precedence

The important thing to remember is the concept. If we see a bug in an expression that uses multiple operators, we should be aware that the problem might be caused by operator precedence, and we may need to add brackets.

Pointers vs References

To put all these concepts together, let's look at how we can get our Increment function that we originally implemented with references to use pointers instead. Here's the version using a reference:

1void Increment(int& Number){
2  Number++;
3}
4
5int main(){
6  int x{1};
7  Increment(x);
8}

And here it is using a pointer:

1void Increment(int* Number){
2  (*Number)++;
3}
4
5int main(){
6  int x{1};
7  Increment(&x);
8}

The notable changes here are:

On line 1: Our function no longer accepts an int& (a reference to an integer). Instead, it now expects an int* (a pointer to an integer)

On line 2: Our function body can no longer treat Number as an integer. It is now a pointer, so we need to dereference it before accessing or modifying the underlying value

On line 7: We can no longer just pass an int into our function and have the compiler implicitly convert it to the correct type for us automatically.

When using a pointer, we need to be more explicit. Therefore, we use the address of operator & to ensure our function receives the pointer it expects.

Why use references if pointers are more powerful?

Now that we've introduced pointers, it would be easy to dismiss references as being unnecessary. Pointers can fulfill the same need and more, given they are less restrictive.

However, references do have their benefits. The above code samples hopefully demonstrate that references are a bit easier to work with.

Additionally, the restrictions put on references are with good reason. In more complicated examples, the additional work of managing pointers can get a lot more complex.

So, it's a good practice to prefer references where possible. We should only use pointers when restrictions around references make them unusable for the specific problem at hand.

Test your Knowledge

Dereferencing Pointers

What should we put in the body of this function to double the value pointed at by the Number pointer?

void Double(int* Number) {
  // ??
}

The Arrow Operator: ->

When we’re working with pointers to our custom types, a common requirement we’ll have is to dereference the pointer, and then access one of its members.

The member access operator . has higher precedence than the dereferencing operator *, so we need to use brackets here:

void Combat(Monster* Enemy){
  (*Enemy).TakeDamage(50);
}

C++ provides an alternative syntax for this, called the arrow operator. We can think of it as combining the dereferencing operator * and the member access operator .:

void Combat(Monster* Enemy){
  Enemy->TakeDamage(50);
}

This is much more common, therefore, it will be our preferred approach going forward.

Note, that we only use the -> operator with pointers to an object. When we have the actual object by value, or a reference to it, the . is still used to access its members.

// We use . with objects
Character MyCharacter;
MyCharacter.TakeDamage(50);

// We use . with references
Character& Reference { MyCharacter };
Reference.TakeDamage(50);

// We use -> with pointers
Character* Pointer { &MyCharacter };
Pointer->TakeDamage(50);
Test your Knowledge

Member Access

Given the following code, what could we put on line 7 to call the Equip function of the Weapon passed in as a parameter?

class Weapon {
public:
  void Equip() {}
};

void PrepareForBattle(Weapon SelectedWeapon) {
  // ???
}

Given the following code, what could we put on line 7 to call the Equip function of the Weapon passed in as a parameter?

class Weapon {
public:
  void Equip() {}
};

void PrepareForBattle(Weapon& SelectedWeapon) {
  // ??
}

Given the following code, what could we put on line 7 to call the Equip function of the Weapon passed in as a parameter?

class Weapon {
public:
  void Equip() {}
};

void PrepareForBattle(Weapon* SelectedWeapon) {
  // ??
}

Null Pointers

When working with pointers, we’ll often need a way to represent empty values.

For example, we may be making a game where our player can have a weapon:

class Weapon {};

class Character {
public:
  Weapon* mWeapon;
};

To make a pointer point to nothing, representing the absence of a value, we use the nullptr keyword:

class Weapon {};

class Character {
public:
  Weapon* mWeapon{nullptr};
};

We should never attempt to dereference a nullptr using the * or -> operator. If we need to dereference a pointer, and we think it may be a nullptr, we can first check for that condition using an if statement:

#include <iostream>
using namespace std;

class Weapon {
public:
  string mName{"Iron Sword"};
};

class Character {
public:
  Weapon* mWeapon{nullptr};
};

int main(){
  Character Player;
  Weapon Sword;
  if (!Player.mWeapon) {
    cout << "I am unarmed";
  }
  Player.mWeapon = &Sword;
  if (Player.mWeapon) {
    cout << "\nBut not any more! Behold my "
      << Player.mWeapon->mName;
  }
}
I am unarmed
But not any more! Behold my Iron Sword

Finally, let's see a slightly more complex example. Our classes can contain references to other objects of the same class.

For example, a Character can have a member variable that is a reference or a pointer to another Character object.

This is the case for the mEnemy variable within our Character class below:

#include <iostream>
using namespace std;

class Character {
public:
  Character(string Name): mName{Name}{}

  void SetEnemy(Character* Enemy){
    mEnemy = Enemy;
  }

  void LogEnemy(){
    if (mEnemy) {
      cout << "\nEnemy: " << mEnemy->mName;
    } else {
      cout << "\nI don't have an enemy";
    }
  }

  string mName;
  Character* mEnemy{nullptr};
};

int main(){
  Character Player{"Anna"};
  Player.LogEnemy();

  Character Enemy{"Goblin Warrior"};
  Player.SetEnemy(&Enemy);
  Player.LogEnemy();

  Character AnotherEnemy{"Vampire Bat"};
  Player.SetEnemy(&AnotherEnemy);
  Player.LogEnemy();
}
I don't have an enemy
Enemy: Goblin Warrior
Enemy: Vampire Bat

Summary

In this lesson, we explored the fundamental concepts of pointers. The key points include:

  • Understanding the address-of operator (&) and how it differs from a reference.
  • Learning about pointers, their declaration, and how they point to memory locations.
  • Grasping the concept of dereferencing pointers to access or modify the value at the memory address they point to.
  • Recognizing the importance of operator precedence in expressions involving pointers.
  • Comparing pointers and references, understanding their uses, and the advantages of references in certain scenarios.
  • Utilizing the arrow operator (>) for member access in objects pointed to by pointers.
  • Handling null pointers using nullptr and ensuring safety checks before dereferencing.

Preview: Exploring the this Pointer

In our upcoming lesson, we'll delve into the this keyword. This special pointer points to the object on which a member function operates.

By using this, we unlock new possibilities and approaches in our code. Key Highlights of the Upcoming Lesson:

  • The this Pointer: We'll explore what this is and how it inherently becomes part of every member function.
  • Chainable Methods: Discover how this can enable the chaining of methods, allowing for more fluent and readable code.
  • Self-Referential Code: See examples of how this can be used to compare objects or pass the current object to other functions.
  • Operator Overloading: Learn how this can be used to correctly overload operators that modify their operands, such as ++ and *=.

Was this lesson useful?

Next Lesson

The this Pointer

Learn about the this pointer in C++ programming, focusing on its application in identifying callers, chaining functions, and overloading operators.
3D art showing a fantasy pirate character
Ryan McCombe
Ryan McCombe
Updated
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, Unlimited Access
Memory, References and Pointers
3D art showing a progammer setting up a development environment
This lesson is part of the course:

Intro to C++ Programming

Become a software engineer with C++. Starting from the basics, we guide you step by step along the way

Free, unlimited access

This course includes:

  • 60 Lessons
  • Over 200 Quiz Questions
  • 95% Positive Reviews
  • Regularly Updated
  • Help and FAQ
Next Lesson

The this Pointer

Learn about the this pointer in C++ programming, focusing on its application in identifying callers, chaining functions, and overloading operators.
3D art showing a fantasy pirate character
Contact|Privacy Policy|Terms of Use
Copyright © 2024 - All Rights Reserved