CSC 101 Lecture Notes Week 6

CSC 101 Lecture Notes Week 6
More on Functions
Program Scoping


  1. Using functions to generalize repeated computations.
    1. A parameterized function defines a generalized computation.
      1. Rather than containing specific concrete values to be computed, the parameters allow the function to operate on a range of values.
      2. This allows a function to be called from any number of places where previously the body of the function would have been repeated with different specific values.
    2. As an example, suppose we updated the specification of last week's change- making program to require that the correct singular or plural spelling is used and that no output is produced if the amount of a denomination is zero.
    3. Here precisely is the update to the spec:
      
      
      
      // The output consists of a line reporting the total change due, followed by a
      // blank line, followed by zero or more lines of the form
      //
      //     <amount> <denomination>
      //
      // where <amount> is how many of a given denomination is returned in change and
      // <denomination> is one of "dollar(s)", "quarter(s)", "dime(s)", "nickel(s)",
      // or "penny(ies)".  If the amount for a given denomination is 0, then no line
      // for that denomination is output.  If the amount is 1, then the value 1 and
      // the singular spelling of the denomination name are output.  If the amount is
      // 2 or more, then the value and plural spelling of the denomination name are
      // output.
      //
      // The program performs multiple change-making transactions by continuing to
      // accept input until the user enters a value of zero for the purchase amount.
      
      
      
    4. Here is a before and after of the computation related to this specification change:
      
      Before:
      
      
          //
          // 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;
      
      
      After:
          //
          // Output the results of the pieces of change computations.
          //
          cout << "Change in dollars through pennies is:" << endl;
      
          //
          // If there are > 0 dollars in change, output the amount of dollars.  Note
          // the correct use of "dollar" vs "dollars".
          //
          if (dollars != 0) {
              if (dollars == 1) {
                  cout << dollars << " dollar" << endl;
              }
              else {
                  cout << dollars << " dollars" << endl;
              }
          }
      
          //
          // Output quarters as for dollars.
          //
          if (quarters != 0) {
              if (quarters == 1) {
                  cout << quarters << " quarter" << endl;
              }
              else {
                  cout << quarters << " quarters" << endl;
              }
          }
      
          //
          // Output dimes as for dollars.
          //
          if (dimes != 0) {
              if (dimes == 1) {
                  cout << dimes << " dime" << endl;
              }
              else {
                  cout << dimes << " dimes" << endl;
              }
          }
      
          //
          // Output nickels as for dollars.
          //
          if (nickels != 0) {
              if (nickels == 1) {
                  cout << nickels << " nickel" << endl;
              }
              else {
                  cout << nickels << " nickels" << endl;
              }
          }
      
          //
          // Output pennies as for dollars.
          //
          if (pennies != 0) {
              if (pennies == 1) {
                  cout << pennies << " penny" << endl << endl;
              }
              else {
                  cout << pennies << " pennies" << endl << endl;
              }
          }
      
    5. The "After" version of the program is clearly very repetitive, not to mention that it violates the 25-line rule for the function it is in.
      1. This code is a perfect candidate for "functionizing".
      2. In doing so, we will replace the five chunks of similar code with five single-line function calls, plus a function that contains a generalized version of the repeated code.

  2. Refining by functionization.
    1. When we have a repetitive piece of code, such as the preceding example, we need to ask ourselves the following questions:
      1. What part of the repeated segment is the same across all repetitions?
      2. What parts are different?
      3. Does the code involve changing the value of any variables?
      4. Can the code be viewed as producing a single value?
    2. Based on the answers to these questions, we write a function to generalize the computation as follows:
      1. The body of the function contains the part of the segment that is the same across all repetitions.
      2. We supply one function parameter for each part of the code that is different across all repetitions.
      3. For the parts of the code that involve a changing variable value, we use a reference parameter, so the function body can effect a permanent change to the variable at the calling site.
      4. If the code can be viewed as producing a single value, we make that value the return value of the function.
    3. Here are the answers to these questions for the change-outputting segment of code above:
      1. The basic conditional logic structure is the same across all segments.
      2. The following are the differences:
        1. the denomination variable (dollars, quarters, etc.),
        2. the plural spelling of the denomination ("dollars", "quarters", etc.)
        3. the singular spelling of the denomination ("dollar", "quarter", etc.)
      3. This code does not involve changing the value of any variables.
      4. The code does not produce a value, since it just performs output using cout.
    4. Based on these answers, here are the declaration, invocations, and definition of the function that generalizes the repeated code segment:
      1. Function declaration (i.e., function prototype):
        
        
        
        ////
        //
        // Function OutputDenomination outputs the amount of a particular denomination
        // to the terminal, if the amount is non-zero.  The inputs are the integer
        // amount of the denomination, a string for the singular spelling of the
        // denomination name, and a string for the plural spelling of the denomination
        // name.
        //
        ////
        void OutputDenomination(
            int amount,                         // Total amount of change due
            char* singular_name,                // Singular spelling of denomination
            char* plural_name                   // Plural spelling of denomination
        );
        
        
        
      2. Function invocations (these replace the five repeated chunks of code):
        
        
        
            //
            // Output the results of the pieces of change computations.
            //
            OutputDenomination(dollars, "dollar", "dollars");
            OutputDenomination(quarters, "quarter", "quarters");
            OutputDenomination(dimes, "dime", "dimes");
            OutputDenomination(nickels, "nickel", "nickels");
            OutputDenomination(pennies, "penny", "pennies");
        
        
        
      3. Function definition:
        
        
        
        void OutputDenomination(int amount, char* singular_name, char* plural_name) {
            if (amount != 0) {
                if (amount == 1) {
                    cout << amount << " " << singular_name << endl;
                }
                else {
                    cout << amount << " " << plural_name << endl;
                }
            }
        }
        

  3. Another functionizing example.
    1. Now consider the following piece of change-making code:
      
      
      
          //
          // 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);
      
      
      
    2. The repetitive computation here is only two lines of code, so it is not quite as compelling a candidate for functionizing as the previous case.
      1. Nevertheless, it is a good example of a repetitive computation worth functionizing.
      2. Plus in this case, we need to use reference parameters in the function definition.
    3. Here are the answers to the questions about functionizing a repetitive piece of code:
      1. The use of the change variable and the arithmetic expressions are the same across each code segment.
      2. The names of the denomination variables and the amounts of the denominations are different in each segment.
      3. The code does involve changing variables.
      4. The code does not produce a single value (rather, it modifies two variables).
    4. Based on these answers, here are the declaration, invocations, and definition of the function that generalizes this repeated code segment:
      1. Function declaration (i.e., function prototype):
        
        
        
        ////
        //
        // Function ComputeDenomination computes the amount of change due in a
        // particular denomination and updates the change due by subtracting the
        // computed amount.  The inputs are an empty integer amount variable, an
        // integer total amount variable, and the integer value of the denomination.
        // The outputs are the computed amount in the first parameter and the updated
        // change in the second parameter.
        //
        ////
        void ComputeDenomination(
            int& amount,                        // Amount of denomination
            int& change,                        // Total change due
            int denomination_value              // Value in cents of the denomination
        );
        
        
        
      2. Function invocations (these replace the five repeated chunks of code):
        
        
        
            //
            // Compute the number of each denomination of change and decrement the
            // change due by the amount computed each time.
            //
            ComputeDenomination(dollars, change, 100);
            ComputeDenomination(quarters, change, 25);
            ComputeDenomination(dimes, change, 10);
            ComputeDenomination(nickels, change, 5);
            pennies = change;
        
        
        
      3. Function definition:
        
        
        
        void ComputeDenomination(int& amount, int& change, int denomination_value) {
            amount = change / denomination_value;
            change = change - (amount * denomination_value);
        }
        
    5. Observations:
      1. In order for the change and denomination variables to be change within the function, they must be declared as reference parameters.
      2. The two lines of computation are structurally the same in terms of the assignment statements and arithmetic computation.
      3. Given the mnemonic names of the parameters, the function reads rather nicely as a generalized computation.

  4. Some analogies between human work doers versus program work doers.
    1. When humans do real work (as opposed to attend meetings or class lectures), they generally do so individually.
    2. In this sense, a function in a program is like a person doing some real work.
    3. Now, when people do work they generally need to receive inputs and they produce outputs.
    4. Suppose, for example, your boss wants you update a document.
      1. The boss gives as input the document to be updated.
      2. You produce as output a new version of the document.
      3. The function declaration for this piece of work looks like this:
        
        
        
        ////
        //
        // Given an old document, make the following changes and produce a new
        // document.  The changes are ... .
        //
        ////
        Document UpdateDocument(Document old_doc);
        
        
        
      4. That is, the function takes an old document as input and returns a new document as its output.
    5. As another example, suppose the nature of the work is not to produce a completely new document, but to edit a couple existing documents by marking them up.
      1. In this case your boss gives you the two documents, you mark them up, and return them.
      2. The function declaration for this piece of work looks like this:
        
        
        
        ////
        //
        // Given two old document, make the following editorial changes and return the
        // marked up documents.  The changes are ... .
        //
        ////
        void MarkupDocuments(Document& old_doc1, Document& old_doc2);
        
        
        
      3. Here the two documents are input as reference parameters, since they are to be directly edited in the function.
    6. As a third example, suppose the boss tells you to produce an entirely new document, based on information you must generate yourself.
      1. In this case you receive no inputs, and produce a new document as output.
      2. Here is the function declaration:
        
        
        
        ////
        //
        // Produce a new document, containing the following information ... .
        //
        ////
        Document ProduceNewDocument();
        
        
        
    7. As final example, suppose your boss tells you that you must present your ideas on a particular subject at an upcoming staff meeting.
      1. The boss does not need any specific document from you, just a verbal presentation.
      2. Also, no specific input is provided, since you are assumed to be able to come up with all the information you need yourself.
      3. Here is the function declaration:
        
        
        
        ////
        //
        // Give an oral presentation on the following material ... .
        //
        ////
        void GiveOralPresentation();
        
        
        
      4. The analogy here is to a function that uses cout to "present" its output information, without producing any specific data outputs.
    8. In general:
      1. Functions need inputs, unless they produce an entirely new piece of data based on information that is entirely computed within the function.
      2. Functions need to produce output, unless they produce effects only by doing external output via cout or file output.
      3. Functions that produce a single data value are best written as value-returning functions.
      4. Functions that need to produce more than one output and/or modify a variable to produce output use reference parameters.

  5. Program scoping made simple
    1. You can skip pages 389-404 in Chapter 8 of the textbook.
    2. Here is all you need to know about C++ scope and lifetime.
      1. In CSC 101 programs, variables are declared in exactly one place -- at the beginning of the block of a function.
      2. The scope of a variable, that is where it can be used, is within the block of the function where it is declared.
      3. The lifetime of a variable, that is when its memory is active, is during the time that its function is active.
        1. When a function is inactive, i.e., after it returns, its variables have no active memory.
        2. Therefore, as noted last week, variables must be reinitialized each time their function is invoked.

  6. Some differences of opinion between Fisher and textbook.
    1. The module (i.e., box) in a concrete design is always identical with a function (contrast "When to Use a Function" on page 325).
    2. Designwise, a value-returning function is superior to a void function with single reference parameter, except when C++ rules otherwise make a value-returning function impossible.
      1. A specific case where a value-returning function will not work is for functions that need to return a file variable, i.e., an ifstream or ofstream.
      2. In such cases, a reference parameter must be used to return the ifstream or ofstream value.
    3. Fisher categorically disagrees with the following statement on page 337:
      "Sometimes it's useful for documentation purposes to supply names for the parameters [in a function prototype}."
      1. It is indispensable for documentation purposes to supply names for the parameters in a function prototype.
      2. Further, it is required by convention for all CSC 101 programs to do so.
    4. Global variables are strictly illegal in CSC 101 programs.


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