References & Pointers
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
= nullptror a real address, never bare. - Check before use — test
ptr != nullptrbefore 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
- Write a function
reset(int &value)that sets its argument to 0. Verify the caller's variable changed. - Write
swap(int &a, int &b)and use it on two variables. - Declare a pointer to an
int, point it at a variable, print both the address and the value. - Set a pointer to
nullptrand guard a dereference with anif. - Explain to a friend when you would choose a reference instead of a pointer.