Skip to content
Laviyx Learn

Try

C++ course menu

C++ References: Aliases, const References and Reference Parameters

Understand C++ references: what an lvalue reference is, how it aliases a variable, const references, references in range-based for loops and function parameters, returning references safely, and references vs pointers.

Intermediate9 min readUpdated
On this page

A reference is another name for an existing variable. Once created, the reference and the original are the same object: reading or changing one reads or changes the other. You have already used references in function parameters; this lesson explains exactly how they work.

Creating a reference#

Add & after the type:

C++
#include <iostream>

int main() {
    int score = 80;
    int& alias = score;   // alias is another name for score

    alias += 5;
    std::cout << score << ' ' << alias << '\n';

    score = 100;
    std::cout << alias << '\n';
}
Output
85 85
100

There is only one integer in memory here, with two names.

Rules for references#

  1. A reference must be initialized when it is created: int& r; does not compile.
  2. A reference cannot be re-seated: it refers to the same variable for its whole life. Assigning to it changes the value of that variable; it does not make it refer to something else.
  3. There are no "null references": a reference always refers to an object (unless you create a dangling one by mistake).
C++
#include <iostream>

int main() {
    int a = 1;
    int b = 2;
    int& r = a;

    r = b;       // copies b's value INTO a; r still refers to a
    b = 50;

    std::cout << a << ' ' << b << ' ' << r << '\n';
}
Output
2 50 2

const references#

A const reference gives read-only access. It is the standard way to look at a large object without copying it:

C++
#include <iostream>
#include <string>

int main() {
    std::string title = "Learn C++ Step by Step";
    const std::string& view = title;

    std::cout << view.size() << '\n';
    // view += "!";   // error: view is read-only
    title += "!";      // the original can still change
    std::cout << view << '\n';
}
Output
22
Learn C++ Step by Step!

A const reference can also bind to a temporary value, such as a literal or the result of an expression, and keeps that temporary alive as long as the reference exists: const std::string& s = std::string("hi") + "!"; is valid.

References as function parameters#

This is where references are used most:

C++
#include <iostream>
#include <string>
#include <vector>

void add_item(std::vector<std::string>& cart, const std::string& item) {
    cart.push_back(item);
}

void print_cart(const std::vector<std::string>& cart) {
    for (const auto& item : cart) std::cout << "- " << item << '\n';
}

int main() {
    std::vector<std::string> cart;
    add_item(cart, "Milk");
    add_item(cart, "Bread");
    print_cart(cart);
}
Output
- Milk
- Bread
  • std::vector<std::string>& cart lets add_item change the caller's vector.
  • const std::string& item and const std::vector<...>& cart avoid copies and promise not to change anything.

References in loops#

The same idea applies in a range-based for:

C++
#include <iostream>
#include <vector>

int main() {
    std::vector<int> stock = {5, 0, 12};

    for (int& s : stock) {
        s += 10;   // changes the real elements
    }
    for (const int& s : stock) {
        std::cout << s << ' ';
    }
    std::cout << '\n';
}
Output
15 10 22

Returning references#

A function can return a reference, which lets the caller read or modify an element inside an object that outlives the call:

C++
#include <iostream>
#include <vector>

int& largest(std::vector<int>& values) {
    std::size_t best = 0;
    for (std::size_t i = 1; i < values.size(); ++i) {
        if (values[i] > values[best]) best = i;
    }
    return values[best];   // refers into the caller's vector: safe
}

int main() {
    std::vector<int> v = {4, 19, 7};
    largest(v) = 0;        // set the largest element to 0
    for (int x : v) std::cout << x << ' ';
    std::cout << '\n';
}
Output
4 0 7

This is safe because values belongs to the caller. Returning a reference to a local variable is never safe, because the local is destroyed when the function returns (see Scope and Lifetime).

References vs pointers#

C++ also has pointers, which hold the address of a variable. They overlap with references, but differ in important ways:

ReferencePointer
Must be initializedYesNo (but should be)
Can be nullNoYes (nullptr)
Can change what it refers toNoYes
Syntax to useLike the variable itself*p to reach the value

Prefer references when something must always refer to a valid object (most function parameters). Use pointers when "nothing" is a valid option, or when you need to change what is referred to. The next lesson covers Pointers.

Common mistakes#

Practice#

Practice exercise

Write void normalize(std::vector<double>& marks, double max_mark) that converts every mark to a percentage of max_mark in place. Normalize {18, 25, 9.5} out of 25 and print the result.

Show solution
C++
#include <iostream>
#include <vector>

void normalize(std::vector<double>& marks, double max_mark) {
    for (double& m : marks) {
        m = m / max_mark * 100;
    }
}

int main() {
    std::vector<double> marks = {18, 25, 9.5};
    normalize(marks, 25);
    for (double m : marks) std::cout << m << ' ';
    std::cout << '\n';
}
Output
72 100 38

Key takeaway#

Finished this lesson?