Wrox Press C++ Tutorial


Understanding Functions

Let's first look at the broad principles of how a function works. A function is a self-contained block of code with a specific purpose. A function has a name that both identifies it and is used to call it for execution in a program. The name of a function is global, but is not necessarily unique in C++, as we shall see in the next chapter. However, functions which perform different actions should generally have different names.

The name of a function is governed by the same rules as those for a variable. A function name is, therefore, a sequence of letters and digits, the first of which is a letter, and where an underscore counts as a letter. The name of a function should generally reflect what it does, so for example, you might call a function that counts beans CountBeans().

You pass information to a function by means of arguments specified when you invoke it. These arguments need to correspond with parameters appearing in the definition of the function. The arguments that you specify replace the parameters used in its definition when the function is executed. The code in the function then executes as though it was written using your argument values. The relationship between arguments in the function call and its parameters is illustrated below:

In this example, the function returns the sum of the two arguments passed to it. In general, a function returns either a single value to the point in the program where it was called, or nothing at all, depending on how the function is defined. You might think that returning a single value from a function is a constraint, but the single value returned can be a pointer which might contain the address of an array, for example. We will see more about how data is returned from a function a little later in this chapter.

Why Do You Need Functions?

One major advantage that a function offers is that it can be executed as many times as necessary from different points in a program. Without the ability to package a block of code into a function, programs would end up being much larger, since you would typically need to replicate the same code at various points in them. But the real reason that you need functions is to break up a program into easily manageable chunks.

Imagine a really big program, a million lines of code let's say. A program of this size would be virtually impossible to write without functions. Functions allow a program to be segmented so that it can be written piecemeal, and each piece tested independently before bringing it together with the other pieces. It also allows the work to be divided among members of a programming team, with each team member taking responsibility for a tightly specified piece of the program, with a well-defined functional interface to the rest of the code.

Structure of a Function

As we have seen when writing the function main(), a function consists of a function header which identifies the function, followed by the body of the function between curly braces containing the executable code for the function. Let's look at an example. We could write a function to raise a value to a given power, that is, compute xn:

double power(double x, int n)    // Function header
{                                // Function body starts here...
   double result = 1.0;          // Result stored here
   for(int i = 1; i<=n; i++)
      result *= x;
   return result;
}                                // ...and ends here

The Function Header

Let's first examine the function header in this example. This is the first line of the function:

double power(double x, int n)    // Function header

It consists of three parts:

The return value is returned to the calling function when the function is executed, so when the function is called, it will have a value of type double in the expression in which it appears.

Our function has two parameters: x, the value to be raised to a given power, which is of type double; and the value of the power, n, which is of type int. The computation that the function performs is written using these parameter variables together with another variable, result, declared in the body of the function.

Note that no semicolon is required at the end of the function header.

The General Form of a Function Header

The general form of a function header can be written as follows:

return_type FunctionName(parameter_list)

The return_type can be any legal type. If the function does not return a value, the return type is specified by the keyword void. The keyword void is also used to indicate the absence of parameters, so a function that has no parameters and doesn't return a value would have this header:

void MyFunction(void)

An empty parameter list also indicates that a function takes no arguments, so you could omit the keyword void between the parentheses as follows:

void MyFunction()

A function with a return type specified as void should not be used in an expression in the calling program. Because it doesn't return a value, it can't sensibly be part of an expression, so using it in this way will cause the compiler to generate an error message.

The Function Body

The desired computation in a function is performed by the statements in the function body following the function header. In our example, the first of these declares a variable result which is initialized with the value 1.0. The variable result is local to the function, as are all automatic variables declared within a function body. This means that the variable result ceases to exist after the function has completed execution.

The calculation is performed in the for loop. A loop control variable i is declared in the for loop which will assume successive values from 1 to n. The variable result is multiplied by x once for each loop iteration, so this occurs n times to generate the required value. If n is 0, the statement in the loop won't be executed at all because the loop continuation condition will immediately fail, and so result will be left as 1.0.

As we've said, all the variables declared within the body of a function, as well as the parameters, are local to the function. There is nothing to prevent you from using the same names for variables in other functions for quite different purposes. Indeed, it's just as well this is so, because it would be extremely difficult to ensure variable names were always unique within a program containing a large number of functions, particularly if the functions were not all written by the same person.

The scope of variables declared within a function is determined in the same way that we discussed in the first chapter. A variable is created at the point at which it is defined and ceases to exist at the end of the block containing it. There is one type of variable that is an exception to this - those declared as static. We'll discuss static variables a little later in this chapter.

Be careful about masking global variables with local variables of the same name. We discussed this situation back in Chapter 2 and saw how we could use the scope resolution operator :: to avoid any problems.

The return Statement

The return statement returns the value of result to the point where the function was called. What might immediately strike you is that we just said result ceases to exist on completing execution of the function - so how is it returned? The answer is that a copy of the value being returned is made automatically, and this copy is available to the return point in the program.

The general form of the return statement is as follows,

return expression;

where expression must evaluate to a value of the type specified in the function header for the return value. The expression can be any expression you want, as long as you end up with a value of the required type. It can include function calls - even a call of the same function in which it appears, as we shall see later in this chapter.

If the type of return value has been specified as void, there must be no expression appearing in the return statement. It must be written simply as:

return;

Using a Function

Before you can use a function in a program, you must declare the function using a statement called a function prototype.

Function Prototypes

A prototype of a function provides the basic information that the compiler needs, to check that a function is used correctly. It specifies the parameters to be passed to the function, the function name and the type of the return value - essentially, it contains the same information as appears in the function header, with the addition of a semicolon. Clearly, the number of parameters and their types must be the same in the function prototype as they are in the function header in the definition of the function.

The prototypes for the functions used in a program must appear before the statements calling the functions, and are usually placed at the beginning of a program. Header files appear between < and > in a #include statement. The ones we've been including for standard library functions include the prototypes of the functions provided by the library.

For our power() example, we could write the prototype as follows:

double power(double value, int index);

Don't forget that a semicolon is required at the end of a function prototype. Without it, you will get error messages from the compiler.

Note that we have specified different names for the parameters in the function prototype to those we used in the function header when we defined the function. This is just to indicate that it's possible. Most often, the same names are used in the prototype and in the function header in the definition of the function, but this doesn't have to be so. The parameter names in the function prototype can be selected to aid understanding of the significance of the parameters.

If you like, you can even omit the names altogether in the prototype, and just write the following:

double power(double, int);

This is enough for the compiler to do its job. However, it's better practice to use some meaningful name in a prototype, since it aids readability and, in some cases, makes all the difference between clear and confusing code. If you have a function with two parameters of the same type (suppose our index was also of type double in the function power(), for example), the use of suitable names can indicate which parameter appears first and which second.

Try It Out - Using a Function

We can see how all this goes together in an example using our power() function:

// Ex4_01.cpp
// Declaring, defining and using a function
#include <iostream>
using namespace std;
double power(double x, int n); // Function prototype

int main(void)
{
   int index = 3;              // Raise to this power
   double x = 3.0;             // Different x from that in function power
   double y = 0.0;
   y = power(5.0, 3);          // Passing constants as arguments

   cout << endl
        << "5.0 cubed = " << y;
   cout << endl
        << "3.0 cubed = " 
        << power(3.0, index);  // Outputting return value

   // Using a function as an argument
   x = power(x, power(2.0, 2.0));

   cout << endl         //with auto conversion of 2nd parameter
        << "x = " << x;

   cout << endl;
   return 0;
}

// Function to compute integral powers of a double value
// First argument is value, second argument is power index
double power(double x, int n)
{                            // Function body starts here...
   double result = 1.0;      // Result stored here
   for(int i = 1; i<=n; i++)
      result *= x;
   return result;
}                            // ...and ends here

This shows some of the ways in which we can use the function power(), specifying the arguments in a variety of ways. If you run this example, you'll get the following output:

How It Works

After the usual #include statement for input/output and the using directive, there is the prototype for the function power(). If you tried deleting this and recompiling the program, the compiler wouldn't be able to process the calls to the function in main().

In a change to previous examples, we've used the new keyword void in the function main() where the parameter list would usually appear to indicate that no parameters are to be supplied. Previously, we left the parentheses enclosing the parameter list empty, which is also interpreted in C++ as indicating that there are no parameters.

As we saw, the keyword void can also be used as the return type for a function to indicate that no value is returned. If you specify the return type of a function as void, you must not place a value in any return statement within the function - otherwise you'll get an error message from the compiler.

You'll have gathered from some of our previous examples that using a function is very simple. To use the function power() to calculate 5.03 and store the result in a variable y in our example, we've written this:

y = power(5.0, 3);

To recap, the values 5.0 and 3 here are called arguments. They happen to be constants, but any expression can be used as an argument, as long as a value of the correct type is ultimately produced. The arguments substitute for the parameters x and n, which were used in the definition of the function. The computation is performed using these values, then a copy of the result, 125, will be returned to the calling function, main(), which will then be stored in y. You can think of the function as having this value in the statement or expression in which it appears. We then output the value of y to the screen:

cout << endl
        << "5.0 cubed = " << y;

The next call of the function is used within the output statement,

cout << endl
        << "3.0 cubed = "
        << power(3.0, index); // Outputting return value

so the value returned is transferred directly to the output stream. Since we haven't stored the returned value anywhere, it is otherwise unavailable to us. The first argument in the call of the function here is a constant, while the second argument is a variable.

The function power() is used next in this statement:

// Using a function as an argument
   x = power(x, power(2.0, 2.0));

Here the function will be called twice. The first call to the function will be that which appears as an argument to the first call. Although the arguments are both specified as 2.0, the function will actually be called with the first argument as 2.0 and the second argument as 2. The compiler will convert the double value specified for the second argument to int, because it knows from the function prototype (shown again below) that the type of the second parameter has been specified as int.

double power(double x, int n);     // Function prototype

There's a possible loss of data in converting from a double to an int, something the compiler has instituted. This is a dangerous programming practice, and it is not at all obvious from the code that this conversion is intended. As we saw in the first chapter, it is far better to be explicit in your code, and to pass the argument as an int in the first place, if that is the type that the function requires.

The double result 4.0 will be returned, and after converting this to int, the compiler will insert this value as the second argument in the next call of the function, with x as the first argument. Since x has the value 3.0, the value of 3.04 will be computed and the result, 81.0, stored in x. This sequence of events is illustrated below:

Again, this involves an implicit conversion by the compiler from a double to an int. We could write this code using a static cast to make the fact that this conversion is intended absolutely clear:

x = power(x, static_cast<int>(power(2.0, static_cast<int>(2.0))));


© 1998 Wrox Press