Today's lab explores some C++ statements that we can use to write more sophisticated functions. The exercise consists of two parts: In the first part, we examine a problem whose solution requires more complicated behaviors than we have seen thus far, and introduce the new statements to elicit that behavior. In the second part, we introduce a new program-development tool called the debugger and use it to study the behavior of the new statements.
Our problem today is to write an interactive payroll program, that a small-business owner might use to simplify computing the payroll at his or her business. For the sake of simplicity, we will assume that all employees at the business are hourly employees.
Recall that object-centered design involves several stages:
Today's exercise is to use these stages to develop a program that solves our payroll problem.
As always, spending a bit of time planning how to attack our problem will result in a better solution. To do so, we follow the steps of object-centered design.
Behavior. We can begin by visualizing and writing down how we want our program to behave. One approach is to have our program behave something like the following:
This program computes the payroll interactively. To begin, enter the number of employees: 3 Enter the name, hours and rate for employee 1: Joe 25 5.25 Joe 131.25 Enter the name, hours and rate for employee 2: Mary 35 6.15 Mary 215.25 Enter the name, hours and rate for employee 3: Sue 45 6.15 Sue 292.125Put into words, our program should
display on the screen a greeting, followed by a prompt for the number of employees, which it should then read from the keyboard. For each employee, our program should then display a prompt for their name, hours and rate of pay. The program should then read these values from the keyboard, and compute and display the employee's pay, along with their name.
Objects. If we identify the nouns in this behavioral description, we get the following list:
| Description | Type | Kind | Name |
|---|---|---|---|
| The screen | ostream | varying | cout |
| A greeting | string | constant | -- |
| A prompt for input | string | constant | -- |
| The number of employees | int | varying | numberOfEmployees |
| The keyboard | istream | varying | cin |
| An employee's name | string | varying | name |
| An employee's hours of work | double | varying | hours |
| An employee's rate of pay | double | varying | rate |
| An employee's pay | double | varying | pay |
From this list, we can build a precise specification of how our program is to behave:
Input: The number of employees; Each employee's name, hours of work, and rate of pay. Output: Each employee's name and pay.Use this information to complete the specification of payroll.cpp.
Operations. If we identify the operations in our behavioral description, we get this list:
| Description | Predefined? | Name | Library? |
|---|---|---|---|
| display a string (greeting, prompts, labels) | yes | << | iostream |
| Read an int (numberOfEmployees) from the keyboard | yes | >> | iostream |
| read a string (name) from the keyboard | yes | >> | iostream |
| read a double (hours, rate) from the keyboard | yes | >> | iostream |
| compute an employee's pay, given their hours and rate from the keyboard | no | ?? | ?? |
| display a double (pay) on the screen | yes | << | iostream |
| repeat operations 3-6 once for each employee | yes | for | -- |
As indicated, most of these operations are provided for us by C++. Operations 1-4 and 6 should be familiar by now. There is no predefined C++ capability to perform operation 5, and since accounting for overtime pay makes it non-trivial, we will write a function to perform this operation. Since it seems like an operation that might be useful again some day, we will store it in a library. Finally, operation 7 involves a new C++ statement that we haven't seen before called the for statement, which provides a convenient way to repeat a group of statements a predetermined number of times.
Algorithm. Even without knowing the details of how we will compute the pay, we can organize our operations into an algorithm for our problem:
int main()
{
}
(These lines are already present in payroll.cpp,
after its opening documentation.)
To make sure that this much is correct before we add to it,
take a moment to translate payroll.cpp using
Build -> Compile.
Given the minimal C++ program, we are ready to encode our algorithm using stepwise translation. We therefore begin with the first step:
1. Via cout, display a greeting on the screen,
plus a prompt for the number of employees.
Coding: This step can be performed using a C++ output statement, which we have seen before:
cout << Value1 << Value2 << ... << ValueN;so add an output statement to payroll.cpp that displays the following message:
This program computes the payroll interactively. To begin, enter the number of employees:Don't forget to #include the file iostream along with using namespace std;!
Before proceeding to the next step, check the correctness of what you just wrote by recompiling your program. The compiler will alert you to any syntax errors in your statement. If an error is listed, you can infer that the error(s) lies in the text ' you just added, since the program was error-free before that. Find your error(s) within those lines and correct them.
When your source program compiles correctly, execute payroll to test that it displays the intended message. If not, the statements you have added contain logic errors. (i.e., the statements you have added are syntactically correct, but they don't accomplish their task correctly.) Compare your program's statements against the output produced by payroll and modify them as needed. When your program is error-free, proceed to the next step of our algorithm.
2. From cin, read an integer, storing it in numberOfEmployees.
Coding: We can encode this step in C++ using an input statement:
cin >> Var1 >> Var2 >> ... >> VarN;Add an input statement to your source program to perform step 1. Don't forget to declare a variable to store numberOfEmployees! Check that what you have added is free of syntax errors before continuing.
Note that we could use an assert() at this point to check that numberOfEmployees is non-negative. However, doing so is unnecessary, thanks to our next step.
3. For each value empNum in the range 1 to numberOfEmployees:
a. Via cout, display a prompt for the name, hours and rate
of employee empNum.
b. From cin, read a string, a double and a double,
storing them in name, hours and rate.
c. Compute pay, using hours and rate.
d. Via cout, display name and pay.
End loop.
Coding: Let's take this step a piece at a time, starting with the outer part (3) and then doing the inner parts (a-d).
3. For each value empNum in the range 1 to numberOfEmployees:
...
End loop.
The purpose of step 3 is to count from 1 to the number of employees, and repeat steps ad that many times. That is, if there are three employees, then steps ad should be repeated three times.
For situations like this that require repetitive behavior, C++ supplies the for statement. The for statement can use its own local variable, called a loop-control variable, to do the counting. A simplified general form of a for statement that counts from firstValue to lastValue is:
for (Type loopVar = firstValue; loopVar <= lastValue; loopVar++)
{
Statements
}
where loopVar is the loop-control variable,
and the Statements between the curley-braces
are called the body of the loop.
The behavior of this statement is as follows:
In our problem, we must count from 1 to the value stored in numberOfEmployees, using empNum as the name of the loop-control variable. To do so, we can write:
for (int empNum = 1; empNum <= numberOfEmployees; empNum++)
{
}
leaving its Statements empty for the moment.
Note that if the user enters a negative value for numberOfEmployees, the body of the loop will not be executed, because the loop's body is only executed if the condition controlling the loop evaluates to true, and a negative value for numberOfEmployees will make this condition false.
Add this to payroll.cpp and then check its syntax. When the compiler generates no errors, proceed to steps a-d.
3a. Via cout, display a prompt for the name, hours and rate
of employee empNum.
This is a normal output statement, like those we have seen before,
except that in addition to displaying a string, it displays the value of
our loop-control variable to generate an "employee number."
That is, if numberOfEmployees is 3, then our loop will
execute three times, so add an output statement to the body of the loop
that will generate the following:
Enter the name, hours and rate for employee 1: Enter the name, hours and rate for employee 2: Enter the name, hours and rate for employee 3:
Then check the syntax of what you have written using the compiler. When your program translates correctly, run it and enter 3 for the number of employees, to verify that what you have written is free of logic errors.
3b. From cin, read a string, a double and a double,
storing them in name, hours and rate.
This step can be encoded by adding a normal input statement to the body of the loop, after the output statement, so take a moment to do so.
However, before such a statement will compile correctly, the objects name, hours and rate must be declared. This raises an interesting question: Where should these objects be declared?
If we declare name, hours and rate within the body of the loop, then they will be redeclared anew each execution of the body, wasting time. For the sake of efficiency, they should instead be declared immediately before the for statement. That way, they will still be declared near their first use, but time will not be wasted reprocessinging their declarations every time the loop body executes.
Add the necessary statements to your program to (i) declare name, hours and rate(outside the loop), and (ii) fill these objects with values entered from the keyboard (inside the loop). To declare name as a string, you will need to include the string system file:
#include <string>Then check your code's syntax using the compiler, and continue when it is correct.
Anytime we program an input operation, we should consider the possibility of human error: what could the user do to foul up our program? There are several possibilities here:
Since any of these three errors will cause our program to generate incorrect pay amounts, our code should safeguard against them. To do so, add an assert() to the program that only allows the program to continue if hours is non-negative, and hours is 168 or less, and rate is non-negative. Use a single assert() call to do so. (Don't forget its #include directive!)
Then check the syntax of what you have written using the compiler, then run your program and test that your assert() halts the program if any of these three errors occur. When it correctly "catches" these errors, continue.
3c. Compute pay, using hours and rate.
This operation is not predefined, and so we will design and build a function to perform it.
Function Analysis. To compute pay for an arbitrary employee, our function needs their hours and rate. Since these values will differ from employee to employee, our function should receive these values from its caller.
Function Behavior. Our function should receive the hours and rate from its caller. If hours is less than or equal to the number of hours in a work week (40 in the U.S.), then our function should compute the total pay as hours times rate. Otherwise, it should compute the normal pay as the work week times the rate; compute the overtime pay as the overtime hours times the rate; and then compute the total pay as the sum of the overtime pay and the normal pay.
Function Objects. Ignoring nouns like "our function" and "caller", we can identify the following objects in this description:
| Description | Type | Kind | Movement | Name |
|---|---|---|---|---|
| hours of work | double | varying | received | hours |
| pay rate ($$/hour) | double | varying | received | pay |
| hours in a work week | double | constant | local | NORMAL_WEEK |
| total pay | double | varying | returned | totalPay |
| normal pay | double | varying | local | normalPay |
| overtime pay | double | varying | local | overtimePay |
| overtime hours | double | varying | local | hours - NORMAL_WEEK |
| overtime pay factor | double | constant | local | OVERTIME_FACTOR |
For each received object, we must provide a parameter to store that value. We can thus specify the behavior of our function as follows:
Receive: hours and rate, both doubles. Precondition: 0 <= hours and hours <= 168 and 0 <= rate. Return: the total pay, a double.These observations allow us to create the following stub for our function:
double ComputePay(double hours, double rate)
{
}
Each object that is neither received nor returned must be defined
as a local object, within the body of our function.
For example, OVERTIME_FACTOR will be a local constant
double object, defined to have the value 1.5;
while normalPay will be a local variable double object.
Since this seems like a function that might be reuseable some day, we will store its definition in a library named pay. Open pay.cpp, and write this stub there. Since other programs besides payroll.cpp may be calling this function (and not checking the precondition ahead of time), make the first line in the function an assert() call that checks the function's precondition. (Copy-and-paste your assert() from payroll.cpp.)
Before this stub, add an include directive:
#include "pay.h"to insert the header file of our pay library into the implementation file when it is compiled. Then add a prototype of this function to the header file pay.h and the documentation file pay.doc. Finally, copy-and-paste the function's specification into pay.doc.
Function Operations. From our behavioral description, we have these operations:
| Description | Defined? | Name | Library? | |
|---|---|---|---|---|
| 1 | receive hours and rate from caller | yes | function call mechanism |
built-in |
| 2 | compare hours and NORMAL_WEEK in less-than-or-equal relationship |
yes | <= | built-in |
| 3 | compute totalPay normally (hours times rate) |
yes | * | built-in |
| 4 | compute totalPay for overtime | |||
| 4a | compute normalPay (NORMAL_WEEK times rate) |
yes | * | built-in |
| 4b | compute overtimePay (hours minus NORMAL_WEEK times rate times OVERTIME_FACTOR) |
yes | -, *, * | built-in |
| 4c | compute totalPay (normalPay plus overtimePay) |
yes | + | built-in |
| 5 | if (2) is true, perform (3), otherwise perform (4) | yes | if | built-in |
| 6 | return totalPay | yes | return | built-in |
We have seen each of these operations before, except for operation (5), which introduces a new behavior in which we must select one group of statements or another, but not both. As we shall see shortly, the C++ if statement provides this behavior.
Function Algorithm. We can organize our objects and operations as follows:
Function Coding. To encode this algorithm in our ComputePay() stub, we must know the syntax of the C++ if statement. The general pattern is as follows:
if (BooleanExpression)
{
Statements1
}
else
{
Statements2
}
Here BooleanExpression is any expression that
evaluates to true or false, and Statements1 and
Statements2 are 1 or more C++ statements.
Statements1 is sometimes called the true section
of the if, and Statements2 is called its
false section.
The reason for these names is that when execution reaches an if
statement, its BooleanExpression is evaluated.
If it is true, then the statements in Statements1 run,
and those in Statements2 are skipped.
Otherwise, the opposite occurs -- the statements in
Statements1 are skipped, and those in
Statements2 run.
For our problem, we want BooleanExpression to perform the comparison described in operation (2). In the stub for ComputePay(), we can add the following if statement that compares hours and NORMAL_WEEK using the less-than-or-equal relationship:
if (hours <= NORMAL_WEEK)
{
}
else
{
}
As a parameter, object hours is defined for us,
but NORMAL_WEEK is not a parameter, and so must be declared.
Add a statement at the beginning of the function that declares
NORMAL_WEEK as a constant whose value is 40
(or whatever the normal working week is in your country).
To check this much, compile pay.cpp, and you should just get the one error for not providing any return value. Continue if this is the case, otherwise fix the other errors.
Coding operations (3) and (4) is straightforward, so add statements to the if's true section to perform (3), and add statements to its false section to perform (4). Note that each section of the if computes totalPay, however the true section uses the formula appropriate for no overtime, while the false section uses the formula appropriate for overtime. Since each section uses totalPay, it must be declared prior to both sections (i.e., before the if statement).
Finish the function by adding a return statement that returns the value of totalPay. Then recheck the correctness of ComputePay(), continuing when it is free of syntax errors.
Now that we have a syntactically correct ComputePay() function to perform step 2c of our program's algorithm, we are ready to code that step by calling our function. In the for loop within payroll.cpp, add an assignment statement with a call to ComputePay(). Don't forget to pass it hours and rate as arguments!
To check the correctness of our call, build payroll.exe which will compile payroll.cpp and link its object file to that of pay.cpp. When what you have written is free of syntax errors, continue to the final step of our algorithm.
3d. Via cout, display name and pay.
This step is easily implemented using an output statement. Take a few moments and add an output statement so that if employee Chris earned $150.75, the following is displayed:
Pay Chris $150.75Use whatever symbol is appropriate for your country's currency.
When you are finished, use Build to retranslate our program. Then test it a few times using sample data, to check that it computes correct results.
Recall that program maintenance is the final stage of program development, in which modifications and improvements are added during its lifetime. Some studies have shown that the cost to write a program is only 20% of its total cost -- the rest is consumed by maintenace! One of the goals of object-oriented orogramming is to reduce this maintenance cost, by writing code that is reusable.
To simulate program maintenance, we can improve payroll by modifying it so that it displays pay in monetary format (i.e., with 2 decimal digits instead of the default number). The number of decimal digits in a real number is called the precision of that number. To show only two decimal digits, we must alter the default precision. As we have seen before, this can be done with an I/O manipulator called setprecision(), whose pattern is:
setprecision( IntegerExpression )In addition to setting the precision, certain status indicators (called flags) in an ostream must be set appropriately for monetary format. This is accomplished with two other manipulators
If
Modify your source program so that pay is displayed with precision 2,
and using the fixed and showpoint manipulators.
Don't forget to #include the iomanip library!
Then test the correctness of your modification.
While the compiler will inform us of any syntax errors
our program contains, it cannot detect logic errors.
The simplest way to check for logic errors is to examine the values
of the variables in our program as the program executes,
and verify that they are what they are supposed to be.
Visual C++ provides a simple way to check variable values,
using a special tool called a debugger.
A debugger allows you to execute a program statement-by-statement,
and examine the values of variables (or expressions) as the program runs.
To use the debugger, choose
Use F11 again to step into the program's main function.
Then take a moment to reposition your windows so that you can see both the
Developers Studio window and the payroll.exe window.
The yellow arrow should be pointing at the first statement in
payroll.cpp, which is an output statement.
Rather than stepping into the call to the insertion function (<<),
we should instead step over the call using
Using F10, step through the remainder of the program,
one line at a time.
If necessary, you can always use Debug -> Restart
to begin again at the program's beginning.
Note that when you reach the input statement, execution will not
proceed further until you switch to the payroll.exe window
and enter the required data.
Enter the data and continue stepping.
Note in particular how the program behaves when execution
reaches the for loop.
A nice feature of this debugger is that when you reach a declaration
statement (e.g., numberOfEmployees),
an entry for each object in that statement automatically
appears in the auto subwindow at the bottom of the
Developer's Studio window, and persists there until execution
reaches a line of code where that object is no longer accessible.
This allows you to examine the values of your program's variables
and see how the execution of a statement affects them.
As was mentioned earlier, you can always restart the debugger at
the beginning of the program by choosing Debug -> Restart.
However, if you continue stepping until you reach the end of the program,
one of the anomolies of the Visual C++ debugger (or is it a feature?)
is that you can keep stepping beyond the end of the program
and Visual C++ will wrap back around and restart the program!
Using either of these methods, restart the debugger at the program's
beginning.
Sometimes stepping one line at a time can get tedious.
One way to skip past a block of code is to position the cursor
at the end of the block of code (i.e., where you want execution to stop)
and then choose
If you need to stop at the same place more than once
(e.g., you want to stop at the same line inside a loop every repetition),
an alternative is to set a breakpoint --
a place such that execution will stop whenever it reaches that line.
To set a breakpoint, position the cursor inside your for loop
in payroll.cpp and then click the Insert/Remove Breakpoint
button (this button is at the right end of the button bar and has a white
upraised hand icon signifying stop) or use the F9
keyboard shortcut.
You should see a red "stop sign" appear in the left margin of that line
within the loop.
If you then choose Debug -> Go or use the F5 shortcut,
the program will run until it encounters the breakpoint
(or until the program terminates).
You can remove a breakpoint by placing the cursor on the same line as
the breakpoint and clicking the Breakpoint button again.
Set a breakpoint at the line containing the call to ComputePay()
and then continue in the exercise.
While there are situations where we want to step over a function
(such as a system function like << or >>), there are other
situations where we want to step into the function.
For example, to examine the behavior of our ComputePay()
function, we might want to step into it, rather than over it.
To illustrate, run your program using Debug -> Go
and enter some non-overtime value for hours
and a value for wage.
The program should then continue to the breakpoint you just set.
If you now use F11 to step into ComputePay(),
the yellow arrow should "jump" from the call
to the first line of the function.
Note that the auto window changes, showing the function's parameters
and their values.
The objects from the main function disappear since they
cannot be accessed within ComputePay().
You can then step through the function using F11 or F10.
As you step past declaration statements
(e.g., totalPay), the objects being defined
should appear in the auto window.
Note how the program behaves when execution reaches the if
statement: the code that computes "normal" pay is performed,
while the code that computes overtime pay is skipped.
Use Debug -> Go to continue execution of the program,
and when it pauses for input again, this time enter an overtime
value for hours (and a value for wage).
When the program stops at the breakpoint again, step into
ComputePay() and note that this time control
skips the 'normal' part of the if but moves through
its overtime part.
If at any time you want to leave a function
(this is especially useful if you mistakenly step into a system function),
choose Debug -> Step Out
(or using its Shift+F11 shortcut)
which will complete the execution of the function currently executing
and stop back in its caller.
Whenever you want, you can quit using the debugger and return to the
normal Visual C++ environment by choosing
Debug -> Stop Debugging or its Shift+F5 shortcut.
The key things to remember in using the debugger are these:
From this exercise, you should now know the basics of using the debugger,
as well as have a better understanding of how if statements
and for loops work.
Forward to the Homework Projects
cout << Val1
<< setprecision(2)
<< fixed << showpoint
<< Val2 << ... << ValN;
then real values output before the manipulators
(i.e., Val1 )
will be displayed with the default formatting,
but real values following them (i.e., Val2 ... ValN)
will be displayed with the format specified.
(If your compiler is not ANSI-compliant, you may have to insert the older
manipulator setiosflags(ios::fixed | ios::showpoint)
instead of fixed and showpoint.)
Part II: Using the Debugger
Starting the Debugger
Build -> Debug -> Step Into
(or use the F11 shortcut) to step into the program.
A payroll.exe window will appear briefly as your program
begins to run, and then control will return to Visual C++.
Note that a yellow arrow has appeared in the left margin at the beginning
of the main function.
Note also that the Build menu has been replaced by a new
Debug menu.
Debug -> Step Over
or the F10 keyboard shortcut.
Remember, use F11 to step into a function,
F10 to step over it.
Examining Object Values
Restarting
Setting and Clearing Breakpoints
Debug -> Run to Cursor
which will execute that block and then stop execution where you have
positioned the cursor.
Stepping Into and Out Of a Function
Quitting the Debugger
Summary
I make it a practice to remember these two keyboard shortcuts
(F10 and F11),
because I use them most frequently when debugging.
I use the other debugging commands much less frequently,
and so I tend to use the menu or button choices for them:
Phrases you should now understand:
Object-Centered Design, Algorithm, Coding, Testing, Debugging,
Stepwise Translation, Selective Execution, If Statement,
Repetitive Execution, For Statement,
Separate Compilation, I/O Manipulator.
Submit:
Hard copies of your final version of payroll.cpp,
pay.h, pay.cpp and pay.doc;
plus a script file showing the execution of payroll.
Copyright 1998 by
Joel C. Adams. All rights reserved.