Functions
Reviewed & published by Brayan K
By the end of this lesson you'll be able to write your own C++ functions with parameters and return values, choose between passing by value, reference, and const reference, give parameters default values, and overload a function name — the skills that turn long scripts into clean, reusable code.
Part of the free C++ course at LearnCodingFast — hands-on lessons with worked examples and the output they print, plus practice exercises and a quick quiz.
What You'll Learn
- Declare and define functions with parameters and return types
- Pass by value vs by reference (int&) vs const reference (const std::string&)
- Give parameters default arguments (rightmost only)
- Overload one function name for different parameter types
- Write a prototype/declaration so code can call a function defined later
- Know when inline matters (and when to ignore it)
💡 Real-World Analogy
A function is a recipe card. You write the steps once — "make a coffee" — and from then on you just say "make a coffee" instead of re-listing every step. The parameters are the ingredients you hand over (the bean, the cup size); the return value is the finished coffee handed back. Passing by value is like giving the chef a photocopy of your shopping list — they can scribble on it and your original is untouched. Passing by reference is handing over the original list itself, so any note they make is on your copy. That single distinction explains most "why didn't my value change?" confusion in C++.
1. Declaring & Defining Functions
A function has a return type (what it hands back), a name, a list of parameters (its inputs), and a body. The shape is always the same: returnType name(parameters) { ... return value; }. Use void when the function just does something and hands nothing back. Read this worked example, run it, then you'll write your own.
#include <iostream>
#include <string>
using namespace std;
// A function = a named block of code you can call by name, again and again.
// Pattern: returnType name(parameters) { body; return value; }
// void = "returns nothing". It just DOES something.
void greet(string name) {
cout << "Hello, " << name << "!" << endl; // prints, returns nothing
}
// Returns an int. The 'return' hands a value back to the caller.
int add(int a, int b) {
return a + b; // the result of add(...) IS this value
}
// Two parameters, returns a double (a decimal number).
double averageOf(double a, double b) {
return (a + b) / 2.0;
}
int main() {
greet("Alice"); // Hello, Alice!
greet("Bob"); // Hello, Bob!
int sum = add(15, 27); // capture the returned value
cout << "15 + 27 = " << sum << endl; // 15 + 27 = 42
double avg = averageOf(8.0, 5.0); // use the return value directly too
cout << "average = " << avg << endl; // average = 6.5
return 0;
}
// ✅ Expected output:
// Hello, Alice!
// Hello, Bob!
// 15 + 27 = 42
// average = 6.5Your turn. The program below is almost complete — fill in the two blanks marked ___ using the hints, then run it.
#include <iostream>
using namespace std;
// 🎯 YOUR TURN — finish the rectangleArea function, then press "Try it Yourself".
// Write a function that takes width and height and RETURNS their product.
int rectangleArea(int width, int height) {
return ___; // 👉 multiply the two parameters (width * height)
}
int main() {
int area = ___; // 👉 call rectangleArea(5, 3) and store the result
cout << "Area: " << area << endl;
// ✅ Expected output:
// Area: 15
return 0;
}2. By Value vs by Reference vs const Reference
How you write a parameter decides whether the function can change the caller's data. By value (int x) gives the function a private copy — changes stay inside. By reference (int&x) lets the function edit the caller's actual variable. By const reference (const std::string&) gives read-only access with no copy, which is the efficient way to pass big objects like strings and vectors you only need to read.
| Method | Syntax | Changes caller? | Use when |
|---|---|---|---|
| By value | f(int x) | ❌ No (copy) | Small types; you want safety |
| By reference | f(int &x) | ✅ Yes | Must modify the original |
| By const ref | f(const string &s) | ❌ No (read-only) | Large objects; efficiency |
#include <iostream>
#include <string>
using namespace std;
// Pass by VALUE — the function gets a COPY. Changes stay inside the function.
void tryToDouble(int x) {
x *= 2; // only the copy changes
}
// Pass by REFERENCE (int&) — the function works on the CALLER's variable.
void doubleInPlace(int &x) {
x *= 2; // the caller's value really changes
}
// Pass by CONST REFERENCE (const std::string&) — no copy, AND read-only.
// Perfect for big objects you only need to read, like strings or vectors.
void announce(const string &message) {
cout << "Announcement: " << message << endl;
// message = "no"; // ❌ would not compile — const = read-only
}
int main() {
int n = 10;
tryToDouble(n);
cout << "After tryToDouble: " << n << endl; // 10 (unchanged — it was a copy)
doubleInPlace(n);
cout << "After doubleInPlace: " << n << endl; // 20 (changed — by reference)
string title = "Functions are reusable";
announce(title); // Announcement: Functions are reusable
return 0;
}
// ✅ Expected output:
// After tryToDouble: 10
// After doubleInPlace: 20
// Announcement: Functions are reusableNow you try. Right now addBonus takes a copy, so the caller's score never changes. Add the one character that turns the parameter into a reference:
#include <iostream>
using namespace std;
// 🎯 YOUR TURN — make addBonus change the caller's variable.
// Add 'bonus' to 'score'. Because score should change for the CALLER,
// it must be passed BY REFERENCE.
void addBonus(int ___ score, int bonus) { // 👉 add the & so it's a reference (int &score)
score += bonus;
}
int main() {
int score = 100;
addBonus(score, 50);
cout << "Score: " << score << endl;
// ✅ Expected output:
// Score: 150
return 0;
}3. Default Arguments & Overloading
Default arguments let a parameter fall back to a value when the caller leaves it out — handy for optional settings. They must be the rightmost parameters. Overloading means giving several functions the same name but different parameter lists; the compiler picks the right one from the argument types. Note: overloads must differ in their parameters, not just the return type.
#include <iostream>
#include <string>
using namespace std;
// DEFAULT ARGUMENT — if the caller omits width, it becomes 1.0.
// Defaults must be the RIGHTMOST parameters.
double area(double length, double width = 1.0) {
return length * width;
}
// FUNCTION OVERLOADING — same name, DIFFERENT parameter lists.
// The compiler picks the right one from the argument types.
void show(int value) {
cout << "int: " << value << endl;
}
void show(double value) {
cout << "double: " << value << endl;
}
void show(const string &value) {
cout << "string: " << value << endl;
}
int main() {
cout << "area(5, 3) = " << area(5.0, 3.0) << endl; // 15 (both given)
cout << "area(5) = " << area(5.0) << endl; // 5 (width defaults to 1.0)
show(42); // int: 42 -> picks show(int)
show(3.14); // double: 3.14 -> picks show(double)
show("hello"); // string: hello -> picks show(const string&)
return 0;
}
// ✅ Expected output:
// area(5, 3) = 15
// area(5) = 5
// int: 42
// double: 3.14
// string: hello🔎 Deep Dive: declarations (prototypes) & inline
C++ reads a file top to bottom, so a function must be known before you call it. A declaration (or prototype) announces a function's signature without its body — note the semicolon. You can declare it up top and write the full definition later, even below main().
int add(int a, int b); // declaration / prototype — just the signature
int main() {
cout << add(2, 3); // works: the compiler already knows add
}
int add(int a, int b) { // definition — the actual body, defined later
return a + b;
}inline is a hint that the compiler may paste a small function's body straight into the call site instead of making a real call. Modern compilers decide this for you, so you rarely add it for speed. Its real day-to-day use is letting a function be defined in a header that many files include without a "multiple definition" linker error.
inline int square(int x) { return x * x; } // safe to define in a headerPro Tips
- 💡 Pass big objects by const&: const string &s avoids copying the whole string and protects it from accidental edits.
- 💡 One function, one job: if a function is doing several things, split it — small functions are easier to name, test, and reuse.
- 💡 Defaults belong in one place: put a default argument on the declaration or the definition, not both, or the compiler complains.
- 💡 Overload by type, not by return: int f() and double f() with the same parameters won't compile — the parameter list must differ.
Common Errors (and the fix)
- "My value didn't change!" — you passed by value, so the function edited a copy. If the caller's variable must change, take the parameter by reference: void f(int &x) instead of void f(int x).
- "control reaches end of non-void function" — a function declared to return something has a path that never hits a return. Make sure every branch returns a value.
- "call of overloaded 'f(...)' is ambiguous" — two overloads match the arguments equally well (e.g. an int argument that could convert to double or long). Make the argument's type explicit or remove the clashing overload.
- "'add' was not declared in this scope" — you called a function before the compiler had seen it. Add a declaration/prototype above the call, or move the definition higher.
- "default argument given for parameter 1 ... after ..." — a parameter with a default sits to the left of one without. Move all defaulted parameters to the right.
📋 Quick Reference
| Concept | Syntax |
|---|---|
| Void function | |
| Return a value | |
| Declaration / prototype | |
| Default argument | |
| By reference | |
| Const reference | |
| Overloading | Same name, different parameter list |
| Inline |
Mini-Challenge: Greeting & Total
No blanks this time — just a brief and an outline to keep you on track. Write the two functions yourself (one uses a default argument, the other combines defaults with multiple inputs), then check your output against the comments.
#include <iostream>
#include <string>
using namespace std;
// 🎯 MINI-CHALLENGE: a small greeting + total tool
// 1. Write string greeting(string name, string title = "friend")
// that RETURNS "Hi <title> <name>!" (title defaults to "friend").
// 2. Write int total(int a, int b, int c = 0) that RETURNS a + b + c
// (c defaults to 0 so it works with two OR three numbers).
// 3. In main(): print greeting("Sam") and greeting("Dr. Lee", "Professor"),
// then print total(2, 3) and total(2, 3, 4).
//
// ✅ Expected output:
// Hi friend Sam!
// Hi Professor Dr. Lee!
// 5
// 9
int main() {
// your code here
return 0;
}🎉 Lesson Complete
- ✅ A function is returnType name(parameters) { ... return value; }; use void when nothing comes back
- ✅ By value copies; by reference (int&) edits the caller; const reference (const std::string&) is read-only with no copy
- ✅ Default arguments must be the rightmost parameters
- ✅ Overloading reuses a name with different parameter lists — not just a different return type
- ✅ A declaration/prototype lets you call a function defined later; inline mainly enables header definitions
Practice quiz
What return type means a function hands nothing back?
- null
- none
- void
- empty
Answer: void. void means the function returns nothing — it just does something.
If you pass an int by value and the function changes it, what happens to the caller's variable?
- It stays unchanged (the function got a copy)
- It changes too
- It becomes 0
- The program won't compile
Answer: It stays unchanged (the function got a copy). Pass by value gives the function a copy, so the caller's variable is untouched.
How do you let a function modify the caller's variable?
- Pass by value
- Use const
- Return void
- Pass by reference, e.g. void f(int &x)
Answer: Pass by reference, e.g. void f(int &x). A non-const reference (int &x) lets the function edit the caller's actual variable.
Why pass a large object as 'const std::string&'?
- It is required for all strings
- No copy is made and it is read-only
- It makes the function return faster
- It converts the string to a number
Answer: No copy is made and it is read-only. A const reference avoids copying the big object and guarantees the function won't change it.
Where must default arguments appear in a parameter list?
- Rightmost
- Leftmost
- Anywhere
- Only in the middle
Answer: Rightmost. Defaults must be the rightmost parameters so the compiler can match arguments left to right.
What must differ between overloaded functions?
- Only the return type
- The function name
- The parameter list (types or number)
- Nothing
Answer: The parameter list (types or number). Overloads must differ by their parameter list, not just the return type.
What is a function declaration (prototype)?
- The function body
- The signature ending in a semicolon, with no body
- A call to the function
- A comment describing it
Answer: The signature ending in a semicolon, with no body. A declaration/prototype gives the name, return type, and parameter types, ending with a semicolon.
Given 'double area(double length, double width = 1.0)', what does area(5.0) return?
- 0
- 1
- 6
- 5
Answer: 5. width defaults to 1.0, so 5.0 * 1.0 = 5.
What is the main practical use of 'inline' in modern C++?
- Guaranteeing a speed boost
- Allowing a function definition in a header without multiple-definition errors
- Making a function private
- Preventing recursion
Answer: Allowing a function definition in a header without multiple-definition errors. inline mainly lets a function be defined in a header included by many files without linker errors.
What does 'add(15, 27)' return for 'int add(int a, int b){ return a + b; }'?
- 1527
- 27
- 42
- 15
Answer: 42. It returns the sum a + b, which is 42.
Continue this course
- Previous: Range-Based for Loops
- Next: Recursion — Functions that call themselves — and the call stack
- Quick reference: C++ cheat sheet
Frequently asked questions
What is the difference between a declaration and a definition?
A declaration (also called a prototype) tells the compiler a function's name, return type, and parameter types, ending with a semicolon: int add(int, int);. A definition provides the actual body. You can declare a function near the top of a file and define it later — that lets main() call functions written below it.
When should I pass by value, by reference, or by const reference?
Pass by value for small types (int, double, bool, char) when the function only needs a copy. Use a non-const reference (int&) when the function must change the caller's variable. Use a const reference (const std::string&) for large objects you only read — it avoids an expensive copy and the const stops you from changing them by accident.
Why must default arguments be the rightmost parameters?
Because C++ matches arguments to parameters left to right. If a middle parameter had a default but a later one did not, the compiler could not tell which arguments you meant to skip. So once one parameter has a default, every parameter to its right must have one too.
How does function overloading decide which version to call?
Overloads must differ by their parameter list (the types or number of parameters), not just the return type. The compiler looks at the argument types at the call site and picks the best match. If two overloads match equally well, you get an 'ambiguous call' error and must make the types clearer.
What does inline do?
inline is a hint that the compiler may replace a call to a small function with the function's body directly, avoiding call overhead. Modern compilers usually decide this for you, so you rarely need it for speed. Its main practical use today is allowing a function definition to live in a header included by many files without a 'multiple definition' linker error.