CSC 101 Lecture Notes Week 5

CSC 101 Lecture Notes Week 5
Introduction to Functional Program Design

-- REVISED --


  1. Quiz UNAlert:
    1. The Quiz originally scheduled for lab Wednesday is CANCELLED.
    2. Instead, we'll spend the first part of the lab discussing the design of Programming Assignment 4.

  2. Note the following update to the class schedule and reading assignment for this week.
    1. This week we will cover the rest of Chapter 4, most of Chapter 7, and part of Chapter 8, in the Dale textbook.
    2. The revised reading assignment for this week is as follows:
      1. Chapter 4, pages 153-178 (this covers program design)
      2. Chapter 7, pages 323-356 (this covers functions)
      3. Chapter 8, pages 404-414 (this covers more on functions)

  3. Discussion of programming assignment 4 -- see the revised handout.

  4. Introduction to program design
    1. Thus far we have written programs entirely within one main function.
      1. As our programs have become larger, this has developed into an unwieldy way to organize the program code.
      2. It becomes difficult to see "the forest from the trees" in the one large function.
    2. What we need is a way to organize our programs in a higher-level form, where we can see an architectural design of the program.
    3. Such a design will allow us to focus on the large-grain structure of the program, independent of lower-level coding details.

  5. Design basics
    1. There are a number of program design styles in computer science.
      1. We will focus on one of the most basic, called functional design.
      2. Synonyms for this term include top-down design (as the book calls it), structured design, stepwise refinement, and others.
    2. The basic idea of functional design is to decompose a large program into a collection of smaller subprograms.
      1. As a whole, the collection of subprograms work together to solve a problem.
      2. Each subprogram represents a major step of the algorithm that the overall program implements.
    3. Functional designs are represented graphically using tree-like diagrams
      1. The diagrams consist of boxes, containing the description or mnemonic name of a subprogram.
      2. The boxes are organized hierarchically, with the highest level subprogram at the top, with lower-level subprograms below it.
      3. At each level of the diagram, the lower-level boxes have more concrete details than the more abstract subprograms on the levels above.
    4. Figure 1 shows a high-level design of the change-making program we have been using as an example in lectures.
      
      

      Figure 1: High-level design for change making program.


      
      

  6. An initial design example
    1. To see how a C++ program can be organized using functional design principles, we will look at two versions of our change making program.
      1. The first version implements the entire problem solution in a single main function.
      2. The second version implements the solution using a main function, plus three subfunctions, as shown in the design diagram in Figure 2
        
        

        Figure 2: C++ design for change making program.


        
        
    2. The difference between Figures 1 and 2 is only minor.
      1. Figure 1 is a high-level design, in which the subprograms are described using descriptive English phrases.
      2. Figure 2 is a more concrete design, in which the subprograms are the names of actual C++ functions.
      3. Both designs represent the same program structure.
    3. Here is the one-function version of the change-making program:
      ////
      //
      // This program makes change, given an amount of purchase and an amount
      // tendered.  The output is a total amount of change, followed by change in
      // five denominations of money: dollars, quarters, dimes, nickels, and pennies.
      // All inputs and outputs are in integer cents.
      //
      // The program performs two input validity checks and outputs an appropriate
      // error message if either check fails.  The validity checks are:
      //
      //     (1) The inputs are both positive.
      //     (2) The amount tendered is greater than or equal to the purchase amount.
      //
      // The program performs multiple change-making transactions by continuing to
      // accept input until the user enters a value of zero for the purchase amount.
      //
      // Author: Gene Fisher (gfisher@calpoly.edu)
      // Created: 26par99
      // Modified: 26apr99
      //
      ////
      
      #include <iostream.h>
      
      int main() {
      
          //
          // Declare program variables to hold the purchase amount, amount tendered,
          // total change amount, and the amounts of each denomination of change,
          // from dollars to pennies.
          //
          int purchase;                       // Amount of purchase
          int tendered;                       // Amount tendered
          int change;                         // Total change due
          int dollars;                        // Number of dollars  in change
          int quarters;                       //   "    "  quarters "    "
          int dimes;                          //   "    "  dimes    "    "
          int nickels;                        //   "    "  nickels  "    "
          int pennies;                        //   "    "  pennies "    "
      
          //
          // Prompt for and input the amount of purchase, for the first transaction.
          //
          cout << "Input the amount of the purchase, in cents (enter 0 to stop): ";
          cin >> purchase;
      
          //
          // Continue processing transactions until a purchase amount of 0 is
          // entered.
          //
          while (purchase != 0) {
      
              //
              // Prompt for and input the amount tendered.
              //
              cout << "Input the amount tendered, in cents: ";
              cin >> tendered;
              cout << endl;
      
              //
              // Perform input validity checks.
              //
              if (tendered <= 0 || purchase <= 0) {
                  cout << "Sorry, your purchase cannot be completed since"
                       << endl
                       << " both inputs must be greater than zero."
                       << endl << endl;
              }
              else if (tendered < purchase) {
                  cout << "Sorry, your purchase cannot be completed since"
                       << endl
                       << " the amount tendered is less than the amount of the purchase."
                       << endl << endl;
              }
              else {
                  //
                  // Compute the total amount of change due, in cents.
                  //
                  change = tendered - purchase;
      
                  //
                  // Output the total change amount, followed by a blank line.
                  //
                  cout << "Total change due = " << change << endl
                       << endl;
      
                  //
                  // Compute the number of dollars in change by dividing the total
                  // change by 100.
                  //
                  dollars = change / 100;
      
                  //
                  // Compute the remaining amount of change by subtracting the amount
                  // of change in dollars from the total change.
                  //
                  change = change - (dollars * 100);
      
                  //
                  // Proceed in the same way as for dollars with quarters, dimes,
                  // nickels, and pennies.
                  //
                  quarters = change / 25;
                  change = change - (quarters * 25);
                  dimes = change / 10;
                  change = change - (dimes * 10);
                  nickels = change / 5;
                  pennies = change - (nickels * 5);
      
                  //
                  // Output the results of the pieces of change computations.
                  //
                  cout << "Change in dollars through pennies is:" << endl
                       << "    " << dollars << " dollars" << endl
                       << "    " << quarters << " quarters" << endl
                       << "    " << dimes << " dimes" << endl
                       << "    " << nickels << " nickels" << endl
                       << "    " << pennies << " pennies" << endl << endl;
      
              }
      
              //
              // Prompt for and input the amount of purchase for the next
              // transaction.
              //
              cout << "Input the amount of the purchase, in cents (enter 0 to stop): ";
              cin >> purchase;
          }
      
          return 0;
      }
      
    4. Here is the multi-function version, that follows the design in Figure 2:
      ////
      //
      // This program makes change, given an amount of purchase and an amount
      // tendered.  The output is a total amount of change, followed by change in
      // five denominations of money: dollars, quarters, dimes, nickels, and pennies.
      // All inputs and outputs are in integer cents.
      //
      // The program performs two input validity checks and outputs an appropriate
      // error message if either check fails.  The validity checks are:
      //
      //     (1) The inputs are both positive.
      //     (2) The amount tendered is greater than or equal to the purchase amount.
      //
      // The program performs multiple change-making transactions by continuing to
      // accept input until the user enters a value of zero for the purchase amount.
      //
      // Author: Gene Fisher (gfisher@calpoly.edu)
      // Created: 26par99
      // Modified: 26apr99
      //
      ////
      
      #include <iostream.h>
      #include "Boolean.h"
      
      
      ////
      //
      // Function ReadInput prompts for and reads an integer value from the
      // terminal.  The prompting message is input as a string.  The input value is
      // returned.
      //
      ////
      int ReadInput(
          char* prompt                        // Prompting message
      );
      
      
      ////
      //
      // Function CheckInputs performs two input validity checks on a given purchase
      // amount and amount tendered.  The validity checks are:
      //
      //     (1) The inputs are both positive.
      //     (2) The amount tendered is greater than or equal to the purchase amount.
      //
      // If either check fails, an error message is output to the terminal.
      //
      ////
      Boolean CheckInputs(
          int purchase,                       // Amount of purchase
          int tendered                        // Amount tendered
      );
      
      
      ////
      //
      // Function ComputeChange performs change-making computations, given a purchase
      // amount and an amount tendered.  The computations entail determining the
      // amount of change in each of five denominations: dollars, quarters, dimes,
      // nickels, and pennies.  The denomination amounts are output in five separate
      // lines to the terminal.
      //
      ////
      void ComputeChange(
          int purchase,                       // Amount of purchase
          int tendered                        // Amount tendered
      );
      
      
      ////
      //
      // Function main performs change-making transactions until a purchase amount of
      // zero is entered by the user.
      //
      ////
      int main() {
      
          //
          // Declare program variables to hold the purchase amount and amount
          // tendered.
          //
          int purchase;                       // Amount of purchase
          int tendered;                       // Amount tendered
      
          //
          // Prompt for and input the amount of purchase, for the first transaction.
          //
          purchase = ReadInput(
              "Input the amount of the purchase, in cents (enter 0 to stop): ");
      
          //
          // Continue processing transactions until a purchase amount of 0 is
          // entered.
          //
          while (purchase != 0) {
      
              //
              // Prompt for and input the amount tendered.
              //
              tendered = ReadInput("Input the amount tendered, in cents: ");
              cout << endl;
      
              //
              // Perform input validity checks and proceed if checks succeed.
              //
              if (CheckInputs(purchase, tendered)) {
      
                  //
                  // Perform the change making computations and output the results.
                  //
                  ComputeChange(purchase, tendered);
              }
      
              //
              // Prompt for and input the amount of purchase for the next
              // transaction.
              //
              purchase = ReadInput(
                  "Input the amount of the purchase, in cents (enter 0 to stop): ");
          }
      
          return 0;
      }
      
      int ReadInput(char* prompt) {
      
          int input;
      
          cout << prompt;
          cin >> input;
      
          return input;
      }
      
      
      Boolean CheckInputs(int purchase, int tendered) {
      
          Boolean result = TRUE;              // Result of checks
      
          //
          // Check that both inputs are positive.
          //
          if (tendered <= 0 || purchase <= 0) {
              cout << "Sorry, your purchase cannot be completed since"
                   << endl
                   << " both inputs must be greater than zero."
                   << endl << endl;
              result = FALSE;
          }
      
          //
          // Check that tendered is >= purchase.
          //
          else if (tendered < purchase) {
              cout << "Sorry, your purchase cannot be completed since"
                   << endl
                   << " the amount tendered is less than the amount of the purchase."
                   << endl << endl;
              result = FALSE;
          }
      
          return result;
      }
      
      
      void ComputeChange(int purchase, int tendered) {
      
          //
          // Declare function variables to hold total change amount and the amounts
          // of each denomination of change, from dollars to pennies.
          //
          int change;                         // Total change due
          int dollars;                        // Number of dollars  in change
          int quarters;                       //   "    "  quarters "    "
          int dimes;                          //   "    "  dimes    "    "
          int nickels;                        //   "    "  nickels  "    "
          int pennies;                        //   "    "  pennies "    "
      
          //
          // Compute the total amount of change due, in cents.
          //
          change = tendered - purchase;
      
          //
          // Output the total change amount, followed by a blank line.
          //
          cout << "Total change due = " << change << endl
               << endl;
      
          //
          // Compute the number of dollars in change by dividing the total
          // change by 100.
          //
          dollars = change / 100;
      
          //
          // Compute the remaining amount of change by subtracting the amount
          // of change in dollars from the total change.
          //
          change = change - (dollars * 100);
      
          //
          // Proceed in the same way as for dollars with quarters, dimes,
          // nickels, and pennies.
          //
          quarters = change / 25;
          change = change - (quarters * 25);
          dimes = change / 10;
          change = change - (dimes * 10);
          nickels = change / 5;
          pennies = change - (nickels * 5);
      
          //
          // Output the results of the pieces of change computations.
          //
          cout << "Change in dollars through pennies is:" << endl
               << "    " << dollars << " dollars" << endl
               << "    " << quarters << " quarters" << endl
               << "    " << dimes << " dimes" << endl
               << "    " << nickels << " nickels" << endl
               << "    " << pennies << " pennies" << endl << endl;
      }
      

    Details of C++ Functions

  7. C++ functions are the concrete building blocks of a functional design.
    1. The functions declared in a C++ program constitute the components of a functional design.
    2. To design a C++ program, one draws the function diagram, labeled with names of the program functions, as shown in Figure 2 above.
    3. If the designer wants to include details of function input and output, a function diagram can be annotated, as shown in Figure 3.
      
      

      Figure 3: Change-making program design with input/output annotations.


      
      
      1. The down-pointing arrow annotations show the types of the values that go into the function, via its parameters.
      2. The up-pointing arrow annotations show the types of values that are returned from the function, via its reference parameters and/or return value.
  8. Syntax of C++ function declarations and invocations.
    1. There are three syntactic forms related to functions:
      1. A function prototype that declares a function before it is used.
      2. A function definition that fully defines the function with its body.
      3. A function invocation that appears within the body of a calling function.
    2. Function prototype syntax:

    3. Function definition syntax:

    4. Function invocation (i.e., call) syntax:



  9. The layout of a properly-designed C++ program file.
    Overall Program Comment
    
    Library Includes
    
    Comment for First Function
    Prototype for First Function
    
     . . .
    
    Comment for Nth Function
    Prototype for Nth Function
    
    Definition of Function main
    
    Definition of First Function
    
     . . .
    
    Definition of Nth Function
    

  10. Flow of control among program functions.
    1. All C++ programs begin when the operating system (e.g., UNIX or Windows) calls the main function of the program.
    2. When main calls another function, control is temporarily transfered to the beginning of that function.
    3. When the called function returns, control resumes at the statement immediately after the original function call.
    4. Functions may call other functions, which in turn call other functions, to any depth of call nesting.
    5. Whenever a function returns, control resumes in the calling function, immediately after the call.

  11. Value-returning versus void functions.
    1. Functions that are declared using void as the return datatype are called void, or non-value-returning functions.
      1. A void function returns by running to the end of its body, i.e., to the closing curly brace.
      2. Void functions do not return a value to the caller.
    2. All other functions are called value-returning
      1. A value-returning function must contain a return statement of the form
        return Expression
        where Expression is any legal expression of the same type as the return type of the function.
      2. Value-returning functions always return a value to the caller.
    3. Given the difference in how void versus value-returning functions behave, they must be called differently.
      1. A void function is called in a statement by itself, as in
        MakeChange(purchase, tendered);
        
      2. A value-returning function must be called within an expression, as in
        purchase = ReadInput( ... );
        
        or
        if (CheckInputs(purchase, tendered)) { ...
        

  12. Parameter details
    1. Terminology
      1. A formal parameter is the variable name declared in a function heading.
      2. An actual parameter is the expression appearing in a function call.
      3. A value parameter is a formal parameter that receives a copy of the value of its actual parameter expression.
      4. A reference parameter is a formal parameter that shares memory with the variable that is its actual parameter.
    2. Syntactically, a reference parameter is declared with an ampersand '&' after the parameter datatype; with no '&', the parameter is value.
    3. Actual-formal parameter matching
      1. When a function is called, the actual parameters are matched up positionally with their corresponding formals.
      2. Each actual parameter must have the same datatype as its corresponding formal.
      3. There must be exactly the same number of actual parameters as formal parameters.
    4. Details of value parameter passing.
      1. The actual parameter is evaluated to produce a value.
      2. The value is copied into the formal parameter variable, which resides in a local memory location within the function.
      3. Any modifications to a value parameter within the function are lost as soon as the function returns.
    5. Details of reference parameter passing.
      1. The actual parameter must be a variable.
      2. When the function is called, the formal parameter variable and the actual parameter variable share the same memory location, which resides outside of the function, where it was called.
      3. Any modifications to a reference parameter within the function are reflected in the variable outside of the function, even after the function returns.

  13. Local variables.
    1. The variables declared within the block of a function body are local to the function.
    2. That is, they are separate memory locations from variables in any other functions, including main.
    3. Even if local variables within one function are named the same as in another function, they are still separate.
    4. The values stored in local variables are valid only as long as the function is active.
    5. This means that local variables must be (re)initialized each time the function is called.

  14. Global variables
    1. Variables declared outside the body of any function are called global.
    2. Global variables are never used in CSC 101 programs.
    3. We will discuss why in upcoming lectures.

    More on General Design Principles

  15. Top-down design definition versus bottom-up design refinement
    1. When a programmer first begins to solve a problem, a design is done top- down.
      1. Such top-down design is critical to making the problem solving process manageable.
      2. It provides the programmer with a high-level architectural view of the program, independent of lower-level programming details.
      3. It's analogous to the architectural floor plan for a house, where the details of the individual pieces of lumber and hardware are left out.
    2. Once an initial architectural design is made, the lower-level programming begins.
    3. During the course of lower-level programming, new design details may be discovered.
      1. This is particularly true for inexperienced programmers, who do not know enough about programming to build a design completely top-down.
      2. As the details of a function become clear during implementation, it can turn out that the function is getting too big, and needs to be further broken down into additional functions.
      3. This is a bottom-up form of design.
    4. It is important that top-down design is the initial step in building a program.
      1. As programmers gain experience, they are better able to understand how much work will be involved in a program, and therefore their initial top-down design will require less bottom-up refinement.
      2. However, it is reasonable for even experienced programmers to perform some bottom-up design refinement during program implementation.

  16. More on design refinement
    1. There are two major reasons for using functions:
      1. To organize a program into components of manageable size, thereby making the program easier to understand, test, and modify.
      2. To avoid duplicating similar computations, thereby making the program less bulky.
    2. In the design example above, the first of these two reasons was used primarily.
    3. In the example we'll consider next week, we'll focus on the second of these reasons.
      1. Duplication of code can often be discovered during bottom-up refinement, when a programmer notices that a single piece of computation can be reused in several places.
      2. In such cases, the code is put into a function, and the function is called from each of the places where the code was formerly duplicated
    4. Design refinement is not always bottom up.
      1. Design refinement is generally necessary when a program specification is changed or enhanced.
      2. In such cases, the design is refined top-down to include new functions to meet the updated specification.
      3. In the example coming up, we'll also consider this reason for design refinement.

  17. Fisher's prime directive.
    1. During the course of design, an important question is "How do I know when I have enough (or too many) functions?"
    2. The high-level answer is "when each function performs a task that is a single cohesive unit of work".
    3. The concrete answer is "when each function performs a task that is a single cohesive unit of work, and is no more than 25 lines of code".
    4. This is the 25 line rule, and it must be strictly obeyed in all remaining CSC 101 programs, starting with Programming Assignment 4.


index | lectures | labs | handouts | assignments | solutions | grades | help