The second version of the Time example begins with the Chapter 5 Time example, extending it by adding pass-by-reference and pass-by-pointer. Specifically, it makes the following changes:
The modified program's organization still has one header and two source code files. Modifying the print function (requirement 1) and adding the read function (requirement 2) involves editing both "Time" files (Time.h and Time.cpp). Testing the changes requires editing the driver. The three-file organization and function patterns make it easy to convert the example from a structure to a class with member functions in a subsequent chapter.
Header files typically contain declarations, implying a program may include them many times without causing conflicts or programming errors. A structure (or class) specification qualifies as a declaration because it doesn't create objects or allocate memory. Similarly, function prototypes qualify as declarations because they don't have bodies for the compiler to translate to machine code and they don't use memory.
struct Time
{
int hours;
int minutes;
int seconds;
};
Time make_time(int h, int m, int s);
Time make_time(int s);
Time add(Time t1, Time t2);
//void print(Time t); // (a) pass-by-value (or pass-by-copy)
void print(Time& t); // (b) pass-by-reference
void read(Time* t); // (c) pass-by-pointer
// alternate versions
void print(Time* t); // (d) pass-by-pointer
void read(Time& t); // (e) pass-by-reference
#include <iostream>
#include <iomanip>
#include "Time.h"
using namespace std;
Time make_time(int h, int m, int s) // (a)
{
Time temp;
temp.hours = h;
temp.minutes = m;
temp.seconds = s;
return temp;
}
Time make_time(int s) // (b)
{
Time temp;
temp.hours = s / 3600;
s %= 3600; // shortcut for s = s % 3600;
temp.minutes = s / 60;
temp.seconds = s % 60;
return temp;
}
Time add(Time t1, Time t2)
{
int i1 = t1.hours * 3600 + t1.minutes * 60 + t1.seconds;
int i2 = t2.hours * 3600 + t2.minutes * 60 + t2.seconds;
return make_time(i1 + i2);
}
//void print(Time t)// (a) void print(Time& t) // (b) { cout.fill('0'); cout << t.hours << ":" << setw(2) << t.minutes << ":" << setw(2) << t.seconds << endl; cout.fill(' '); } void read(Time* t) // (c) { cout << "Please enter the hours: "; cin >>t-> hours; cout << "Please enter the minutes: "; cin >>t-> minutes; cout << "Please enter the seconds: "; cin >>t-> seconds; } // Alternate implementations void print(Time* t) // (d) { cout.fill('0'); cout << t->hours << ":" << setw(2) << t->minutes << ":" << setw(2) << t->seconds << endl; cout.fill(' '); } void read(Time& t) // (e) { cout << "Please enter the hours: "; cin >> t.hours; cout << "Please enter the minutes: "; cin >> t.minutes; cout << "Please enter the seconds: "; cin >> t.seconds; }
In a bottom-up implementation, programmers use "drivers" to test functions. They frequently "hard code" argument values in the calls because the drivers don't solve "real" or application problems - they are just a sequence of test calls. Programmers often follow a cyclic development process of implementing and testing a function or a small set of functions. Cyclic development has at least two advantages:
Following a cyclic process has advantages, but a functional "critical mass" is necessary for any testing. Specifically, the driver must build, populate (i.e., fill with data), and display objects. When a driver can perform these basic tasks, it can begin testing the (typically) more complex process functions.
#include <iostream>
#include "Time.h"
using namespace std;
int main()
{
Time t = make_time(3666);
Time t;
read(& t);
print(t);
Time s = make_time(1, 30, 4);
Time s;
read(& s);
print(s);
Time u = add(t, s); // adds two Time structs, stores the sum in u
print(u); // prints the sum
return 0;
}
| View | Download | Comments |
|---|---|---|
| Time.h | Time.h | The Time structure specification and function prototypes |
| Time.cpp | Time.cpp | The Time function definitions |
| driver.cpp | driver.cpp | A driver testing the Time functions |