Pointers & References

You already met references briefly, in the Functions lesson, as a way to let a function modify a caller's variable. This lesson covers them properly, alongside pointers — the more direct, C-style tool for working with memory addresses — and lays out exactly how the two differ.

Pointers: the direct approach

A pointer holds a memory address. & gets the address of a variable, and * in a declaration marks a variable as a pointer; * in front of a pointer dereferences it, reaching the value it points to:

</> pointer_basics.cpp
#include <iostream>

int main() {
    int age = 30;
    int* agePtr = &age;

    std::cout << "Value of age: " << age << std::endl;
    std::cout << "Value through pointer: " << *agePtr << std::endl;

    *agePtr = 31;
    std::cout << "Value of age after change: " << age << std::endl;
    return 0;
}
Terminal output
Value of age: 30
Value through pointer: 30
Value of age after change: 31

agePtr stores age's address, not its value. *agePtr follows that address back to the actual int, so reading it gives 30, and assigning through it with *agePtr = 31 changes age itself — there's only one int here, just two ways to reach it.

References: a safer alias

A reference, declared with & after the type, is simpler than a pointer: it's just another name for an existing variable, with no separate dereference step needed:

</> reference_basics.cpp
#include <iostream>

int main() {
    int age = 30;
    int& ageRef = age;

    ageRef = 31;
    std::cout << "age: " << age << std::endl;
    std::cout << "ageRef: " << ageRef << std::endl;
    return 0;
}
Terminal output
age: 31
ageRef: 31

ageRef and age refer to the exact same storage — assigning to ageRef is indistinguishable from assigning to age directly, no * needed to read or write through it.

The key differences

Three things separate a reference from a pointer, and they're the reason references are usually preferred when either would work:

</> differences.cpp
#include <iostream>

int main() {
    int x = 5;
    int y = 10;

    int* ptr = &x;
    ptr = &y;              // a pointer can be reassigned to point elsewhere
    std::cout << *ptr << std::endl;

    int& ref = x;
    ref = y;                // this does NOT rebind ref -- it assigns y's value into x
    std::cout << x << " " << ref << std::endl;
    return 0;
}
Terminal output
10
10 10

Reassigning ptr makes it point at a completely different variable. Writing ref = y looks similar but means something entirely different: ref is permanently bound to x from the moment it's declared, so ref = y just copies y's value into x — it can never be made to refer to y instead.

Note: a pointer can be nullptr, meaning it points at nothing, and dereferencing a null or otherwise invalid pointer is undefined behavior — often a crash, sometimes worse. A reference has no null state at all; it must be bound to a real variable the moment it's declared. That guarantee is exactly why references are the default choice for function parameters, and pointers are reserved for cases that specifically need reassignment or the ability to represent "no value."