Creating Functions - Becoming a Functional C++Programmer - C++ For Dummies (2014)

C++ For Dummies (2014)

Part II

Becoming a Functional C++Programmer

image

image Visit www.dummies.com/extras/cplusplus for great Dummies content online.

In this part…

· Writing functions

· Using arrays

· Passing pointers

· Defining constants and macros

· Visit www.dummies.com/extras/cplusplus for great Dummies content online

Chapter 6

Creating Functions

In This Chapter

arrow Writing functions

arrow Passing data to functions

arrow Naming functions with different arguments

arrow Creating function prototypes

arrow Passing by value versus passing by reference

arrow Providing default values for arguments

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 just 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 that implements some well-defined subset 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\n"
<< "of numbers. Terminate each sequence\n"
<< "by entering a negative number.\n"
<< "Terminate the series by entering an\n"
<< "empty sequence.\n"
<< 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 negative...
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 << endl;
}

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
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.

image 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() or until control reaches the closed brace at the end of the function.

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 of the expression sumSequence() is the value returned by the function. This value is stored in the variable accumulatedValue and then 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.

image 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, until 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 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.

image 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 labeledvoid.

image 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 << "\nThe square is:" << (value * value) << "\n";
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\n"
<< "series of numbers. Terminate each sequence\n"
<< "by entering a negative number.\n"
<< "Terminate the series by entering an\n"
<< "empty sequence.\n"
<< 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 negative...

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 << "\nThe total of the values squared is "
<< accumulatedValue << endl << endl;
}

// wait until user is ready before terminating program
// to allow the user to see the program results
cout << "Press Enter to continue..." << endl;
cin.ignore(10, '\n');
cin.get();
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 a status indicator. The program returns a 0 if the program terminates normally. Any other value can be used to indicate an error — the actual value returned indicates 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.

image 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). But if there's any doubt, I can be differentiated from other Stephens by including my family name. In the same way, overloaded functions can be differentiated by their argument lists.

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.

image 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 declaration 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 aftermain() 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 prototypedeclaration.

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 declarations for the common system functions that we’ve been using, such as cout << “string”. 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.

Defaulting Arguments

You can provide default values for arguments in your function declaration. Consider the following simple example:

// isLegal - return true if the age is greater
// than or equal to the minimum age
// which defaults to 21
bool isLegal(int age, int minAge = 21)
{
return age >= minAge;
}

This function returns a true if the first argument passed, age, is greater than the second argument, minAge, and the second argument defaults to 21 if you don't say otherwise in the function call. Thus, the following calls are both legal:

legal = isLegal(age); // same as isLegal(age, 21)
if (inLouisiana())
{
legal = isLegal(age, 18);
}

The call isLegal(age) is completely equivalent to isLegal(age, 21). C++ just provides the default argument for you. The call to isLegal(age, 18) ignores the default value.

image Normally the defaults are provided in the prototype declarations.

You can default more than one argument, but defaults must be defined from right to left and filled in from left to right:

// the following is legal
bool isWorkingAge(int age, int minAge=18, int maxAge=65);

// check if the worker is between 18 and 65
legal = isWorkingAge(age);

// check if worker is between 21 and 65
legal = isWorkingAge(age, 21);

// check if work is between 21 and 60
legal = isWorkingAge(age, 21, 60);

// the following does NOT check if the worker is
// between 18 and 60
legal = isWorkingAge(age, 60);

The first call uses the default values for both the minimum and maximum age (18 and 65, respectively). The second call uses the default maximum age of 65 but supplies a different minimum age of 21. The third call provides both an explicit minimum and maximum age.

image The last call does not check whether age is between 18 and 60 as you might expect. In this case, the call is made with a minimum age of 60 and a maximum age of 65.

image Default arguments can sometimes confuse C++ when combined with function overloading. For example, the following is not legal:

bool isLegal(int age);
bool isLegal(int age, int minAge = 21); // not allowed

The problem is that if you called isLegal(10), C++ wouldn't know which one of the two functions to call: the first function with just one argument or the second function with the second argument defaulted.

Passing by Value and Passing by Reference

C++ normally passes arguments to functions by value. That is, if I call a function fn(n), it is the value of n that gets passed to the function. This allows me to make calls like the following:

fn(a + b); // pass the value of a + b

What gets passed in this snippet is the result of the expression a + b.

This has a perhaps surprising side effect demonstrated by the following snippet:

void multiplyByTwo(int m)
{
m *= 2;
}

int n = 1;
multiplyByTwo(n);

cout << "n = " << n << endl;

You may be surprised to find out that this example prints out n = 1.

Let's go through the example one step at a time:

1. The main program declares the variable n and initializes it to 1.

2. The program then passes the value of n (1) to the function multiplyByTwo() and calls it m.

3. The function multiplies the value passed it by two and stores the result in the local variable

4. multiplyByTwo() discards m upon returning.

5. The main program displays the unchanged value of n (1).

image This is called pass by value — the alternative is called pass by reference.

You can tell C++ that you want to pass not the value of a variable but a reference to a variable by adding an ampersand (&) to the type, as in the following snippet:

void multiplyByTwo(int& m) // referential argument
{
m *= 2;
}

int n = 1;
multiplyByTwo(n);

cout << "n = " << n << endl;

This example does the following:

1. The main program declares the variable n and initializes it to 1.

2. The program then passes a reference to n to the function multiplyByTwo() which calls that reference m.

3. The function multiplies by two the variable referenced by m and saves the results back into the variable referenced by m (in other words, the variable n).

4. The main program displays the changed value of n (2).

image Arrays (which I introduce in the next chapter) are always passed by reference for reasons that I will explain in Chapter 8.

I will have a lot more to say about reference arguments in Chapter 8.

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 inlocalVariable 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.