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 thereturnstatement.- Trailing return syntax
auto f(int x) -> intis 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 declaredRemember
- 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.