📝 Lesson 7: Functions
Learn to write modular, reusable C++ code using functions — from parameters and return values to overloading, default arguments, and recursion.
🎯 Learning Objectives
By the end of this lesson, you will be able to:
- Understand the anatomy of a function: return type, name, parameters, and body.
- Distinguish pass-by-value from pass-by-reference and choose the right one.
- Overload functions and supply default parameter values.
- Use function prototypes to organize code and write recursive functions with proper base cases.
Estimated Time: 90–120 minutes
Project: Build a modular calculator that uses separate functions for each operation, a display menu, and input validation.
In This Lesson
What Are Functions?
Think of functions like recipes in a cookbook. Instead of writing out how to make chocolate chip cookies every time you want them, you just refer to the recipe. Functions let you write code once and use it many times!
Anatomy of a Function
A function has four main parts, like a machine with input, processing, and output!
Function Types and Examples
Complete Function Examples
// 1. Simple function with no parameters
void printWelcome() {
cout << "Welcome to the program!" << '\n';
cout << "Let's learn functions!" << '\n';
}
// 2. Function with parameters
void greetUser(string name, int age) {
cout << "Hello, " << name << "!" << '\n';
cout << "You are " << age << " years old." << '\n';
}
// 3. Function that returns a value
int square(int number) {
return number * number;
}
// 4. Function with multiple parameters and return
double calculateBMI(double weight, double height) {
return weight / (height * height);
}
// 5. Using the functions
int main() {
printWelcome(); // Call function with no parameters
greetUser("Alice", 25); // Call with parameters
int result = square(5); // Store return value
cout << "5 squared is: " << result << '\n';
double bmi = calculateBMI(70, 1.75);
cout << "BMI: " << bmi << '\n';
return 0;
}
Function Call Flow
When you call a function, it's like taking a detour - you pause the main road, take the function path, then return where you left off!
Pass by Value vs Pass by Reference
Pass by value is like giving someone a photocopy - they can mark it up without affecting your original. Pass by reference is like giving them your actual document!
💡 const reference parameters
You'll often see parameters written as const string& name — a const reference. The & passes the argument by reference (no copy, so it's efficient for large objects), while const promises the function won't modify it — the speed of pass-by-reference with the safety of pass-by-value. It's the standard way to pass big read-only arguments. You'll study const in depth in Lesson 12: Const Correctness; for now, read const T& as "an efficient, look-but-don't-touch parameter."
Reference Parameter Examples
// Pass by value - original unchanged
void tryToDouble(int num) {
num = num * 2;
cout << "Inside function: " << num << '\n';
}
// Pass by reference - original changed
void actuallyDouble(int &num) {
num = num * 2;
cout << "Inside function: " << num << '\n';
}
// Swap function - classic reference example
void swap(int &a, int &b) {
int temp = a;
a = b;
b = temp;
}
// Using const reference for large objects (efficient, read-only)
void printArray(const int arr[], int size) {
for (int i = 0; i < size; i++) {
cout << arr[i] << " ";
}
cout << '\n';
}
int main() {
int x = 10;
tryToDouble(x);
cout << "After tryToDouble: " << x << '\n'; // Still 10
actuallyDouble(x);
cout << "After actuallyDouble: " << x << '\n'; // Now 20
int a = 5, b = 8;
swap(a, b);
cout << "a=" << a << ", b=" << b << '\n'; // a=8, b=5
return 0;
}
Function Overloading: Same Name, Different Jobs
Function overloading is like having multiple tools with the same name but different uses - a "cut" function might cut paper, fabric, or video!
Function Overloading Example
// Different parameter types
int add(int a, int b) {
return a + b;
}
double add(double a, double b) {
return a + b;
}
// Different number of parameters
int add(int a, int b, int c) {
return a + b + c;
}
// Different parameter order
void display(int num, const string& text) {
cout << "Number: " << num << ", Text: " << text << '\n';
}
void display(const string& text, int num) {
cout << "Text: " << text << ", Number: " << num << '\n';
}
int main() {
cout << add(5, 3) << '\n'; // Calls int version: 8
cout << add(5.5, 3.2) << '\n'; // Calls double version: 8.7
cout << add(1, 2, 3) << '\n'; // Calls three-parameter version: 6
display(42, "Answer"); // Number: 42, Text: Answer
display("Answer", 42); // Text: Answer, Number: 42
return 0;
}
Default Parameters: Optional Values
Default parameters are like restaurant orders - "I'll have the usual" means you get your default choice!
// Function with default parameters
void greet(const string& name, const string& greeting = "Hello") {
cout << greeting << ", " << name << "!" << '\n';
}
// Multiple defaults (must be rightmost)
void createWindow(int width = 800, int height = 600, const string& title = "My Window") {
cout << "Creating " << width << "x" << height << " window: " << title << '\n';
}
int main() {
greet("Alice"); // Uses default: "Hello, Alice!"
greet("Bob", "Hi"); // Uses provided: "Hi, Bob!"
createWindow(); // All defaults: 800x600 "My Window"
createWindow(1024, 768); // Custom size, default title
createWindow(1920, 1080, "Game"); // All custom values
return 0;
}
Function Prototypes: Declare First, Define Later
Function prototypes are like movie trailers - they tell you what's coming without showing the whole thing!
Recursion: Functions Calling Themselves
Recursion is like Russian nesting dolls - each doll contains a smaller version of itself until you reach the smallest one!
Recursion Examples
// Factorial: n! = n × (n-1) × ... × 1
int factorial(int n) {
if (n <= 1) { // Base case
return 1;
}
return n * factorial(n - 1); // Recursive case
}
// Fibonacci sequence
int fibonacci(int n) {
if (n <= 1) { // Base cases
return n;
}
return fibonacci(n - 1) + fibonacci(n - 2);
}
// Sum of array elements recursively
int sumArray(int arr[], int size) {
if (size == 0) { // Base case: empty array
return 0;
}
return arr[size - 1] + sumArray(arr, size - 1);
}
// Palindrome checker
bool isPalindrome(const string& str, int start, int end) {
if (start >= end) { // Base case: middle reached
return true;
}
if (str[start] != str[end]) {
return false;
}
return isPalindrome(str, start + 1, end - 1);
}
Practice Exercise: Calculator Functions
🏋️ Build a Modular Calculator
Create a calculator using functions for each operation:
- Functions for add, subtract, multiply, divide
- A display menu function
- Input validation function
- Main calculator loop
#include <iostream>
using namespace std;
// TODO: Create function prototypes here
int main() {
int choice;
double num1, num2, result;
do {
displayMenu();
cin >> choice;
if (choice >= 1 && choice <= 4) {
cout << "Enter two numbers: ";
cin >> num1 >> num2;
// TODO: Call appropriate function based on choice
cout << "Result: " << result << '\n';
}
} while (choice != 5);
return 0;
}
✅ Solution
double add(double a, double b);
double subtract(double a, double b);
double multiply(double a, double b);
double divide(double a, double b);
void displayMenu();
bool isValidChoice(int choice);
Function Best Practices
Common Function Patterns
Challenge Exercise: Text Adventure Game
🏋️ Advanced Function Challenge
Create a text adventure game using functions for:
- Room descriptions
- Player movement
- Inventory management
- Combat system
- Save/Load game state
✅ Solution
- void describeRoom(int roomId)
- int movePlayer(int currentRoom, char direction)
- bool addToInventory(string item)
- int combat(int playerHealth, int enemyHealth)
- void saveGame(int room, int health)
🎯 Quick Quiz
Question 1: Given void swap(int &a, int &b) that swaps its two arguments, what is printed after int a = 5, b = 8; swap(a, b); cout << a << " " << b;?
Question 2: Given the overloads int add(int a, int b), double add(double a, double b), and int add(int a, int b, int c), which overload does the call add(1, 2, 3) resolve to?
Question 3: What happens if a recursive function, like factorial, is written without a base case?
Summary
🎉 Key Takeaways
- Functions break programs into manageable, reusable pieces
- Parameters pass information into functions
- Return values send results back to the caller
- Pass by reference allows functions to modify original variables
- Overloading lets you create multiple versions of the same function
- Default parameters make functions more flexible
- Prototypes allow flexible code organization
- Recursion solves problems by breaking them into smaller versions
📚 Additional Resources
🚀 What's Next?
You can now break programs into clean, reusable functions. Next, in Lesson 8: Pointers and Dynamic Memory, you'll work directly with memory addresses — passing data by pointer, allocating memory yourself with new/delete, and learning to manage it safely.
🎉 Functions unlocked!
You now write code the way professionals do — in small, reusable, testable pieces. That habit will pay off in every program you build from here on.