Lesson 13 - Functions in the C++ language
In the previous exercise, Solved tasks for C++ lessons 11-12, we've practiced our knowledge from previous lessons.
Lesson highlights
Are you looking for a quick reference on declaring C++ functions instead of a thorough-full lesson? Here it is:
Declaring functions:
{CPP_IMPORTS}
void greet(void)
{
cout << "Hi, welcome!" << endl;
}
int main()
{
greet(); // calling the function
greet(); // calling it again
return 0;
}
A function taking parameters and returning a value:
{CPP_IMPORTS}
int rectangle_area(int width, int height)
{
int result = width * height;
return result;
}
int main()
{
cout << "The area of the rectangle is " << rectangle_area(10, 20) << " cm^2" << endl;
return 0;
}
Recursion when a function is calling itself:
{CPP_IMPORTS}
int factorial(int x)
{
if (x == 1)
return 1;
return x * factorial(x - 1);
}
int main()
{
cout << factorial(10) << endl;
return 0;
}
Would you like to learn more? A complete lesson on this topic follows.
Today's lesson on C++ is about a very important topic - functions. We already
know that we write code into the main()
function. This didn't
matter much for the short, educational programs we've made that could only do a
single thing. However, consider writing a program that is thousands of lines
long. Surely, you agree it'd be very hard to work with a large "noodle" of code,
i.e. if it was all in a single file and a single function. Furthermore, if we
wanted to perform the same command sequences at multiple places of our program,
we'd have to copy it all over again or to jump from one place in the program to
another. Both of these options are very confusing.
Functional decomposition
We sometimes refer to dividing a program into multiple functions as functional decomposition. Don't be intimidated by the term. It's really all about what our application needs to do and create a function in our source code for each of those tasks. In real-life applications, we usually also create some auxiliary functions to wrap it all together. For example, a function for printing the application menu or splitting a complex function up into many simple functions to keep the program readable.
Functions are sometimes referred to as subroutines or subprograms. If a
function doesn't return a value (more on this later on), it may be called a
procedure in some programming languages. Functions for larger applications,
where there are a lot of them, are gathered into multiple modules/libraries. You
know these very well from writing #include <iostream>
which
loads the library (module) for working with standard input/output (with the
console). Similarly, mathematical functions are gathered in the
cmath
system library. We'll also learn to create these sort of
modules/libraries further along the way.
Creating functions
A function is a logical block of code which we write once and then are able
to call it multiple times without having to write it all over again. We'll
declare functions in the global scope, somewhere above the main()
function. They'll look similar to main()
. Let's add a function to
our source code which will write "Hi, welcome!"
.
We'll show the entire source code just to be illustrative:
#include <iostream> using namespace std; void greet(void) { cout << "Hi, welcome!" << endl; } int main() { return 0; }
The void
keyword in the function definition specifies that the
function doesn't return a value. Now, we have to call the function to execute
it. Of course, are only able to do after it's been declared. Otherwise, the
compiler won't know what to do with the function (that's why we declared our
function before main()
). Add the following line to
main()
:
{CPP_IMPORTS}
void greet(void)
{
cout << "Hi, welcome!" << endl;
}
{CPP_MAIN_BLOCK}
greet(); // calling the function
{/CPP_MAIN_BLOCK}
The result:
Console application
Hi, welcome!
Functions with parameters
A function can have any number of input parameters (they're sometimes called
arguments) which we write into the parentheses in its definition. We influence a
function's behavior using parameters. Consider a situation where we need to
greet our users by their names. With this in mind, let's add on to our function
by incorporating a name
parameter and specify it later with a
concrete value when calling the function:
void greet(string name) { cout << "Hi " << name << ", welcome!" << endl; }
Now, we'll change the function call in main()
to this:
{CPP_IMPORTS}
void greet(string name)
{
cout << "Hi " << name << ", welcome!" << endl;
}
{CPP_MAIN_BLOCK}
greet("Carl"); // calling the function
{/CPP_MAIN_BLOCK}
If we wanted to greet multiple people, we no longer have to write
cout <<"Hi ...
for each of them. Instead, we'd simply
call our function:
{CPP_IMPORTS}
void greet(string name)
{
cout << "Hi " << name << ", welcome!" << endl;
}
{CPP_MAIN_BLOCK}
greet("Carl");
greet("David");
greet("Mary");
{/CPP_MAIN_BLOCK}
The result:
Console application
Hi Carl, welcome here!
Hi David, welcome here!
Hi Mary, welcome here!
The function's return value
A function can also return a value. Let's put our greeting example off to the
side and create a function for computing the area of a rectangle. Furthermore,
we'll design it so that we're able to use the result in another calculation.
Therefore, instead of writing the result, we'll return it as the return value.
Every function can return a single value using the return
command
which will also terminate the function, i.e. any other code after it won't be
executed. We specify the data type of the return value before the function
definition. Add the following function to your program:
int rectangle_area(int width, int height) { int result = width * height; return result; }
In real-world applications, our function would probably compute something
more complex so it'd be worthwhile for us to implement it. However, as an
example, computing the area of a simple rectangle will do. We name functions
using lowercase letters, whole words and using under_scores instead of spaces.
Although the C++ language is full of abbreviations, I highly suggest you avoid
them. For example, a function named birth_date()
is much more clear
than a function named bird()
which makes it hard to tell what the
function even does.
If we wanted to print the area of a rectangle now, we'd simply call our
function directly in cout
. The rectangle's area would be computed
as the first. Then, this value would be returned and passed as an input
parameter to the cout
object which would then print it. Let's try
it out by entering 10
and 20
cm as the rectangle's
width and height:
{CPP_IMPORTS}
int rectangle_area(int width, int height)
{
int result = width * height;
return result;
}
{CPP_MAIN_BLOCK}
cout << "The area of the rectangle is " << rectangle_area(10, 20) << " cm^2" << endl;
{/CPP_MAIN_BLOCK}
Console application
The area of the rectangle is: 200 cm^2
If you find it confusing, feel free to use an auxiliary variable:
{CPP_IMPORTS}
int rectangle_area(int width, int height)
{
int result = width * height;
return result;
}
{CPP_MAIN_BLOCK}
int area = rectangle_area(10, 20);
cout << "The area of the rectangle is " << area << " cm^2" << endl;
{/CPP_MAIN_BLOCK}
However, we didn't decide to return the result as the function's return value just to print it. Let's take advantage of this decision by computing the sum of the areas of two rectangles:
{CPP_IMPORTS}
int rectangle_area(int width, int height)
{
int result = width * height;
return result;
}
{CPP_MAIN_BLOCK}
int total_area = rectangle_area(10, 20) + rectangle_area(20, 40);
cout << "The sum of the areas of the rectangles is: " << total_area << " cm^2" << endl;
{/CPP_MAIN_BLOCK}
The result:
Console application
The sum of the areas of the rectangles is: 1000 cm^2
In regards to previous exercises we've done throughout the course, feel free to modify some of them and split them up into functions. According to good software design practices, a source code should always be divided into functions (ideally into libraries/modules, more on this later on) to keep it clear. We omitted this prior to this lesson to keep things simple, but now, please remember to do so
The main advantage to using functions is clarity and keeping code shorter (we are able to write a function once and call it a hundred times from multiple places in our program). If we decided to modify the function, we would only have to do it at one place and the changes would affect all of the function calls immediately. The latter significantly decreases the possibility of making an error. In the greeting example, if we changed the greeting text in the function, it would affect all three of the calls. If we didn't have the code in a function, we'd have to modify three sentences and it's very likely we'd make a typo somewhere.
Recursion
Last of all, we'll take a little peek into an advanced topic - recursion. A recursive function is a function which calls itself in its body. Such a function needs information to determine when it should end (end the recursive calling). Otherwise, it'd call itself over and over again until the program is terminated due to insufficient memory. Recursion is used very often in various algorithms.
In functional programming languages, recursion is used instead of loops.
Let's consider a for
loop which sums up the numbers from
1
to 10
. We can achieve the same result with recursion
as well. The function will call itself again with a number which has been
incremented by one or it'll terminate itself (depending on its current
state).
int loop(int current_index, int final_index, int sum) { if (current_index == final_index) return sum; return loop(current_index + 1, final_index, sum + current_index); }
We'll call the function like this:
{CPP_IMPORTS}
int loop(int current_index, int final_index, int sum)
{
if (current_index == final_index)
return sum;
return loop(current_index + 1, final_index, sum + current_index);
}
{CPP_MAIN_BLOCK}
cout << loop(0, 10, 0) << endl; // beginning of the recursion
{/CPP_MAIN_BLOCK}
We could do the same using a for
loop:
{CPP_CONSOLE}
int sum = 0;
int a;
for (a = 0; a < 10; a++)
sum += a;
cout << a << endl;
{/CPP_CONSOLE}
As you can see, reading code using recursion is not as easy as reading code using loops. However, that's not all. Using recursion creates additional memory requirements since parameters and return values have to be passed over and over again. Generally speaking, most programs using recursion can be rewritten to not do so. Let's create a sample program that computes a factorial. We'll show the versions with and without recursion.
int factorial(int x) { if (x == 1) return 1; return x * factorial(x - 1); }
We'd call the function like this:
{CPP_IMPORTS}
int factorial(int x)
{
if (x == 1)
return 1;
return x * factorial(x - 1);
}
{CPP_MAIN_BLOCK}
cout << factorial(10) << endl;
{/CPP_MAIN_BLOCK}
Here's the one using loops:
{CPP_CONSOLE}
int result = 1;
int x = 10;
for (int i = 2; i <= x; i++)
result *= i;
cout << result << endl;
{/CPP_CONSOLE}
You may encounter recursion in existing source codes or at job interviews. However, it's probably best to avoid recursion, or at least for now. Recursion is also able to waste the entire call stack quickly and terminate the program. Furthermore, it's difficult to understand. If you're confused by it, we'll go into it in detail in the algorithms course where there's enough space dwelve in the topic.
In the next lesson, Solved tasks for C++ lesson 13, we'll introduce one of the basic aspects of the C language - structures.
In the following exercise, Solved tasks for C++ lesson 13, we're gonna practice our knowledge from previous lessons.