Module 3.1

Functions in C++

Learn to write modular, reusable code with C++ functions. Master function declaration, definition, parameters, return types, overloading, and inline functions for cleaner, more maintainable programs.

45 min read
Beginner
Hands-on Examples
What You'll Learn
  • Function declaration & definition
  • Parameters & arguments
  • Return types & values
  • Function overloading
  • Default & inline functions
Contents
01

Introduction to Functions

Functions are reusable blocks of code that perform specific tasks. They help you organize code, avoid repetition, and make programs easier to understand, test, and maintain. Functions are fundamental to modular programming.

Concept

Function

A function is a named block of code that performs a specific task. It can accept input (parameters), process data, and return output (return value). Functions promote code reuse and modularity.

Syntax: return_type function_name(parameters) { body }

Why Use Functions?

Reusability

Write once, use many times

Modularity

Break complex problems into parts

Readability

Meaningful names explain purpose

Debugging

Test and fix isolated units

Your First Function

#include <iostream>
using namespace std;

// Function definition
void sayHello() {
    cout << "Hello, World!" << endl;
}

int main() {
    // Function call
    sayHello();  // Output: Hello, World!
    sayHello();  // Output: Hello, World!
    sayHello();  // Output: Hello, World!
    
    return 0;
}
02

Function Declaration & Definition

In C++, you can separate the function declaration (prototype) from its definition (implementation). This is essential for organizing larger programs and using header files.

Function Declaration (Prototype)

#include <iostream>
using namespace std;

// Function declarations (prototypes) - tell compiler about functions
int add(int a, int b);          // Parameters can have names
int multiply(int, int);          // Or just types
void greet(string name);

int main() {
    // Now we can call functions before their definitions
    cout << add(5, 3) << endl;        // 8
    cout << multiply(4, 7) << endl;   // 28
    greet("Alice");                    // Hello, Alice!
    
    return 0;
}

// Function definitions (implementations)
int add(int a, int b) {
    return a + b;
}

int multiply(int x, int y) {
    return x * y;
}

void greet(string name) {
    cout << "Hello, " << name << "!" << endl;
}

Function Anatomy

// Function anatomy:
// return_type function_name(parameter_list) { function_body }

int calculateArea(int length, int width) {
//│     │              │        │
//│     │              └────────┴── Parameters (inputs)
//│     └───────────────────────── Function name
//└─────────────────────────────── Return type
    
    int area = length * width;   // Function body
    return area;                  // Return statement
}

// void means no return value
void printMessage() {
    cout << "No return needed" << endl;
    // return; is optional for void functions
}

Declaration vs Definition

Aspect Declaration (Prototype) Definition (Implementation)
Purpose Tells compiler function exists Contains actual code
Body No body, ends with semicolon Has body with curly braces
Location Usually in header files (.h) Usually in source files (.cpp)
Can appear Multiple times Only once

Practice Questions: Declaration & Definition

Task: Write a function prototype (declaration) for a function called greet that takes a string parameter name and returns nothing.

Show Solution
void greet(string name);

Task: Write the declaration and definition separately for a function multiply that takes two integers and returns their product.

Show Solution
// Declaration (prototype)
int multiply(int a, int b);

// Definition (implementation)
int multiply(int a, int b) {
    return a * b;
}
03

Parameters & Arguments

Parameters are variables in the function definition that receive values. Arguments are the actual values passed when calling the function. C++ supports pass by value and pass by reference.

Pass by Value

#include <iostream>
using namespace std;

// Pass by value: function receives a COPY
void doubleValue(int num) {
    num = num * 2;  // Only modifies the copy
    cout << "Inside function: " << num << endl;
}

int main() {
    int x = 10;
    cout << "Before: " << x << endl;  // 10
    
    doubleValue(x);                     // Pass x
    // Inside function: 20
    
    cout << "After: " << x << endl;   // 10 (unchanged!)
    
    return 0;
}

Pass by Reference

#include <iostream>
using namespace std;

// Pass by reference: function receives the ORIGINAL variable
void doubleValue(int& num) {  // Note the & (reference)
    num = num * 2;  // Modifies the original
    cout << "Inside function: " << num << endl;
}

int main() {
    int x = 10;
    cout << "Before: " << x << endl;  // 10
    
    doubleValue(x);                     // Pass x by reference
    // Inside function: 20
    
    cout << "After: " << x << endl;   // 20 (changed!)
    
    return 0;
}

// Practical example: swapping two values
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

// Usage:
// int x = 5, y = 10;
// swap(x, y);  // Now x=10, y=5

Const Reference (Read-Only)

#include <iostream>
#include <string>
using namespace std;

// Const reference: efficient AND prevents modification
void printInfo(const string& name, const int& age) {
    cout << name << " is " << age << " years old" << endl;
    // name = "Changed";  // Error! Cannot modify const reference
}

// Why use const reference?
// 1. Efficiency: No copying large objects
// 2. Safety: Prevents accidental modification
// 3. Can accept literals: printInfo("Alice", 25);

int main() {
    string myName = "Bob";
    int myAge = 30;
    
    printInfo(myName, myAge);    // Works with variables
    printInfo("Alice", 25);       // Works with literals too
    
    return 0;
}

Multiple Parameters

#include <iostream>
using namespace std;

// Multiple parameters of different types
double calculateBMI(double weight, double height) {
    return weight / (height * height);
}

// Array as parameter (decays to pointer)
double average(int arr[], int size) {
    double sum = 0;
    for (int i = 0; i < size; i++) {
        sum += arr[i];
    }
    return sum / size;
}

int main() {
    double bmi = calculateBMI(70.0, 1.75);
    cout << "BMI: " << bmi << endl;
    
    int scores[] = {85, 90, 78, 92, 88};
    double avg = average(scores, 5);
    cout << "Average: " << avg << endl;
    
    return 0;
}

Practice Questions: Parameters & Arguments

Task: Write a function swap that swaps the values of two integers using pass by reference.

Show Solution
void swap(int& a, int& b) {
    int temp = a;
    a = b;
    b = temp;
}

Task: Write a function that takes an array and its size, and returns both minimum and maximum values through reference parameters.

Show Solution
void findMinMax(int arr[], int size, int& min, int& max) {
    min = max = arr[0];
    for (int i = 1; i < size; i++) {
        if (arr[i] < min) min = arr[i];
        if (arr[i] > max) max = arr[i];
    }
}

// Usage:
int nums[] = {5, 2, 8, 1, 9};
int minVal, maxVal;
findMinMax(nums, 5, minVal, maxVal);
// minVal = 1, maxVal = 9

Task: Write a function that increments each element of an array by a given value. The array should be modified in place.

Show Solution
void incrementAll(int arr[], int size, int value) {
    for (int i = 0; i < size; i++) {
        arr[i] += value;
    }
}

// Usage:
int nums[] = {1, 2, 3, 4, 5};
incrementAll(nums, 5, 10);
// nums is now {11, 12, 13, 14, 15}
04

Return Types & Values

Functions can return values of any type. The return type must be declared, and the returned value must match (or be convertible to) that type. Use void when no return value is needed.

#include <iostream>
#include <string>
using namespace std;

// Return int
int square(int n) {
    return n * n;
}

// Return double
double divide(double a, double b) {
    if (b == 0) {
        return 0.0;  // Handle division by zero
    }
    return a / b;
}

// Return bool
bool isEven(int num) {
    return num % 2 == 0;
}

// Return string
string getGrade(int score) {
    if (score >= 90) return "A";
    if (score >= 80) return "B";
    if (score >= 70) return "C";
    if (score >= 60) return "D";
    return "F";
}

// Return void (no return value)
void printStars(int count) {
    for (int i = 0; i < count; i++) {
        cout << "*";
    }
    cout << endl;
    // return; is optional
}

int main() {
    cout << "5 squared: " << square(5) << endl;           // 25
    cout << "10 / 3: " << divide(10, 3) << endl;          // 3.33...
    cout << "Is 4 even? " << isEven(4) << endl;           // 1 (true)
    cout << "Grade for 85: " << getGrade(85) << endl;    // B
    printStars(5);                                          // *****
    
    return 0;
}

Multiple Return Statements

#include <iostream>
using namespace std;

// Early return for validation
int factorial(int n) {
    if (n < 0) {
        return -1;  // Error indicator
    }
    if (n == 0 || n == 1) {
        return 1;   // Base case
    }
    
    int result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

// Finding values
int findMax(int a, int b, int c) {
    if (a >= b && a >= c) return a;
    if (b >= a && b >= c) return b;
    return c;
}

int main() {
    cout << "5! = " << factorial(5) << endl;      // 120
    cout << "Max: " << findMax(10, 25, 15) << endl; // 25
    
    return 0;
}

Returning References

#include <iostream>
using namespace std;

// Return reference to modify external variable
int& getElement(int arr[], int index) {
    return arr[index];  // Returns reference to array element
}

// Return const reference for read-only access
const string& getLonger(const string& a, const string& b) {
    return (a.length() > b.length()) ? a : b;
}

int main() {
    int numbers[] = {10, 20, 30, 40, 50};
    
    // Can use returned reference to modify
    getElement(numbers, 2) = 100;  // Change arr[2] to 100
    
    cout << numbers[2] << endl;  // 100
    
    string s1 = "Hello";
    string s2 = "World!";
    cout << getLonger(s1, s2) << endl;  // World!
    
    return 0;
}
Never return a reference to a local variable! Local variables are destroyed when the function ends, leaving a dangling reference.

Practice Questions: Return Types

Task: Write a function absoluteValue that returns the absolute value of an integer.

Show Solution
int absoluteValue(int n) {
    if (n < 0) {
        return -n;
    }
    return n;
}

Task: Write a function isPrime that returns true if a number is prime, false otherwise.

Show Solution
bool isPrime(int n) {
    if (n <= 1) return false;
    if (n <= 3) return true;
    if (n % 2 == 0) return false;
    
    for (int i = 3; i * i <= n; i += 2) {
        if (n % i == 0) return false;
    }
    return true;
}
05

Function Overloading

Function overloading allows multiple functions to have the same name but different parameters. The compiler selects the correct version based on the arguments passed. This is a key feature of C++ polymorphism.

#include <iostream>
#include <string>
using namespace std;

// Overloaded print functions - same name, different parameters
void print(int value) {
    cout << "Integer: " << value << endl;
}

void print(double value) {
    cout << "Double: " << value << endl;
}

void print(string value) {
    cout << "String: " << value << endl;
}

void print(int a, int b) {
    cout << "Two integers: " << a << ", " << b << endl;
}

int main() {
    print(42);           // Calls print(int)
    print(3.14);         // Calls print(double)
    print("Hello");      // Calls print(string)
    print(10, 20);       // Calls print(int, int)
    
    return 0;
}
/* Output:
Integer: 42
Double: 3.14
String: Hello
Two integers: 10, 20
*/

Overloading Rules

#include <iostream>
using namespace std;

// Valid overloads: different parameter types or count
int add(int a, int b) {
    return a + b;
}

double add(double a, double b) {
    return a + b;
}

int add(int a, int b, int c) {
    return a + b + c;
}

// INVALID: Cannot overload by return type alone
// int getValue();
// double getValue();  // Error! Same parameters

// INVALID: Cannot overload by parameter names only
// void func(int x);
// void func(int y);  // Error! Same signature

int main() {
    cout << add(5, 3) << endl;         // 8 (int version)
    cout << add(2.5, 3.7) << endl;     // 6.2 (double version)
    cout << add(1, 2, 3) << endl;      // 6 (three-param version)
    
    return 0;
}

Practical Example: Area Calculator

#include <iostream>
using namespace std;

const double PI = 3.14159;

// Area of square
double area(double side) {
    return side * side;
}

// Area of rectangle
double area(double length, double width) {
    return length * width;
}

// Area of circle (using different parameter name doesn't help distinguish,
// but different type would - here we use a flag)
double area(double radius, bool isCircle) {
    if (isCircle) {
        return PI * radius * radius;
    }
    return radius * radius;
}

// Area of triangle
double area(double base, double height, char type) {
    return 0.5 * base * height;
}

int main() {
    cout << "Square (5): " << area(5.0) << endl;
    cout << "Rectangle (4x6): " << area(4.0, 6.0) << endl;
    cout << "Circle (r=3): " << area(3.0, true) << endl;
    cout << "Triangle (b=4, h=5): " << area(4.0, 5.0, 't') << endl;
    
    return 0;
}

Practice Questions: Function Overloading

Task: Create three overloaded display functions that print an int, a double, and a string respectively.

Show Solution
void display(int value) {
    cout << "Integer: " << value << endl;
}

void display(double value) {
    cout << "Double: " << value << endl;
}

void display(string value) {
    cout << "String: " << value << endl;
}

Task: Create overloaded max functions that find the maximum of 2 integers and 3 integers.

Show Solution
int max(int a, int b) {
    return (a > b) ? a : b;
}

int max(int a, int b, int c) {
    return max(max(a, b), c);
}
06

Default Arguments

Default arguments provide fallback values for parameters when no argument is supplied. They must be specified from right to left in the parameter list.

#include <iostream>
#include <string>
using namespace std;

// Default argument example
void greet(string name = "Guest", string greeting = "Hello") {
    cout << greeting << ", " << name << "!" << endl;
}

int main() {
    greet();                    // Hello, Guest!
    greet("Alice");             // Hello, Alice!
    greet("Bob", "Hi");         // Hi, Bob!
    
    return 0;
}

Default Argument Rules

#include <iostream>
using namespace std;

// Rule 1: Defaults must be from right to left
void func1(int a, int b = 10, int c = 20);  // Valid
// void func2(int a = 5, int b, int c = 20);  // Error! b has no default

// Rule 2: Declare defaults only once (usually in prototype)
void display(int x, int y = 100);  // Declare default here

int main() {
    func1(1);           // a=1, b=10, c=20
    func1(1, 2);        // a=1, b=2, c=20
    func1(1, 2, 3);     // a=1, b=2, c=3
    
    display(5);         // x=5, y=100
    display(5, 50);     // x=5, y=50
    
    return 0;
}

// Don't repeat default in definition
void func1(int a, int b, int c) {
    cout << a << ", " << b << ", " << c << endl;
}

void display(int x, int y) {  // No default here
    cout << x << ", " << y << endl;
}

Practical Example: Formatting Output

#include <iostream>
#include <iomanip>
using namespace std;

void printLine(int length = 40, char symbol = '-') {
    for (int i = 0; i < length; i++) {
        cout << symbol;
    }
    cout << endl;
}

void printCentered(string text, int width = 50, char fill = ' ') {
    int padding = (width - text.length()) / 2;
    cout << string(padding, fill) << text 
         << string(width - padding - text.length(), fill) << endl;
}

int main() {
    printLine();                // 40 dashes
    printLine(20);              // 20 dashes
    printLine(30, '*');         // 30 asterisks
    
    printCentered("WELCOME");
    printCentered("MENU", 30);
    printCentered("Title", 40, '=');
    
    return 0;
}
Best Practice: Use default arguments for values that are commonly the same, but specify defaults in the declaration (header file), not the definition.

Practice Questions: Default Arguments

Task: Write a function power that calculates base^exp, with a default exponent of 2 (square).

Show Solution
double power(double base, int exp = 2) {
    double result = 1;
    for (int i = 0; i < exp; i++) {
        result *= base;
    }
    return result;
}

// Usage:
power(5);     // 25 (5^2)
power(2, 10); // 1024 (2^10)

Task: Write a function repeatString that repeats a string n times with a separator. Default: repeat 1 time with space separator.

Show Solution
string repeatString(string text, int times = 1, string separator = " ") {
    string result = text;
    for (int i = 1; i < times; i++) {
        result += separator + text;
    }
    return result;
}

// Usage:
repeatString("Hi");           // "Hi"
repeatString("Hi", 3);        // "Hi Hi Hi"
repeatString("Hi", 3, "-");   // "Hi-Hi-Hi"
07

Inline Functions

Inline functions suggest to the compiler to insert the function code directly at the call site, avoiding function call overhead. They're ideal for small, frequently-called functions.

#include <iostream>
using namespace std;

// Inline function - compiler may insert code at call site
inline int max(int a, int b) {
    return (a > b) ? a : b;
}

inline int square(int n) {
    return n * n;
}

inline bool isPositive(int num) {
    return num > 0;
}

int main() {
    int x = 10, y = 20;
    
    // Compiler may replace this with: (x > y) ? x : y
    cout << "Max: " << max(x, y) << endl;
    
    // May become: 5 * 5
    cout << "Square of 5: " << square(5) << endl;
    
    if (isPositive(x)) {
        cout << x << " is positive" << endl;
    }
    
    return 0;
}

When to Use Inline

Good Candidates
  • Very small functions (1-3 lines)
  • Frequently called functions
  • Simple getters/setters
  • Simple mathematical operations
Poor Candidates
  • Large functions (increases code size)
  • Functions with loops
  • Recursive functions
  • Functions with static variables

Inline in Classes

#include <iostream>
using namespace std;

class Rectangle {
private:
    double width, height;
    
public:
    // Methods defined inside class are implicitly inline
    double getWidth() { return width; }
    double getHeight() { return height; }
    
    void setWidth(double w) { width = w; }
    void setHeight(double h) { height = h; }
    
    double area() { return width * height; }
};

int main() {
    Rectangle rect;
    rect.setWidth(5);
    rect.setHeight(3);
    
    cout << "Area: " << rect.area() << endl;  // 15
    
    return 0;
}
Note: The inline keyword is a suggestion, not a command. Modern compilers may inline functions automatically or ignore the keyword based on optimization settings.

Practice Questions: Inline Functions

Task: Write inline functions min and max that return the smaller/larger of two integers.

Show Solution
inline int min(int a, int b) {
    return (a < b) ? a : b;
}

inline int max(int a, int b) {
    return (a > b) ? a : b;
}

Task: Create a Circle class with inline getter/setter for radius and inline methods for area and circumference.

Show Solution
class Circle {
private:
    double radius;
    
public:
    // Implicitly inline (defined in class)
    double getRadius() { return radius; }
    void setRadius(double r) { radius = r; }
    
    double area() { return 3.14159 * radius * radius; }
    double circumference() { return 2 * 3.14159 * radius; }
};
08

Recursion

Recursion is when a function calls itself. It's useful for problems that can be broken down into smaller, similar subproblems. Every recursive function needs a base case to stop the recursion.

#include <iostream>
using namespace std;

// Factorial using recursion
int factorial(int n) {
    // Base case: stop recursion
    if (n == 0 || n == 1) {
        return 1;
    }
    // Recursive case: call itself
    return n * factorial(n - 1);
}

// How it works:
// factorial(5)
// = 5 * factorial(4)
// = 5 * 4 * factorial(3)
// = 5 * 4 * 3 * factorial(2)
// = 5 * 4 * 3 * 2 * factorial(1)
// = 5 * 4 * 3 * 2 * 1
// = 120

int main() {
    cout << "5! = " << factorial(5) << endl;  // 120
    cout << "0! = " << factorial(0) << endl;  // 1
    
    return 0;
}

More Recursion Examples

#include <iostream>
using namespace std;

// Fibonacci sequence
int fibonacci(int n) {
    if (n <= 1) return n;  // Base case
    return fibonacci(n - 1) + fibonacci(n - 2);  // Recursive case
}

// Sum of digits
int sumDigits(int n) {
    if (n == 0) return 0;  // Base case
    return (n % 10) + sumDigits(n / 10);
}

// Power function
int power(int base, int exp) {
    if (exp == 0) return 1;  // Base case
    return base * power(base, exp - 1);
}

// Count down
void countdown(int n) {
    if (n == 0) {
        cout << "Blast off!" << endl;
        return;  // Base case
    }
    cout << n << "..." << endl;
    countdown(n - 1);  // Recursive call
}

int main() {
    cout << "Fibonacci(7): " << fibonacci(7) << endl;  // 13
    cout << "Sum of digits(12345): " << sumDigits(12345) << endl;  // 15
    cout << "2^10: " << power(2, 10) << endl;  // 1024
    
    countdown(5);
    
    return 0;
}
Recursion Caution:
  • Always have a base case to prevent infinite recursion
  • Each recursive call uses stack memory (risk of stack overflow)
  • Can be slower than iterative solutions due to function call overhead
  • Consider iterative alternatives for performance-critical code

Practice Questions: Recursion

Task: Write a recursive function sum that calculates the sum of all integers from 1 to n.

Show Solution
int sum(int n) {
    if (n <= 0) return 0;  // Base case
    return n + sum(n - 1); // Recursive case
}

// sum(5) = 5 + 4 + 3 + 2 + 1 = 15

Task: Write a recursive function reverse that reverses a string.

Show Solution
string reverse(string s) {
    if (s.length() <= 1) return s;  // Base case
    return reverse(s.substr(1)) + s[0];  // Recursive case
}

// reverse("hello") = "olleh"

Task: Write a recursive function gcd to find the Greatest Common Divisor of two numbers using Euclidean algorithm.

Show Solution
int gcd(int a, int b) {
    if (b == 0) return a;  // Base case
    return gcd(b, a % b);  // Recursive case
}

// gcd(48, 18) = gcd(18, 12) = gcd(12, 6) = gcd(6, 0) = 6

Key Takeaways

Function Basics

Declaration, definition, return type, parameters

Pass by Value/Reference

Copy vs original; use & for modification

Overloading

Same name, different parameters

Default Arguments

Fallback values from right to left

Inline Functions

Reduce call overhead for small functions

Recursion

Function calls itself; needs base case

Knowledge Check

Quick Quiz

Test what you have learned about C++ functions

1 What is a function prototype?
2 How do you pass a variable by reference?
3 What is function overloading?
4 Which declaration is valid for default arguments?
5 What is required for a recursive function?
6 When should you use inline functions?
Answer all questions to check your score