On this page

Functions

Every program so far has been one list of statements between the braces of main. That stops working as programs grow: the same five statements appear in three places, and nobody can say what line 60 is for.

A function is a group of statements with a name. You have called functions since module 1: circle, rectangle and text are functions somebody else wrote. In this lesson you write your own.

A name for some statements

This program prints a short menu with a rule above and below the dishes.

#include <iostream>

void print_rule() {
    std::cout << "----------\n";
}

int main() {
    std::cout << "Menu\n";
    print_rule();
    std::cout << "Soup\n";
    std::cout << "Bread\n";
    print_rule();
}
Menu
----------
Soup
Bread
----------

The four lines above main are a function definition. It has four parts.

Part What it is
void the return type: what the function gives back. void means nothing. Later sections use other types here
print_rule the name
( and ) parentheses for the function's inputs. This function has none
{ ... } the body: the statements that run when the function is called

A definition does not run anything. It only says what print_rule means.

The program still starts at the first statement of main. Follow it in order.

  1. main prints Menu.
  2. print_rule(); is a call. main stops at this line, the body of print_rule runs from its first statement to its closing brace, and then main carries on with the next statement.
  3. main prints Soup and Bread.
  4. The second call runs the same body again.

So the order in which statements run is not the order in which they are written. The body of print_rule is written first and runs second and fifth. A function that is never called never runs.

main is a function too. Its definition has the same four parts: a return type int, the name main, empty parentheses and a body. It is the one function you never call yourself: starting the program calls it.

Inputs

print_rule does the same thing on every call. Most functions need to be told something: where to draw, how many to print. A function receives such values through its parameters.

#include "scimigo.h"
using namespace scimigo;

void draw_target(int x, int y) {
    circle(x, y, 45, "red");
    circle(x, y, 30, "white");
    circle(x, y, 15, "red");
}

int main() {
    canvas(600, 400);
    draw_target(120, 120);
    draw_target(300, 250);
    int right = 480;
    draw_target(right, 60 + 60);
}

Between the parentheses of the definition are two parameters, int x and int y. Each is written like a variable declaration without a value: a type and a name. A comma separates them.

A parameter is a new variable that belongs to the function. It gets its starting value from the call. The values written in the call are the arguments.

Take the call draw_target(300, 250);. Before the body runs, x is created with the value 300 and y with the value 250. Then the three circle statements run, and every x and y in them means 300 and 250. On the next call x and y are created again, with the next call's values.

Three things to notice.

  • Arguments are matched to parameters by position: the first argument goes to the first parameter. Names play no part in it. draw_target(250, 300) is a different target.
  • An argument can be any expression of the right type. In the last call, right is read and 60 + 60 is worked out first, and the function receives 480 and 120.
  • The number of arguments must equal the number of parameters. draw_target(300) does not compile.

A parameter can have any type you know. This function takes text.

#include <iostream>
#include <string>

void greet(std::string name) {
    std::cout << "Hello, " << name << ".\n";
}

int main() {
    greet("Ada");
    std::string guest = "Grace";
    greet(guest);
}
Hello, Ada.
Hello, Grace.

Giving a value back

draw_target and greet do something you can see. Other functions exist to work something out and hand the answer to the caller. Such a function names the type of its answer where void stood, and hands the answer over with return.

#include <iostream>

int square(int n) {
    return n * n;
}

int main() {
    int side = 6;
    int area = square(side);
    std::cout << area << "\n";
    std::cout << square(3) + square(4) << "\n";
}
36
25

The first line of the definition now reads: square takes one int and gives back an int.

return n * n; works out n * n and returns it: the function ends, and the value takes the place of the call. So int area = square(side); becomes int area = 36;, and square(3) + square(4) becomes 9 + 16. A call that returns a value can be used anywhere a value of that type can.

return ends the function at once. Statements after it in the body do not run. That makes a function with several return statements easy to read: the first one reached decides the answer.

#include <iostream>
#include <string>

std::string grade(int marks) {
    if (marks >= 70) {
        return "distinction";
    }
    if (marks >= 40) {
        return "pass";
    }
    return "fail";
}

int main() {
    std::cout << grade(85) << "\n";
    std::cout << grade(55) << "\n";
    std::cout << grade(12) << "\n";
}
distinction
pass
fail

For 85 the first return runs and the function is over. The second if is never tested, which is why no else is needed. For 12 both conditions are false and the last line returns "fail".

Two rules follow from the return type.

  • Every way through the body must end at a return with a value of that type. If grade lacked its last line, a call with 12 would reach the closing brace with nothing to give back. The compiler may accept such a function, and what the program then does is not defined.
  • The caller has to use the value. The statement square(5); on its own works out 25 and throws it away.

main returns an int as well. The value goes to whatever started the program, and 0 means "finished without trouble". main is the only function allowed to leave its return out; the compiler then supplies return 0;.

The function works on a copy

A parameter is a new variable. When the argument is a variable of the caller, the parameter starts with a copy of its value. After that the two have nothing to do with each other.

#include <iostream>

void double_it(int n) {
    n = n * 2;
    std::cout << "n in double_it: " << n << "\n";
}

int main() {
    int apples = 4;
    double_it(apples);
    std::cout << "apples in main: " << apples << "\n";
}
n in double_it: 8
apples in main: 4

Follow the two variables.

Moment apples (in main) n (in double_it)
before the call 4 does not exist
the call starts 4 4, copied from apples
after n = n * 2; 4 8
the call has ended 4 does not exist

The assignment changed n, and n disappeared when the function ended. apples was read once, at the start of the call, and never written to. This is called pass by value: the function is passed the value, not the variable.

It makes no difference what the parameter is called. If it were named apples too, there would still be two variables, one in each function.

A function that is meant to change something of the caller's returns the new value, and the caller stores it.

#include <iostream>

int doubled(int n) {
    return n * 2;
}

int main() {
    int apples = 4;
    apples = doubled(apples);
    std::cout << "apples in main: " << apples << "\n";
}
apples in main: 8

The assignment apples = doubled(apples); is in main, where apples lives. The function only supplies the value.

Pass by value is also a guarantee you can rely on when reading a program. After double_it(apples); you know apples is unchanged without reading the body of double_it. Module 10 shows a second way to pass a variable, for the cases where a function really must change it.

Functions that return nothing

A function whose return type is void gives nothing back. It is called for what it does: it prints, or it draws. A call to it is a whole statement.

A function with any other return type is called for what it gives back. A call to it is a value, to be stored, printed or used in an expression.

#include <iostream>

void print_square(int n) {
    std::cout << n * n << "\n";
}

int square(int n) {
    return n * n;
}

int main() {
    print_square(5);
    int area = square(5);
    std::cout << area + 1 << "\n";
}
25
26

Both functions work out 25. print_square sends it to the output, and the program can do nothing more with it. square hands it to main, which stores it, adds to it, and decides for itself what to print. Printing a value and returning a value are different things, and a function that is asked to return a value should not print it.

Since a void function gives nothing back, there is nothing to store. This program does not compile.

#include <iostream>

void print_square(int n) {
    std::cout << n * n << "\n";
}

int main() {
    int area = print_square(5);
    std::cout << area << "\n";
}

The compiler answers with a message like this one.

your_program.cpp:8:9: error: cannot initialize a variable of type 'int' with an rvalue of type 'void'

Read it as: line 8 tries to give an int variable a value, and the thing on the right has type void, which is no value at all.

A void function may still use return, written with no value: return;. It ends the function at once, as always.

#include <iostream>

void print_stars(int count) {
    if (count <= 0) {
        std::cout << "(none)\n";
        return;
    }
    for (int i = 0; i < count; ++i) {
        std::cout << "*";
    }
    std::cout << "\n";
}

int main() {
    print_stars(5);
    print_stars(0);
}
*****
(none)

Each call has its own variables

A variable declared inside a function, a parameter included, is a local variable of that function. It is created when the call reaches it and is gone when the call ends. No other function can see it.

#include <iostream>

int sum_to(int n) {
    int total = 0;
    for (int i = 1; i <= n; ++i) {
        total += i;
    }
    return total;
}

int main() {
    int total = 1000;
    std::cout << sum_to(4) << "\n";
    std::cout << sum_to(10) << "\n";
    std::cout << total << "\n";
}
10
55
1000

There are two variables named total here. One belongs to main and holds 1000 throughout. The other belongs to sum_to.

  • The name total inside sum_to always means sum_to's variable. The name total inside main always means main's. Neither function can reach the other's.
  • Each call gets a fresh one. The second call does not start from the 10 that the first call ended with. int total = 0; runs again and makes a new variable.

This is the rule from module 4, that a variable ends at the closing brace of the braces it was declared in, applied to the braces of a function body.

It also means a function cannot help itself to the caller's variables. This program does not compile, because price is declared in main and print_price has no variable of that name.

#include <iostream>

void print_price() {
    std::cout << "Price: " << price << "\n";
}

int main() {
    int price = 30;
    print_price();
}

Whatever a function needs from its caller comes in through a parameter: void print_price(int price), called as print_price(price);.

A variable can also be declared outside every function, above them all. That is a global variable: every function can read and change it, which is why it is avoided, since any line anywhere in the program may be the one that changed it.

Telling the compiler first

The compiler reads a program once, from the top. When it reaches a call, it must already know the function's name, its parameter types and its return type. So far that has been true because each function was defined above main.

Here main comes first, and the program does not compile.

#include <iostream>

int main() {
    std::cout << cube(3) << "\n";
}

int cube(int n) {
    return n * n * n;
}

The compiler answers with a message like this one.

your_program.cpp:4:18: error: use of undeclared identifier 'cube'

On line 4 the compiler has not yet seen cube. That the definition follows three lines later does not help; it does not look ahead.

A declaration tells the compiler what it needs to know. It is the first line of the definition, followed by a semicolon where the body would be.

#include <iostream>

int cube(int n);

int main() {
    std::cout << cube(3) << "\n";
}

int cube(int n) {
    return n * n * n;
}
27

int cube(int n); says: a function named cube exists, it takes one int and returns an int. That is enough to compile the call on line 6. The definition can then come anywhere below.

The declaration and the definition must agree: the same name, the same parameter types in the same order, the same return type. A declaration with no definition anywhere is also an error, reported at the end when the pieces of the program are joined.

With declarations at the top, main can come first. Many programmers prefer that order: a reader meets the outline of the program before its details.

One name, several versions

Two functions may have the same name if their parameter lists differ, in the number of parameters or in their types. This is called overloading. For each call, the compiler looks at the arguments and picks the version that fits them.

#include <iostream>

int half(int n) {
    return n / 2;
}

double half(double x) {
    return x / 2;
}

int sum_of(int a, int b) {
    return a + b;
}

int sum_of(int a, int b, int c) {
    return a + b + c;
}

int main() {
    std::cout << half(7) << "\n";
    std::cout << half(7.0) << "\n";
    std::cout << sum_of(1, 2) << "\n";
    std::cout << sum_of(1, 2, 3) << "\n";
}
3
3.5
3
6

7 is an int, so half(7) calls the first version, where n / 2 is a division of two ints and the fraction is dropped. 7.0 is a double, so half(7.0) calls the second. The type of the argument decides, as the compiler reads it in the program text: 7.0 is a double although its value is a whole number.

The two sum_of functions differ in how many parameters they have, so the number of arguments picks one.

Two things do not make functions different: the names of the parameters and the return type. int half(int n) and int half(int number) are the same function defined twice, and that does not compile.

Use overloading for functions that do the same job on different kinds of input. Two functions that do different jobs should have different names.

A usual value

Sometimes one argument is nearly always the same. A parameter can be given a default argument: a value written after = in the parameter list, used when the call leaves that argument out.

#include <iostream>
#include <string>

void print_line(int width, std::string mark = "-") {
    for (int i = 0; i < width; ++i) {
        std::cout << mark;
    }
    std::cout << "\n";
}

int main() {
    print_line(12);
    print_line(12, "=");
    print_line(3, "ab");
}
------------
============
ababab

print_line(12); gives one argument. The compiler fills in the second from the default, exactly as if you had written print_line(12, "-");. A call that gives both arguments uses its own.

Two rules.

  • Parameters with defaults come last in the list. Arguments are matched from the left, so only arguments at the end can be left out.
  • When a function has a declaration, the default is written in the declaration only. The declaration is what the compiler has seen when it reaches the call. Writing the default again in the definition does not compile.
#include <iostream>

int area_of(int width, int height = 1);

int main() {
    std::cout << area_of(5) << "\n";
    std::cout << area_of(5, 4) << "\n";
}

int area_of(int width, int height) {
    return width * height;
}
5
20
Your turn

Now try it yourself

Predict what a program will do, run it, and step through it line by line. The checks tell you when your own code works.

Loading the lab…

In this lab

  1. Follow the call

    This program has a function named middle, written above main. Decide what it prints before you run it.

  2. A flower anywhere

    draw_flower draws four pink petals and a yellow centre. It has no parameters, so every call draws the same flower in the same place: its centre is always at (150, 200).

    Change it so that the caller says where the flower goes.

    1. Make the first line void draw_flower(int x, int y), where (x, y) is the centre of the flower.
    2. In the body, replace the fixed numbers with x and y. The petals are 20 to the left of the centre, 20 to the right, 20 above and 20 below.
    3. In main, call it three times, to draw flowers with their centres at (150, 200), (300, 120) and (450, 200).
  3. What comes back?

    distance_from_zero has two return statements, and a statement that prints straight after the first one. main calls it twice and prints what it receives.

  4. Does the caller's variable change?

    add_one adds 1 to its parameter. main calls it with its variable score, and afterwards prints score.

    Decide what the line that starts with after: shows. This is the most important prediction of the module.

  5. Nothing to give back

    draw_flower is a void function. The programmer hoped it would report how many petals it drew, and stores the result of the call in an int.

  6. Two variables, one name

    main declares a variable named total. So does add_bonus, and it changes its own.

  7. main first

    In this program main comes first and perimeter is defined below it. Press Run and read the message: it names the line of the first call.

    Make the program compile without moving anything. Add a declaration of perimeter above main: its first line, then a semicolon.

    The program should print:

    Perimeter: 18
    Perimeter: 26
    
  8. Which one is called?

    Three functions are named show. One takes an int, one a double, one a std::string. Each prints the name of its own parameter type, then the value.

  9. A usual value

    draw_flower(x, y) draws a pink flower. Give it a third parameter for the colour of the petals, with pink as its usual value.

    1. Make the first line void draw_flower(int x, int y, std::string colour = "pink").
    2. Draw the four petals in colour. The centre stays yellow.
    3. In main, make the middle flower "purple" by adding a third argument to its call. Leave the other two calls with two arguments.
  10. draw_house

    main is written and calls a function that does not exist yet. Press Run to see what the compiler says about that. Then write the function, first line included, above main.

    First line: void draw_house(int x, int y, int size)

    What it draws, in this order:

    1. A wall: an orange square with its top-left corner at (x, y) and sides of size.
    2. A roof: a brown bar lying on top of the wall. It is size / 4 high, and it sticks out by size / 4 on the left and on the right, so it starts at x - size / 4 and its top is at y - size / 4.
    3. A door: a black rectangle, size / 4 wide and size / 2 high. Its left edge is size / 4 from the wall's left edge, and its bottom is the bottom of the wall.

    size is always a multiple of 4, so the divisions leave no remainder.

    The checks call your function themselves, with values that main never uses. If its first line has other parameter types, or another name, the checks cannot be built and you will be told so.

  11. is_leap_year

    main reads years until the input ends and prints one line for each. It asks a function is_leap_year for the answer. Write that function above main.

    First line: bool is_leap_year(int year)

    What it returns: true if the year is a leap year, false if not. The rule has three parts.

    • A year that divides by 4 is a leap year,
    • except a year that divides by 100, which is not,
    • except a year that divides by 400, which is.

    So 2024 is a leap year, 2023 is not, 1900 is not, and 2000 is.

    The function returns its answer. It prints nothing: main does the printing.

  12. clamp and max_of_three

    Write two small functions above main. Each returns an int and prints nothing.

    int clamp(int value, int low, int high) keeps a value inside a range. It returns low if value is below low, high if value is above high, and value itself otherwise. The caller promises that low is not greater than high. For example clamp(15, 0, 10) is 10, clamp(-4, 0, 10) is 0 and clamp(7, 0, 10) is 7.

    int max_of_three(int a, int b, int c) returns the largest of its three values. Two or all three of them may be equal.

    The parameters must be in the order given. The checks call both functions with values main never uses.

  13. A street

    main is given and comes first. Its loop draws a street of houses with a tree between each pair. It calls two functions that do not exist yet. Do not change main.

    Put a declaration of each function above main, and the two definitions below main.

    void draw_house(int x, int y, int size = 60) is the house from the step before: an orange wall, which is a square with its top-left corner at (x, y) and sides of size, a brown roof size / 4 high that starts at (x - size / 4, y - size / 4) and is size + size / 2 wide, then a black door size / 4 wide and size / 2 high at (x + size / 4, y + size / 2). You may copy your own function. The usual size is 60.

    void draw_tree(int x, int y, std::string colour = "green") draws a tree that stands on the point (x, y):

    1. a trunk: a brown rectangle 10 wide and 40 high, centred on x, with its bottom at y;
    2. the leaves: a circle of radius 25 in colour, with its centre at (x, y - 55), drawn after the trunk.

    Remember where a usual value is written when a function has both a declaration and a definition.

  14. is_prime

    In module 5 you tested one number for being prime inside main. Turn that test into a function, and use it.

    First line: bool is_prime(int n)

    What it returns: true if n is prime: a whole number of at least 2 that no number from 2 to n - 1 divides. So 0 and 1 are not prime, and neither is any negative number. The function prints nothing.

    main reads one whole number n and prints every prime below n, smallest first, each on its own line. For the input 20 it prints 2, 3, 5, 7, 11, 13, 17 and 19. For an input of 2 or less it prints nothing.

  15. Two areas

    Write two functions with the same name, above main.

    double area(double radius) returns the area of a circle: 3.14159 times the radius times the radius.

    double area(double width, double height) returns the area of a rectangle.

    Both return their answer and print nothing. main is given; it prints 12.5664 and 13.5.

  16. digit_count

    Write a function that says how many digits a whole number has, above main.

    First line: int digit_count(int n)

    What it returns: the number of digits of n, where n is 0 or more. 7 has one digit, 42 has two, 2026 has four. 0 has one digit.

    Dividing an int by 10 removes its last digit. Your function may change n as it counts: n is the function's own copy, so the caller's variable is safe. main prints number after the call to show this.

    The function prints nothing.

  17. print_table

    Write a function that prints a multiplication table, above main.

    First line: void print_table(int rows, int cols = 10)

    What it prints: rows lines. Line number r, counting from 1, holds the products r × 1, r × 2, and so on up to r × cols. Each product is printed with std::setw(4), and each line ends with \n.

    So print_table(2, 3) prints

       1   2   3
       2   4   6
    

    and print_table(2) prints two lines of ten products each.

  18. The function that did nothing

    A shop takes 20% off. apply_discount multiplies the price by 0.8, and yet the program prints You pay 50.00. Run it and see.

    Repair it in two places.

    1. Make apply_discount return the new price: its first line becomes double apply_discount(double price).
    2. In main, use what comes back, so that price holds the new price before it is printed.

    Leave the first line of main, double price = 50.0;, as it is. The checks try other prices there. The program should print You pay 40.00.

  19. Take main apart

    This main is about forty lines long. It draws a scene and prints a summary. It works, and it is hard to read.

    First run it, and note the picture and the three printed lines. Then split it into at least four functions without changing what it draws or prints. main should end up as a short list of calls that reads like an outline of the scene.

    Look for statements that belong together, and for groups of statements that are repeated with different numbers. A repeated group is one function with parameters, called several times.

    Nothing checks this one. When you have finished, go through the list below.