Fundamentals
C++ functions: the full picture
Short answer: a function is a named block with typed inputs (parameters) and one typed output (the return value). Calling it runs the block with specific inputs and hands back the result.
Anatomy
int add(int a, int b) { // return type, name, parameter list
return a + b; // body, ends by returning an int
}
Four things to read in that line: int is what comes back, add is the name, int a, int b are the inputs, and return produces the output. A function with no return value is declared void.
Calling is just substitution
#include <iostream>
using namespace std;
int add(int a, int b) {
return a + b;
}
int square(int x) {
return x * x;
}
int main() {
cout << add(2, 3) << endl; // 5
cout << square(add(1, 1)) << endl; // (2)^2 = 4 — a call inside a call
return 0;
}
The second line is the point: a function call is a value, so it can sit anywhere a literal could. That is what makes functions composable.
Parameters are copies
This is the single most important fact about C++ functions:
#include <iostream>
using namespace std;
void tryToChange(int n) {
n = 999; // changes the local copy only
}
int main() {
int x = 5;
tryToChange(x);
cout << x << endl; // still 5
return 0;
}
To actually change the caller's variable you must pass an address (pointer) or a reference. The full comparison, with diagrams, is in pass by value, pointer or reference.
Return vs print
A function that returns can be used in expressions; one that prints cannot:
#include <iostream>
using namespace std;
int doubled(int n) { return n * 2; }
void printed(int n) { cout << n * 2 << endl; }
int main() {
int total = doubled(10) + 1; // works: doubled() is a value
cout << total << endl;
printed(10); // works, but cannot be reused
return 0;
}
House rule: compute in functions (return), print in main. It keeps the function testable and reusable.
Scope: where names live
#include <iostream>
using namespace std;
int counter = 0; // global: visible everywhere
int bump() {
int local = 5; // local: exists only during this call
counter++;
return counter + local;
}
int main() {
cout << bump() << endl; // 6
cout << bump() << endl; // 7 — the global survived
// cout << local; // error: local is not in scope here
return 0;
}
Prefer locals, pass what a function needs as parameters, and return what it produces. Globals are convenient and quietly make code hard to reason about.
Why split code at all
- One definition, many uses. Change the rule in one place, and every call site follows.
- Names become sentences.
isPrime(n)reads better than twelve lines of trial division. - Testability. A function with parameters and a return value can be checked by looking at its output; a block that prints and mutates globals cannot.
- Recursion becomes possible. A function can call itself — the basis of recursion.
The three bugs
- Falling off the end. Declared to return
intbut a path has noreturn— undefined behaviour, not a compile error. - Expecting pass-by-value to mutate. The most common beginner confusion; the function changes a copy.
- Declared before use. C++ needs to know the signature before the call — that is what a declaration (or a definition above
main) provides.
Practice: Prime Check and GCD are both cleaner with a helper function. Read next: recursion, then passing modes.