C++ Primer, Chapter 6

Functions

How code gets reused, how data flows in and out, and why the way you pass arguments changes everything.

Prerequisites: Chapter 2 (Variables) + Chapter 3 (Strings & Vectors).
9
Chapters
4+
Simulations

Chapter 0: Why Functions?

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.

The core idea: Functions let you give a name to a computation. Once named, you can use it without thinking about how it works. That's abstraction — the single most important concept in programming.
Function Call Flow

Click "Call fact(n)" to watch the call stack in action. The caller pauses, the function runs, the result comes back.

Ready
What is the primary benefit of putting code into a function?

Chapter 1: Function Basics

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 vs Arguments

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;.

TermWhereExample
ParameterDefinition: int fact(int val)val
ArgumentCall site: fact(5)5

Local Variables and Lifetime

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.

Local static variables are the exception. Declared with 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;
}
What is the difference between a parameter and an argument?

Chapter 2: Pass by Value

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.

Pointer Parameters

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
The pointer trap: Passing a pointer by value lets you modify the pointed-to object, but you can't change what the caller's pointer points to. If you reassign the pointer parameter, only the local copy changes. The caller never sees it.
Pass by Value vs Reference

Call increment both ways. By value: the original is untouched. By reference: the original changes.

After calling increment(n) where increment takes int x by value, what happens to n?

Chapter 3: Pass by Reference

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

Avoiding Copies with const Reference

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();
}
Lippman's rule: Reference parameters that are not changed should be references to 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.

Returning Multiple Values

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;
}
Why should you use const string &s instead of string s for a parameter you only read?

Chapter 4: Overloading

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)

What Counts as "Different"

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.

Top-level const does NOT count. 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.

Function Matching

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.

Can two overloaded functions differ only in their return type?

Chapter 5: Default Arguments

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, '#')
You can't skip. If you want to override the third parameter, you must provide the first two as well. Arguments are matched left to right. This is why you should order parameters from "most likely to change" to "least likely to change."

Where to Declare Defaults

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.

Given void f(int a, int b = 10, int c = 20), which call is valid?

Chapter 6: Call Stack Visualizer

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.

Recursion is when a function calls itself. Each recursive call gets its own stack frame with its own copy of the local variables. The frames stack up until a base case stops the recursion, then they unwind one by one, each returning its result to the caller above.
Recursive Call Stack

Watch fact(n) push frames onto the stack, hit the base case, then unwind. Use the slider to change n.

n 5
Ready
Notice: Each frame has its own copy of 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.

Chapter 7: inline & constexpr

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.

inline Functions

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 is a request, not a command. The compiler is free to ignore it. Most modern compilers inline functions on their own when they judge it profitable, regardless of the keyword. Still, marking small helper functions as inline signals your intent.

constexpr Functions

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)
Rules for constexpr functions: The return type and all parameter types must be literal types. The body must contain exactly one return statement (relaxed in C++14 to allow more). A constexpr function is implicitly inline.
When is a constexpr function evaluated at compile time?

Chapter 8: Beyond — Connections

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 ChapterBuilds Toward
Pass by referenceOperator overloading (Ch 14), move semantics (Ch 13)
OverloadingFunction matching rules, template specialization (Ch 16)
const referenceconst member functions in classes (Ch 7)
Call stack / recursionDynamic memory and allocation (Ch 12)
constexprTemplate metaprogramming (Ch 16)
Lippman's advice: "Use reference parameters to avoid unnecessary copies. Use reference to const for parameters you don't need to change." This one rule, consistently applied, eliminates entire categories of bugs and performance problems.

Continue Reading

Next up: Chapter 7: Classes — where functions become member functions and data gets encapsulated.