Functions
Functions package a piece of logic under a name so you can reuse it, test it and reason about it on its own. C++ functions have typed parameters and return values, can be declared before they're defined, and can return several values at once with structured bindings.
Defining and calling
#include <iostream> double rectangle_area(double width, double height) { return width * height; } void print_divider() { std::cout << "----------\n"; } int main() { print_divider(); std::cout << "Kitchen: " << rectangle_area(4.2, 3.0) << " m2\n"; std::cout << "Hall: " << rectangle_area(6.0, 1.2) << " m2\n"; print_divider(); }
---------- Kitchen: 12.6 m2 Hall: 7.2 m2 ----------
A function has a return type (void for none), a name, a parameter list and a body. return sends a value back and ends the function.
Declarations
A function must be declared before it's called. A declaration (or prototype) gives the signature without the body; the definition can come later — or in another file (lesson 48):
#include <iostream> bool is_prime(int n); // declaration int main() { for (int n = 1; n <= 20; ++n) { if (is_prime(n)) std::cout << n << ' '; } std::cout << '\n'; } bool is_prime(int n) { // definition if (n < 2) return false; for (int d = 2; d * d <= n; ++d) { if (n % d == 0) return false; } return true; }
2 3 5 7 11 13 17 19
In C++, empty parentheses mean "no parameters" — no void needed.
Return type deduction
auto as a return type lets the compiler deduce it from the return statements. A trailing return type (-> type) is another way to write the type, common with templates and lambdas:
#include <iostream> auto average(int a, int b) { // deduced: double return (a + b) / 2.0; } auto square(int x) -> long long { // trailing return type return 1LL * x * x; } int main() { std::cout << average(3, 4) << ' ' << square(100000) << '\n'; }
3.5 10000000000
Returning several values
Return a struct (or std::pair/std::tuple) and unpack it with a structured binding:
#include <iostream> #include <vector> struct MinMax { int min; int max; }; MinMax min_max(const std::vector<int>& values) { MinMax r{values.front(), values.front()}; for (int v : values) { if (v < r.min) r.min = v; if (v > r.max) r.max = v; } return r; } int main() { std::vector<int> temps{14, 9, 21, 17, 6}; auto [lo, hi] = min_max(temps); std::cout << "min " << lo << ", max " << hi << '\n'; }
min 6, max 21
This is clearer than C-style output parameters. Lesson 14 explains the const std::vector<int>& parameter.
[[nodiscard]]
Mark a function whose result must not be ignored — for example one that reports success or failure. The compiler warns if a caller throws the result away:
#include <string> [[nodiscard]] bool save(const std::string& filename) { return !filename.empty(); } int main() { save("notes.txt"); // result ignored }
main.cpp:8:5: warning: ignoring return value of function declared with 'nodiscard' attribute [-Wunused-result]
Recursion
A function can call itself, as long as it has a base case that stops the recursion:
#include <iostream> unsigned long long factorial(unsigned n) { return n <= 1 ? 1 : n * factorial(n - 1); } int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); } int main() { std::cout << factorial(20) << ' ' << gcd(1071, 462) << '\n'; }
2432902008176640000 21
Each call uses stack space, so for very deep repetition a loop is safer. Recursion suits naturally nested problems such as trees and divide-and-conquer algorithms.
Good functions
- Do one thing, and name it after what it does:
parse_date,is_valid,print_report. - Keep them short enough to read at a glance.
- Take inputs as parameters and give results as return values, rather than relying on global variables.
- Prefer returning a struct over several output parameters.
Try it yourself
Write count_vowels and use it on a few words.
Programming: 3 rhythm: 0 Education: 5