Functions

A function is a named piece of code that takes values, computes something and returns a value with return. Write one when a solution does the same computation in several places or for many values, such as a digit sum or a primality check inside a loop.

Explanation

A program must say which of two numbers, 507 and 38, has the larger sum of digits. Without functions, the loop that adds up the digits is written twice: n % 10 is the last digit, and n /= 10 drops it (integer division). Then an if compares the two sums:

int a = 507, b = 38;
int sa = 0;
while (a > 0) {
    sa += a % 10;
    a /= 10;
}
int sb = 0;
while (b > 0) {
    sb += b % 10;
    b /= 10;
}
if (sa > sb) cout << "first";
else cout << "second";

The two loops are the same, and only the names change. A function lets you write the loop once and use it for any number:

int digitSum(int n) {
    int s = 0;
    while (n > 0) {
        s += n % 10;
        n /= 10;
    }
    return s;
}

// in main:
int a = 507, b = 38;
if (digitSum(a) > digitSum(b)) cout << "first";
else cout << "second";

The parts of the function have these names:

  • int before the name is the return type, the type of the returned value.

  • digitSum is the name.

  • int n is the parameter. A function can have several, separated by commas.

  • The code in braces is the body.

  • return ends the function.

The value written in a call, such as a in digitSum(a), is the argument. A call runs the body with each parameter set to a copy of its argument, and then the call is replaced by the returned value. Library functions such as max(a, b) and sqrt(x) work the same way.

Because a call is replaced by its returned value, it can stand anywhere a number can: in a condition, in a sum, or as the argument of another call. In digitSum(digitSum(9999)) the inner call runs first and returns 36, and the outer call gets 36 and returns 9.

A chain from left to right: the number 9999, an arrow labelled digitSum 1 leading to 36, then an arrow labelled digitSum 2 leading to 9; 36 and 9 are blue as returned values.

Fig. 1. The inner call runs first, and its returned value is the argument of the outer call.

Let's follow one call step by step. Step through it and watch n and x.

int x = 507;
int s = digitSum(x);
cout << x << ' ' << s;
Two boxes of variables: main with x = 507 and s, and the call digitSum with n and s. n starts as a copy of 507 and becomes 50, 5 and 0 while s becomes 0, 7, 7 and 12; then the call turns grey, main's s gets 12, and x stays 507.

int x = 507; puts 507 into x. The box is main, and its s has no value yet.

1/7

Fig. 2. Step through the call with the buttons or the arrow keys. Blue cells have just changed, and a grey box is a call that has ended.

The parameter n started as a copy of x, so the loop could make n zero while x stayed 507.

Both functions also have a variable named s, and they are two different variables. A variable declared inside a function is a local variable: it belongs to that call, and other functions can't see it. Parameters are local variables too: n is created at the call and disappears when the call ends.

References

Sometimes a function must change the caller's variables. Say a program has two numbers, x = 3 and y = 8, and must swap them. A first try is a void function that swaps its two parameters:

void swapValues(int a, int b) {
    int t = a;
    a = b;
    b = t;
}

// in main:
int x = 3, y = 8;
swapValues(x, y);
cout << x << ' ' << y;

It prints 3 8. The function swapped its own copies, a and b, just as n changed in digitSum while x stayed 507.

To make the function work on x and y themselves, write & after the type of each parameter:

void swapValues(int& a, int& b) {

The & makes the parameter a reference, another name for the variable passed in. Now a is x and b is y, and the same body prints 8 3.

After swapValues(x, y) with x = 3 and y = 8: by value, x and y still hold 3 and 8 while the copies a and b, in red, hold 8 and 3; by reference, the cells named both x and a, and y and b, in blue, hold 8 and 3.

Fig. 3. Red: the copies were swapped. Blue: a and b name the cells of x and y.

This is called passing by reference, and a normal parameter is passed by value. An argument passed by reference must be a variable, so swapValues(x, 5) doesn't compile. The library function swap(x, y) already does this job, and it takes its parameters by reference too.

Returning early

return ends the function at once, even in the middle of the body. So a function can have several returns, and the first one reached gives the returned value:

int sgn(int x) {
    if (x > 0) return 1;
    if (x < 0) return -1;
    return 0;
}

For sgn(-4) the second line returns -1, and the last line never runs. A void function has no value to return, but it can still leave early with return;.

In C++

// return type, name, parameters (copies)
int bigger(int a, int b) {
    // body: compute the returned value from the parameters
    if (a > b) return a;
    return b;                      // return: the call is replaced by this value
}

void order(int& a, int& b) {       // & : works on the caller's variables
    if (a > b) swap(a, b);
}

In a complete program the function goes above main, and main calls it once for each number. The input has t, then t numbers, for example:

3
507
1000
99
#include <iostream>
using namespace std;

int digitSum(int n) {
    int s = 0;
    while (n > 0) {
        s += n % 10;
        n /= 10;
    }
    return s;
}

int main() {
    int t;
    cin >> t;
    for (int i = 0; i < t; i++) {
        int n;
        cin >> n;
        int s = digitSum(n);
        cout << s << '\n';
    }
}

For a yes-or-no question, return bool. A loop can call such a function, for example to count the primes up to n. Divisors up to \sqrt{n} are enough, so the loop in isPrime stops when i * i > n:

bool isPrime(int n) {
    if (n < 2) return false;
    for (int i = 2; i * i <= n; i++) {
        if (n % i == 0) return false;
    }
    return true;
}

// in main, after reading n:
int cnt = 0;
for (int i = 1; i <= n; i++) {
    if (isPrime(i)) cnt++;
}

Three calls of order put three numbers in increasing order:

int a = 7, b = 2, c = 4;
order(a, b);
order(b, c);
order(a, b);

For 7 2 4 the first call gives 2 7 4 and the second gives 2 4 7. The third call is needed for inputs such as 4 7 2, where the second call moves 2 into the middle.

Common mistakes

  • Printing instead of returning. With int bigger(int a, int b) { if (a > b) cout << a << ' '; else cout << b << ' '; }, the line cout << bigger(3, 8) + 1; printed 8 and then a meaningless number, because the call returned no value. Return the value and let main print it.

  • Returning in both branches inside a loop. With if (n % i == 0) return false; else return true; the loop stops after checking 2, so isPrime(9) returns true. Put return true; after the loop.

  • A parameter type too small for the computation. long long f(int x) { return 1 + x + x * x; } returns -486618623 for x = 1000000000, because x * x is computed in int, which holds numbers only up to about 2 \cdot 10^9, and only then becomes long long. Make the parameter long long x (integer types).

Similar techniques

  • Recursion is a function that calls itself on a smaller case, and each call has its own copies of the parameters.

  • Sorting with your own order passes a comparator, a function that returns bool, to sort.

  • Arrays and vectors should be passed as const vector<int>& when the function only reads them, so they aren't copied.

Practice

Was this page helpful?