How code gets reused, how data flows in and out, and why the way you pass arguments changes everything.
Imagine writing a program that computes factorials. You need 5! in one place, 10! in another, and n! from user input in a third. Without functions, you'd copy-paste the same loop three times. Change the algorithm? Fix it in three places. Miss one? Bug.
A function is a named block of code you write once and call from anywhere. It takes inputs (parameters), does work, and returns a result. Functions are the basic unit of abstraction in C++ — they let you name an operation, hide its details, and reuse it.
Click "Call fact(n)" to watch the call stack in action. The caller pauses, the function runs, the result comes back.
A function definition has four parts: a return type, a name, a parameter list (in parentheses), and a body (in braces). The parameter list can be empty, but the parentheses are required.
c++ int fact(int val) { int ret = 1; while (val > 1) ret *= val--; return ret; // return type must match int } int main() { int j = fact(5); // j == 120 cout << "5! is " << j; }
Parameters are the variables declared in the function definition. Arguments are the actual values you pass when you call the function. The argument initializes the parameter — just like int val = 5;.
| Term | Where | Example |
|---|---|---|
| Parameter | Definition: int fact(int val) | val |
| Argument | Call site: fact(5) | 5 |
Variables defined inside a function are local — they exist only while the function is executing. When the function returns, they're destroyed. Parameters are also local variables, initialized from the arguments.
static, they persist across calls. A classic use: counting how many times a function has been called. The variable is initialized once and keeps its value.c++ size_t count_calls() { static size_t ctr = 0; // initialized ONCE, persists return ++ctr; }
When you pass an argument by value, the function gets a copy. Anything the function does to its parameter has no effect on the original. This is the default in C++.
c++ void increment(int x) { x += 1; // modifies the COPY, not the original } int n = 5; increment(n); // n is still 5!
This is safe — the caller's data can never be accidentally changed. But it has costs: copying a large object (like a vector with a million elements) is expensive.
Passing a pointer by value copies the pointer, not the pointed-to object. The function gets its own pointer, but both the original and the copy point to the same data:
c++ void reset(int *ip) { *ip = 0; // changes the object ip points to ip = 0; // changes only the local copy of the pointer } int i = 42; reset(&i); // i is now 0
Call increment both ways. By value: the original is untouched. By reference: the original changes.
increment(n) where increment takes int x by value, what happens to n?A reference parameter is an alias for the argument. Instead of copying, the function operates directly on the caller's object. Use & in the parameter declaration:
c++ void reset(int &i) { // i is a reference to the argument i = 0; // changes the caller's variable directly } int j = 42; reset(j); // j is now 0
For large objects you only need to read, use const reference. You get the efficiency of no-copy without the risk of accidental modification:
c++ bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); }
const. This is both safer and more flexible — a const reference can bind to a literal, a temporary, or a const object. A plain reference cannot.A function can return only one value. But with reference parameters, you can effectively return multiple results by having the function write into the caller's variables:
c++ // Returns position of first 'c'; also sets 'occurs' to count string::size_type find_char( const string &s, char c, string::size_type &occurs) { auto ret = s.size(); occurs = 0; for (decltype(ret) i = 0; i != s.size(); ++i) { if (s[i] == c) { if (ret == s.size()) ret = i; ++occurs; } } return ret; }
const string &s instead of string s for a parameter you only read?C++ lets you define multiple functions with the same name, as long as their parameter lists differ. The compiler picks the right version based on the arguments you pass. This is called function overloading.
c++ void print(const char *cp); // version 1: C-string void print(const int *beg, const int *end); // version 2: array range void print(const string &s); // version 3: std::string print("hello"); // calls version 1 print(s); // calls version 3 (s is a string)
Parameter lists must differ in the number or types of parameters. Return type alone is not enough — two functions that differ only in return type are an error.
void f(int) and void f(const int) are the same function — because the caller's argument is copied either way, the const makes no observable difference. But void f(int&) and void f(const int&) are different, because one can bind to non-const objects and the other can bind to const objects.When you call an overloaded function, the compiler tries to find the best match. If no function matches, it's an error. If multiple functions match equally well, it's ambiguous — also an error. The compiler prefers exact type matches over conversions.
A default argument is a value automatically used when the caller omits the corresponding argument. Defaults are declared in the function declaration, and they must appear at the end of the parameter list:
c++ string screen( int ht = 24, int wid = 80, char bg = ' '); screen(); // screen(24, 80, ' ') screen(66); // screen(66, 80, ' ') screen(66, 256); // screen(66, 256, ' ') screen(66, 256, '#');// screen(66, 256, '#')
Default arguments are typically specified in the function declaration (in a header file), not the definition. The compiler needs to see the default at the call site. You can add defaults to later declarations, but you cannot redefine one that already has a default.
void f(int a, int b = 10, int c = 20), which call is valid?Every time a function is called, a new stack frame is pushed onto the call stack. The frame holds the function's parameters and local variables. When the function returns, the frame is popped and destroyed. Let's watch this happen with a recursive function.
Watch fact(n) push frames onto the stack, hit the base case, then unwind. Use the slider to change n.
val. When fact(1) returns 1, it doesn't destroy the frames above — it returns to fact(2), which multiplies 2 * 1 and returns to fact(3), and so on. The result accumulates on the way back up.Function calls have overhead: push a stack frame, copy arguments, jump to the function body, jump back. For tiny functions called millions of times, this overhead matters. C++ gives you two tools to eliminate it.
The inline keyword asks the compiler to replace the call with the function body. No stack frame, no jump — just the code pasted in place:
c++ inline const string &shorterString( const string &s1, const string &s2) { return s1.size() <= s2.size() ? s1 : s2; }
inline signals your intent.A constexpr function can be evaluated at compile time if its arguments are constant expressions. The result is baked into the binary — no function call at all:
c++ constexpr int scale(int cnt) { return 2 * cnt; } int arr[scale(10)]; // OK: 20 computed at compile time int n = 5; int x = scale(n); // OK: computed at runtime (n is not constexpr)
return statement (relaxed in C++14 to allow more). A constexpr function is implicitly inline.constexpr function evaluated at compile time?This chapter covered the mechanics of functions: how arguments are passed, how overloading works, how the call stack manages frames, and how inline and constexpr can eliminate call overhead. Functions are the backbone of every C++ program.
| This Chapter | Builds Toward |
|---|---|
| Pass by reference | Operator overloading (Ch 14), move semantics (Ch 13) |
| Overloading | Function matching rules, template specialization (Ch 16) |
| const reference | const member functions in classes (Ch 7) |
| Call stack / recursion | Dynamic memory and allocation (Ch 12) |
| constexpr | Template metaprogramming (Ch 16) |
Next up: Chapter 7: Classes — where functions become member functions and data gets encapsulated.