Tutorial

C++ for Beginners Part 4: Functions

Break your programs into reusable, testable pieces with C++ functions. Covers declaration vs definition, parameters and return values, pass-by-value vs pass-by-reference, default parameters, function overloading, and an introduction to recursion.


SERIES

C++ for Beginners: A Complete Guide

An eight-part beginner-friendly journey through C++: from writing your first program and understanding variables, through control flow, functions, and arrays, to the heart of C++ — pointers, classes, inheritance, and polymorphism. Each article is self-contained and builds on the previous, giving you both a quick reference and a progressive path to mastery.

Table of Contents

What is a function?

A function is a named, reusable block of code. Functions let you:

  • Avoid repeating the same logic
  • Give meaningful names to complex operations
  • Test units of code in isolation
  • Split large programs into manageable pieces

Defining a function

C++
return_type function_name(parameter_list) {
    // body
    return value;
}

A concrete example — a function that adds two integers:

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

Call it like this:

C++
int result = add(3, 4);      // result = 7
std::cout << add(10, 20);    // prints 30

The void return type

If a function doesn't return anything, use void:

C++
void printGreeting(std::string name) {
    std::cout << "Hello, " << name << "!
";
}

printGreeting("Alice");  // Hello, Alice!

A void function can have an early return; (no value) to exit early:

C++
void printPositive(int n) {
    if (n <= 0) return;   // bail out early
    std::cout << n << " is positive
";
}

Declaration vs definition

The function body must come before any code that calls it — unless you provide a forward declaration (prototype):

C++
// Forward declaration — just the signature, ends with ;
int multiply(int a, int b);

int main() {
    std::cout << multiply(6, 7);   // OK because of the declaration above
}

// Definition — the actual body, anywhere in the file
int multiply(int a, int b) {
    return a * b;
}

In large projects, declarations go in header files (.h) and definitions in source files (.cpp).


Multiple parameters and types

Functions can have any number of parameters of any types:

C++
double hypotenuse(double a, double b) {
    return std::sqrt(a*a + b*b);
}

std::string repeat(std::string s, int times) {
    std::string result = "";
    for (int i = 0; i < times; i++) result += s;
    return result;
}

Pass by value vs pass by reference

Pass by value (the default)

The function receives a copy of the argument. Changes inside the function do not affect the original:

C++
void doubleIt(int n) {
    n *= 2;    // only the local copy changes
}

int x = 5;
doubleIt(x);
std::cout << x;  // still 5

Pass by reference (&)

Add & to receive the original variable, not a copy:

C++
void doubleIt(int& n) {
    n *= 2;    // modifies the caller's variable
}

int x = 5;
doubleIt(x);
std::cout << x;  // 10

Reference parameters are also useful for large objects (avoid copying):

C++
// Passing a string by const reference — no copy, no modification
void print(const std::string& s) {
    std::cout << s << "
";
}

The const& pattern is the most common way to pass large objects efficiently in C++.

When to use each

Pass by value Pass by reference Pass by const ref
Purpose Small/primitive types Need to modify caller Large object, read-only
Copies data Yes No No
Can modify original No Yes No

Default parameters

You can give parameters default values, which are used when the caller omits them:

C++
void printBorder(char symbol = '*', int width = 20) {
    for (int i = 0; i < width; i++) std::cout << symbol;
    std::cout << "
";
}

printBorder();           // ********************
printBorder('-');        // --------------------
printBorder('=', 10);   // ==========

Rules:

  • Default parameters must be at the end of the parameter list.
  • Usually put defaults in the declaration (header), not the definition.

Function overloading

C++ allows multiple functions with the same name but different parameter lists:

C++
int    square(int n)    { return n * n; }
double square(double n) { return n * n; }
float  square(float n)  { return n * n; }

The compiler picks the right version based on the argument types:

C++
square(5);     // calls square(int)
square(2.5);   // calls square(double)
square(1.5f);  // calls square(float)

This is called compile-time polymorphism. Overloaded functions must differ in parameter types or count — differing only in return type is not allowed.


Returning multiple values

C++ functions return one value, but you can return multiple via:

std::pair or std::tuple

C++
#include <utility>   // for std::pair

std::pair<int, int> minmax(int a, int b) {
    if (a < b) return {a, b};
    return {b, a};
}

auto [lo, hi] = minmax(7, 3);
std::cout << lo << " " << hi;  // 3 7

Output parameters (reference)

C++
void divide(int dividend, int divisor, int& quotient, int& remainder) {
    quotient  = dividend / divisor;
    remainder = dividend % divisor;
}

int q, r;
divide(17, 5, q, r);
std::cout << "17 / 5 = " << q << " remainder " << r;
// 17 / 5 = 3 remainder 2

Recursion

A recursive function calls itself. Every recursion needs:

  1. A base case — when to stop
  2. A recursive case — a call that moves toward the base case

Factorial

C++
int factorial(int n) {
    if (n <= 1) return 1;           // base case
    return n * factorial(n - 1);   // recursive case
}

std::cout << factorial(5);  // 5 × 4 × 3 × 2 × 1 = 120

Trace of factorial(4):

Code
factorial(4) = 4 × factorial(3)
             = 4 × 3 × factorial(2)
             = 4 × 3 × 2 × factorial(1)
             = 4 × 3 × 2 × 1
             = 24

Fibonacci

C++
int fibonacci(int n) {
    if (n <= 1) return n;
    return fibonacci(n - 1) + fibonacci(n - 2);
}

for (int i = 0; i < 10; i++) {
    std::cout << fibonacci(i) << " ";
}
// 0 1 1 2 3 5 8 13 21 34

Note: This naive implementation is exponential. For large n, use a loop or memoization. Recursion is elegant for understanding but not always the most efficient approach.


Practical example: simple calculator

C++
#include <iostream>

double add(double a, double b)      { return a + b; }
double subtract(double a, double b) { return a - b; }
double multiply(double a, double b) { return a * b; }
double divide(double a, double b)   {
    if (b == 0) {
        std::cout << "Error: division by zero
";
        return 0;
    }
    return a / b;
}

int main() {
    double x, y;
    char op;
    std::cout << "Enter: number operator number (e.g. 10 + 5): ";
    std::cin >> x >> op >> y;

    double result = 0;
    switch (op) {
        case '+': result = add(x, y);      break;
        case '-': result = subtract(x, y); break;
        case '*': result = multiply(x, y); break;
        case '/': result = divide(x, y);   break;
        default:  std::cout << "Unknown operator
"; return 1;
    }

    std::cout << x << " " << op << " " << y << " = " << result << "
";
}

Key takeaways

  • Functions group related code under a meaningful name.
  • void for functions that don't return a value; return exits a function.
  • Pass by reference (&) to modify the caller's variable or avoid copying large objects.
  • Use const& for large read-only parameters.
  • Overloading lets you use the same name for operations on different types.
  • Every recursive function needs a base case and a path toward it.

Next up: arrays, strings, and the C++ Standard Library — the containers you'll use every day.


Was this article helpful?

w

webencher Editorial

Software engineers and technical writers with 10+ years of combined experience in algorithms, systems design, and web development. Every article is reviewed for accuracy, depth, and practical applicability.

More by this author →