When programs get longer and more complicated we often have to reuse the same piece of code. For example, we might have to calculate the average of several different arrays of ints, but creating a variable, iterating over all the elements and summing, then dividing by the number of elements can get quite tedious when done multiple times. To save us from such tedious work we can make use of functions, which are sections of code that can be run multiple times without having to rewrite them.

Here we calculate the average of two different sized arrays by using a function instead of writing the same code twice. A function accepts some variables as arguments, and then uses those arguments to calculate a return value which the function evaluates to. In this case our function takes an array of ints (note that we don’t put the size in the [] because the size varies) and the number of elements in that array as arguments and then returns a float value equal to the average of the elements in the array.

Like variables, functions must be declared before they can be used. Unlike variables though—which can be declared anywhere—functions can only be declared outside of any other code block, in this case on line 3. The return type of the function is placed first and the name of the function comes after. Following that are the arguments of the function separated by , and placed within (), followed by a ; at the end of the line. A function can have no arguments at all, in which case the () are empty but they must be included.

Once a function has been declared in can be used even if it doesn’t have any code associated with it. For example, on lines 10 and 11 we pass the two arrays and their sizes to the calculateAverage function, which will then evaluate to their average. This is known as calling the function. Functions can be used in any expression that literals such as 3, 'c', or 2.92f can be, as the function is simply replaced by its return value.

The function still needs to be defined somewhere though, in this case we’ve placed in after main. (Which is also like a function, you can see the similarities with the syntax! You can’t call it like we did with calculateAverage though, and it doesn’t need a declaration.) The first line of the definition looks identical to the declaration but without the ;, which is instead replaced with {} surrounding the body of the function. Everything within the {} is run when the function is called.

Everything else is just standard stuff we’ve seen before, though at the end of the function you must have a return statement which is followed by a value of the same type as the return type of the function. The given value is what the function will evaluate to. return also ends the function, and so placing it in a different place is perfectly valid, but the function will end when it reaches it. For example, a function that returned the index of the first value greater than 3 would use a return statement as soon as that value was found.

void Functions

Earlier we briefly mentioned the void type and how it’s used as a kind of placeholder in some circumstances. Function return types are one such circumstance, and if you don’t want a function to return a value then you can declare it to be of type void.

As an aside, note that we have omitted the declaration of the printSmallSquare function and instead only included the definition. This is fine but you still have to essentially declare the function before it’s used, so now the definition must be placed before main. It’s the most use when your program is just a single file, but that isn’t always going to be the case, and definitely won’t be for larger projects.

Default Function Arguments

Sometimes functions have a lot of arguments, but some have implied values that don’t normally need to be changed. For example, we might have a function that takes the base 10 logarithm of a number, but can also calculate the logarithm in other bases as well. We can therefore make the base have a default value of 10, but let it take other values too.

Default arguments are great, but because the order of the arguments is important, all arguments with default values must be at the end of the function. Additionally, if a function has multiple default arguments then you must give values to all the arguments before the ones you are leaving as default.

C++ allows you to overload functions by reusing their name but specifiying different return types and arguments. For example, you could have two log functions, one that used floats and another that used doubles.

The compiler will automatically work out which version of log to use depending on the type of the arguments passed to it. This means that functions which differ only in their return type cannot be overloaded. An overloaded version of a function is not required to be related to the original, but its bad practice for there to be no link. Don’t write a function to log events and call it the same as a function to calculate logarithms!

References and Functions

We saw earlier that references were a way of creating an additional label for the same variable which allowed you to modify the variable they referred to by just using the reference. They have an excellent application to functions, where we’ll often want to create a function that modifies one or more of its arguments. Simply passing a variable won’t work, because the function only sees the value of the variable; we say it is passed by value. By passing by reference the function can modify one of its arguments and that change will be reflected to the variable passed to it.

However, trying to call increase(3.4, 10.0) will result in a compile error!

Now is the time to make the distinction between l-values and r-values. All parts of expressions in C++ are made up of l-values and r-values, essentially with l-values on the left of an expression and r-values on the right. l-values are things that exist beyond a single expression, such as variables, whereas r-values are temporary values that don’t exist after they are used. This includes things such as literals like 3.4 which cannot be modified. This is why expressions like

don’t make any sense; they have r-values on the left.

Technically the type& name syntax does not specify a reference but an l-value reference—a reference to an l-value—but 3.4 is an r-value and so compilation fails. It’s possible to define an r-value reference, but we won’t cover them here.

Functions can also use references as return types, which can extend the lifetime (scope) of a variable outside of the function, however this feature is currently useless to us without learning some additional things first. Just remember that it’s possible!

Functions in Other Files

The declaration and definition of a function do not need to be in the same file, and by using include directives you can move entire function definitions out of the file containing main. To do this you must create a header file with the extension .hpp or .h and a corresponding source .cpp file. The header will contain the function declaration, the source file will contain the declaration, and the main source file main.cpp should contain an include directive to include the header file. A code example is simpler!

This is a great way of separating and simplifying code, but it can cause problems if a source file includes the same header file twice. This problem isn’t as simple to fix as just not writing #include "power.hpp" twice in one file, as you may be in the sitation where you have two headers—say area.hpp and volume.hpp—which both include the power.hpp header. If you include both of these in one source file, then power.hpp will be included twice—once for each header—and you will get an error. To fix this, we use preprocessor directives. power.hpp would become

The preprocessor can define certain identifiers, called macros, which are a bit like functions except are run when the program is compiled and not when it is opened. Here we check if a macro name unique to this header has already been defined, and if it has we tell the compiler not to process any of the file. If it isn’t already defined, we define it and the compiler proceeds as normal. This way the header is effectively only included once.

Exercise

A vector is a mathematical object which as well as having a sign also has a direction specified by a component along each axis (x, y, and z, for example). The vector $\mathbf{v} = (2, 1, 2)$ has an x component of 2, a y component of 1, and a z component of 2. The magnitude of a vector is defined to be the length of the line from the origin $(0, 0, 0)$ to the components of the vector, and is equal to the square root of the sum of the squares of its components. The length of $\mathbf{v}$ is $\sqrt{2^2 + 1^2 + 2^2} = \sqrt{9} = 3$.

Your task is to write a mag function which takes either a vector with an arbitrary number of components or a single float and computes their magnitude. You should also write the function to calculate the square root yourself (no cmath for those of you who’ve investigated the standard library), which can be calculated by starting with an initial guess $x_0$ and then iteratively computing $x_{n+1} = \frac{x_n}{2} + \frac{a}{2 x_n}.$ As $n$ gets larger $x_n$ tends to the actual square root. (This recurrence relation was derived using the Newton Rhapson procedure.)

Both of these should be declared and defined in a single header and source file.

Solution