References & Pointers

Scope: References and pointers both let code reach a variable without copying it. They are the foundation of efficient functions and of C++'s memory model. This lesson teaches both, shows when each is appropriate, and gives concrete safety rules.

References

A reference (&) is an alias: a second name for an existing variable. Writing through a reference changes the original — there is no copy involved.

int score = 90;
int &alias = score;    // alias is another name for score
alias = 95;           // score is now 95
std::cout << score;   // prints 95

A reference must be bound when it is declared — there is no "unbound" reference — and it can never be rebound to another variable. It always refers to the same object for its whole life.

const references

When a function only needs to read an argument, pass a const reference. It avoids the copy cost while guaranteeing the function cannot modify the original:

void show(const std::string &name) {   // read-only access, no copy
    std::cout << "Hello " << name << "\n";
}

Pointers

A pointer is a variable that stores the address of another object. Where a reference is a permanent alias, a pointer is a value: it can be created, reassigned, compared, and set to "point at nothing".

int score = 90;
int *ptr = &score;      // & takes the address; ptr "points to" score
std::cout << ptr;       // prints an address, e.g. 0x7ffd...

The type int * reads as "pointer to int". Every pointer knows the type it points to, which lets the compiler check the operations you perform through it.

nullptr

A pointer that points at nothing holds nullptr. Always initialize pointers to nullptr and check before use — an uninitialized pointer is a time bomb.

int *ptr = nullptr;         // safe starting state: points at nothing
if (ptr != nullptr) {       // guard before dereferencing
    std::cout << *ptr;
}

Dereferencing

The unary * operator follows the pointer to reach the object it points to. Dereferencing nullptr is undefined behavior — a crash in practice.

int a = 5, b = 7;
int *p = &a;
*p = 100;          // a becomes 100 (write through the pointer)
p = &b;            // p now points to b instead
*p = 200;          // b becomes 200

References vs pointers

The two concepts overlap, and the choice is mostly about intent:

  • References: must be bound at declaration, never null, never rebound. Use them for function parameters and local aliases.
  • Pointers: can be null, can be reassigned, can be stored in arrays. Use them when the object may be absent or when the identity must change over time.

Modern C++ guidance: prefer references by default; use pointers only when nullability or reassignment is genuinely needed.

Passing to functions

Function parameters are copied by default. To avoid the copy you can pass a reference or a pointer.

Pass by reference

A reference parameter aliases the caller's variable, so writes inside the function affect the original — useful for output parameters or in-place updates:

void increment(int &value) {   // reference parameter
    value++;                   // modifies the caller's variable
}

int main() {
    int n = 10;
    increment(n);
    std::cout << n;            // 11
    return 0;
}

Pass by pointer

Passing a pointer keeps the option of "nothing to update" — the function checks for nullptr:

void increment(int *value) {
    if (value != nullptr) {   // guard: caller may pass nullptr
        (*value)++;
    }
}

int main() {
    int n = 10;
    increment(&n);            // pass the address
    increment(nullptr);       // safe: the guard skips
    return 0;
}

Pointers and arrays

In C++, the name of an array decays to a pointer to its first element in most contexts. Pointer arithmetic then moves between elements: p + 1 points to the next element, not the next byte.

int data[3] = {10, 20, 30};
int *p = data;          // p points to data[0]
std::cout << *p << "\n";           // 10
std::cout << *(p + 1) << "\n";     // 20 — one element forward
std::cout << p[2] << "\n";         // 30 — pointer indexing works too

This is powerful and subtle. For daily code, prefer containers such as std::vector that manage bounds and size for you; keep pointer arithmetic for the algorithms and low-level code where it truly matters.

Safety rules

Pointers cause most of C++'s infamous crashes. These four rules eliminate the classic ones:

  • Always initialize — declare with = nullptr or a real address, never bare.
  • Check before use — test ptr != nullptr before dereferencing.
  • Never return a pointer to a local variable — the variable dies when the function ends, leaving a dangling pointer.
  • Prefer smart pointers for ownership — the memory lesson introduces std::unique_ptr, which frees memory automatically.

Practice

  1. Write a function reset(int &value) that sets its argument to 0. Verify the caller's variable changed.
  2. Write swap(int &a, int &b) and use it on two variables.
  3. Declare a pointer to an int, point it at a variable, print both the address and the value.
  4. Set a pointer to nullptr and guard a dereference with an if.
  5. Explain to a friend when you would choose a reference instead of a pointer.