Lecture 5
Functions
You've written functions before in Python. C++ functions work the same way — you give a block of code a name, declare what it takes in, and declare what it gives back. The main difference is that C++ requires you to be explicit about types everywhere.
The return type (int) comes before the function name. Each parameter declares its type. If a function doesn't return anything, use void as the return type.
Functions must be declared before they are called. Either define the function above main, or use
a forward declaration (just the signature, no body) above main and put the full definition
below it.
const
Before we talk about how to pass arguments to functions, we need to understand const.
const marks a variable as read-only — once initialized, its value cannot be changed. Attempting to reassign a const variable is a compile error.
You'll encounter const most often in function parameters, where it tells both the compiler and the reader: "this function promises not to modify this value."
Call-by-value
When you pass an argument by value, C++ makes a copy of it. The function works on its own local copy — any changes inside the function have no effect on the original variable.
When to use it: when the function doesn't need to modify the original, and the type is small (int, double, char, etc.). Copying a small value is cheap.
Passing large types like std::string or std::vector by value copies the entire contents —
which can be expensive. For those, use a reference instead (see below).
Call-by-reference
When you pass an argument by reference, the function receives an alias to the original variable. There is no copy — the function operates directly on the caller's data.
When to use it: when the function needs to modify the original variable.
Read-only references (const T&)
Sometimes you want to avoid an expensive copy, but the function has no reason to modify the value. Use const T& — you get the efficiency of a reference with the safety of a read-only guarantee.
When to use it: when the function only reads the argument and the type is large enough that copying matters (std::string, std::vector, structs). For small types like int and double, plain pass-by-value is fine — const int& buys nothing there.
const T& is the most common parameter style in real C++ code. When you're not modifying an
argument and the type is non-trivial, default to const T&.
Call-by-pointer
You can also pass a pointer to a variable. The function receives the address and can modify the original by dereferencing it — the same effect as a mutable reference, but spelled differently.
Notice that the call site must explicitly pass &n — the & makes it visible to the reader that the function could modify n. With a reference, that visibility is hidden in the function signature.
The other key difference: a pointer can be nullptr. A function taking a pointer should guard against it:
When to use it: when the argument might legitimately be absent (nullptr is a valid state), or when interfacing with C-style APIs that use pointers. In modern C++, prefer references when you know the argument always exists — they can't be null and the syntax is cleaner.
Summary
| Style | Syntax | Modifies original? | Can be null? | Use when |
|---|---|---|---|---|
| By value | void f(int x) | No | No | Small types, read-only |
| By const ref | void f(const int& x) | No | No | Large types, read-only |
| By ref | void f(int& x) | Yes | No | Need to mutate the original |
| By pointer | void f(int* x) | Yes (via *) | Yes | Nullability needed, or C APIs |
Exercises
Q1.
Without running the code, predict what this program prints. Then verify.
Q2.
Each function below has a const& parameter. Without running the code, determine whether each call compiles. If it doesn't, explain why.
Q3.
Without running the code, trace through this program and predict the final values of x and y.
Q4.
Without running the code, predict what this program prints.
Q5.
Write three versions of a function called scale that multiplies a value by a given factor:
scale_value(int x, int factor)— returns the scaled value (pass by value, no mutation)scale_ref(int& x, int factor)— modifiesxin place (pass by reference)scale_ptr(int* x, int factor)— modifies*xin place (pass by pointer); do nothing ifptrisnullptr
All three should produce the same end result when called on the same input.
Practice Problems
- Power of Two — Easy
- Find Greatest Common Divisor of Array — Easy
- Climbing Stairs — Easy