Chapter 6. Creating Functions

In This Chapter

  • Writing functions

  • Passing data to functions

  • Naming functions with different arguments

  • Creating function prototypes

  • Working with include files

The programs developed in prior chapters have been small enough that they can be easily read as a single unit. Larger, real-world programs are often many thousands if not millions of lines long. Developers need to break up these monster programs into smaller chunks that are easier to conceive, describe, develop, and maintain.

C++ allows programmers to divide their code into exactly such chunks known as functions. A function is a small block of code that can be executed as a single entity. This allows the programmer to divide her program into a number of such entities, each solving some well-defined subset of the problem of the overall program. Functions are themselves broken up into smaller, more detailed functions in a pyramid of ever smaller, more detailed solutions that make up the complete program.

This divide-and-conquer approach reduces the complexity of creating a working program of significant size to something achievable by a mere mortal.

Writing and Using a Function

Functions are best understood by example. This section starts with the example program FunctionDemo, which simplifies the NestedDemo program I discussed in Chapter 5 by defining a function to contain part of the logic. Then this section explains how the function is defined and how it is invoked, using FunctionDemo as a pattern for understanding both the problem and the solution.

The NestedDemo program in Chapter 5 contains at least three parts that can be easily separated both in your mind and in fact:

  • An explanation to the operator as to how data is to be entered

  • An inner loop that sums up a single sequence of numbers

  • An outer loop that repeatedly invokes the inner loop until the accumulated value is 0

Separating the program along these lines allows the programmer to concentrate on each piece of the program separately. The following FunctionDemo program shows how NestedDemo can be broken up by creating the functions displayExplanation() and sumSequence():

// FunctionDemo - demonstrate the use of functions
//                by breaking the inner loop of the
//                NestedDemo program off into its own
//                function
#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

// displayExplanation - prompt the user as to the rules
//                      of the game
void displayExplanation(void)
{
    cout << "This program sums multiple series
"
         << "of numbers. Terminate each sequence
"
         << "by entering a negative number.
"
         << "Terminate the series by entering an
"
         << "empty sequence.
"
         << endl;
    return;
}

// sumSequence - add a sequence of numbers entered from
//               the keyboard until the user enters a
//               negative number.
//               return - the summation of numbers entered
int sumSequence(void)
{
    // loop forever
    int accumulator = 0;
    for(;;)
    {
         // fetch another number
         int nValue = 0;
         cout << "Enter next number: ";
         cin  >> nValue;
// if it's
         if (nValue < 0)
         {
             // ...then exit from the loop
             break;
         }

         // ...otherwise add the number to the
         // accumulator
         accumulator += nValue;
     }

    // return the accumulated value
    return accumulator;
}

int main(int nNumberofArgs, char* pszArgs[])
{
    // display prompt to the user
    displayExplanation();

    // accumulate sequences of numbers...
    for(;;)
    {
        // sum a sequence of numbers entered from
        // the keyboard
        cout <<, "Enter next sequence" << endl;
        int accumulatedValue = sumSequence();

        // terminate the loop if sumSequence() returns
        // a zero
        if (accumulatedValue == 0)
        {
            break;
        }

        // now output the accumulated result
        cout << "The total is "
             << accumulatedValue
             << "
"
             << endl;
    }

    cout << "Thank you" << endl;
    // wait until user is ready before terminating program
    // to allow the user to see the program results
    system("PAUSE");
    return 0;
}

Defining our first function

The statement void displayExplanation(void) is known as a function declaration — it introduces the function definition that immediately follows. A function declaration always starts with the name of the function preceded by the type of value the function returns and followed by a pair of open and closed parentheses containing any arguments to the function.

The return type void means that displayExplanation() does not return a value. The void within the argument list means that it doesn't take any arguments either. (We'll get to what that means very soon.) The body of the function is contained in the braces immediately following the function declaration.

Tip

Function names are normally written as a multiword description with all the words rammed together. I start function names with lowercase but capitalize all intermediate words. Function names almost always appear followed by an open and close parenthesis pair.

A function doesn't do anything until it is invoked. Our program starts executing with the first line in main() just like always. The first non-comment line in main() is the call to displayExplanation():

displayExplanation();

This passes program control to the first line in the displayExplanation() function. The computer continues to execute there until it reaches the return statement at the end of displayExplanation().

Defining the sumSequence() function

The declaration int sumSequence(void) begins the definition of the sumSequence() function. This declaration says that the function does not expect any arguments but returns a value of type int to the caller. The body of this function contains the same code previously found in the inner loop of the NestedDemo example.

The sumSequence() function also contains a return statement to exit the program. This return is not optional since it contains the value to be returned, accumulator. The type of value returned must match the type of the function in the declaration, in this case int.

Calling the function sumSequence()

Return back to the main() function in FunctionDemo again. This section of code looks similar to the outer loop in NestedDemo.

The main difference is the expression accumulatedValue = sumSequence(); that appears where the inner loop would have been. The sumSequence() statement invokes the function of that name. The value returned by the function is stored in the variable accumulatedValue. Then this value is displayed. The main program continues to loop until sumSequence() returns a sum of 0, which indicates that the user has finished calculating sums.

Divide and conquer

The FunctionDemo program has split the outer loop in main() from the inner loop into a function sumSequence() and created a displayExplanation() to get things kicked off. This division wasn't arbitrary: Both functions in FunctionDemo perform a logically separate operation.

Tip

A good function is easy to describe. You shouldn't have to use more than a single sentence, with a minimum of such words as and, or, unless, or but. For example, here's a simple, straightforward definition: "The function sumSequence accumulates a sequence of integer values entered by the user." This definition is concise and clear. It's a world away from the NestedDemo program description: "The program explains to the user how the program works AND then sums a sequence of positive values AND generates an error if the user enters a negative number AND displays the sum AND starts over again until the user enters a zero-length sum."

The output of a sample run of this program appears identical to that generated by the NestedDemo program.

Understanding the Details of Functions

Functions are so fundamental to creating C++ programs that getting a handle on the details of defining, creating, and testing them is critical. Armed with the example FunctionDemo program, consider the following definition of function: A function is a logically separated block of C++ code.

The function construct has the following form:

<return type> name(<arguments to the function>)
{
    // ...
    return <expression>;
}

The arguments to a function are values that can be passed to the function to be used as input information. The return value is a value that the function returns. For example, in the call to the function square(10), the value 10 is an argument to the function square(). The returned value is 100 (if it's not, this is one poorly named function).

Both the arguments and the return value are optional. If either is absent, the keyword void is used instead. That is, if a function has a void argument list, the function does not take any arguments when called (this was the case with the FunctionDemo program). If the return type is void, the function does not return a value to the caller.

Tip

The default argument type to a function is void, meaning that it takes no arguments. A function int fn(void) may be declared as int fn().

Understanding simple functions

The simple function sumSequence() returns an integer value that it calculates. Functions may return any of the intrinsic variable types described in Chapter 2. For example, a function might return a double or a char. If a function returns no value, the return type of the function is labeled void.

Tip

A function may be labeled by its return type — for example, a function that returns an int is often known as an integer function. A function that returns no value is known as a void function.

For example, the following void function performs an operation but returns no value:

void echoSquare()
{
    int value;
    cout << "Enter a value:";
    cin >> value;
    cout << "
 The square is:" << (value * value) <<
          "
";
    return;
}

Control begins at the open brace and continues through to the return statement. The return statement in a void function is not followed by a value. The return statement in a void function is optional. If it isn't present, execution returns to the calling function when control encounters the close brace.

Understanding functions with arguments

Functions without arguments are of limited use because the communication from such functions is one-way — through the return value. Two-way communication is through function arguments.

Functions with arguments

A function argument is a variable whose value is passed to the calling function during the call operation. The following SquareDemo example program defines and uses a function square() that returns the square of a double-precision float passed to it:

// SquareDemo - demonstrate the use of a function
//              which processes arguments

#include <cstdio>
#include <cstdlib>
#include <iostream>
using namespace std;

// square - returns the square of its argument
//          doubleVar - the value to be squared
//          returns - square of doubleVar
double square(double doubleVar)
{
    return doubleVar * doubleVar;
}

// displayExplanation - prompt the user as to the rules
//                      of the game
void displayExplanation(void)
{
    cout << "This program sums the square of multiple
"
         << "series of numbers. Terminate each sequence
"
         << "by entering a negative number.
"
         << "Terminate the series by entering an
"
         << "empty sequence.
"
         << endl;
    return;
}
// sumSquareSequence - accumulate the square of the number
//                entered at the keyboard into a sequence
//                until the user enters a negative number
double sumSquareSequence(void)
{
    // loop forever
    double accumulator = 0.0;
    for(;;)
    {
        // fetch another number
        double dValue = 0;
        cout << "Enter next number: ";
        cin  >> dValue;

        // if it's

        if (dValue < 0)
        {
            // ...then exit from the loop
            break;
        }

        // ...otherwise calculate the square
        double value = square(dValue);

        // now add the square to the
        // accumulator
        accumulator += value;
     }

    // return the accumulated value
    return accumulator;
}

int main(int nNumberofArgs, char* pszArgs[])
{
    displayExplanation();

    // Continue to accumulate numbers...
    for(;;)
    {
        // sum a sequence of numbers entered from
        // the keyboard
        cout << "Enter next sequence" << endl;
        double accumulatedValue = sumSquareSequence();

        // terminate if the sequence is zero or negative
        if (accumulatedValue <= 0.0)
{
            break;
         }

        // now output the accumulated result
        cout << "
The total of the values squared is "
             << accumulatedValue
             << "
"
             << endl;
    }

    cout << "Thank you" << endl;

    // wait until user is ready before terminating program
    // to allow the user to see the program results
    system("PAUSE");
    return 0;
}

This is essentially the same FunctionDemo program, except that the sumSquareSequence() function accumulates the square of the values entered and returns them as a double rather than an int. The function square() returns the value of its one argument multiplied by itself. The change to the sumSequence() function is simple: Rather than accumulate the value entered, the function now accumulates the result returned from square().

Functions with multiple arguments

Functions may have multiple arguments that are separated by commas. Thus, the following function returns the product of its two arguments:

int product(int arg1, int arg2)
{
    return arg1 * arg2;
}

main() exposed

The "keyword" main() from our standard program template is nothing more than a function — albeit a function with strange arguments but a function nonetheless.

When C++ builds a program from source code, it adds some boilerplate code that executes before your program ever starts. (You can't see this code without digging into the bowels of the C++ library functions.) This code sets up the environment in which your program operates. For example, this boilerplate code opens the default input and output channels cin and cout.

After the environment has been established, the C++ boilerplate code calls the function main(), thereby beginning execution of your code. When your program finishes, it exits from main(). This enables the C++ boilerplate to clean up a few things before turning control over to the operating system that kills the program.

The arguments to main() are complicated — we'll review those later. The int returned from main() is an error indicator. The program returns a 0 if the program terminates normally. Any other value can be used to indicate the nature of the error that caused the program to quit.

Overloading Function Names

C++ must have a way of telling functions apart. Thus, two functions cannot share the same name and argument list, known as the extended name or the signature. The following extended function names are all different and can reside in the same program:

void someFunction(void)
{
    // ....perform some function
}
void someFunction(int n)
{
    // ...perform some different function
}
void someFunction(double d)
{
    // ...perform some very different function
}
void someFunction(int n1, int n2)
{
    // ....do something different yet
}

C++ knows that the functions someFunction(void), someFunction(int), someFunction(double), and someFunction(int, int) are not the same.

Tip

This multiple use of names is known as function overloading.

Programmers often refer to functions by their shorthand name, which is the name of the function without its arguments, such as someFunction(), in the same way that I have the shorthand name Stephen (actually, my nickname is Randy, but work with me on this one).

Here's a typical application that uses overloaded functions with unique extended names:

int intVariable1, intVariable2;
double doubleVariable;

// functions are distinguished by the type of
// the argument passed
someFunction();               // calls someFunction(void)
someFunction(intVariable1);  // calls someFunction(int)
someFunction(doubleVariable);// calls someFunction(double)
someFunction(intVariable1, intVariable2); // calls
                             // someFunction(int, int)

// this works for constants as well
someFunction(1);           // calls someFunction(int)
someFunction(1.0);         // calls someFunction(double)
someFunction(1, 2);        // calls someFunction(int, int)

In each case, the type of the arguments matches the extended names of the three functions.

Warning

The return type is not part of the extended name of the function. The following two functions have the same name, so they can't be part of the same program:

int someFunction(int n);    // full name of the function
                            // is someFunction(int)
double someFunction(int n); // same name
long l = someFunction(10);  // call which function?

Here C++ does not know whether to convert the value returned from the double version of someFunction() to a long or promote the value returned from int version.

Defining Function Prototypes

A function must be declared before it can be used. That's so C++ can compare the call against the definition to make sure that any necessary conversions are performed. However, a function does not have to be defined when it is first declared. A function may be defined anywhere in the module. (A module is another name for a C++ source file.)

Consider the following code snippet:

int main(int nNumberofArgs, char* pszArgs[])
{
    someFunc(1, 2);
}
int someFunc(double dArg1, int nArg2)
{
    // ...do something
}

main() doesn't know the proper argument types of the function someFunc() at the time of the call. C++ might surmise from the call that the full function definition is someFunc(int, int) and that its return type is void; however, the definition of the function that appears immediately after main() shows that the programmer wants the first argument converted to a floating point and that the function does actually return a value.

I know, I know — C++ could be less lazy and look ahead to determine the extended name of someFunc() on its own, but it doesn't. What is needed is some way to inform main() of the full name of someFunc() before it is used. This is handled by what we call a function prototype.

A prototype declaration appears the same as a function with no body. In use, a prototype declaration looks like this:

int someFunc(double, int);
int main(int nNumberofArgs, char* pszArgs[])
{
    someFunc(1, 2);
}
int someFunc(double dArg1, int nArg2)
{
    // ...do something
}

The prototype declaration tells the world (at least that part of the world after the declaration) that the extended name for someFunc() is someFunction(double, int). The call in main() now knows to cast the 1 to a double before making the call. In addition, main() knows that someFunc() returns an int value to the caller.

It is common practice to include function prototypes for every function in a module either at the beginning of the module or, more often, in a separate file that can be included within other modules at compile time. That's the function of the include statements that appear at the beginning of the Official C++ For Dummies program template:

#include <cstdio>
#include <cstdlib>
#include <iostream>

These three files cstdio, cstdlib and iostream include prototype definitions for the common system functions that we've been using, such as system("PAUSE"). The contents of these files are inserted at the point of the #include statement by the compiler as part of its normal duties.

Chapter 10 is dedicated to include files and other so-called preprocessor commands.

Variable Storage Types

Variables are also assigned a storage type depending on where and how they are defined in the function as shown in the following example:

int globalVariable;
void fn()
{
    int localVariable;
    static int staticVariable = 1;
}

Variables declared within a function like localVariable are said to be local. The variable localVariable doesn't exist until execution passes through its declaration within the function fn(). localVariable ceases to exist when the function returns. Upon return, whatever value that is stored in localVariable is lost. In addition, only fn() has access to localVariable — other functions cannot reach into the function to access it.

By comparison, the variable globalVariable is created when the program begins execution and exists as long as the program is running. All functions have access to globalVariable all the time.

The keyword static can be used to create a sort of mishling — something between a global and local variable. The static variable staticVariable is created when execution reaches the declaration the first time that function fn() is called, just like a local variable. The static variable is not destroyed when program execution returns from the function, however. Instead, it retains its value from one call to the next. If fn() assigns a value to staticVariable once, it'll still be there the next time fn() is called. The initialization portion of the declaration is ignored every subsequent time execution passes through.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.139.86.18