Skip to main content

📝 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 Components double calculateAverage(int num1, int num2) Return Type What it gives back Function Name How to call it Parameters What it needs { Function Body: The actual code }

Function Types and Examples

graph TD A[Function Types] --> B[No Return, No Parameters] A --> C[Return Value, No Parameters] A --> D[No Return, With Parameters] A --> E[Return Value, With Parameters] B --> F["void greet() {<br/> cout << 'Hello!';<br/>}"] C --> G["int getRandom() {<br/> return rand();<br/>}"] D --> H["void print(string msg) {<br/> cout << msg;<br/>}"] E --> I["int add(int a, int b) {<br/> return a + b;<br/>}"]

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!

Pass by Value vs Pass by Reference Pass by Value x = 10 Original a = 10 Copy void change(int a) { a = 20; } int x = 10; change(x); // x is still 10! Pass by Reference x = 10 Original Reference void change(int &a) { a = 20; } int x = 10; change(x); // x is now 20!

💡 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!

graph TD A[Function with Defaults] --> B["greet(name, greeting='Hello')"] B --> C["greet('Alice')"] B --> D["greet('Bob', 'Hi')"] C --> E["Uses default: 'Hello Alice'"] D --> F["Uses provided: 'Hi Bob'"]
// 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!

Function Organization Without Prototypes ❌ Order matters! // Must define first int add(int a, int b) { return a + b; } // Then use int main() { add(5, 3); } With Prototypes ✓ Flexible organization! // Prototype at top int add(int a, int b); int main() { add(5, 3); // Can use! } // Define anywhere int add(int a, int b) { return a + b; }

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:

  1. Functions for add, subtract, multiply, divide
  2. A display menu function
  3. Input validation function
  4. 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

graph TD A[Function Best Practices] --> B[Single Responsibility] A --> C[Descriptive Names] A --> D[Reasonable Parameters] A --> E[Document Purpose] B --> F[One function, one job] C --> G[calculateAverage not calc] D --> H[3-4 parameters max] E --> I[Comments explain why]

Common Function Patterns

Common Function Patterns Validator Pattern bool isValid(int value) { return value >= 0 && value <= 100; } Calculator Pattern double calculate(double a, double b) { return /* some formula */; } Converter Pattern double toMeters(double feet) { return feet * 0.3048; } Finder Pattern int findMax(int arr[], int size) { /* search logic */ } Display Pattern void printReport(/* data */) { /* format and display */ }

Challenge Exercise: Text Adventure Game

🏋️ Advanced Function Challenge

Create a text adventure game using functions for:

  1. Room descriptions
  2. Player movement
  3. Inventory management
  4. Combat system
  5. 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
graph LR A[Master Functions] --> B[Write Modular Code] B --> C[Reuse Solutions] C --> D[Build Complex Programs] D --> E[Professional Software!]

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