Art Show | Course Home | Homework | Labs | Programming | Schedule & Lecture Notes | Submit

Saint Louis University

Computer Science P125
Introduction to Computer Science

Michael Goldwasser

Spring 2005

Dept. of Math & Computer Science

Lab 07

Programmer-defined functions and variable scope


Getting started

We will ask you a series of questions during this lab. Please download this answer form file in which you can record your answers to the various questions posed in this lab. Please answer each question before going on to the next section of the lab. When you are completely finished, submit your answers electronically.

Overview

Our goal for this lab is to explore the idea of writing a programmer-defined function. As part of this we will explore the use of parameters, the use of a return value, as well as the issue of variable scope.

For exploring these issues, we will consider a very simple task this week. We will develop a program which prompts the user for an integer, prompts the user for a second integer, and then prints out the value of the first value minus the second. (sorry -- this isn't a very flashy lab this week, but we wish to focus on a simple example)

Without the use of functions, we could implement such a program quite directly, as:

#include <iostream>
using namespace std;

int main() {
   int first;
   int second;
   cout << "Enter the first integer: ";
   cin >> first;
   cout << "Enter the second integer: ";
   cin >> second;
   cout << first << " - "
        << second << " = "
        << (first-second) << endl;
}

Cut-and-paste the above program into your account. Compile and execute it and answer the following questions.

Question 1

  1. Describe what happens when you enter 5 and 3 for the inputs?
  2. Describe what happens when you enter 5.1 and 3 for the inputs?
  3. Describe what happens when you enter saint and louis for the inputs?

As you may have noticed, we have a bit of a problem when the user enters data which is not integral, as expected. It turns out that getting such input from a user in a reliable way is more complicated that it first seems. We will have to come back to this issue a bit later, and see a more robust way to do it.

As preparation for that point, let's first aim at rewriting our code so that it instead relies on a program-defined function we will call robustReadInt. We will start by leading you through some failed attempts at redesigning the program and will ask for you thoughts.

Consider the first (failed) attempt by simply moving two of the lines that were previously in the main function up to our newly defined function.

#include <iostream>
using namespace std;

void robustReadInt() {
   cout << "Enter the first integer: ";
   cin >> first;
}

int main() {
   int first;
   int second;
   robustReadInt();
   cout << "Enter the second integer: ";
   cin >> second;
   cout << first << " - "
        << second << " = "
        << (first-second) << endl;
}

Cut-and-paste the above program into your account. Try to compile the program and then answer the following questions.

Question 2

  1. What precise error message is given by the compiler?
  2. Explain in your own words the meaning and cause of the reported problem in this code?

Here is another attempt.

#include <iostream>
using namespace std;

void robustReadInt() {
   int first;
   cout << "Enter the first integer: ";
   cin >> first;
}

int main() {
   int first;
   int second;
   robustReadInt();
   cout << "Enter the second integer: ";
   cin >> second;
   cout << first << " - "
        << second << " = "
        << (first-second) << endl;
}

Cut-and-paste the above program into your account. Try to compile it. It should be syntactically valid.

Question 3

  1. Execute the program and enter 5 and 3 as the inputs. What is the exact output generated by the program?
  2. Execute the program and enter 50 and 30 as the inputs. What is the exact output generated by the program?
  3. Explain in your own words the reason for such observed output based upon the given source code?

Here is yet another attempt, this time declaring our function to have a return type.

#include <iostream>
using namespace std;

int robustReadInt() {
   int first;
   cout << "Enter the first integer: ";
   cin >> first;
}

int main() {
   int first;
   int second;
   robustReadInt();
   cout << "Enter the second integer: ";
   cin >> second;
   cout << first << " - "
        << second << " = "
        << (first-second) << endl;
}

Cut-and-paste the above program into your account.

Question 4

  1. Does the program have any syntactic errors (a.k.a, compiler errors)? If so, explain.
  2. If it compiled, execute the program on a few example cases. Does the program have any semantic errors (a.k.a. logical errors)? If so, explain what you observe and give your own explanation as to why.

Let's try again.

#include <iostream>
using namespace std;

int robustReadInt() {
   int first;
   cout << "Enter the first integer: ";
   cin >> first;
   return first;
}

int main() {
   int first;
   int second;
   robustReadInt();
   cout << "Enter the second integer: ";
   cin >> second;
   cout << first << " - "
        << second << " = "
        << (first-second) << endl;
}

Cut-and-paste the above program into your account.

Question 5

  1. Comparing this version of our source code to the previous version, what precisely is different?
  2. Execute the program and enter 5 and 3 as the inputs. What is the exact output generated by the program?
  3. Explain in your own words the reason for such observed output based upon the given source code?

And yet another attempt...

#include <iostream>
using namespace std;

int robustReadInt() {
   int first;
   cout << "Enter the first integer: ";
   cin >> first;
   return first;
}

int main() {
   int first;
   int second;
   first = robustReadInt();
   cout << "Enter the second integer: ";
   cin >> second;
   cout << first << " - "
        << second << " = "
        << (first-second) << endl;
}

Cut-and-paste the above program into your account.

Question 6

  1. Comparing this version of our source code to the previous version, what precisely is different?
  2. Execute the program and enter 5 and 3 as the inputs. What is the exact output generated by the program?
  3. Explain in your own words the reason for such observed output based upon the given source code?

At this point, we'd like to consider the fact that we do not simply want to use our new function to read the first value, but the second as well. It would be a poor design decision to create another almost identical version of the function for this purpose. So let's try to use the existing function as follows:

#include <iostream>
using namespace std;

int robustReadInt() {
   int first;
   cout << "Enter the first integer: ";
   cin >> first;
   return first;
}

int main() {
   int first;
   int second;
   first = robustReadInt();
   second = robustReadInt();
   cout << first << " - "
        << second << " = "
        << (first-second) << endl;
}

Cut-and-paste the above program into your account and recompile.

Question 7

  1. Execute the program and enter 5 and 3 as the inputs. What is the exact output generated by the program?
  2. Do you notice any other unusual aspects of the execution, when compared with some earlier versions? Explain.
  3. Did our function successfully read the first int?
  4. Did our function successfully read the second int?
  5. What is the significance of our use of the variable first within the context of the robustReadInt function? Explain how this effects your answer to the previous two questions.
  6. What would you suggest as a more meaningful name for that variable in a general context?

Let's consider the issue of the prompt which is given to the user before inputting a value. One approach is that we could have the prompt generated within main immediately before calling the function. But we preferred a design in which the prompt is generated from within the robustReadInt function. The most recent version of our code was our attempt to get such a behavior.

Please revise the previous version of the code so that it uses the more meaningful variable name within the robustReadInt function (as discussed in the previous set of questions) and so that it expects a single parameter which is a string which is used as a prompt. Then adjust the main function so that the two calls to the other function read as:

   first = robustReadInt("Enter the first integer: ");
   second = robustReadInt("Enter the second integer: ");

Cut-and-paste the above program into your account and recompile.

Question 8

Cut-and-paste your entire working code into your answer document.

This is the end of the formal lab. Please submit your single answer file electronically.


Extra Time?

We never got around to discussing one of the original problems, namely what happens when the user enters a non-integral expression when we were expecting an integral expressions (such as when user types saint louis when asked for the two values).

The first concept we need to introduce is that there is actually a way to tell whether an input extraction operation succeeded. In particular, an expression such as:

cin >> blah
has the effect of attempting to read data from the input stream into the given variable. It also turns out that that very same expression can be viewed syntactically as a boolean value. That boolean value will be equivalent to true if the extraction succeeded for the given data type, and false if it failed for one reason or another. Therefore we can modify our robustReadInt to be even more robust, by starting its body as:
// print desired prompt to user

// prepare to read data
int  value;
bool success;
success = (cin >> value);
In looking at this last line, there are two effects. An attempt is made to read data from the input stream with the resulting data stored into 'value'. Yet it will also be that 'success' is set to reflect whether or not the read was successful. (as we have seen, in the case where the read failed, the resulting 'value' itself is not meaningful).

Modifying your full code so that you check whether or not the read succeeded. Once you have done that, the more robust thing is to followup with a while loop that has the general behavior:

while not successful:
    give error message to user to explain.
    Repeat the original prompt.
    make another attempt at reading data (again recording whether you succeed).
Try to see if you can do this as well, to get a truly robust routine for reading an int from the user. You may discover there is one more catch. When you think you have the logic worked out, try it out. See what happens when you enter errant input.

Don't be discouraged if your code still doesn't work. There is even more to the story. When cin makes a failed attempt to read a particular data type it goes into an error mode and remains there until further notice. Furthermore the characters from the input stream which were considered but not interpreted correctly are not removed from the input stream after the failed attempt. Therefore, once cin fails once, it will continue to fail repeatedly, without even waiting for more input from the user.

To fix this, we need to take two more steps, shown in bold below.

while not successful:
    give error message to user to explain.
    Repeat the original prompt.
    cin.clear();
    cin.ignore(numeric_limits<int>::max(),'\n');
    make another attempt at reading data (again recording whether you succeed).
The clear command gets the object out of its error mode. Now it is ready to be used again. The ignore command says that characters should be taken out of the stream and ignored until reaching the next newline character. The point of this routine is to remove the errant characters that caused us trouble earlier from the stream. Without this second command, if we were to again try to read an int we would again fail because we'd be considering the same characters that we know are errant. Obviously, there is no reason that you should know this. But isn't in interesting that you need all of this to get a truly robust method?!

One of our moral in this final exercise is to show that coming up with truly robust code takes great effort in understanding the mechanisms and in describing how to behave when unforeseen issues arise. The other moral is to point out how good it is to be using a programmer-defined function in this case so that all of this effort only needs to be detailed in one place. Once that routine works, we can call it over and over again from various points in our code.

If you get this working, please feel free to submit your sourcecode. We're not offering any true extra credit but we'd be happy to give feedback if you wish.

Michael Goldwasser
CS-P125, Spring 2005
Last modified: Saturday, 26 February 2005
Art Show | Course Home | Homework | Labs | Programming | Schedule & Lecture Notes | Submit