Functions

We’ve used functions in our code a lot so far: all of our code has been inside the “main” function, and we’ve also used a variety of functions when working with vectors and strings, such as size() and pop_back(). As you may see, functions are great because they allow us to to “give a name” to a certain set of code, making it easier to use that code repeatedly. For example, rather than needing to manually determine the size of a vector every time, we can just use the vector’s size() function, which contains all the code needed for getting its size.

Here, I’ll show you how to create and use your own functions, and I’ll go into more detail about how they actually work.

Let’s start with a very simple function (that you probably won’t ever need in a real program):

#include <iostream>

double add(double num1, double num2) {
    return num1 + num2;
}

int main() {
    std::cout << add(4.1, 1.7) << "\n";
    return 0;
}

In this program, “add” is a function with two inputs (“parameters”) of data type double (containing a decimal point): num1 and num2. You can see that, when using the add function in line 8, we put two numbers (separated by a comma) in parentheses. Each of these numbers is an “argument” to the function, meaning that they act as the function’s inputs. These numbers correspond to the parameters for the add function, so 4.1 is num1 and 1.7 is num2.

The add function also “returns” a value of data type double (indicated by the word double before the function name). This means that, when the function is actually used (in line 8), the value it takes on is whatever value the function returns. In the case of the add function, the value returned is equal to the sum of num1 and num2. What’s returned is denoted by the use of the word return followed by whatever you want to return.

When running this program, you should see the value “5.8” printed to the screen because the add function adds num1 (4.1) and num2 (1.7) and returns that value.

Of course, creating a function for addition isn’t necessary since the code below will do the same thing, but this is just an example to get started.

#include <iostream>

int main() {
    std::cout << 4.1 + 1.7 << "\n";
    return 0;
}

Void

Functions don’t actually need to return a value. By using a return type of “void“, we can avoid the need for a return statement:

#include <iostream>
#include <string>

void print(std::string text) {
    std::cout << text << "\n";
}

int main() {
    print("Hello world!");
    return 0;
}

Variable Scope

This is not specifically about functions, but it’s very important to start thinking about now: All variables in C++ follow specific “scoping” rules. Very generally, variables created within a set of curly braces cannot be used outside of those curly braces (excluding classes and structs, which we’ll get to later). For example, the code below will not compile:

#include <iostream>

double add(double num1, double num2) {
    double sum = num1 + num2;
    return sum;
}

int main() {
    std::cout << add(4.1, 1.7) << "\n";
    std::cout << sum << "\n";
    return 0;
}

Here, sum can only be used within the add function because it is created within the add function. The sum variable is considered to be a “local” variable because it is “local” to the add function. For completion, also be aware that function parameters (e.g. num1 and num2 above) cannot be used outside of their corresponding function.

These rules also apply to if statements and loops, so the code below won’t compile either:

#include <iostream>

int main() {
    int number = 10;
    if (number > 5) {
        int anotherNumber = 25;
    }
    else {
        int anotherNumber = 20;
    }
    std::cout << anotherNumber << "\n";
    return 0;
}

A solution to the issue with the code above would be to declare the anotherNumber variable outside of the if statement:

#include <iostream>

int main() {
    int number = 10;
    int anotherNumber;
    if (number > 5) {
        anotherNumber = 25;
    }
    else {
        anotherNumber = 20;
    }
    std::cout << anotherNumber << "\n";
    return 0;
}

Pass by Value

Arguments (inputs) passed to a function (like the string “Hello world!” above) are passed “by value” in C++. Let’s take a look at what that means.

#include <iostream>

void addFive(int number) {
    number += 5;
}

int main() {
    int number = 4;
    std::cout << "Number before adding 5: " << number << "\n";
    addFive(number);
    std::cout << "Number after adding 5: " << number << "\n";
    return 0;
}

(Note: Because of variable scope rules, the number variable in the addFive function is completely different from the number variable in the main function.)

If you run the code above, you’ll see that the “Number before adding 5” is the same as the “Number after adding 5.” That is, the number variable is not modified by the addFive function.

Why is this? It’s all because, when passing arguments to a function, the function first makes a copy of the argument and only uses that copy within the function. Here’s another example:

#include <iostream>
#include <string>

void removeLastLetter(std::string str) {
    str.pop_back();
}

int main() {
    std::string hello = "Hello world!";
    std::cout << "String before removing last letter: " << hello << "\n";
    removeLastLetter(hello);
    std::cout << "String after removing last letter: " << hello << "\n";
    return 0;
}

Just like the last example, the string’s values before and after using the removeLastLetter function are equivalent because the hello variable itself is not actually modified by the removeLastLetter function. Only a copy of the variable is modified.

Later, when going over “pointers” in C++, you’ll see one way of working around this issue. Another is to just return the modified value from the function:

#include <iostream>
#include <string>

std::string removeLastLetter(std::string str) {
    str.pop_back();
    return str;
}

int main() {
    std::string hello = "Hello world!";
    std::cout << "String before removing last letter: " << hello << "\n";
    hello = removeLastLetter(hello);
    std::cout << "String after removing last letter: " << hello << "\n";
    return 0;
}

Recursion

Functions in C++ are actually allowed to call themselves. This is what’s known as recursion.

One way of using recursion is for performing the factorial operation, which takes a number and multiplies it by each number smaller than it, down to 1. For example, 5! (5 factorial) = 5 * 4 * 3 * 2 * 1 = 120. Here’s how recursion can be used for this:

#include <iostream>

int factorial(int number) {
    if (number == 1) {
        return 1;
    }
    return number * factorial(number - 1);
}

int main() {
    std::cout << "4! = " << factorial(4) << "\n";
    std::cout << "5! = " << factorial(5) << "\n";
    return 0;
}

A good way to visualize this approach is with a tree:

factorial(4) is equal to 4 * factorial(3), which is equal to 4 * 3 * factorial(2), and so on…

One thing to be very careful of when using recursion is to ensure that there’s some way for the recursion to end. In the case of factorial, the recursive process ends when reaching a value of 1. You can see this in the code where just the number 1 is returned, as opposed to further evaluation of the factorial. Without doing this, the recursion would go on forever until your computer can recurse no longer.

It turns out that recursion is never the only way of solving a problem, so there’s always some solution to a problem just using loops. For many problems, however, recursion is the simplest way to go.

Challenge Problem

Write a program containing a function called “factorial” that returns the factorial of a parameter without using recursion.

If you get stuck, check out my solution here.