CS 111 Assignment 6
Tutorial on programmer-defined functions


  1. Raising a number to an integer power.   Compile powerFunctionDemo.cpp and run it. This program prompts the user for a base and an exponent, then raises the base to the exponent power, then displays both an echo of the input and a result.

    The program defines two functions, the main function and a power function which raises a number to a specified power.

    Consider first the question of how to raise a floating-point base to an integer exponent power, as is done in the power function of powerFunctionDemo.cpp. How can we calculate the power?

    If the exponent is a non-negative integer, we can begin with the number 1 and multiply it by the base repeatedly, exponent times. In our power function, base is raised to a non-negative integer exponent power as follows:

       double product = 1;
       for ( int i = 0; i < exponent; i++ )
          product = product * base;
    

    Note that product was initialized to 1 just before the loop, which iterates exponent times.

    But what about negative exponents? Note that any number x raised to a negative power is equal to 1/x raised to the corresponding positive power. So, in our power function, if exponent is negative, we first replace exponent by the corresponding positive exponent and base by its reciprocal:

       if ( exponent < 0 )  {
          exponent = 0 - exponent;
          base = 1 / base;
       }  // if exponent < 0
    

    The new value of base can then be raised to a positive power in the same manner that our power function normally uses for non-negative exponents.


  2. When a function is executed.   When any program is executed, it starts at the top of the main function. The main function is executed one statement at a time, top to bottom, until a function call is encountered, such as the call to the power function in powerFunctionDemo.cpp:

       double result = power(enteredBase, enteredExponent);
    

    At this point, the main function temporarily stops executing and turns over control to the power function. Then the power function is executed, top to bottom. Then, when the power function is finished, execution of the main function resumes where it left off.

    Look now at the definition of the power function in powerFunctionDemo:

    double power(float base, int exponent)
    {
       // If exponent is negative, do necessary conversions
       // to raise reciprocal of base to a positive power:
       if ( exponent < 0 )
       {
          exponent = 0 - exponent;
          base = 1 / base;
       }  // if exponent < 0
    
       // Raise base to a non-negative power:
       double product = 1;
       for ( int i = 0; i < exponent; i++ )
          product = product * base;
       return product;
    }  // function power
    

    The line at the top, before the opening curly brace:

    double power(float base, int exponent)
    

    is called the function heading. The rest of the function definition, including the curly braces and everything in between, is called the function body.

    Within the body of the power function, observe that the variables base and exponent are used in ways which assume they already have values. For example, the very first line of the function body contains a condition testing the value of exponent:

       if ( exponent < 0 )
    

    Such a test would be pointless unless exponent has already been given a (non-garbage) value. So, the above line of code assumes that exponent has, somehow, already gotten a value. But how and where did exponent get its value, before even the very first line of the function body?

    Earlier in this course, you were taught that every variable must first be (1) declared and (2) given a value, before the variable can be used in any way which assumes it already has a value. In powerFunctionDemo.cpp, where are the variables base and exponent declared, and when and where do they receive their first value?

    The variables base and exponent are declared between the parentheses of the function heading:

    double power(float base, int exponent)
    

    Variables declared between the parentheses of a function heading are known as formal parameters. Most of the other variables you've seen so far have been declared, not between the parentheses of a function heading, but, rather, inside the function body. Variables declared inside a function body are called local variables.

    An important difference between parameters and local variables is that parameters can be given their values automatically when the function is called.

    In the main function of our program powerFunctionDemo.cpp, the power function is called as follows:

       double result = power(enteredBase, enteredExponent);
    

    Here, the main function's local variables enteredBase and enteredExponent are passed as arguments to the power function. Note that enteredBase and enteredExponent have previously been declared and given values within the main function, before the call to the power function.

    When the above call to the power function is executed, one of the first things that happens is that the formal parameters are automatically assigned the values of the corresponding arguments. In other words, an automatic equivalent of the following initializations:

       float base = enteredBase;
       int exponent = enteredExponent;
    

    This occurs before the body of the power function itself is executed in response to the call. Thus, when the first line of the function body is executed, base and exponent have already been given their values.

    Observe also that the power function does not do any output. There are no cout statements, nor any statements to output anything to a file. Yet, somehow, the program does output the value computed by the power function. In powerFunctionDemo.cpp, the program's output is done in the main function. So, the power function must somehow communicate the computed value of the power to the main function.

    Look again at the call to the power function inside the body of the main function:

       double result = power(enteredBase, enteredExponent);
    

    Observe that the call to power is on the right-hand side of an assignment statement. The power function generates (returns) a value which, in the above assignment statement, is assigned to the variable result, one of the main function's local variables.

    Where does the returned value come from? A function's returned value is the value of the expression next to the word return in the return statement. In the power function, the returned value is the value of product, a local variable of the power function.

    (If a function has more than one return statement, then the returned value is the value of the expression in whichever return statement actually gets executed. If a function has multiple return statements, only one of them can get executed per call to the function, because the function quits when a return statement is executed.)


  3. Value-returning functions vs. void functions.   So far, most of the functions you have used have been value-returning functions. A call to a value-returning function is an expression with a value, and can be used any place where an expression with a value of a given data type is appropriate. Such places include: (1) on the right-hand side of an assignment statement, (2) as part of a larger expression (e.g. an arithmetic expression), and (3) as an argument to another function (provided that the argument corresponds to a value parameter, as explained later in this tutorial).

       double x = sin(10.5);
       double y = 4 + log(100.0) * 3;
       double z = exp( log(x) );
    

    In order for statements like the above to be valid, the function's return value must be of a data type appropriate to what is expected. For example, if a function returns a value of type double, it cannot be assigned to a variable of type int without explicit casting.

    A call to a value-returning function can be used EITHER (1) as part of a larger statement, as illustrated above, or (2) as a statement by itself. For example, in Assignment 6 you used the parseForbinInt function, which is defined in textUtilityC.cpp. Recall that parseForbinInt is a value-returning function which returns a value of type bool which is supposed to indicate whether the C-string that was passed to it as an argument was a valid representation of an int value. As you saw, the parseForbinInt function's error-checking aspect has not been developed yet, but, if you were, then you could use the function's return value as the condition of an if statement:

       int number;
       if ( ! parseForbinInt(argv[1], number) )
       {
          cout << "You must enter integers only." << endl;
          return 1;
       }  // if
    

    But a call to parseForbinInt can also be used as a statement by itself to read a C-string into a variable of some numeric data type, without any error checking:

       int number;
       parseForbinInt(argv[1], number);
    

    When a call to a value-returning function is used as a statement by itself, not as part of a larger statement, the returned value is ignored and forgotten about. In the above example, nothing is done with the value returned by parseForbinInt.

    Another example:   The getline function returns a value. However, in all examples of its use that we have seen so far, we did not make any use of the return value. Instead, we have just used calls to getline as statements by themselves, as follows:

       ifstream inputFile;
       inputFile.open("textDataFile.txt");    
    
       string line;
       getline(inputFile, line);
    

    (The getline function, which reads a line of text, is declared in header file <iostream>.)

    Not all functions return values. A void function does not return a value. A call to a void function is not an expression with a value. Hence, a call to a void function can be used ONLY as a statement by itself. A call to a void function is not an expression which can be used as part of a larger statement.

    The use of void functions is illustrated in asterisksRectangle4.cpp, which we will discuss later.


  4. Why we use functions.   To illustrate some of the reasons why we write programs with functions, let's now look at a program which ought to be divided into three functions, but which, instead, is written in the form of one long main function. Compile asterisksRectangle3.cpp and run it. This program displays a rectangle of asterisks and then pauses, prompting the user to press ENTER to continue. After the user presses ENTER, it displays another, larger rectangle of asterisks, and then pauses again. After the user presses ENTER again, a third rectangle of asterisks is displayed, also of a size different from the previous ones.

    Look now at the source code. This program begins with a nested loop similar to asterisksRectangle.cpp in the Assignemtn 5 files. Then the program is paused as follows:

       cout << endl << "Press ENTER to continue....";
       string line;
       getline(cin, line);
       cout << endl;
    

    The getline function, with cin as an argument, reads a line of text input by the user. (In this program, the line of text may be empty, i.e. the user does not need to type anything before pressing [Enter].) In order to input a line of text, the program must wait for the user to press [Enter]. Thus the call to getline pauses the program.

    The endl manipulator is used to print blank lines before and after "Press ENTER to continue....", separating that prompt from the rectangles of asterisks.

    Observe that the three rectangles of asterisks are generated by three separate pairs of nested for loops, which are identical except that they use different numbers. Likewise, the statements involved in pausing the program are written twice (except for the variable declaration).

    These redundancies can ve eliminated by re-writing the program with two additional functions besides the main function, as is done in asterisksRectangle4.cpp. One of these two additional functions draws a rectangle with specified dimensions, and the other function pauses the program.

    Compile asterisksRectangle4.cpp and run it. Observe that it behaves exactly like asterisksRectangle3.cpp. Look at the source code of asterisksRectangle4.cpp and observe that the main function is now much shorter and easier to read:

    int main()
    {
       printRectangle(5, 15);    // uses cout
       pause();                  // uses cin and cout
       printRectangle(7, 20);    // uses cout
       pause();                  // uses cin and cout
       printRectangle(10, 25);   // uses cout
    
       return 0;
    }  // function main
    

    When a program is run, the main function is executed from top to bottom. Each time a function call is encountered, the main function temporarily stops executing and waits for the called function to do its thing. Then, when the called function finishes executing, the main function continues executing until another function call is encountered. And so on, until the main funciton finishes.

    As we have seen, one of the reasons we write functions is to avoid writing redundant code. Functions enable us to write re-usable chunks of code.

    Another, even more important reason for writing functions is simply to divide a program up into manageable chunks that are much easier to debug than one long program would be. Large programs, including nearly all commercial software, would be impossible to debug if they were not organized into smaller chunks, such as functions.


  5. More about functions in general.   In our present example, the main function calls two functions, printRectangle and pause, both of which are defined below the main function.

    Above the headings of all the function definitions are comments explaining what each function does. Here, we are using multi-line comments that begin with "/*" and end with "*/", instead of the one-line comments we have always used previously, which begin with "//" and continue to the end of a line.

    Look again at one of the calls to printRectangle in the main function:

       printRectangle(5, 15);
    

    Expressions between the parentheses of a function call are called arguments, also known as actual parameters. Here, the arguments are numeric literals. But they could also be variables or more complex expressions, e.g. arithmetic expressions or calls to other value-returning functions. For example, one possibility would be:

       int x = 5;
       printRectangle(x, 3 + (int) sqrt(5));
    

    When an expression is used between the parentheses of a function call, we say that that expression has been passed as an argument to the function, or passed as a parameter to the function.

    Compare the calls to printRectangle with the heading of the printRectangle function definition:

    void printRectangle(int rows, int columns)
    

    In between the parentheses of the function definition heading are formal parameters, also known as just parameters. Each formal parameter specifies the data type that is allowed for the corresponding argument when the function is called.

    The printRectangle function takes two parameters specifying the dimensions of the rectangle: (1) the number of rows and (2) the number of columns (i.e. the number of asterisks per row).

    The pause function takes no parameters at all. When a function takes no parameters, its name is followed by an empty pair of parentheses, in both the function heading and all calls to the function. For example, to call the pause function:

       pause();
    

    Both the printRectangle and pause functions are defined below the main function. The two functions are also declared above the main function, using function prototypes:

    void printRectangle(int rows, int columns);
    void pause();
    

    Observe that the function prototypes are identical to the headings of the function definitions, except that the prototypes are followed by semicolons, whereas the function headings are not. Another difference between function definition headings and function prototypes is that, in prototypes, variable names for the parameters are optional. For example, the prototype for the printRectangle function could have been written as follows:

    void printRectangle(int, int);
    

    On the other hand, in function definition headings, variable names are required for the parameters.

    A multi-function program should be written with prototypes at the top for all functions except the main function, to ensure that the compiler will allow every function to be called by any of the other functions in the file. If a function is not declared, then the compiler will not be able to handle calls to the function anywhere in the file above the function definition. (The resulting error message will complain about an "implicit declaration.")


  6. Defining void functions.   Look again at the printRectangle and pause functions defined in asterisksRectangle4.cpp. Observe that they have the word void in the heading, instead of a data type, to indicate that they do not return values. Observe also that they do not end with return statements.

    A function which returns a value MUST end with a return statement -- as does the main function, for example. However, a void function does not need to end with a return statement.

    A return statement can still be used in a void function in order to make the function quit early, before the end of the function body. For example, in the printRectangle function, return statements are used at the end of error-checking blocks, to prevent the rest of the function from being executed in the event of erroneous values of the parameters:

    void printRectangle(int rows, int columns)
    {
       // Check the number of rows:
       if ( rows < 0 || rows > 22 )  {
          cout << "rows=" << rows
                    << ", must be in range [0, 22]." << endl;
          return;
       }  // if
    
       // Check the number of columns:
       if ( columns < 0 || columns > 40 )  {
          cout << "columns=" << columns
                    << ", must be in range [0, 40]." << endl;
          return;
       }  // if
    
       // Print the rectangle:
       for ( int i = 0; i < rows; i++ )  {
          for ( int j = 0; j < columns; j++ )
             cout << " *";
          cout << endl;
       }  // for i
    }  // function printRectangle
    

    Note also that the return statements consist of just the word "return," which is different from the return statement in the main function. When a function returns a value, any return statement in that function's definition must consist of the word "return" followed by an expression with a value. On the other hand, in a void function, the return statements, if any, consist of just the word "return."


  7. Examples of value-returning functions.   A function which returns a value behaves somewhat like a function in mathematics. Calls to such a function deliberately resemble the function notation you are accustomed to in math:

       y = f(x);
       z = g(x, y);
       w = h(3 + f(x), 2);
    

    That's why subprograms in C++ and some other programming languages are referred to as "functions." In Pascal, subprograms that return a value are called "functions," whereas subprograms that do not return a value are called "procedures." In C and C++, all subprograms are called "functions," whether or not they return a value. (In Java, all subprograms are called "methods," not functions.)

    Look again at powerFunctionDemo.cpp. Recall that this program defines two functions, the main function and a power function which raises a floating-point number to a specified inteer power. In the main function, the power function is called on the right-hand side of an assignment statement:

       double result = power(enteredBase, enteredExponent);
    

    Thus, the power function generates (returns) a value which, in the above statement, is assigned to the variable result.

    A function which returns a value must both (1) declare the type of the returned value in its heading, and (2) end with a return statement. For example, the power function definition has the following heading:

    double power(float base, int exponent)
    

    indicating that the function returns a value of type double. The body of the power function ends with the following statement:

          return product;
    

    In a function that returns a value, the return statement must consist of of the keyword return followed by an expression whose value is to be returned. In this example, the power function returns the value of the variable product, which was declared to be of type double.

    More generally, the returned value may be any expression with a value of the appropriate data type (or which converts automatically to the appropriate data type). The expression to the right of the word return may be a variable, a literal (as in the main function), or a more complex expression with a value, such as an arithmetic expression or a call to another function which returns a value.


  8. Formal parameters vs. local variables.   Look again at the function definition headings of both the printRectangle function in asterisksRectangle4.cpp:

    void printRectangle(int rows, int columns)
    

    and the power function in powerFunctionDemo.cpp:

    double power(float base, int exponent)
    

    In both headings, in between the parentheses are declarations of the formal parameters. Observe that the parameter declarations look like variable declarations. Indeed, they ARE variable declarations.

    So far, we have used two kinds of variables: (1) local variables, declared within the body of a function (i.e. within the curly braces of the function body), and (2) formal parameters, declared within the parentheses of a function heading.

    Both kinds of variables can be accessed only within the function in which they are declared. In addition, if a local variable is declared within a smaller block (e.g. a loop) within the function, it can be accessed only within the smaller block.

    Recall that a local variable must be given a value before it is used in any way which assumes it already has a value. (Otherwise, it contains an unpredictable value, known as garbage.)

    On the other hand, in the printRectangle function, note that the formal parameters rows and columns are NOT given values within the function. That's because they are automatically given values when the function is called. For example, when printRectangle is called in the main function as follows:

       printRectangle(5, 15);
    

    the formal parameters receive the values indicated by the arguments. The parameter rows receives the value 5, and the parameter columns receives the value 15. The parameters receive their values before the body of the function is executed. Thus, they do not need to be given values within the body of the function.


  9. Value parameters.   We will now consider two kinds of formal parameters: value parameters and reference parameters.

    In the examples we have looked at here, the printRectangle function in asterisksRectangle4.cpp and the power function in powerFunctionDemo.cpp, all the parameters have been value parameters. We will see some examples using reference parameters later.

    Before we explain what reference parameters are, let's first examine the behavior of value paremters in more detail. In asterisksRectangle4.cpp, when printRectangle is called in the main function as follows:

       printRectangle(5, 15);
    

    the formal parameters receive the values indicated by the arguments. When the function is called, the first thing that happens is an automatic equivalent of the following initialization statements:

       int rows = 5;
       int columns = 15;
    

    (The above initialization statements do not, themselves, occur in the program. The point here is that an equivalent of the above statements is automatically executed when the function is called.)

    In other words, memory locations are set aside for variables rows and columns, and the values 5 and 15 are put in those memory locations. This occurs as the first step when the function is called, before the body of the function is executed.

    Look again at the power function in powerFunctionDemo.cpp. Its heading declares the formal parameters base and exponent:

    double power(float base, int exponent)
    

    When the power function is called in the main function, as follows:

       double result = power(enteredBase, enteredExponent);
    

    then the first thing that happens is an automatic equivalent of:

       float base = enteredBase;
       int exponent = enteredExponent;
    

    In other words, memory locations are set aside for the paremeters base and exponent, and into them are copied the values of the main function's local variables enteredBase and enteredExponent, which are names of other memory locations.

    Run the program valueParemetersDemo.cpp again, using a negative exponent. For example, if you use the program to raise 2 to the -3 power, the output and interactive input will look like this:

    This program will raise a floating-point number to an integer power.
    Enter base (floating-point):>2
    Enter exponent (integer):>-3
    2 to the -3 power is 0.125.
    

    In the source code, look at the following statement in the main function, after the call to the power function:

       cout << enteredBase << " to the " << enteredExponent
                   << " power is " << result << "." << endl;
    

    Look at the line of output corresponding to the above statement. Observe that the variables enteredBase and enteredExponent still have their input values, the same values thay had before the call to the power function -- even though, WITHIN the power function, the values of base and exponent have been changed (in the case of a negative exponent). For example, when raising 2 to the -3 power, the if block at the beginning of the power function will change the value of exponent from -3 to 3, and will change the value of base from 2 to 0.5. Yet the values of enteredBase and enteredExponent remain 2 and -3, respectively.

    Why is this? Because base and enteredBase are two separate variables, i.e. names of two separate memory locations; and, likewise, exponent and enteredExponent are names of two separate memory locations. When the power function is called, variables base and exponent are created, i.e. memory locations are reserved for them, and the values of enteredBase and enteredExponent are copied into base and exponent, respectively. Later, when the values of base and exponent are changed, this has no effect on the values stored in the separate memory locations enteredBase and enteredExponent.

    In general, whenever a variable is passed as an argument to a function and the corresponding formal parameter is a value parameter, the argument variable's value cannot be changed by the function call.

    Thus, a value parameter is a means by which information can flow into the function from when the function is called, but a value parameter is NOT a means by which information can flow out of the function to anywhere else in the program. Conversely, the function's return value is a means by which information can flow out of the function, but not into it,

    In the call to the power function in the main function of powerFunctionDemo.cpp, the arguments to the function are variables. But an argument corresponding to a value parameter does not need to be a variable. For example, in the calls to the printRectangle function in the main function of asterisksRectangle4.cpp, the arguments are integer literals. An argument corresponding to a value parameter may be any expression with a value of an appropriate data type. The argument may be a variable, a literal, or a more complex expression, such as an arithmetic expression or another function call. When the function is called, the value of the argument expression is copied to the formal parameter.


  10. Reference parameters.   Consider the calls to the getline function in asterisksRectangle3.cpp and asterisksRectangle4.cpp:

       string line;
       getline(cin, line);
    

    The getline function reads a line of text and puts it in a string variable which is used as the second argument to the function, in this case the string variable line. Thus, the value stored in the argument variable line is changed by the function call.

    Thus, getline behaves very differently from the power function in powerFunctionDemo.cpp. As explained earlier, because the power function's parameters are both value parameters, a call to the power function cannot change the value of a value of a variable that has been passed to it as an argument, even if the value of the formal parameter is changed within the function definition.

    The second parameter of the getline function is not a value parameter. Instead, it is a reference parameter. If a variable is passed as an argument correponding to a reference parameter, then the argument variable's value can indeed be changed by the function call.

    For another example involving a reference parameter, see echoWhile.cpp in the Assignemtn 7 files. It calls the get function of cin to read a single character into a variable of type char. Thus, the call to the get function changes the value of the char variable that has been passed to it as an argument. Thus, the corresponding formal parameter must be a reference parameter, not a value parameter.

    Recall that a value parameter is a means by which information can flow into the function but not out of it, whereas a return value is a means by which information can flow out of a function but not into it. A reference parameter is a means by which information can flow either in or out of a function, or both.

    Below, we will sometimes speak about incoming parameters and outgoing parameters, which we will sometimes refer to as in-parameters and in-parameters, respectively. An incoming parameter is used to bring information into a function. An outboing parameter is used to send information out of a function. A value parameter can be used ONLY as an incoming parameter, not as an outgoing parameter. On the other hand, a reference parameter can be used as an incoming parameter, and outgoing parameter, or both.

    Earlier, we said that when a function is called, an argument corresponding to a value parameter may be any expression with a value of an appropriate data type. On the other hand, an argument corresponding to a reference parameter MUST be a variable. It cannot be any other kind of expression.

    Unlike a value parameter, a reference parameter is not assigned the VALUE of an argument when the function is called. Instead, when the function is called, a reference parameter becomes another name for the SAME MEMORY LOCATION that is also named by argument variable. That's why a change to the value of a reference parameter results in an identical change to the value of the corresponding argument variable. A reference parameter and the corresponding argument variable are, in effect, two names for the SAME variable, i.e. the same memory location, which can hold only one value at a time.

    If a parameter of a simple data type is being used as an incoming parameter only, not an outgoing parameter, it should be a value parameter, not a reference parameter, to prevent accidentally changing the value of the argument in case the value of the parameter is changed inside the function. A parameter of a simple data type should be a reference parameter only if it is being used as an outgoing parameter (either as an outgoing parameter only or as both an incoming parameter and an outgoing parameter).

    Unlike a parameter of a simple data type, a parameter of a structured data type, e.g. a class, should nearly always be a reference parameter, to save memory space, even if it is being used as an in-parameter only. Recall that a value parameter makes a duplicate copy of the value in memory -- a copy which, in the case of a structured data type, might take up a significant amount of extra memory.

    Look again at powerFunctionDemo.cpp. Observe that the main function devotes quite a few lines of code to the task of input, including the associated prompts and error-checking. We can simplify the main function greatly by delegating the input and related tasks to a separate function, as is done in inputFunctionDemo.cpp.

    Compile inputFunctionDemo.cpp and run it. Observe that it behaves exactly like powerFunctionDemo.cpp.

    Then look at the source code of inputFunctionDemo.cpp. It defines a function inputNumbers, which inputs the numbers. The inputNumbers function is called in the main function as follows:

       float enteredBase;
       int enteredExponent;
       if ( ! inputNumbers(enteredBase, enteredExponent) )
                      // The inputNumbers function, called above,
                      // uses cin and cout.
          return 1;
    

    In order to serve the purpose of inputting numbers into the main function, the call to inputNumbers must be able to store values in the main function's variables enteredBase and enteredExponent. Thus, it must be able to change the values of those variables. In other words, the corresponding parameters must be reference parameters.

    Look at the heading of the inputNumbers function definition:

    bool inputNumbers(float& base, int& exponent)
    

    Observe the ampersands after the data types in the formal parameter declarations. The ampersands indicate that the parameters are reference parameters.

    The inputNumbers function also returns a value of type bool indicating whether the input was successful. (It returns true if the user input the numbers correctly and false if, for example, the user typed non-numeric input.

    In the main function, the call to inputNumbers is used as the condition of an if clause, where an expression of type bool is expected.

    Look carefully at the body of the inputNumbers function and make sure you understand how it works.

    Note that, in this example, the reference parameters are used as outgoing parameters only, not as incoming parameters. In the call to inputNumbers in the main function, it does not matter what values the argument variables enteredBase and enteredExponent have before the function is called. Within the inputNumbers function itself, the formal parameters base and exponent are given values; and, BEFORE they are given values, they are not used in any way which assumes that they already have values. Thus, in the main function, it is not necessary to give values to enteredBase and enteredExponent before the inputNumbers function is called.

    Indeed, before inputNumbers is called, the argument variables enteredBase and enteredExponent have garbage values. Such garbage-valued arguments are OK when the corresponding formal parameters are reference parameters which are being used as outgoing parameters only. However, garbage-valued argument variables would NOT be okay if the corresponding formal parameters were value parameters, or if they were reference parameters being used as in-parameters as well as out-parameters.

    Do not confuse incoming parameters and out-parameters with input and output. Incoming parameters and outgoing parameters pertain to a function's interaction with its caller, i.e. the part of the program that called the function, whereas input and output pertain to a program's interaction with things outside the program, e.g. the keyboard, the screen, or a text data file. An input function, such as inputNumbers, obtains information from outside the program and then transmits that information, usually via reference parameters or a return value, to the caller of the input function. An output function, on the other hand, receives information, usually via value parameters, from the caller, and then transmits that information to some target outside the program. Thus, an input function typically has outgoing parameters, NOT incoming parameters, whereas an output function typically has incoming parameters, not outgoing parameters.


  11. An example using reference parameters as both incoming parameters and outgoing parameters.   In the inputNumbers function in inputFunctionDemo.cpp, the reference parameters are used as outgoing parameters only, not incoming parameters. We will now look at an example of a function in which reference parameters are used to send information in both directions.

    Compile swapDemo.cpp and run it. This program swaps the values of two variables, using a call to a function named swap, defined below the main function.

    In the main function, note that the variables n1 and n2 are first given values. Then the swap function is called, changing the values of both variables.

    Observe that the swap function is written in a manner which assumes that the parameters already have values when the function is called. For example, the first statement in the swap function is:

       int temp = a;
    

    which assumes that the parameter a already has a value. It has the value which was given to the corresponding argument variable n1 in the main function. Thus, the parameter is being used to bring information INTO the function.

    Later in the swap function, the values of both parameters are changed, resulting in changes to the values of the argument variables too. Thus, the parameters send information OUT of the function as well as into the function.


  12. Global variables   So far, we have discussed two kinds of variables: local variables and formal parameters, both of which are declared within a function definition and can be accessed only within the body of function in which they are declared. There is also another kind of variable, known as a global variable. A global variable is declared OUTSIDE of any function and can be accessed by any function in the file, below the point where the global variable is declared.

    The most important thing you need to know about global variables is that, for the most part, YOU SHOULD NOT USE THEM. The use of global variables makes a large program much harder to debug than it would be otherwise, because, to determine what may have gone wrong with a global variable, you need to check all the functions in the file, whereas, to determine what may have gone wrong with a parameter or local variable, you need only to check the function or block in which the variable is declared.

    However, there are two global variables you have been using routinely: cin and cout, both of which are declared in library header file <iostream>.

    In all program you write for this course, you should NOT use any global variables at all except for cin and cout.

    However, it's OK to use global constants. A constant is like a variable (i.e. it is a name given to a memory location), except that its value cannot change. Because a constant's value does not change, you don't have to look at the entire file to determine what may have gone wrong with the value of a global constant. Thus, global constants do not cause the kinds of difficulties that global variables can cause.


  13. Comments.   From henceforth, all programs you write for homework should have comments similar to those in asterisksRectangle4.cpp, powerFunctionDemo.cpp, and inputFunctionDemo.cpp.

    First, look at the constants above the functions. Each such comment begins with a copy of the function heading, followed by a general but precise description of what the function does, both normally and in case of error. After the general comment are comments pertaining to the parameters, the return value, and any global variables used within the function. The point of these comments is to document all the ways that information can flow between the function and the rest of the program, either into or out of the function. Without such documentation, a function is much harder to debug than it would be otherwise, especially in a large program with lots of functions.

    There are six ways that information can flow between a function and its caller: (1) into the function via a value parameter, (2) into the function via a reference parameter, (3) out of the function via a reference parameter, (4) into the function via a global variable, (5) out of the function via a global variable, and (6) out of the function via a return value.

    Observe that the comments for the reference parameters and global variables specify both preconditions and postconditions. A precondition is a statement that needs to be true about the value of a parameter or global variable BEFORE the function is called, in order for the function to work properly. For example, a precondition might specify a range of acceptable values of an incoming parameter. A postcondtion is a statement that is guaranteed to be true about the value of a parameter or global variable AFTER the function has been called, at least provided that all the preconditions were true.

    Note that a precondition (on a parameter or global variable) pertains to information flowing into the function from its caller, whereas a postcondition pertains to information flowing out of the function, back to the part of the program that called it. When we speak of information flowing into a function via a parameter, we are talking about the value of the corresponding argument BEFORE the function is called, because arguments are transmitted to formal parameters as the very FIRST thing that happens when the function is called. On the other hand, when we speak of information flowing out of a function via a reference parameter, we are talking about the value of the argument AFTER the function has been called. Likewise, a return value is a means by which information flow out of the function, as the very LAST thing that happens when the function finishes executing.

    We don't bother to specify preconditions and postconditions for value parameters and return values because, for value parameters and return values, the information flows in only one direction. Thus, anything we say about the value of a value parameter can be assumed to be a precondition only, not a postcondition. Likewise, anything we say about a return value can be assumed to be a postcondition only, not a precondition.

    In the comments above the inputNumbers function in inputFuncitonDemo.cpp, the preconditions for both the reference parameters are "none." That's because, as we said eariler, the reference parameters are being used as outgoing parameters only, so it does not matter what values the parameters have before the function is called. However, because a reference parameter might have a precondition, the absence of a precondition is indicated explicitly via the comment "Precondition: none," rather than simply omitting a precondition altogether.

    If a reference parameter is being used as an incoming parameter only, not an outgoing parameter, then the function must not CHANGE the value of the parameter, because any change in the value of the parameter will also change the value of the argument variable and thus be a flow of information out of the function to the part of the program that called the function. So, if a reference parameter is being used as an incoming parameter only, the postcondition should indicate that the value of the parameter is unchanged.

    Regarding global variables, a function is considered to use a global variable if it uses the variable either directly (within the function definition itself) or indirectly (within the definitions of functions called by the definition. All CALLS to functions whose definitions use global variables should be documented. For example, in the main function of printRectangle3.cpp:

       printRectangle(5, 15);    // uses cout
       pause();                  // uses cin and cout
       printRectangle(7, 20);    // uses cout
       pause();                  // uses cin and cout
       printRectangle(10, 25);   // uses cout
    

    Likewise, in the main function of inputFunctionDemo.cpp:

       float enteredBase;
       int enteredExponent;
       if ( ! inputNumbers(enteredBase, enteredExponent) )
                      // The inputNumbers function, called above,
                      // uses cin and cout.
          return 1;
    

    And, in both these programs, the use of cin and cout is mentioned in the comments above the main function, even though the main function does not necessarily use them directly.

    Note that the comments above the main function does not specify preconditions and postconditions for the global variables. It would be pointless to specify preconditions and postconditions above the main function, because nothing is done to the values of the variables either before or after the main function is called. However, preconditions and postconditions for the global variables should be specified in the comments above all OTHER functions which use them, either directly or indirectly.

    Below is a brief example of a style of comments different from what we have illustrated here. In inputFunctionDemo.cpp, we could add brief comments within the function definition heading themselves indicating whether each parameter is being used as an incoming parameter or outgoing parameter. If we did so, the inputNumbers function heading would look like:

    bool inputNumbers( /* out */ float& base,
                       /* out */ int& exponent)
    

    And the power function heading would look like:

    double power( /* in */ float base,
                  /* in */ int exponent)
    

    Note that "*/" indicates the END of a comment that begins with "/*". Thus, anything on a line AFTER "*/" is NOT ignored by the compiler. Thus the formal parameter declarations are still part of the program that the compiler sees.

    On the other hand, comments that begin with "//" necessarily do extend to the end of a line.

    You may find the "in" and "out" comments helpful to use in your own programs, but they are not required, because you should write your programs in such a way that incoming parameters and outgoing parameters can be easily recognized as follows:

    1. A value parameter is ALWAYS an incoming parameter only, never an outgoing parameter (unless the value happens to be a pointer, but we have not yet covered pointers).

    2. A reference parameter can be used as an incoming parameter, an outgoing parameter, or both, as implied by its precondition and postcondition:

      • If the parameter is being used as an outgoing parameter, but not an incoming parameter, then its precondition should be "none."

      • If the parameter is being used as an incoming parameter, but not an outgoing parameter, then its postcondition should say that its value is unchanged.

      • If a reference parameter has neither a precondition of "none" nor a postcondition of "unchanged", this should be taken as implying that the reference parameter is being used as both an incoming parameter and an outgoing parameter. The precondition pertains to the parameter's use as in incoming parameter, whereas the postcondition pertains to the parameter's use as an outgoing parameter.

      Of course, the actual body of the function definition needs to be consistent with the comments.

    Look now at the comments within the function definitions, other than documentation of the use of global variables.

    Observe that there is a comment introducing each group of statements which perform a particular task. Taken together, these comments specify, informally, the algorithm used within the function definition.

    When writing a program or a function, it is a good idea to write the steps informally on paper before you begin writing code. Then, when you begin writing code, it is a good idea to type the comments FIRST before you begin typing code. Within each function definition, you should first type all the comments that informally spell out the steps of your algorithm. Then, underneath each of these comments, insert the code necessary to carry them out.


  14. Matters of style.   Some textbooks exhibit a strong preference for void functions over value-returning functions, except in very limited circumstances. It should be noted that this preference is not standard.

    One rule found in some textbooks is: "If the function must return more than one value or modify any of the caller's arguments, do not use a value-returning function." In other words, if more than one piece of information must be sent out of the function to the caller, some textbooks recommend that it be sent out via reference parameters only, with no return value.

    However, in the C and C++ standard libraries, there are many functions which modify their caller's arguments and also return a value. For example, it is very common for an input function to modify the value of one or more variables that have been passed to it as arguments AND also return a value indicating whether input was successful, as our example function inputNumbers does.


  15. Text file input, text file output, command-line arguments, and an example of the use of a static local variable.   Compile inputFunctionDemo2.cpp and run it. First, run it without command-line arguments. Then run it with the filename powersInput.txt (one of the hw08 files) as a command-line argument. Then try running it with powersInputWrong.txt (another one of the hw08 files).

    Note that the text data file is assumed to contain pairs of numbers, of which the first number is a floating-point number and the second pair is an integer. An error message is printed if the program is unable to read the second number in a pair, for whatever reason. Note that the error message indicates how many numbers were have been read successfully so far, before an incomplete pair was encountered.

    Look now at the source code. Look first at the comment above the main function. Observe the documentation of the command-line arguments. In all programs you write in Assignment 5 and afterward, you should document the command-line arguments similarly, if command-line arguments are used, as well as the use of cout and/or cin, if they are used.

    Look now at the body of the main function. First, in the block where the number of command-line arguments, note that the task of printing out the instructions has been delegated to a parameterless void function, printInstructions.

    This program also defines and uses an inputNumbers function which inputs pairs of numbers from a text file, rather than prompting the user for keyboard input. Besides base and exponent, this function takes a parameter inFile, of type ifstream, for text file input. Note that the ifstream object is a reference parameter, not a value parameter. When passed as a parameter to a function, an object of class ifstream or ofstream should always be a reference parameter, as most objects should be in general.

    In the main function, a call to the inputNumbers function -- whose return type is bool -- is used as the condition of a while loop. Each call to inputNumbers inputs one pair of numbers. Thus, the loop is executed every time a pair of number is input successfully, quitting the first time input fails, e.g. because the end of the file has been reached, or because the last pair is incomplete, or because some non-numeric text has been encountered.

    If an incomplete pair has been encountered, the number of successful inputs so far is printed. Look now at the inputNumbers function definition to see how it counts successful inputs. This is done using a static local variable, which can be accessed only inside the block where it is declared, but remembers its value between calls to the function. (On the other hand, a non-static local variable cannot remember its value between function calls, because it ceases to exist when the block in which it is declared has finished executing.)

    Look at the comments above the inputNumbers function, especially regarding the ifstream object.

    Now compile asterisksRectangle5.cpp and run it. Look at the resulting output text file rectangles.txt, and then look at the source code. Observe this program's use of an ofstream object as a reference parameter to the function printRectangle, which prints the rectangle. Observe the comments above the printRectangle function, especially regarding the ofstream object.

    In both inputFunctionDemo2.cpp and asterisksRectangle5.cpp, observe that the ifstream and ofstream objects are opened (i.e. they are told what file they should read from or write to) before the call to the function which does the actual reading or writing, in these cases the functions inputNumbers and printRectangle.


Back to: