Chapter 9: Pointers & References
Pointers & References in C++: The Gateway to Real Power (and Responsibility!)
Hello my fantastic student! ๐ Welcome to Lesson 9 โ Pointers & References โ one of the most important, most powerful, and most feared topics in C++!
Many beginners get scared of pointers โ but donโt worry! Iโm going to explain everything very slowly, very clearly, with tons of real-life analogies, step-by-step examples, visual explanations, common mistakes, and modern best practices.
By the end of this lesson, you will:
- Understand exactly what pointers and references are
- Know how to use them safely
- Know when and why to use each
- Master const correctness
- Use smart pointers (the modern, safe way)
Letโs start!
1. What is a Pointer? (Raw Pointers: * and &)
Real-life analogy: A pointer is like a house address. The house is the data (the actual value). The address is the pointer โ it tells you where the data lives in memory.
Two key operators:
| Operator | Meaning | Example |
|---|---|---|
| & | Address-of operator: โGive me the memory address of this variableโ | &x |
| * | Dereference operator: โGo to this address and get (or change) the value thereโ | *ptr |
Basic pointer example:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#include <iostream> int main() { int age = 25; // normal variable int* ptr = &age; // ptr now holds the memory address of age std::cout << "Value of age: " << age << "\n"; // 25 std::cout << "Address of age: " << &age << "\n"; // some hex number, e.g. 0x7ffee4c0a4ac std::cout << "Value stored in ptr: " << ptr << "\n"; // same hex number std::cout << "Value pointed to by ptr: " << *ptr << "\n"; // 25 // Change value through pointer *ptr = 30; std::cout << "Now age is: " << age << "\n"; // 30 !! return 0; } |
Visual memory picture:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
Memory: [ ... ] [ age: 25 ] โ at address 0x7ffee4c0a4ac [ ... ] ptr โ 0x7ffee4c0a4ac (points to age) *ptr โ 25 |
2. nullptr โ The Safe โNullโ Pointer
Never use plain 0 or NULL for null pointers in modern C++!
Use nullptr โ itโs type-safe and clear.
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
int* ptr = nullptr; // points to nothing (safe!) if (ptr == nullptr) { std::cout << "Pointer is null\n"; } // Dangerous (old style - avoid!): // int* bad = NULL; // can cause subtle bugs // int* worse = 0; |
Very common mistake โ dereferencing null pointer:
|
0 1 2 3 4 5 6 7 |
int* ptr = nullptr; *ptr = 100; // CRASH! (segmentation fault / undefined behavior) |
Rule: Always initialize pointers โ either to a valid address or to nullptr.
3. References (&) โ Safer Alternative to Pointers
A reference is like a second name for an existing variable. It must be initialized when declared and cannot be changed to refer to something else.
Syntax:
|
0 1 2 3 4 5 6 7 |
int x = 10; int& ref = x; // ref is now another name for x |
Example:
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> void increment(int& num) { // reference parameter num++; // changes the original variable! } int main() { int value = 5; int& alias = value; std::cout << value << "\n"; // 5 std::cout << alias << "\n"; // 5 alias = 100; std::cout << value << "\n"; // 100 !! increment(value); std::cout << value << "\n"; // 101 return 0; } |
Key differences: Pointer vs Reference
| Feature | Pointer (*) | Reference (&) |
|---|---|---|
| Can be null? | Yes (nullptr) | No โ must be initialized |
| Can be reassigned? | Yes (point to something else) | No โ always refers to same variable |
| Syntax to declare | int* ptr | int& ref |
| Syntax to use | *ptr to access value | Just use ref like normal variable |
| Memory address | Has its own address | No separate address |
| Safety | Dangerous if misused | Safer, cleaner |
Modern rule (2025+): Use references whenever possible. Use pointers only when you need nullability or reassignment.
4. const Correctness โ Super Important!
const makes things read-only โ prevents accidental changes.
Four common patterns:
- Constant variable
|
0 1 2 3 4 5 6 |
const int MAX = 100; // cannot change MAX |
- Pointer to constant data (data cannot change, pointer can)
|
0 1 2 3 4 5 6 7 8 9 10 |
const int* ptr; // *ptr cannot be modified int x = 10; ptr = &x; *ptr = 20; // ERROR! ptr = nullptr; // OK |
- Constant pointer (pointer cannot change, data can)
|
0 1 2 3 4 5 6 7 8 |
int* const ptr = &x; // ptr cannot point elsewhere *ptr = 30; // OK ptr = &y; // ERROR! |
- Constant pointer to constant data (nothing can change)
|
0 1 2 3 4 5 6 |
const int* const ptr = &x; |
Best practice โ function parameters:
|
0 1 2 3 4 5 6 7 8 9 |
void print(const std::string& s) { // read-only, no copy! std::cout << s << "\n"; // s += "!"; // ERROR โ const! } |
Rule of thumb: If a function only reads data โ use const & or const *.
5. Smart Pointers โ Modern, Safe Memory Management (C++11+)
Raw pointers are dangerous because you must manually delete memory โ easy to forget โ memory leaks!
Smart pointers do the cleanup automatically (RAII โ Resource Acquisition Is Initialization).
Include: #include <memory>
A. std::unique_ptr โ Exclusive ownership (most common)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
#include <iostream> #include <memory> int main() { // Modern way โ no manual delete! std::unique_ptr<int> ptr = std::make_unique<int>(42); std::cout << *ptr << "\n"; // 42 // When ptr goes out of scope โ memory is automatically freed! // Transfer ownership auto ptr2 = std::move(ptr); // ptr is now nullptr std::cout << *ptr2 << "\n"; // 42 if (ptr == nullptr) { std::cout << "ptr is now empty\n"; } return 0; } |
B. std::shared_ptr โ Shared ownership (reference counting)
|
0 1 2 3 4 5 6 7 8 9 10 11 12 |
auto p1 = std::make_shared<int>(100); auto p2 = p1; // now both point to same memory auto p3 = p2; std::cout << *p1 << " " << *p2 << " " << *p3 << "\n"; // all 100 // Memory freed only when LAST shared_ptr is destroyed |
C. std::weak_ptr โ Non-owning reference (breaks cycles)
Used to avoid circular references with shared_ptr.
|
0 1 2 3 4 5 6 7 8 9 10 11 |
std::shared_ptr<int> sp = std::make_shared<int>(50); std::weak_ptr<int> wp = sp; if (auto locked = wp.lock()) { // try to get shared_ptr std::cout << *locked << "\n"; // 50 } |
Modern recommendation (2025+):
- Use std::unique_ptr for single ownership (almost always)
- Use std::shared_ptr only when you really need shared ownership
- Never use raw new/delete in modern C++ unless you have a very special reason
6. Full Practical Example โ Dynamic Student Array with Smart Pointers
|
0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
#include <iostream> #include <memory> #include <string> #include <vector> class Student { public: std::string name; int age; Student(std::string n, int a) : name(std::move(n)), age(a) {} void print() const { std::cout << name << " (" << age << ")\n"; } }; int main() { // Modern: vector of unique_ptr (or just vector<Student>) std::vector<std::unique_ptr<Student>> classRoom; classRoom.push_back(std::make_unique<Student>("Alice", 20)); classRoom.push_back(std::make_unique<Student>("Bob", 21)); classRoom.push_back(std::make_unique<Student>("Charlie", 19)); std::cout << "Class list:\n"; for (const auto& student : classRoom) { student->print(); } // Memory automatically freed when vector goes out of scope! return 0; } |
Your Mini Homework (Try These!)
- Create a function swap that takes two pointers and swaps the values they point to.
- Write a function that takes const std::string& and prints it in uppercase.
- Create a unique_ptr<int> inside a function, pass ownership out using std::move, and print the value in main().
- Declare a const int* const ptr and explain why both parts are const.
Youโre doing absolutely phenomenal! Youโve just unlocked one of the most powerful parts of C++ โ and you did it safely with modern tools!
Next lesson: Dynamic Memory & Containers (vector, map, etc.) โ the real workhorses of C++!
Any questions? Confused about unique_ptr vs shared_ptr? Want more examples with const or references? Just ask โ your friendly C++ teacher is right here for you! ๐
