4.5. Functions#
A function is a group of statements that is executed when it is called
from some point of the program. Writing an entire program in main(), is
not very practical except for small programs written by a single
individual. Typically, we factor common operations into independent
functions.
Splitting a complex program into multiple smaller functions has three primary benefits:
First, our programs are simpler to understand because they are a sequence of function calls rather than the code sequence to accomplish each operation.
Second, we can reuse the functions across multiple programs.
Third, it is easier to distribute the work across multiple programmers or groups within a project.
This chapter covers the basic rules for writing C++ functions. It also briefly discusses overloading and illustrates the use of recursive functions.
4.5.1. Declaring functions#
A function has a name and a type, much like a variable (see Section 4.3). The functionās type is defined by its return value, that is, the value the function passes back to the program. In addition, the type of arguments required by a function is important.
Before the compiler can process any code where a function is called, the compiler must first have been provided with information on that function, namely:
the name and return data type of the function and
the data type of each argument.
This information also referred to as the function prototype, and providing the compiler with function prototypes such that it can recognize subsequent function calls is called declaring the function. Note that when we declare a function prototype, we do not actually have to define the function yet. Defining a function is the process of informing the compiler of the body of the function with the actual instructions to execute.

The prototype above yields the following information to the compiler:
funcis the function namethe function is called with two arguments: the first argument is of type
int, the second of typedoublethe return value of the function is of type
long.
Examples of function prototypes are:
int fibonacci(int number); // providing variable names like `number` is optional, only the type `int` is required
double pow(double, double);
This informs the compiler that the function fibonacci() is of type
int, i.e. its return value is of type int, and it expects an
argument of type int. The second function pow() is of type double
and two arguments of type double must be passed to the function when
it is called.
Optionally, the type of an arguments may be followed by a variable name. However, the names are viewed only as a comment for future programmers to understand the intended use of the argument, and are completely ignored by the compiler.
Standard function prototypes do not need to be declared, nor should they
be, as they have already been declared in standard header files. If the
header file is included in the programās source code by means of the
# include directive, the function can be used immediately.
#include <cmath>
Following this directive, the mathematical standard functions, such as
sin(), cos(), and pow(), are available.
4.5.1.1. Header Files#
In general, it is a good idea to store function declarations (and later
in this manual class declarations) in separate header files. A header
file contains no functional code, just the function declarations and is
stored as a .h file in the include/ folder. By using header files,
you can include the same declarations in multiple source (.cpp) files
without the need to declare them in each. A typical header file looks as
follows:
/*
* example_header.h
*
* Created on: Jul 14, 2014
* Author: jeff
*/
// Preprocessor definition guards against multiple declaration of the functions inside this file.
#ifndef EXAMPLE_HEADER_H_
#define EXAMPLE_HEADER_H_
// List of includes required for the functions you are declaring
#include <string>
// Functions and classes to declare
std::string concatenate_strings(std::string a, std::string b);
// End of the multiple inclusion guard
#endif /* EXAMPLE_HEADER_H_ */
The preprocessor commands #ifndef, #define and #endif are a
construct that ensure the functions in your header file are only
declared once, even if multiple source files include it. Including this
header file is done with the following line:
#include "<project_name>/example_header.h"
Where you should replace <project_name> with the name of the
project you used in the CMakeLists.txt
4.5.2. Defining functions#
Functions can be defined in any order, however, the first function is
often main().
This makes the program easier to understand, since you
start reading at the point where the program starts to execute. The
function test() is shown below as an example and followed by the
general form of a function.
#include <iostream>
void test( int, double ); // Prototype, these should normally be put in a separate header file!
int main()
{
std::cout << "\nNow function test() will be called.\n";
test(10, -7.5); // Call
std::cout << "\nAnd back again in main()." << std::endl;
return 0;
}
void test(int arg1, double arg2 ) // Definition
{
std::cout << "\nIn function test()."<< "\n 1. argument: " << arg1 << "\n 2. argument: " << arg2 << std::endl;
}
And the general form of a function is
[type] name([declaration_list]) // Function header
{ // Beginning
.
.
What will be done // Function block
.
.
} // End
The example can be read as follows:
type: is the function type, that is, the type of the return value.
name: is the function name, which is formed like a variable name and should indicate the purpose of the function.
declaration list: contains the names of the parameters and declares their types. The list can be empty, as for the function
main(). A list of declarations that contains only the word void is equivalent to an empty list.
The parameters declared in a list are no more than local variables. They
are created when the function is called and initialized by the values of
the arguments. For example, when test(10, -7.5) is called, the
parameter arg1 is initialized with a value of 10 and arg2 with -7.5.
The left curved bracket indicates the start of a function block, which contains the statements defining what the function does.
4.5.2.1. Prototype and definition#
In a function definition the function header is similar in form to the prototype of a function. The only difference when a function is defined is that the name and declaration list are not followed by a semicolon but by a function code block. The prototype is the declaration of the function and thus describes only the formal interface of that function. This means you can omit parameter names from the prototype, whereas compiling a function definition will produce machine code.
4.5.3. Return value of functions#
The program below shows how a function area() is defined and called.
As previously mentioned, you must declare a function before calling it.
The prototype provides the compiler with all the information it needs to
perform the following actions when a function is called:
check the number and type of the arguments
correctly process the return value of the function.
When a function is called, an argument of the same type as the parameter
must be passed to the function for each parameter. The arguments can be
any kind of expressions, as the following example with the argument
y+1 shows. The value of the expression is always copied to the
corresponding parameter.
// area.cpp
// Example for a simple function returning a value.
//-----------------------------------------------------
#include <iostream>
#include <iomanip>
double area(double, double); // Declaring the area function prototype
int main()
{
double x = 3.5, y = 7.2, res;
res = area(x, y+1); // Call the declared function
std::cout << std::fixed << std::setprecision(2); // this configures cout to print double numbers up to two decimal places
std::cout << "\n The area of a rectangle "
<< "\n with width " << std::setw(5) << x
<< "\n and length " << std::setw(5) << y+1
<< "\n is " << std::setw(5) << res << std::endl;
return 0;
}
// Defining the function area():
// Computes the area of a rectangle.
double area(double width, double len)
{
return (width * len); // Returns the result.
}
Tip
The example above uses some functions to manipulate
how std::cout prints text and numbers to the terminal.
You can read up more about std::setprecision() and std::setw()
on the online documentation on iomanip from the C++ standard library,
including examples.
If you search the online manual for āstd fixedā, you find that std::fixed prevents std::cout from using scientific notation.
4.5.3.1. Return statement#
When the program flow reaches a return statement or the end of a
function code block, it branches back to the function that called it. If
the function is any type other than void, the return statement will
return a value.
return [EXPRESSION];
The value of the ā[EXPRESSION]ā will be the return value. If the type of
this value does not correspond to the function type, the function type
is converted, where possible. If the expression in the return statement,
or the return statement itself, is missing, the return value of the
function is undefined and the function type must be void. Functions of
the void type, such as the standard function srand(), will perform
an action but not return any value (as was discussed in Section 4.3.1).
4.5.4. Passing arguments#
A function declaration specifies for each of its arguments whether it is pass by value or pass by reference. Arguments that are pass by value will result be copied when the function is called, and the function can only operate on their own copy, leaving the original variables untouched.
However, function arguments can also be pass by reference. In this case, the function is receiving a direct reference to the original object as an argument, instead of a copy. The function can therefore access the data in the original variable directly, and possibly modify it.
Under the hood, a reference is basically a memory address to where the data in the original variable is stored,
as will be explained in more detail in Section 4.7.
For now, it suffices to know that the type of a variable that is passed by reference will be have an additional &, as we will see in a moment.
4.5.4.1. Passing by value#
Most functions that take simple built-in data types as arguments receive these by value, creating a local copy of the variable. For example:
void pass_by_value(int a) {
a++; // Increment a, which is a local copy for this function
}
This function receives its argument int a as a simple pass by value, since the type int uses no reference notation (&).
Passing by value offers some important advantages:
Function arguments can be any kind of expression, even constants.
The called function cannot cause accidental modifications of the arguments in the calling function. For example, calling
pass_by_value(b)will not modify the value of an integerb, because the function will only operate on its temporary copy.The parameters are available as variables within the functions, which can be modified freely if needed. Additional indirect memory access is unnecessary.
However, copying larger objects could be a major disadvantage as it is slow, can have additional computational overhead, lead to more memory consumption, and is often unnecessary. For this reason arrays and large objects from custom classes are often passed by reference instead.
4.5.4.2. Passing by reference#
To avoid copying the data of variables when passing them to a function, you can pass them by reference:
void pass_by_reference(int& a);
This example passes the integer a as a reference. It can be used as
any other integer, but thereās a caveat: the āpass by referenceā
function is working on the same variable a as the code calling it. It
is even allowed to change this variablesā value!
If you want to avoid any accidental change, but still use āpass by referenceā to avoid copying the data,
you can declare the reference as const, which ensures that the variable is not allowed to change (or there will be a compiler error):
void pass_by_reference(const int& a);
Exercise 4.11
Implement a pass by-reference-to-const function. Try to change it in the āpass by referenceā function. Try to compile this code. Remember the kind of compiler output you get, it is a common one!
4.5.4.3. Local objects#
The scope of function parameters and the objects defined within a function applies only to the function block. That is, they are valid within the function only and not related to any objects or parameters of the same name in any other functions.
For example, the program structure
below contains a variable a in the function func1() and in the
function func2(). The variables do not collide because they reference
different memory addresses. This also applies to the variables x in
func1() and func2().

4.5.4.4. Inline definition#
The compiler inserts the code of an inline function at the address where the function is called and thus avoids jumping to a sub-routine. The definition of an inline function is introduced by the inline keyword in the function header.
inline int max(int x, int y)
{
return
(x >= y ? x : y );
}
The program code will expand each time an inline function is called. This is why inline functions should contain no more than one or two instructions. If an inline function contains too many instructions, the compiler may ignore the inline keyword and issue a warning. An inline function must be defined in the source file in which it is called. You cannot simply supply a prototype of the function. The code containing the instructions must also be available to the compiler. It therefore makes sense to define inline functions in header files, in contrast to ānormalā functions. This means the function will be available in several source files.
4.5.4.5. Functions without arguments#
If a function does not expect an argument, the function prototype must be declared as void or the braces following the function name must be left empty.
int rand(void); // or int rand();
The standard function rand() is called without any arguments and returns
a random number between 0 and 32767. A series of random numbers can be
generated by repeating the function call.
4.5.5. Default Arguments#
Default arguments can be defined for functions. This allows you to omit some arguments when calling the function. The compiler simply uses the default values for any missing arguments.
4.5.5.1. Defining default arguments#
The default values of a functionās arguments must be known when the function is called. In other words, you need to supply them when you declare the function.
void moveTo(int x = 0, int y = 0);
Parameter names can be omitted, as usual.
void moveTo(int = 0, int = 0);
The function moveTo() can then be called with or without one or two
arguments.
moveTo(); moveTo(24); moveTo(24, 50);
The first two calls are equivalent to moveTo(0,0); or moveTo(24,0); . It
is also possible to define default arguments for only some of the
parameters. The following general rules apply:
the default arguments are defined in the function prototype. They can also be supplied when the function is defined, if the definition occurs in the same source file and before the function is called
if you define a default argument for a parameter, all following parameters must have default arguments
default arguments must not be redefined within the prototype scope.
4.5.5.2. Calling a function with default arguments#
When calling a function with default arguments you should pay attention to the following points:
you must first supply any arguments that do not have default values
you can supply arguments to replace the defaults
if you omit an argument, you must also omit any following arguments.
You can use default arguments to call a function with a different number of arguments without having to write a new version of the function.
4.5.6. Overloading functions#
Functions in traditional programming languages, such as C, which perform the same task but have different arguments, must have different names. To define a function that calculated the maximum value of two integers and two floating-point numbers, you would need to program two functions with different names.
int int_max( int x, int y );
double dbl_max( double x, double y );
Of course this is detrimental to efficient naming and the readability of your program but luckily, this restriction does not apply to C++.
4.5.6.1. Overloading#
C++ allows you to overload functions, that is, different functions can have the same name.
int max(int x, int y);
double max(double x, double y);
In our example two different function share the same name, max.
The function max() was overloaded for int and double types.
The compiler uses the functionās signature to differentiate between overloaded
functions.
4.5.6.2. Function signatures#
A function signature comprises the number and type of parameters. When a function is called, the compiler compares the arguments to the signature of the overloaded functions and simply calls the appropriate function.
double maxvalue, value = 7.9;
maxvalue = max( 1.0, value );
In this case the double version of the function max() is called. When
overloaded functions are called, implicit type conversion takes place.
However, this can lead to ambiguities, which in turn cause a compiler
error to be issued.
maxvalue = max( 1, value ); // Error!
The signature does not contain the function type, since you cannot deduce the type by calling a function. It is therefore impossible to differentiate between overloaded functions by type.
int search(string key);
string search(string name);
Both functions have the same signature and cannot be overloaded.
4.5.7. Recursive Functions#
A function that calls itself is said to be recursive. This process can also be performed indirectly if the function first calls another function or multiple functions before it is called once more. But a break criterion is always necessary to avoid having the function call itself infinitely. The concept of local objects makes it possible to define recursive functions in C++.
4.5.7.1. Recursion#
Recursion requires local objects to be created each time the function is called, and these objects must not have access to any other local objects from other function calls. What effectively happens is that the local objects are placed on the stack, and thus the object created last is destroyed first.

4.5.7.2. A sample program#
Letās look at the principle of recursion by referring to the sample
program below. The program contains the recursive function getput()
that reads a line of text from the keyboard and outputs it in reverse
order.
The function getput() is first called by main() and reads a
character from the keyboard, storing it in the local variable c.
If the character is not \n, the function getput() calls itself again and
thus reads a further character from the keyboard before storing it in the local variable c.
The chain of recursive function calls is terminated by the user pressing the Return key.
The last character to be read, \n (line feed, like when you press āEnterā),
is output and the program flow branches to the previous getput() instance.
This outputs the second to last character, and so on.
When the first character to have been read has finally been output, the program flow is handed back to main().
// recursive.cpp
// Demonstrates the principle of recursion by a
// function, which reads a line from the keyboard
// and outputs it in reverse order.
// ----------------------------------------------------
#include <iostream>
void getput(void);
int main()
{
std::cout << "Please enter a line of text:\n";
getput();
std::cout << "\nBye bye!" << std::endl;
return 0;
}
void getput()
{
char c;
if (std::cin.get(c) && c != '\n')
{
getput();
}
std::cout.put(c);
}
4.5.7.3. Practical usage#
The logic of various solutions to common problems results in a recursive structure, for example, browsing directory trees, using binary trees for data management, or some sorting algorithms, such as the quick sort algorithm. Recursive functions allow you to formulate this kind of logic in an efficient and elegant manner. However, always make sure that sufficient memory is available for the stack.
Exercise 4.12
The factorial \(n!\) of a positive integer \(n\) is defined as \(n! = 1*2*3 ... *(n-1)*n\), where \(0! = 1\).
Write a function to calculate the factorial of a number.
Argument: A number \(n\) of type unsigned int.
Returns: The factorial \(n!\) of type long double.
Formulate two versions of the function, where the factorial is:
a. calculated using a loop b. calculated recursively.
Test both functions by outputting the factorials of the numbers 0 to 20 as shown opposite on screen.
4.5.8. Libraries#
You will not need to program each ābuilding blockā yourself. Many useful global functions and classes are available from the C++ standard library. In addition, you can use other libraries for special purposes. Often a compiler package will offer commercial class libraries or graphical user interfaces. Thus, a C++ program will be made up of
language elements of the C++ core
global functions and classes from the C++ standard library
functions and classes you have programmed yourself and other libraries.
We will discuss the C++ standard library in the next chapter.