Kodewick
Lesson 10 of 30Contents

References

Lesson 10 of 30 · Part 2 · Control flow and functions · about 15 minutes

Last lesson showed that with pass by value, a function gets a copy and can't change the variable outside. This lesson shows another way C++ offers: references. A reference is another name for a variable.

main.cpp
1#include <iostream>2 3int main() {4    int score = 80;5    int& ref = score;6    ref += 5;7    std::cout << score << '\n';8    std::cout << ref << '\n';9    return 0;10}
Output
85
85

int& ref = score; reads "ref is a reference to score". An & after the type means a reference. ref is not a new variable and has no value of its own; it and score are two names for the same thing. So ref += 5 changes score. Press Step and see what ref points to.

One thing, two names

Whichever name you change it through, the other name sees the same:

main.cpp
1#include <iostream>2 3int main() {4    double price = 20;5    double& p = price;6    price = 25;7    std::cout << p << '\n';8    p = p * 0.8;9    std::cout << price << '\n';10    return 0;11}
Output
25
20

Change price, and p sees 25; change p, and price becomes 20. A reference to an int variable is an int&, to a double a double&, to a std::string a std::string&.

Tip: int& ref and int &ref are the same; the space can go on either side. This course writes the & next to the type.

Bound at once, never changed

A reference must say whose name it is when it is declared. int& ref; on its own is a compile error: g++ says 'ref' declared as reference but not initialized.

Once bound, it is that variable's name for good; it can't be changed. A later ref = other_variable just copies the value over:

Guess first

What does this program show?

1#include <iostream>2 3int main() {4    int x = 3;5    int y = 8;6    int& r = x;7    r = y;8    y = 100;9    std::cout << x << " " << r << '\n';10    return 0;11}

Reference parameters

Where references really shine is function parameters. A reference parameter is another name for the variable given in the call, so changing it in the function changes the variable outside:

main.cpp
1#include <iostream>2 3void add_bonus(int& score) {4    score += 10;5}6 7int main() {8    int score = 80;9    add_bonus(score);10    std::cout << score << '\n';11    return 0;12}
Output
90

One & different from last lesson, and the result goes from 80 to 90. Press Step: entering add_bonus adds a layer, but the score in it is not a new number; it points to the score in main.

The call doesn't show whether a parameter is a reference: add_bonus(score) looks the same either way. So give a function that changes its caller's variables a name that says so, like add_bonus or reset.

Swapping two numbers

Swapping the values of two variables is the classic use of reference parameters. With pass by value, the function would swap two copies; with references, it swaps the two variables outside:

main.cpp
1#include <iostream>2 3void swap_values(int& a, int& b) {4    int temp = a;5    a = b;6    b = temp;7}8 9int main() {10    int x = 3;11    int y = 8;12    swap_values(x, y);13    std::cout << "x = " << x << ", y = " << y << '\n';14    return 0;15}
Output
x = 8, y = 3

Press Step and watch what a and b point to, and how temp helps: it keeps the value of a, which would otherwise be lost after a = b.

The standard library already has one: after #include <utility>, std::swap(x, y); does the same thing.

A reference needs a variable

An int& parameter wants a variable it can change. Give it a number, or the result of a calculation, and the compiler says no:

main.cpp
1#include <iostream>2 3void add_bonus(int& score) {4    score += 10;5}6 7int main() {8    int score = 80;9    add_bonus(90);10    std::cout << score << '\n';11    return 0;12}
Output
main.cpp: In function 'int main()':
main.cpp:9:15: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
    9 |     add_bonus(90);
      |               ^~

cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int': an int& can't be bound to a temporary value. 90 isn't any variable, and after adding 10 there would be nowhere to keep the result, so it isn't allowed. The same goes for add_bonus(score + 5).

const references

Pass by value makes a copy. Copying an int is cheap, but copying a long std::string (and later, containers holding thousands of elements) on every call is a waste.

Use a **const reference** instead: const std::string& text. The & means no copy, use the one outside; the const means the function only reads it, and changing it is a compile error.

main.cpp
1#include <iostream>2#include <string>3 4int count_char(const std::string& text, char c) {5    int n = 0;6    for (std::size_t i = 0; i < text.size(); i++) {7        if (text[i] == c) {8            n++;9        }10    }11    return n;12}13 14int main() {15    std::string fruit = "banana";16    std::cout << count_char(fruit, 'a') << '\n';17    std::cout << count_char("apple", 'p') << '\n';18    return 0;19}
Output
3
2

A const reference has one more advantage: it can bind to a temporary value, so passing a string directly, as in count_char("apple", 'p'), works too. It won't be changed anyway, so there's no need for a place to keep it. (The std::size_t in the loop is the type size() gives: an integer that is never negative. More on it next lesson.)

Choosing how to pass

You want toWriteExamples
read it, small typepass by valueint n, double price, char c, bool ok
read it, maybe largeconst referenceconst std::string& name
change the caller's variablereferenceint& score

One function can mix them:

main.cpp
1#include <iostream>2#include <string>3 4void add_item(const std::string& name, double price, double& total) {5    total += price;6    std::cout << name << ": $" << price << '\n';7}8 9int main() {10    double total = 0;11    add_item("Milk", 6.5, total);12    add_item("Bread", 12, total);13    std::cout << "Total: $" << total << '\n';14    return 0;15}
Output
Milk: $6.5
Bread: $12
Total: $18.5

name is only read, so it's a const reference; price is a small double, passed by value; total keeps adding up, so it's a reference.

Practice

Write the code, then press Check. A hint costs nothing; the answer is there when you need it.

Exercise 1

Reset to zero

Write a function reset that uses a reference parameter to set the variable outside to 0. The program should show 0.

It should print
0
exercise1.cpp
1#include <iostream>2 3int main() {4    int count = 42;5    reset(count);6    std::cout << count << '\n';7    return 0;8}
Output
Press Run or Ctrl+Enter
Exercise 2

Smaller one first

Write a function sort_two(int& a, int& b): if a is bigger than b, swap them. After the call, low holds the smaller one. The program should show 4 9.

It should print
4 9
exercise2.cpp
1#include <iostream>2 3int main() {4    int low = 9;5    int high = 4;6    sort_two(low, high);7    std::cout << low << " " << high << '\n';8    return 0;9}
Output
Press Run or Ctrl+Enter
Exercise 3

Fix it: the curve that didn't count

apply_curve is meant to add 5 points, but the score is still 70. Add just one character so the program shows 75.

It should print
75
exercise3.cpp
1#include <iostream>2 3void apply_curve(int score) {4    score += 5;5}6 7int main() {8    int score = 70;9    apply_curve(score);10    std::cout << score << '\n';11    return 0;12}
Output
Press Run or Ctrl+Enter

What you learned

  • A reference is another name for a variable: int& ref = score;. Changing it through either name is the same.
  • A reference must be bound when declared (int& ref; is a compile error) and can't be rebound; ref = y copies the value.
  • A reference parameter (int& score) lets a function change the caller's variable; swap is the classic example, and the standard library has std::swap.
  • An int& binds only to a variable, not to temporary values like 90 or score + 5.
  • const std::string&: no copy, no changes, and it accepts temporary values too; pass large things you only read this way.
  • Small types you only read: by value. Large ones you only read: const reference. To change it: reference.