header

Functions

In C++, a function is a subprogram or program module that performs a single clearly-defined task. The use of subprograms facilitates team-programming in which different members of the team work on separate subprograms. If a given task is needed in several places, encapsulating that task in a subprogram eliminates the need for duplicating the code in each of those places. Well-defined subprograms can also be used in other programs. For example, the cmath header file defines a number of mathematical functions that can be used in any program.

In a typical program, there will be a function declaration (also called a function prototype), a function definition, and a function invocation. The function declaration or function prototype supplies the compiler with the information it needs about the function: the type of value returned by the function, the name of the function, and the number and types of parameters the function needs. The function declaration may also specify the names of the parameters, but that is optional. Function declarations are placed in a program before main.

The function definition typically follows main and contains the statements that are executed when the function is invoked. The function definition begins with the prototype (except in this case, the parameter names are required) followed by the code for the function enclosed within braces (just like main). The code contains local variable declarations and the executable statements that perform the function's intended task (again, just like main).

The function invocation causes a break in the normal execution of the statements in the current control structure. When a function is invoked, control is transferred to the statements in the definition of the function. When the function terminates, control is transferred back to the code containing the invocation. A function is invoked by using its name followed by a comma-delimited list of the actual parameters that are to be used by the function.

These three (prototype, definition, and invocation) are shown in this simple example program and are discussed in more detail below:

#include <iostream>
using namespace std;

double areaOfRectangle(double, double); <- Prototype

int main()
{
    double width, length;
    
    width = 5.0;
    length = 6.0;
    cout << "Area: ";
    cout << areaOfRectangle(width, length); <- Invocation
    cout << endl;
    return 0;
}

double areaOfRectangle(double side1, double side2) |
{                                                  |
    double area;                                   | <- Definition
    area = side1 * side2;                          |
    return area;                                   |
}                                                  |

Function Declaration (Prototype)

The function prototype specifies three things about a function: what type of value it returns, its name (the function identifier), and a list of the types of values the function needs to perform its task (called parameters or arguments):

type functionName(type, type, ...);

The type before the name of the function is the type of value the function returns. The name can be any valid identifier. The parameter list is enclosed by parentheses and consists of a comma-delimited list of zero or more data types. In the example program above, we need a function whose purpose is to calculate and return the area of a rectangle. Its prototype is:

double areaOfRectangle(double, double)

This tells the compiler that the function returns a double value, is named "areaOfRectangle" and requires two double type parameters. Notice that the prototype provides the compiler with all it needs to compile the application but it does not tell the human reader all he or she needs to know to use this function. Specifically, it doesn't tell the human reader whether the first parameter represents the width or the height. (If you are astute, you will have noticed that it doesn't really matter whether the first parameter represents the width or the length since the area is simply the product of the two parameters. However, in most cases, it is very important to know which parameter is which.)

Function Definition

A function definition contains the code needed to perform the function's task. The function definition begins with the function prototype except this time the names of the formal parameters are specified. After the prototype, the code for the function (including variable declarations and executable statements) is enclosed between braces (just like in main):

type functionName(type id, type id, ...)
{
    variable declarations
    
    executable statements
}

The function definition for our function to calculate the area of a rectangle is:

double areaOfRectangle(double side1, double side2)
{
    double area;
    area = side1 * side2;
    return area;
}

A local variable, area is declared to temporarily store the area of the rectangle. The value of the area is found by multiplying side1 and side2. The value of the area variable is the value returned by the function. In the function definition, side1 and side2 are referred to as formal parameters and serve as temporary storage locations for the lengths of the sides of the rectangle whose area is being calculated. The values (called actual parameters) that will be stored in these memory locations will be specified in the invocation of the function.

Function Invocation

A function invocation does three things. It stops the normal execution of the current block of statements, supplies the function with the values it needs to perform its task, and transfers control to the first executable statement in the function definition. A function invocation includes the name of the function and the values of the actual parameters:

functionName(expression, expression, ...)

The values of the expressions are referred to as the actual parameters and represent the specific values the function will use to perform its task during this specific invocation. An expression can be a literal value, a variable, a constant, or an expression with one or more operators. There are three important properties associated with this list of actual parameters:

  1. The number of actual parameters must be the same as the number of formal parameters specified in the function prototype (and function definition).
  2. The actual parameters must match up with the formal parameters on a one-to-one basis. The first actual parameter corresponds to the first formal parameter, the second actual parameter corresponds to the second formal parameter, and so on.
  3. Each expression must return the type of value associated with the corresponding formal parameter. For example, if the first formal parameter is declared as a double, then the expression for the first actual parameter must result in a double value.

Our function to calculate the area of a rectangle can be invoked several different ways:

areaOfRectangle(10.0, 20.0)
areaOfRectangle(a, b)
areaOfRectangle(2.0*a, 2.0*b)

In the first invocation, the function will return the area of a rectangle with dimensions 10 by 20. In the second invocation, the function will return the area of a rectangle whose dimensions are a by b (where a and b are double variables). In the third invocation, the function will return the area of a rectangle whose dimensions are 2a by 2b.

A function invocation can be used anywhere a variable can be used with one exception: you cannot use a function invocation in any context in which a value would normally be assigned to a variable:

sin(x) = 4;     //ILLEGAL
cin >> sin(x);  // ILLEGAL

The purpose of both of these statements is to assign a value to a variable. You cannot use a function identifier in this context because you can't assign a value to a function.

Here are some examples of valid ways to use a function invocation in place of a variable identifier:

cout << "Area: " << areaOfRectangle(10.0, 20.0) << endl;
myArea = areaOfRectangle(a, b);
totalArea = areaOfRectangle(a, b) + areaOfRectangle(2.0*a, 2.0*b);

In the first statement, the area of a 10 by 20 rectangle will be inserted into the output stream. In the second statement, the area of a rectangle whose dimensions are a by b will be assigned as the value of the variable, myArea. In the third example, the area of a rectangle whose dimensions are a by b will be added to the area of a rectangle whose dimensions are 2a by 2b and the sum assigned as the value of the variable, totalArea.

Putting it All Together

Let's put all the pieces together in our simple example program:

#include <iostream>
using namespace std;

double areaOfRectangle(double, double); <- Prototype

int main()
{
    double width, length;
    
    width = 5.0;
    length = 6.0;
    cout << "Area: ";
    cout << areaOfRectangle(width, length); <- Invocation
    cout << endl;
    return 0;
}

double areaOfRectangle(double side1, double side2) |
{                                                  |
    double area;                                   | <- Definition
    area = side1 * side2;                          |
    return area;                                   |
}

You can trace the execution of this program in the box below. Click "Next" to execute the next statement, click "Prev" to back up to the previous statement, and click "Reset" to start all over again.

The graphic below illustrates the order in which the instructions are executed by this program. Notice the execution of the statements in main are temporarily halted when the function is invoked. Once the value of the area has been returned, program execution resumes in main at the point where the returned value is inserted into the standard output stream.

Void Functions

So far, we have been talking about functions in the traditional mathematical sense of the word. Mathematical functions process data (its parameters or arguments) in order to generate a result. But in a computer program, a subprogram may perform a different kind of task which does not involve any calculations and does not need to return a value. A C++ subprogram that does not return a value is called a void function. To provide for void functions, C++ has a special data type named "void". Only a function can be declared as void. It would make no sense to declare a variable as void because by definition, a variable represents a value of some type. A void function does not represent a value of any type.

Consider a simple function whose purpose is to display some text on the computer monitor:

void displayInstructions(); <- Prototype

int main()
{
    ...
    displayInstructions(); <- Invocation
    ...
    return 0;
}

void displayInstructions()                                            |
{                                                                     |
    cout << "Given the radius of a circle, this program will" << endl;| <- Definition
    cout << "display the diameter, the circumference and the" << endl;|
    cout << "area." << endl << endl;                                  |
}                                                                     |

In the prototype, the function was declared as type void because it does not return any value. The parameter list is empty because this function doesn't need any parameters. The syntax of a void function prototype is the same as it is for value-returning functions.

A value-returning function is invoked by using its name (and actual parameters, if any) as you would use a variable identifier. However, a void function is invoked by using its name (and actual parameters) as a program statement. The invocation of a void function is a C++ statement.

The syntax for defining a void function is no different than the syntax for defining a value-returning function except that there will be no return statement in a void function. Since a void function doesn't return any value, it doesn't need a return statement. A void function terminates when the function has finished executing its last statement.

More about Function Parameters

Function parameters can be classified in three different ways: by the location within the program code, by how the parameter is used, and by how the parameter is passed to the function. It is very important to understand all three of these classifications.

Location: Formal and Actual Parameters

A formal parameter is a block of memory whose contents are used by the function in performing its task. As always, this block of memory is associated with an identifier (the name of the formal parameter) which is specified in the function definition.

An actual parameter is a value specified in the invocation of the function. The actual parameter is the value stored in the corresponding block of memory associated with the formal parameter identifier.

Use: In, Out, and In/Out Parameters

An in parameter serves as input into a function. It represents a value that the function must have in order to carry out its task.

An out parameter serves as output from a function. It represents a value that is generated by the function as part of its task; a value that must be sent back to the section of code that invoked the function. When a function acts like a traditional function, the best approach is to implement the function as a value-returning function as described above. However, suppose we need a function that returns two values rather than one. We cannot implement such a function as a value-returning function because a value-returning function can return only one value; not two. In this context, we would use a void function with two out parameters.

An in/out parameter serves as both input and output. That is, the value of the parameter is needed by the function as input in order to perform its task. However, during the execution of the function, the value of this parameter may change and the new value must be sent back as output.

Passed by Value or by Address

When a parameter is passed by value, the value of the actual parameter is copied to a newly allocated block of memory associated with the corresponding formal parameter identifier. In our discussion above, we illustrated how a parameter is passed by value. When a function is invoked, a block of memory is allocated and associated with the formal parameter identifier. The value of the actual parameter is copied to this memory location and the function works with this copy. Since a value is being passed to the function, the actual parameter can be any expression that returns a value of the appropriate type including a literal constant, a defined constant, a variable, or the invocation of a value-returning function.

When a parameter is passed by address, the formal parameter identifier refers to the same block of memory as the actual parameter. The operating system does not allocate a new block of memory for the formal parameter. This implies that the actual parameter must be associated with a block of memory which, in turn, implies that only variables can be passed by address (because only variables are associated with blocks of memory whose contents are subject to change).

The address operator (&) is used to indicate that the address of the actual parameter is to be passed to the function rather than the value of the actual parameter. Here is the prototype for a function whose parameters are passed by address rather than by value:

void getDimensions(double&, double&)

Both parameters to this function are double addresses (not double values). That is, the addresses of the actual parameters will be passed to the function rather than the values. Of necessity, the actual parameters must be variables. The & is used only in the function prototype and the function definition. It is not used in the function invocation!

Using Void Functions: An Example

Let's modify our earlier example (which finds the area of a rectangle) to use two void functions. One void function will get the length and width of the rectangle and the other will display the corresponding area:

#include <iostream>
using namespace std;

void getDimensions(double&, double&);   <- Prototype
double areaOfRectangle(double, double); <- Prototype
void displayArea(double);               <- Prototype

int main()
{
    double width, length;
    
    getDimensions(width, length); <- Invocation
    displayArea(areaOfRectangle(width, length)); <- Two Invocations
    return 0;
}

void getDimensions(double& wid, double& len)       |
{                                                  |
    wid = 5.0;                                     | <- Definition
    len = 6.0;                                     |
}                                                  |

double areaOfRectangle(double side1, double side2) |
{                                                  |
    double area;                                   | <- Definition
    area = side1 * side2;                          |
    return area;                                   |
}                                                  |

void displayArea(double area)                      |
{                                                  |
    cout << "Area: ";                              |
    cout << area;                                  |<- Definition
    cout << endl;                                  |
}                                                  |

The first void function, getDimensions, has two out parameters. The whole purpose of the function is to get two values that represent the dimensions of a rectangle and return them to main. The main function doesn't really care where these values come from and, in fact, has no way of knowing whether they were assigned (as they are in this simple example), or read from the standard input stream, or read from some file. Because the parameters are out parameters, they must be passed by address. The ampersands in both the function prototype and the function definition indicate that the double type parameters are to be passed by address rather than by value. Notice that the ampersand is not used in the function invocation in main.

The second void function, displayArea, has a single in parameter. The purpose of this function is to display an area. Consequently it needs an area to perform its task. Thus the parameter is an in parameter and is passed by value. Notice in the invocation of this function, the value passed to the function is the value returned by the areaOfRectangle function.

You can trace the execution of this program in the box below. Click "Next" to execute the next statement, click "Prev" to back up to the previous statement, and click "Reset" to start all over again.

The graphic below illustrates the order in which the instructions are executed by this program. Notice the execution of the statements in main are temporarily halted whenever a function is invoked. Once a function terminates, main resumes control just after the point at which the function invocation occurred.