Lesson 9 of 13
Pointers, References & Memory
Learn the three ways to refer to a value in C++ (by copy, by and by pointer) and what happens on the and the heap.
#include <iostream>
int main() {
int x = 42;
int& ref = x; // reference: another name for x. Cannot be null or rebound.
int* ptr = &x; // pointer: holds the ADDRESS of x. Can be null, can move.
ref = 10; // writes through the reference
*ptr = 20; // dereference then write
std::cout << x << "\n"; // 20
std::cout << ptr << "\n"; // the address itself
std::cout << *ptr << "\n"; // the value at that address
int y = 99;
ptr = &y; // pointers can be reseated
// ref = y; // does NOT rebind: this assigns y's VALUE into x
int* nothing = nullptr; // use nullptr, never NULL or 0
if (nothing) std::cout << *nothing; // always check before dereferencing
}#include <iostream>
#include <string>
#include <vector>
// Parameter passing decides who copies what.
void byValue(std::vector<int> v) { v.push_back(1); } // copies the whole vector
void byRef(std::vector<int>& v) { v.push_back(1); } // modifies the caller's
void byConstRef(const std::vector<int>& v) { std::cout << v.size(); } // no copy, no writes
void byPointer(std::vector<int>* v) { if (v) v->push_back(1); } // may be null
// Rule: pass small things (int, double, pointers) by value.
// Pass anything bigger by const& unless you intend to modify it.
struct Point { double x, y; };
// Returning references to locals is a classic disaster:
// int& broken() { int local = 5; return local; } // dangling reference, UB
int main() {
std::vector<int> nums{1, 2, 3};
byValue(nums); std::cout << nums.size() << "\n"; // 3 — unchanged
byRef(nums); std::cout << nums.size() << "\n"; // 4
byPointer(&nums); std::cout << nums.size() << "\n"; // 5
// Range-for: & to modify, const& to read cheaply
for (int& n : nums) n *= 2;
for (const int& n : nums) std::cout << n << ' ';
}#include <iostream>
// The stack: automatic, fast, freed when the scope ends, limited in size.
// The heap: manual (or smart-pointer managed), large, survives the scope.
void stackDemo() {
int arr[1000]; // on the stack, gone at the closing brace
arr[0] = 1;
} // freed automatically
void rawHeapDemo() {
int* single = new int(42); // one int on the heap
int* many = new int[1000]; // an array on the heap
std::cout << *single << " " << many[0] << "\n";
delete single; // every new needs exactly one delete
delete[] many; // array form MUST use delete[]
// The three ways this goes wrong:
// forget delete -> memory leak
// delete twice -> double free, undefined behaviour
// use after delete -> dangling pointer, undefined behaviour
}
int main() {
stackDemo();
rawHeapDemo();
// In modern C++ you almost never write new/delete by hand — the next
// lesson replaces them with std::unique_ptr and std::vector.
}Finished reading?Mark it done to track your progress.
Tip: use the ← and → keys to move between lessons.