Functions

C++ · Essentials · 12 min

Functions are the unit of reuse and testing, and C++'s declare-before-use and pass-by-value rules shape how code is organised.

A function has a return type, a name, parameters and a body: int square(int x) { return x * x; }. Use void when it returns nothing. A non-void function must return a value on every path; falling off the end is undefined behaviour (compilers warn with -Wall).

C++ compiles top to bottom, so a function must be declared before it is called. A declaration (a prototype) is the signature followed by a semicolon: int square(int x);. The definition with the body can come later or live in another file. This split is exactly how headers work: headers hold declarations, .cpp files hold definitions.

By default arguments are passed by value: the parameter is a copy, so changes inside the function don't affect the caller. Copying an int is free, but copying a large std::vector or std::string is not; the next lessons show how references avoid that.

A few modern touches:

  • auto f(int x) { return x * 2; } deduces the return type from the return statement.
  • Trailing return syntax auto f(int x) -> int is equivalent and common in generic code.
  • [[nodiscard]] int parse(...) makes the compiler warn when the caller ignores the result, which is great for error codes.

Recursion works as you'd expect, but each call uses stack space, so very deep recursion can overflow the stack.

Keep functions short and give them one job. A function you can describe in one sentence is easy to test, and the compiler inlines small functions, so splitting code up costs nothing at run time.

Example

#include <iostream>

int square(int x);                  // declaration (prototype)

double average(int a, int b) {      // definition
    return (a + b) / 2.0;
}

void greet(const char* who) {       // void: returns nothing
    std::cout << "hi " << who << '\n';
}

long long factorial(int n) {        // recursion
    return n <= 1 ? 1 : n * factorial(n - 1);
}

void bump(int x) { x += 1; }        // x is a copy: caller's variable unchanged

int main() {
    int v = 4;
    bump(v);
    std::cout << square(v) << ' ' << average(3, 4) << '\n';  // 16 3.5
    std::cout << factorial(10) << '\n';                       // 3628800
    greet("Ann");                                             // hi Ann
}

int square(int x) { return x * x; }  // defined after main: fine, it was declared

Remember

  • Declare before use; a prototype is enough, the definition can come later or elsewhere
  • Arguments are copied by default
  • Mark results callers must check with [[nodiscard]]

Watch out for

  • Forgetting to return a value on some path of a non-void function — it compiles but is undefined behaviour.

Check yourself

  1. `void reset(int n) { n = 0; }` is called as `reset(count);`. What happens to count?
  2. main calls helper(), which is defined further down the file with no earlier declaration. What happens?

Sign in to answer, see the explanations, and get this lesson back as a quick review later.

Official docs: en.cppreference.com/w/cpp/language/functions