Module 2: Functions

  • what is a function?
  • how do we use functions?
  • what are function declarations, definition and calls

Functions are modules in computer programs that contain statements. When a function executes, its statements are executed one at a time: the first statement executes, then the next one, then the next one, etc., until all of the statements in the function have been executed. All the statements in a program must appear inside of a function.

You’ve already seen one function in our “hello world” program: main. Whenever we start running a program, execution begins in the main function. We have already seen when we run this program:

#include <iostream>
using namespace std;

int main() {
	cout << "Hello, world!" << endl;
	return 0;
}

We can write a slightly more interesting program by breaking our statements up into more than one function, adding some new details and connecting the functions’ execution together like so:

#include <iostream>
using namespace std;

int foo(int n);   // function declaration

int main()
{
	foo(42);   // function call
	foo(43);   // function call

	return 0;
}

// function definition (implementation)
int foo(int n)
{
	cout << "the number n is: " << n << endl;
	return n + 1;
}

As in assignment 0, there is more than one function, but the overall program is still quite simple. Still, it is a reasonable basis for starting to explore the syntax and semantics of C++ functions.

Function syntax

The example above demonstrates three critical aspects of function syntax:

  1. functions can be declared,
  2. functions must be defined and
  3. functions are called when we want to use them.

Function declaration

int foo(int n);   // function declaration

A function declaration tells the compiler everything it needs to know in order to use a function (by calling it, as we’ll describe below). This includes:

  1. the function’s name (in this case, foo),
  2. what values you can pass to the function when it starts executing (here, int n) and
  3. what type of value the function will give back (here, int).

Informally, we can think of a function as a machine with a specific name that (potentially) takes some inputs, then executes to do some computation, then (potentially) gives you back some result. The declaration of a function doesn’t tell us anything about how this machine works, but it does tell us how to interact with it. For example, in order to interact with the foo function in our example, you will need to supply it with an integer and you can expect it to give you an integer back in return, after it’s done its work.

More formally, the declaration of a function contains (in order):

  1. a prototype, consisting of:
    1. the return type (the type of value the function will give back or void if it doesn’t give a value back),
    2. the name (which must be a legal identifier),
    3. parentheses containing zero or more parameters (values you need to pass to the function for it to do its job) separated by commas (e.g., int multiply(int x, int y) is a function with two parameters), followed by
  2. a semicolon.

Function definition

// function definition (implementation)
int foo(int n)
{
	cout << "the number n is: " << n << endl;
	return n + 1;
}

A function definition describes how the function is implemented. It also starts with a prototype, like a declaration does, but instead ending there with a semicolon, a function definition includes a body. This is a block of statements that will execute when the function runs. We will discuss the semantics of running functions below, but for now, while we’re considering syntax, notice that there is a statement containing the return keyword in this example. This statement is the thing that “gives back a value in return” when the function is done executing.

Function call

foo(42);   // function call
foo(43);   // function call

A function call is an expresion that makes a function execute. Calling a function is very simple: we write the name of the function and then, in parentheses, we write any values that we need to pass to the function (called arguments). Our example foo function had one parameter of type int, so we need to pass in a single int value as an argument. If a function has more (or fewer) parameters, the function call will need more (or fewer) arguments. Like parameters, multiple arguments are separated by commas (e.g., multiply(4, 5)).

In order for a function to be called, we need to know:

  1. the function’s name,
  2. the function’s parameters and
  3. the function’s return type.

The programmer needs to know this in order to use a function correctly, but the compiler also needs to know all of these details in order to compile working code. These details are, of course, exactly the information provided by a function declaration (and a subset of the information provided by a function definition). So, in C and C++, you must declare and/or define a function before calling it.

Function semantics

The meaning of a function in programming is somewhat distinct to the mathematical concept of function application. Now that we understand the syntax of functions, let’s dive into their semantics.

Calling functions

A function call is a transfer of control from one place in our code to another. When we call a function, we pause what we’re currently doing, take a moment to run the function we’re calling, then once it’s finished, resume where we left off. In order to give ourselves a concrete example to explain things with, here is one very simple function whose mathematics should be quite familiar:

double triangleArea(double base, double height)
{
	return (base * height) / 2;
}

This is a function with two parameters (both double-precision floating point numbers, or real numbers) that returns a real number (a double-precision floating point number). We can call the function a couple of times using the following code:

#include <iostream>
using namespace std;

/**
 * Calculate the area of a triangle.
 *
 * @param base    length of the base @pre >=0
 * @param height  length of the triangle's height
 *                @pre >= 0, same units as base
 *
 * @returns       the triangle's area, in base units squared
 */
double triangleArea(double base, double height);

int main()
{
	cout << "The area of Pythagorus' famous 3-4-5 triangle is ";
	cout << triangleArea(3,4);
	cout << endl << "while that of his 5-12-13 triangle is ";
	cout << triangleArea(5, 12);

	return 0;
}

(this code can also be downloaded as a C++ source file)

When we call a function, we do several things:

  1. we pass arguments into the function’s parameters,
  2. we start running the function from the top of its body with the parameters set to the values of the arguments,
  3. we execute the statements in the function, one at a time, until we reach a return statement or the end and
  4. we treat the returned value (if any) as the result of evaluating the call.

So, when we call triangleArea(3, 4) in the above code, those steps are played out as follows:

  1. we pass the value 3 into the base parameter and 4 into the height parameter,
  2. we start running the triangleArea function with base=3 and height=4,
  3. we execute the only statement in triangleArea, return (base * height) / 2, whose return value can be evaluated as:
    • (base * height) / 2
    • (3 * 4) / 2
    • (12) / 2
    • 6
  4. we take the returned value 6 and treat it as the result of evaluating triangleArea(3,4).

The second time that we call the function, we follow the same steps, executing the same statement within the triangleArea function, but this time with base equal to 5 and height equal to 12. This shows an important principle of programming: functions are implemented once but used many times. Putting frequently-executed code into functions allows us to reduce duplicate code (which is both labour-wasting and error-prone) and promote code re-use, something software designers always strive for.

Note: when a function does not return a value, it uses the special return type void; such functions do not need to have a return statement, and if they do, it is simply return;

Design by contract

Every function declaration should be preceded by a comment that, together with the prototype, defines a contract between the function’s author (who is providing a service with his or her function) and the function’s users (a.k.a., clients). In the example above, the triangleArea declaration was preceeded by the following comment:

/**
 * Calculate the area of a triangle.
 *
 * @param base    length of the base @pre >=0
 * @param height  length of the triangle's height
 *                @pre >= 0, same units as base
 *
 * @returns       the triangle's area, in base units squared
 /

This started with a very abstract, high-level brief description of what the function does (“Calculate the area of a triangle”). Notice that it ignores details like the return type or the number of parameters: that is detail we’ll describe in just a moment, but a brief description will ideally be written in terms of the problem domain (in this example, geometry).

Next, we described the parameters of the function for other programmers, providing additional information that the compiler-visible declaration doesn’t tell us. Note that we annotated our parameter descriptions with the tag @param. This is a conventional way of drawing attention to parameters within a comment, and it can even facilitate the automatic generation of documentation for functions, but it’s not a hard requirement. Also note that our parameter descriptions include @pre tags: we will describe those in the preconditions section below.

Additional (optional) information

It may also be appropriate to provide a description of the value being returned (conventionally using the @returns tag). In this case, since there is an interesting thing happening with units, we’ve been sure to include a @returns tag within our comment. In other cases, however, it may be unnecessary. For example, a function called sin whose brief description is, “Calculate the sine of an angle in radians” does not need to specify again in the comment that the function @returns the sine of x.

In some cases, it may also be appropriate to use a @modifies tag to describe values or physical quantities that the function modifies. For example, if a function changes the robot’s physical position (in a way that is not obvious from the brief description), it may be worth adding a @modifies line. Once again, however, if we have a brief description of, “Move the robot forward by a specified distance”, we don’t need a @modifies robot position line.

Preconditions

Pre-conditions are used to qualify input parameters. It is the client’s responsibility to provide values for the parameters when the program is running and the function gets called. If the pre-conditions are met, the function will work as advertised. Otherwise, the function provider may do anything at all.

Pre-conditions are important for maintenance. When a function implementation is changed, the programmer must continue to adhere to the contract, but may change the behaviour when pre-conditions are violated in any way at all. This is a warning to clients not to depend upon behaviour they observe when preconditions are violated. For example, the triangleArea function states that base and height can’t be negative. If you test how the function works (or examine the code) you’ll see it goes ahead and does the calcualtion anyway. Depending on that and feeding the function negative values is dangerous because future versions of the function might put in a test for negative values and bring the program to a halt if any are detected. Clients who observed the pre-conditions are unaffected. Clients who depend upon undocumented behaviour suddenly find their programs quitting when a new version of the code they are using comes out.

Library Functions

There are thousands of prebuilt functions available in dozens of libraries that come with your C++ compiler. These libraries are collectively called the C++ Standard Libarary. Using libraries allows us to focus on the parts of our design that are unique to our particular problem (e.g., dealing with Memorial student IDs) rather than common to all programming problems (e.g., calculating square roots).

To use (call) pre-defined library functions, we need to tell the compiler where to find their declarations. Since we didn’t write these functions ourselves, we also haven’t written their declarations in our own source file. Instead, we need to point the compiler at an external file using an include directive:

#include <iostream>

This example directive tells the compiler to open the C++ library file called iostream and read the declarations it sees there. Other files will contain other declarations. For example, the cmath header file declares usful mathematical functions like triginomatric functions (e.g., double sin(double x)) and exponentiation (i.e., double pow(double x, double exponent)). To use any of these functions you would add to the top of your file the line #include <cmath&>.

Practice problems

We have already said that, in order to succeed in programming, one needs to practice, practice, practice. You can find practice problems in either of the recommended textbooks, but we will also try to give you problems to work on through these lecture notes. Here are a couple of problems to start off with:

Area

Declare and define a function to compute the area of a circle given its radius. Then call this function from main to print out some areas of circles.

Question: what values could you pass to your function to ensure that it’s working correctly? What outputs would you expect to see?

Note: the value of $\pi$ is specified in the cmath header file as M_PI: to use it, you must first #include <cmath> (as discussed in the Library Functions section)

Acceleration

In classical, non-calculus-based physics, the distance an object travels is given by the equation:

$$ d = \frac{1}{2} a t^2 + v t $$

where $v$ is the initial speed, $a$ is a constant acceleration and $t$ is the time. Declare and define a function to compute $d$ given values of $a$, $v$ and $t$, and then call this function from a main function.

Question: what values could you pass to your function to ensure that it’s working correctly? What outputs would you expect to see?

License: CC BY-NC-SA

(c) 2009–2016 Michael Bruce-Lockhart, Theo Norvell, Dennis Peters and Jonathan Anderson. Licensed under a Creative Commons Attribution–Noncommercial–Share-Alike 2.5 Canada License. Permissions beyond the scope of this license may be available at theteachingmachine.org.