References & Pointers
In the last lesson you saw that a function receives a copy of each argument. Often that is not what you want: a function that adds bonus marks should change the real marks, and a function that prints a long list should not waste time copying it. C++ gives you two tools to refer to an existing object instead of copying it: references and pointers.
References are simpler and safer, so you will use them most. Pointers are more flexible and appear everywhere in older code, in C libraries and in low-level work, so you must understand them too.
References: another name for a variable
A reference is an alias, a second name for an existing variable. You create one by adding & after the type. Whatever you do through the reference happens to the original.
#include <print>
int main() {
int score = 50;
int& alias = score; // alias refers to score
alias += 10;
std::println("score = {}, alias = {}", score, alias);
score = 99;
std::println("score = {}, alias = {}", score, alias);
}score = 60, alias = 60 score = 99, alias = 99
There is only one integer here, with two names. A reference must be initialised when it is created, and it can never be changed to refer to a different variable later.
Passing by reference
The main use of references is in function parameters. A reference parameter lets the function work on the caller's variable directly.
#include <print>
#include <string>
void add_bonus(int& marks, int bonus) {
marks += bonus; // changes the caller's variable
}
void swap_values(int& a, int& b) {
int temp = a;
a = b;
b = temp;
}
void show(const std::string& name, int marks) { // no copy, read-only
std::println("{}: {}", name, marks);
}
int main() {
int maths = 72;
add_bonus(maths, 5);
show("Ananya", maths);
int x = 3;
int y = 8;
swap_values(x, y);
std::println("x = {}, y = {}", x, y);
}Ananya: 77 x = 8, y = 3
Look at show: the parameter is const std::string&. The & avoids copying the string, and const promises the function will not change it. This pass by const reference is the standard way to pass large objects such as strings, vectors and class objects that a function only reads. For small types like int, double and char, passing by value is just as fast and simpler. (The standard library already has std::swap, so in real code you would not write swap_values yourself.)
Pointers: variables that hold addresses
Every variable lives somewhere in memory, and each location has an address, a number. A pointer is a variable whose value is an address. Two operators work with pointers:
&x(address-of) gives the address ofx.*p(dereference) gives the object that pointerppoints to.
#include <print>
int main() {
int runs = 45;
int* p = &runs; // p holds the address of runs
std::println("runs = {}", runs);
std::println("*p = {}", *p); // follow the pointer
*p = 100; // change runs through the pointer
std::println("runs is now {}", runs);
int wickets = 3;
p = &wickets; // a pointer CAN be re-pointed
std::println("*p is now {}", *p);
}runs = 45 *p = 45 runs is now 100 *p is now 3
The type int* means "pointer to int". The * has two jobs: in a declaration it makes a pointer type, and in an expression it dereferences. The address itself is a large number that changes every time you run the program, so lessons rarely print it.
nullptr: pointing at nothing
Unlike a reference, a pointer can be empty. The special value nullptr means "points to nothing". This makes pointers useful for answers that may not exist, such as "the first negative balance, if there is one". Always check before dereferencing: dereferencing nullptr is undefined behaviour and usually crashes the program.
#include <print>
#include <vector>
int* find_first_negative(std::vector<int>& values) {
for (auto& v : values) {
if (v < 0) {
return &v; // address of the item inside the vector
}
}
return nullptr; // nothing found
}
int main() {
std::vector<int> balances = {500, 1200, -300, 800};
if (int* p = find_first_negative(balances); p != nullptr) {
std::println("Found a negative balance: {}", *p);
*p = 0; // fix it in place
}
std::println("Third balance is now {}", balances[2]);
}Found a negative balance: -300 Third balance is now 0
In modern C++, std::optional (see Exceptions, optional & expected) is often a clearer way to say "maybe a value", but pointers like this are still common.
References or pointers?
| Reference | Pointer | |
|---|---|---|
| Can be empty | No | Yes, nullptr |
| Can be re-pointed | No | Yes |
| Must be initialised | Yes | No, but always should be |
| Syntax to use | Like a normal variable | Needs * and -> |
| Typical use | Function parameters | Optional objects, arrays, low-level code |
Rule of thumb: use a reference when something must always exist, and a pointer only when "nothing" is a valid answer or you need to re-point. Never use a raw pointer to own memory you created with new; Smart Pointers vs new & delete shows the modern way.
Dangling references and pointers
A reference or pointer to an object that no longer exists is dangling. Local variables are destroyed when their function returns, so never return a reference or pointer to one:
const std::string& bad_name() {
std::string name = "Kabir";
return name; // name is destroyed here: the reference dangles
}GCC warns "reference to local variable 'name' returned". Using the result is undefined behaviour: it may print garbage, crash, or seem to work today and fail tomorrow. Return by value instead (std::string bad_name()); modern compilers make that cheap.
Common mistakes
- Dereferencing
nullptror an uninitialised pointer. Initialise every pointer, and check fornullptrbefore using*. - Forgetting
&in a parameter.void add_bonus(int marks, int bonus)compiles but changes only a copy. - Returning references to locals. The object dies when the function ends.
- Keeping a pointer into a vector while adding items. When a vector grows it may move its items to new memory, and old pointers and references into it dangle.
Key points
- A reference (
int& r = x;) is another name for an existing object; it cannot be empty or re-pointed. - Pass by reference to let a function change the caller's variable; pass by
const&to avoid copying large objects. - A pointer (
int* p = &x;) stores an address;*preaches the object, andnullptrmeans "nothing". - Prefer references; use pointers when "no object" is valid or re-pointing is needed.
- Never return a reference or pointer to a local variable: it would dangle.
Exercise
Write a function apply_discount(double& price, double percent) that reduces a price in place, and a function const std::string* find_city(const std::vector<std::string>& cities, char first_letter) that returns a pointer to the first city starting with that letter, or nullptr. In main, discount a price of 1299 by 10 percent and print it with two decimals, then look up a city starting with 'K' and one starting with 'Z' in {"Jaipur", "Kochi", "Pune"}.
Show solution
#include <print>
#include <string>
#include <vector>
void apply_discount(double& price, double percent) {
price -= price * percent / 100;
}
const std::string* find_city(const std::vector<std::string>& cities, char first_letter) {
for (const auto& city : cities) {
if (!city.empty() && city[0] == first_letter) {
return &city;
}
}
return nullptr;
}
int main() {
double price = 1299;
apply_discount(price, 10);
std::println("Price after discount: {:.2f}", price);
std::vector<std::string> cities = {"Jaipur", "Kochi", "Pune"};
for (char letter : {'K', 'Z'}) {
if (const std::string* city = find_city(cities, letter); city != nullptr) {
std::println("{}: {}", letter, *city);
} else {
std::println("{}: no city found", letter);
}
}
}Price after discount: 1169.10 K: Kochi Z: no city found
The function returns the address of the string inside the vector, which stays valid as long as the vector is alive and unchanged.
Quick Quiz
1. After int a = 5; int& r = a; r = 9;, what is a?
r is another name for a, so assigning to r changes a.
2. What is the best parameter type for a function that only reads a long std::string?
A const reference avoids the copy and prevents changes; a pointer would also allow nullptr.
3. What does *p mean when p is an int*?
In an expression, * dereferences the pointer to reach the object it points to.
4. Which is a valid reason to use a pointer instead of a reference?
References cannot be empty, so use a pointer (or std::optional) when there may be no object.
Learn C++ with a mentor
Free lessons get you started. Our Java, C & C++ Programming training adds real projects and code reviews from working developers.
- Project-based learning
- Mentor code reviews
- Free counselling call
