Pages

Saturday, April 23, 2016

C++ - Creating Functions

C++ - Creating Functions




In This Tutorial
• Writing functions
• Passing data to functions
• Naming functions with different arguments
• Creating function prototypes
• Working with include files


real-world programs can be many thousands (or millions!) of lines long. Developers need to break up these monster programs into smaller chunks that are easier to conceive, develop, and maintain. C++ allows programmers to divide their code into exactly such chunks (known as functions). As long as a function has a simple description and a well-defined interface to the outside world, it can be written and debugged without worrying about the code that surrounds it. This divide-and-conquer approach reduces the difficulty of creating a working program of significant size. This is a simple form of encapsulation

 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 Another Tutorial 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 contains an inner loop (which accumulates a sequence of numbers) surrounded by an outer loop (which repeats the process until the user quits). Separating the two loops would simplify the program by allowing the reader to concentrate on each loop independently. 

The following FunctionDemo program shows how NestedDemo can be simplified by creating the function sumSequence()

Tip:
Function names are normally written with a set of parentheses immediately following the term, like this: 


// 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;
// 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 value = 0;
cout << "Enter next number: ";
cin >> value;
// if it’s negative...
if (value < 0)
{
// ...then exit from the loop
break;
}
// ...otherwise add the number to the
// accumulator
accumulator= accumulator + value;
}
// return the accumulated value
return accumulator;
}
int main(int nNumberofArgs, char* pszArgs[])
{ 
cout << "This program sums multiple series\n"
<< "of numbers. Terminate each sequence\n"
<< "by entering a negative number.\n"
<< "Terminate the series by entering two\n"
<< "negative numbers in a row\n"
<< endl;
// accumulate sequences of numbers...
int accumulatedValue;
for(;;)
{
// sum a sequence of numbers entered from
// the keyboard
cout << "Enter next sequence" << endl;
accumulatedValue = sumSequence();
// terminate the loop if sumSequence() returns
// a zero
if (accumulatedValue == 0)
{
break;
}
// now output the accumulated result
cout << "The total is "
<< accumulatedValue
<< "\n"
<< 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 the sumSequence() function
The statement int sumSequence(void) begins the definition of the sumSequence() function. The block of code contained in the braces is the function body. The function sumSequence() accumulates a sequence of values entered from the keyboard. This code section is identical to that found in the inner loop of NestedDemo.

Calling the function sumSequence()
Let’s concentrate on the main program contained in the braces following main(). 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. A 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 zero, 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(). This division wasn’t arbitrary: sumSequence() performs a separate role — worth considering by itself — apart from the control features within FunctionDemo

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 ContinueDemo program description:

"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 two zero-length sums."

The output of a sample run of this program appears much like that generated by the NestedDemo program, as follows: 


--------------------------------------------------------------------------------------------------------------
This program sums multiple series
of numbers. Terminate each sequence 
by entering a negative number. 
Terminate the series by entering two
negative numbers in a row 

Enter next sequence
Enter next number: 1
Enter next number: 2  
Enter next number: 3
Enter next number: -1
The total is 6

Enter next sequence
Enter next number: 1
Enter next number: 2
Enter next number: -1
The total is 3 

Enter next sequence
Enter next number: -1
Thank you
Press any key to continue . . . 
--------------------------------------------------------------------------------------------------------------


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.

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

In the example FunctionDemo program, the name of the function is sumSequence(), the return type is int, and no arguments exist. 

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()

The function construct made it possible for me to write two distinct parts of the FunctionDemo program separately. I concentrated on creating the sum of a sequence of numbers when writing the sumSequence() function. I didn’t think about other code that may call the function. 

Similarly, when writing main(), I concentrated on handling the summation returned by sumSequence() while thinking only of what the function did  not how it worked. 


Understanding simple functions
The simple function sumSequence() returns an integer value that it calculates. Functions may return any of the regular types of variables. 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 << “\n The 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. 

Tip:
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
Simple functions 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;
}
// sumSequence - accumulate the square of the number
// entered at the keyboard into a sequence
// until the user enters a negative number
double sumSequence(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= accumulator + value;
}
// return the accumulated value
return accumulator;
}
int main(int nNumberofArgs, char* pszArgs[])
{
cout << "This program sums multiple series\n"
<< "of numbers squared. Terminate each sequence\n"
<< "by entering a negative number.\n"
<< "Terminate the series by entering two\n"
<< "negative numbers in a row\n"
<< endl;
// Continue to accumulate numbers...
double accumulatedValue;
for(;;)
{
// sum a sequence of numbers entered from
// the keyboard
cout << "Enter next sequence" << endl;
accumulatedValue = sumSequence();
// 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;
}
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 the same FunctionDemo() program, except that SquareDemo() accumulates the square of the values entered. 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 a program is built, C++ 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. 

Overloading Function Names
C++ allows the programmer to assign the same name to two or more functions. This multiple use of names is known as overloading functions.

In general, two functions in a single program cannot share the same name. If they did, C++ would have no way to distinguish them. Note, however, that the name of the function includes the number and type of its arguments — but does not include its return argument. Thus the following are not the same functions: 

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++ still knows that the functions someFunction(void)someFunction(int)someFunction(double), and someFunction(int, int) are not the same. Like so many things that deal with computers, this has an analogy in the human world.  

void as an argument type is optional. sumFunction(void) and sumFunction() are the same function. A function has a shorthand name, such as someFunction(), in same way that I have the shorthand name Stephen (actually, my nickname is Randy, but work with me on this one). If there aren’t any other Stephens around, people can talk about Stephen behind his back. If, however, there are other Stephens, no matter how handsome they might be, people have to use their full names — in my case, Stephen Davis. As long as we use the entire name, no one gets confused — however many Stephens might be milling around. Similarly, the full name for one of the someFunctions() is someFunction(int). As long as this full name is unique, no confusion occurs. 

The analogies between the computer world (wherever that is) and the human world are hardly surprising because humans build computers. (I wonder . . . if dogs had built computers, would the standard unit of memory be a gnaw instead of a byte? Would requests group in packs instead of queues?)

Here’s a typical application that uses overloaded functions with unique full names: 


int intVariable1, intVariable2; // equivalent to
// int Variable1;
// int Variable2;
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 full name of the three functions.

Warning:
The return type is not part of the extended name (which is also known as the function signature) 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

Remember:
You’re allowed to mix variable types as long as the source variable type is more restrictive than the target type.
Thus an int can be promoted to a double. The following is acceptable:

int someFunction(int n);
double d = someFunction(10); // promote returned value



The int returned by someFunction() is promoted into a double. Thus the
following would be confusing: 

int someFunction(int n);
double someFunction(int n);
double d = someFunction(10);// promote returned int?
// or use returned double as is

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

Defining Function Prototypes
The programmer may provide the remainder of a C++ source file, or module, the extended name (the name and functions) during the definition of the function.

The target functions sumSequence() and square() — appearing earlier in this Tutorial— are both defined in the code that appears before the actual call. This doesn’t have to be the case: A function may be defined anywhere in the module. (A module is another name for a C++ source file.) 

However, something has to tell the calling function the full name of the function to be called. Consider the following code snippet:  

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


main() doesn’t know the full name of the function someFunc() at the time of
the call. It may surmise from the arguments that the name is someFunc(int, int)
and that its return type is void — but as you can see, this is incorrect.

I know, I know — C++ could be less lazy and look ahead to determine the full name of someFunc()s on its own, but it doesn’t. It’s like my crummy car; it gets me there, and I’ve learned to live with it. 

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 arg1, int arg2)
{
// ...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
the value returned by someFunc() is an int. 

Variable Storage Types
Function variables are stored in three different places. Variables declared within a function are said to be local. In the following example, the variable localVariable is local to the function fn():


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


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 static variable staticVariable is a sort of mix between a local and a global variable. The variable staticVariable is created when execution first reaches the declaration — at roughly when the function fn() is called. The variable is not destroyed when program execution returns from the function. If fn() assigns a value to staticVariable once, it’ll still be there the next time fn() is called. The declaration is ignored every subsequent time execution passes through. 

In case anyone asks, there is a fourth type, auto, but today it has the same meaning as local, so you rarely (if ever) see that declaration type anymore. So whoever asked you about it is probably just being a show off (or showing his age). 


Including Include Files
It’s common to place function prototypes in a separate file (called an include file) that the programmer can then include in her C++ source files. Doing so sets the stage for a C++ preprocessor program (which runs before the actual compiler takes over) to insert the contents of a file such as filename, at the point of a statement such as #include "filename"

A definition for a typical math include file looks like this:


// math include file:
// provide prototypes for functions that might be useful
// in more than one program
// abs - return the absolute value of the argument
double abs(double d);
// square - return the square of the argument
double square(double d);
A program uses the math include file like this: 
// MyProgram -
#include “math”
using namespace std;
// my code continues here


The #include directive says, in effect, Replace this directive with the contents
of the math file. 

The #include directive doesn’t have the format of a C++ statement because it’s interpreted by a separate interpreter that executes before the C++ compiler starts doing its thing. In particular, the # must be in column one and an end-of-line terminates the include statement. The actual file name must be enclosed in either quotes or brackets. Brackets are used for C++ library functions. Use the quotes for any includes that you create. 

The C++ environment provides include files such as cstdio and iostream. In fact, it’s iostream that contains the prototype for the setf() function used in Another Tutorial to set output to hex mode. 

Tip:
For years, programmers followed the custom of using the extension .h to designate include files. In more recent years, the C++ ISO standard removed the .h extension from standard include files. (For example, the include file cstdio was previously known as stdio.h.) Many programmers still stubbornly cling to the “.h gold standard” for their own programs. (What’s in a name? Evidence that even high-tech folks have traditions.) 

0 comments:

Post a Comment