CS1440 - Lab 5
More about Functions

The goals is to learn about:
    void functions (functions that do not return anything)
    functions calling other functions
    functions which return more than one value
    complex programs with multiple function calls

Activity 5-1: Preparation
Create the Lab5 directory under the 1440 directory and complete your work there.

Activity 5-2: Void Functions
We have seen and used functions that return a value. Sometimes, it is convenient to create a function that carries out some activities but does not return a value to the calling function. Such a function may perform some calculations and display the result.  A good example of such a function is one that displays a set of instructions on the screen regarding how a program works.  In program P44.C of the previous lab, we used a function to compute the total cost of purchases.  In that program, not much information or instructions on how the program works was provided.  We can improve it by adding some information that tells the user more about what the program does and what the inputs/outputs are. If we put the extra information in the main function, we will clutter up the main and make it longer and more tedious to read. Instead, we can put the user instructions in a separate function named (what else?) "instructions". This function do not have any argument, it has a name (instruction), and since it does not return any value has a type void. Look at the new version of the program given below and see how the instructions function is being defined and called in the main function.

// P51.C- This program computes the total cost of purchases made.
#include <iostream>
using namespace std;

double total_cost(int number_par, double price_par);
//Computes the total cost, including 5% sales tax,
//on number_par items at a cost of price_par each.

void instructions(void);  //Tells the user what the program is doing.

int main( )
{
        double price, bill;
        int number;

        instructions();
        cout << "Enter the number of items purchased: ";
        cin >> number;
        cout << "Enter the price per item $";
        cin >> price;

        bill = total_cost(number, price);

        cout.setf(ios::fixed);
        cout.setf(ios::showpoint);
        cout.precision(2);

        cout << number << " items at "
                 << "$" << price << " each.\n"
                 << "Final bill, including tax, is $" << bill
                 << endl;

        return 0;
}

double total_cost(int number_par, double price_par)
{
        const double TAX_RATE = 0.05; //5% sales tax
        double subtotal;

        subtotal = price_par * number_par;
        return (subtotal + subtotal*TAX_RATE);
}

void instructions(void)
{
        cout << "\n\nBILL CALCULATOR!\n\n"
             << "This program will calculate the total cost, "
             << "including tax,\n"
             << "of a quantity of items being purchased.  You "
             << "will be asked\n"
             << "for the number of items purchased and the cost "
             << "of each one.\n\n";
}

As you can see,  the function that displays the instructions has 1) a name, instructions, 2) a type, void, and 3) valid parameter(s), none in this case.  Since this function is of type void, it does need to return anything, if it had the return statement, it would return nothing, thus, return;.

To highlight the difference between a void function and a function that return a value,  for each of the two functions in the above program, we have shown the prototype of functions and the calls to them.

prototype:      double total_cost(int number_par, double price_par);
call:         bill = total_cost(number, price);

prototype:      void instructions(void);
call:         instructions();

The total_cost function returns a value of type double.  This value will be "received" by the calling function with the same type.  Thus, when it comes back to the calling function, we have to save the returned value in a variable of the same type. That's why we put the call on the right-hand-side of an assignment statement and assign the return value to the variable, bill. Also, total_cost has two parameters. The first one is an integer and the second one is a double. When we call the total_cost function, we must pass to it two values, an integer and a double.

Now look at the prototype and the call for the instructions function. The instructions function has a void return type (the word void tells C++ that the function returns no value), so it is not necessary to catch any return value. And since the function has no parameters, it is not necessary to send it any values when we call it. Thus the call includes only the name of the function and an empty argument list. Note that the parentheses after the name of the function are necessary. Also note that the word "void" is not used in the call. Instead, the parameter list is simply left blank. It is an error to put the word "void" in the function call.

Don't let the difference between the names of the parameters in the total_cost function and the names of the variables whose values are being passed to the function bother you. When C++ gets to the assignment statement, "bill = total_cost(number, price)", the first thing it does is to look into memory and get the values of number and price. It then sends those values to the total_cost function. The values are used to initialize the parameters of the total_cost function. The total_cost function has no way of knowing what the variables in main are called, and main has no way of knowing what the parameters of the total_cost function are called. The calling mechanism merely copies values from one place in memory to another. The names of those places may be exactly the same or they may be different; it doesn't matter.  Corresponding parameters in the function call, function prototype, and function definition MUST have the same type.

There is one other void function that we might find nice to have for our cost.C program. The formatting lines

         cout.setf(ios::fixed);
         cout.setf(ios::showpoint);
         cout.precision(2);

can moved to a void function so your main program is more readable. That is, you can use the function:
      void format()
      {
         cout.setf(ios::fixed);
         cout.setf(ios::showpoint);
         cout.precision(2);
      }

to do the formatting, if you   (1) put a prototype for the function between the "#include" line and the "int main( )" line,   (2) replace the three lines with a call to the function, and   (3) put the function definition somewhere after the definition of main( ).

Exercise 51
The format function above is not flexible.  In other words, the function will only displays the output with 2 decimal places.  If you need to write your output with a different number of decimal places, let's say 3, then you need to create another format function with a different name, perhaps format3(), and in that function change the precision to 3,
          cout.precision(3);
As you may notice, this is not convenient at all.  Is there any other way to do this?  yes.

Cut a paste the above program P51.C into a file, save the file as ex51.C, add a void function "format" such that it takes two parameters as its input, the number that you wish to print and the number of decimal places. So the prototype will be:
          void format(double x, int n);

Note that your function is still a void function, but this time it is taking two parameters. Now, you have more flexibility. You can call this function any time you wish to change the precision.   Any number that you send to this function as a parameter will be printed with n decimal point.  Suppose we want to print the cost with 3 decimal points, then we can call the format function as, format(cost, 3).

The good thing about the new function is that you call it as many time as you wish to change the format of the output without changing the name of the function each time.

Compile and run the program and see the change in the format of the output.

Activity 5-3: Functions that "Return" More than One Value (call_by-reference)
In Lab(4), you saw examples of functions that returned one value.  In the first example on this lab, you saw a function that didn't return anything.  Sometimes, you want to return more than one value from a function.  The return statement can be used to return one value only.  Thus, we will not use the return statement to return more than one value.  Instead we will use a method to update the parameters that are passed to a function.  Following is an example in which we have used a new method to return a value to the main without using the return statement.

In this example, the get_input function is to obtain two values from the user and then give the two values to the main function. In a sense, then, get_input "returns" two values.  This cannot be done with the return statement because the return statement returns exactly one value. If a function must "return" more than one value, then we must use call-by-reference parameters (one for each "returned" value.)

Simple Example (please pay attention to the color):

// P52.C This function is to illustrate how a function can return two values
#include<iostream>
using namespace std;

// This is the prototype for the function that reads the values for i and j
void get_input(int& i, int& j);

// This is the prototype for the function that add 10 to i and 20 to j
void process(int& i, int j);

int main()
{
     int i,j;

     get_input(i,j);

     cout << "I am about to call function Process, i = " << i << " j = " << j << endl;

     process(i,j);

     cout << "I just came back from function Process, i = " << i << " j = " << j << endl;

    return 0;
}

void get_input(int& i, int& j)
{
       cout << "Please enter two values for i and j separated by a single space, then press <Enter>:";
       cin >> i >> j;
       cout << endl;
       return;   // a void function, returns nothing
}

void process(int& i, int j)
{
      i = i +10;
      j = j +20;
      cout << "Inside function Process \n";
     cout << "I added 10 to i, and 20 to j, i = " << i << " and j = " << j << "\n";
}

Exercise 52
Cut and paste this program into a program called ex52.C, compile then run it.  Which one of the values, i or j, got updated upon return from the function process?  What would you do to get both values updated upon your return to the main? make the necessary changes and run the program to make it works correctly.

Using & we access the address of a variable,  thus, when we make a change in the value in a function, the change will be seen in the calling function as well, i.e., the value gets updated.  Calling a variable by its address is referred to as call-by-reference.

One more note, if we want to use return statements to get the values for i and j, we needed to have two different functions, one to get the value for i (example: get_i) and another to get the value for j (example: get_j).

Both of these methods work, a programmer should make the program work correctly and efficiently while maintaining simplicity.

Activity 5-4: A Complex Program with Multiple Function Calls
Suppose we have been asked to create a program to display rectangles and triangles on the screen. The shapes will be made out of a character chosen by the user. The user may also choose the size of the rectangle or triangle by entering the number of rows and columns for a rectangle and the number of rows for a triangle. Here are some samples:

xxxxx
xxxxx
xxxxx

oooooo
ooooo
oooo
ooo
oo
o

The first shape is a rectangle made of x's with 3 rows and 5 columns. The second shape is a triangle made of o's with 6 rows. All our triangles will be right triangles drawn so that each row after the first row is one character shorter than the row just above it.

Your homework for this lab will be to create this program, but I am going to lead you through its creation.

Sometimes when you write a new function, you make use of a driver program to test it. A driver is a small program written only to test a new function. When you are working on a program that will contain many functions, you should test each function separately from the others. Once a function has been tested, it can be used in a program that is testing a different function, but each function should be tested in a program in which it is the only untested function.

We are also going to make use of function stubs. We will say more about this idea shortly.

The first task to be done when writing a large program is to decide on a design for that program. The most important part of designing a program is deciding what functions need to be written and what kind of function each will be.

We can describe a design most easily with a picture called a structure chart. The structure chart contains a rectangle for each function labeled with the name of the function. The rectangles are drawn in such a way that the chart shows which function calls which. If function X calls function Y, then the rectangle for X will be above that for Y and there will be a line connecting X to Y. The following structure chart describes our new and improved cost.C.

For each function we show the inputs to the function with a downward-pointing arrow, and the outputs with an upward-pointing arrow. If there are more than two outputs, as for the get_input function, we will use call-by-reference parameters for those outputs. Otherwise we will use the return statement. The order in which functions are shown left to right is unimportant in a structure chart. The only information conveyed is "who calls who and how". Make sure you understand why the structure chart for cost.C is drawn the way it is.

Activity 5-5: Design
Here is a structure chart for the draw.C program we are going to write for homework.

Note that the structure chart above for draw.C shows that the main( ) function uses seven other functions:

It also shows that each of the two last named functions, draw_rect( ), and draw_tri( ), uses a function named make_row( ). In addition the structure chart shows what information is given to each of the functions and what information the functions provide to their caller.

In addition to the structure chart in our design, we need to say a few words about what each function does. These explanations actually contain the function prototype (spelled out in English), and they provide information that can later be made part of the function header comments. Here is a brief description of each function to be included in draw.C:

instructions: The instructions function will tell the user that the drawing program will draw rectangles and triangles of different sizes on the screen and that the user may select the size and the character to be used for drawing. It should tell the user that he or she will be making selections from a menu.

menu: The menu function will display the following menu:

      1. Draw rectangle.
      2. Draw triangle.
      3. Quit

It will then ask the user to select one of the choices. The function should verify that the choice is a 1, 2, or 3, and should then return that choice to main.

draw_rect: Given a number of rows, a number of columns, and a character to use for drawing, this function draws a rectangle on the screen. It calls the make_row function to produce each row of the rectangle.

draw_tri: Given a number of rows and a character to use for drawing, this function draws a triangle on the screen. It calls the make_row function to produce each row of the triangle. Each row of the triangle after the first row is one character shorter than the row above it.

make_row: Given a number, num, and a character, ch, this function prints num ch's in a row. It does not place a newline at the end of the row.

get_rows_cols: The get_rows_cols function is called when the user wants to draw a rectangle. The function asks the user for the number of rows and the number of columns to be used in creating the rectangle, and "returns" those numbers to main. (Call-by-reference!)

get_rows: The get_rows function is called when the user wants to draw a triangle. The function asks the user for the number of rows to be used in creating the triangle and returns that number to main.

get_drawchar: The get_drawchar function is called for any shape the user wants to draw. The function asks the user what character should be used in the drawing and returns that character to main.

Activity 5-6: Implementing the design

Do not change the design of the draw.C program. The design was crafted to be a valuable learning experience for you. This is certainly not the only way that the draw.C program could be written, but it is the way you are expected to write it.

One hard part of the program is now done. We have decided how the work to be done by the program should be divided up among all our functions. Now we pick a function to write first and get started. Normally we begin by writing the main function, but this time let's begin by writing the make_row function.

The make_row function has two call-by-value parameters, an integer and a character. Let's call the integer num and the character ch. The function is supposed to write ch on the screen num times. We will need a loop that runs num times to accomplish this task.

Exercise 53
Create a file called driver.C and copy the following into it. The main function here is a driver for the make_row function.

#include <iostream>
using namespace std;

void make_row (int num, char ch);
//Writes num ch's on the screen, all in a row.

int main (void)
{
        int num;
        char ch;

        cout << "\n\nDriver to test make_row function.\n\n";
        cout << "This program will repeatedly ask you for a "
             << "number and a character\n"
             << "and will send that number and character to "
             << "make_row.  When you\n"
             << "are satisfied that make_row works properly, "
             << "enter 0 for the number.";
        cout << "\n\nYour number (0 to quit): ";
        cin >> num;
        while (num > 0)
        {
                cout << "\nYour character: ";
                cin >> ch;
                 make_row(num, ch);
                cout << "\n\nYour number (0 to quit): ";
                cin >> num;
        }
        return 0;
}

A driver is a function designed to test another newly written function. The main function above will not be used in the draw.C program. Its usefulness is temporary, only long enough to be sure that the make_row function is correct.

Now write the make_row function below main. Give it a loop that runs num times, printing ch each time. Once you have written the function, compile and test your program. You can't go forward until your make_row function is working properly.

Activity 5-7:Algorithm
Now let's write the main function for the draw.C program. Here is an algorithm that describes what main must do. See if the algorithm makes sense to you. (The asterisks and color changes will be explained later.)

  * 1.Print instructions for the user.
  * 2.Print the menu and get the user's choice (1, 2, or 3).
  * 3.While the user's choice is not 3
  *      a. If the user's choice is 1 (draw a rectangle)
  *          1.Get the number of rows and the number of columns to use for the rectangle.
  *          2.Get the character to use in drawing the rectangle.
              3.Draw the rectangle.
  *          4.Print the menu again and get the user's next choice.

  *      b. Else if the user's choice is 2 (draw a triangle)
  *           1.Get the number of rows to use for the triangle.
  *           2.Get the character to use in drawing the triangle.
               3.Draw the triangle.
  *           4.Print the menu again and get the user's next choice.

  *     c. Thank the user for using the program.
 

After you think about the order in which we have listed the steps of the algorithm, figure out which instructions of the algorithm go with which function. The complete program should have the functions listed below:

     instructions
     menu
     draw_rect
     draw_tri
     make_row
     get_rows_cols
     get_rows
     get_drawchar

You should now begin to see how the main function will be encoded in C++.

Notice that there is no call in main to the make_row function we have already written. That's because draw_rect and draw_tri are the callers of that function.

Exercise 54
Complete the parts marked with an * and red font in the algorithm above. We shall leave the two parts without asterisks (and in blak font) for later. Create a file called draw.C and use it for your work. Recall that in yout program you should have these things (in order):

    1. A header comment.
    2. The "#include" lines.
    3. Global constants (if you need any -- you probably won't in this program).
    4. Prototypes for your functions.
    5. Definition of main( ).
    6. Definitions of your other functions.

You may find it useful to use stubs. (You'll find a discussion of stubs on pages 206-209 of your text.) You may, if you wish, use the following stubs for the un-asterisked routines in the algorithm above.

void draw_rect(int rows, int cols, char drawch)
{
  cout << "\ndraw_rect( ) called with:"
       << "  rows = "     << rows
       << "  cols = "     << cols
       << "  drawch = '"  << drawch << "'"
       << endl;
  return;
}

void draw_tri(int rows, char drawch)
{
  cout << "\ndraw_tri( ) called with:"
       << "  rows = "     << rows
       << "  drawch = '"  << drawch << "'"
       << endl;
  return;
}
 

Post_Lab
Your assignment is to finish the draw.C program.  Note that the algorithm given for this program is a complete algorithm. We implemented part of it in the lab, so you can use it to complete the program. You must use the design we have presented.

Your program must follow the CS 1440 Style Guidelines.

Bring a printout of your program to the next lab meeting.