Time Class Case Study

Our first example (Figs. 9.19.3) creates class Time and a driver program that tests the class. You have already created several classes in this book. In this section, we review many of the concepts covered in Chapter 3 and demonstrate an important C++ software engineering conceptusing a "preprocessor wrapper" in header files to prevent the code in the header from being included into the same source code file more than once. Since a class can be defined only once, using such preprocessor directives prevents multiple-definition errors.

Figure 9.1. Time class definition.

 1 // Fig. 9.1: Time.h
 2 // Declaration of class Time. 
 3 // Member functions are defined in Time.cpp
 5 // prevent multiple inclusions of header file
 6 #ifndef TIME_H
 7 #define TIME_H
 9 // Time class definition
10 class Time
11 {
12 public:
13 Time(); // constructor
14 void setTime( int, int, int ); // set hour, minute and second
15 void printUniversal(); // print time in universal-time format
16 void printStandard(); // print time in standard-time format
17 private:
18 int hour; // 0 - 23 (24-hour clock format)
19 int minute; // 0 - 59
20 int second; // 0 - 59
21 }; // end class Time
23 #endif

Time Class Definition

The class definition (Fig. 9.1) contains prototypes (lines 1316) for member functions Time, setTime, printUniversal and printStandard. The class includes private integer members hour, minute and second (lines 1820). Class Time's private data members can be accessed only by its four member functions. Chapter 12 introduces a third access specifier, protected, as we study inheritance and the part it plays in object-oriented programming.

Good Programming Practice 9.1

For clarity and readability, use each access specifier only once in a class definition. Place public members first, where they are easy to locate.

Software Engineering Observation 9.1

Each element of a class should have private visibility unless it can be proven that the element needs public visibility. This is another example of the principle of least privilege.

In Fig. 9.1, note that the class definition is enclosed in the following preprocessor wrapper (lines 57 and 23):

// prevent multiple inclusions of header file
#ifndef TIME_H
#define TIME_H


When we build larger programs, other definitions and declarations will also be placed in header files. The preceding preprocessor wrapper prevents the code between #ifndef (which means "if not defined") and #endif from being included if the name TIME_H has been defined. If the header has not been included previously in a file, the name TIME_H is defined by the #define directive and the header file statements are included. If the header has been included previously, TIME_H is defined already and the header file is not included again. Attempts to include a header file multiple times (inadvertently) typically occur in large programs with many header files that may themselves include other header files. [Note: The commonly used convention for the symbolic constant name in the preprocessor directives is simply the header file name in upper case with the underscore character replacing the period.]

Error-Prevention Tip 9.1

Use #ifndef, #define and #endif preprocessor directives to form a preprocessor wrapper that prevents header files from being included more than once in a program.

Good Programming Practice 9.2

Use the name of the header file in upper case with the period replaced by an underscore in the #ifndef and #define preprocessor directives of a header file.


Time Class Member Functions

In Fig. 9.2, the Time constructor (lines 1417) initializes the data members to 0 (i.e., the universal-time equivalent of 12 AM). This ensures that the object begins in a consistent state. Invalid values cannot be stored in the data members of a Time object, because the constructor is called when the Time object is created, and all subsequent attempts by a client to modify the data members are scrutinized by function setTime (discussed shortly). Finally, it is important to note that the programmer can define several overloaded constructors for a class.

Figure 9.2. Time class member-function definitions.

 1 // Fig. 9.2: Time.cpp
 2 // Member-function definitions for class Time.
 3 #include 
 4 using std::cout;
 6 #include 
 7 using std::setfill;
 8 using std::setw;
10 #include "Time.h" // include definition of class Time from Time.h
12 // Time constructor initializes each data member to zero.
13 // Ensures all Time objects start in a consistent state.
14 Time::Time()
15 {
16 hour = minute = second = 0;
17 } // end Time constructor
19 // set new Time value using universal time; ensure that
20 // the data remains consistent by setting invalid values to zero
21 void Time::setTime( int h, int m, int s )
22 {
23 hour = ( h >= 0 && h < 24 ) ? h : 0; // validate hour
24 minute = ( m >= 0 && m < 60 ) ? m : 0; // validate minute
25 second = ( s >= 0 && s < 60 ) ? s : 0; // validate second
26 } // end function setTime
28 // print Time in universal-time format (HH:MM:SS)
29 void Time::printUniversal()
30 {
31 cout << setfill( '0' ) << setw( 2 ) << hour << ":"
32 << setw( 2 ) << minute << ":" << setw( 2 ) << second;
33 } // end function printUniversal
35 // print Time in standard-time format (HH:MM:SS AM or PM)
36 void Time::printStandard()
37 {
38 cout << ( ( hour == 0 || hour == 12 ) ? 12 : hour % 12 ) << ":"
39 << setfill( '0' ) << setw( 2 ) << minute << ":" << setw( 2 )
40 << second << ( hour < 12 ? " AM" : " PM" );
41 } // end function printStandard

The data members of a class cannot be initialized where they are declared in the class body. It is strongly recommended that these data members be initialized by the class's constructor (as there is no default initialization for fundamental-type data members). Data members can also be assigned values by Time's set functions. [Note: Chapter 10 demonstrates that only a class's static const data members of integral or enum types can be initialized in the class's body.]

Common Programming Error 9.1

Attempting to initialize a non-static data member of a class explicitly in the class definition is a syntax error.

Function setTime (lines 2126) is a public function that declares three int parameters and uses them to set the time. A conditional expression tests each argument to determine whether the value is in a specified range. For example, the hour value (line 23) must be greater than or equal to 0 and less than 24, because the universal-time format represents hours as integers from 0 to 23 (e.g., 1 PM is hour 13 and 11 PM is hour 23; midnight is hour 0 and noon is hour 12). Similarly, both minute and second values (lines 24 and 25) must be greater than or equal to 0 and less than 60. Any values outside these ranges are set to zero to ensure that a Time object always contains consistent datathat is, the object's data values are always kept in range, even if the values provided as arguments to function setTime were incorrect. In this example, zero is a consistent value for hour, minute and second.

A value passed to setTime is a correct value if it is in the allowed range for the member it is initializing. So, any number in the range 023 would be a correct value for the hour. A correct value is always a consistent value. However, a consistent value is not necessarily a correct value. If setTime sets hour to 0 because the argument received was out of range, then hour is correct only if the current time is coincidentally midnight.

Function printUniversal (lines 2933 of Fig. 9.2) takes no arguments and outputs the date in universal-time format, consisting of three colon-separated pairs of digitsfor the hour, minute and second, respectively. For example, if the time were 1:30:07 PM, function printUniversal would return 13:30:07. Note that line 31 uses parameterized stream manipulator setfill to specify the fill character that is displayed when an integer is output in a field wider than the number of digits in the value. By default, the fill characters appear to the left of the digits in the number. In this example, if the minute value is 2, it will be displayed as 02, because the fill character is set to zero ('0'). If the number being output fills the specified field, the fill character will not be displayed. Note that, once the fill character is specified with setfill, it applies for all subsequent values that are displayed in fields wider than the value being displayed (i.e., setfill is a "sticky" setting). This is in contrast to setw, which applies only to the next value displayed (setw is a "nonsticky" setting).

Error-Prevention Tip 9.2

Each sticky setting (such as a fill character or floating-point precision) should be restored to its previous setting when it is no longer needed. Failure to do so may result in incorrectly formatted output later in a program. Chapter 15, Stream Input/Output, discusses how to reset the fill character and precision.

Function printStandard (lines 3641) takes no arguments and outputs the date in standard-time format, consisting of the hour, minute and second values separated by colons and followed by an AM or PM indicator (e.g., 1:27:06 PM). Like function printUniversal, function printStandard uses setfill( '0' ) to format the minute and second as two digit values with leading zeros if necessary. Line 38 uses a conditional operator (?:) to determine the value of hour to be displayedif the hour is 0 or 12 (AM or PM), it appears as 12; otherwise, the hour appears as a value from 1 to 11. The conditional operator in line 40 determines whether AM or PM will be displayed.

Defining Member Functions Outside the Class Definition; Class Scope

Even though a member function declared in a class definition may be defined outside that class definition (and "tied" to the class via the binary scope resolution operator), that member function is still within that class's scope; i.e., its name is known only to other members of the class unless referred to via an object of the class, a reference to an object of the class, a pointer to an object of the class or the binary scope resolution operator. We will say more about class scope shortly.

If a member function is defined in the body of a class definition, the C++ compiler attempts to inline calls to the member function. Member functions defined outside a class definition can be inlined by explicitly using keyword inline. Remember that the compiler reserves the right not to inline any function.

Performance Tip 9.1

Defining a member function inside the class definition inlines the member function (if the compiler chooses to do so). This can improve performance.

Software Engineering Observation 9.2

Defining a small member function inside the class definition does not promote the best software engineering, because clients of the class will be able to see the implementation of the function, and the client code must be recompiled if the function definition changes.

Software Engineering Observation 9.3

Only the simplest and most stable member functions (i.e., whose implementations are unlikely to change) should be defined in the class header.


Member Functions vs. Global Functions

It is interesting that the printUniversal and printStandard member functions take no arguments. This is because these member functions implicitly know that they are to print the data members of the particular Time object for which they are invoked. This can make member function calls more concise than conventional function calls in procedural programming.

Software Engineering Observation 9.4

Using an object-oriented programming approach can often simplify function calls by reducing the number of parameters to be passed. This benefit of object-oriented programming derives from the fact that encapsulating data members and member functions within an object gives the member functions the right to access the data members.

Software Engineering Observation 9.5

Member functions are usually shorter than functions in non-object-oriented programs, because the data stored in data members have ideally been validated by a constructor or by member functions that store new data. Because the data is already in the object, the member-function calls often have no arguments or at least have fewer arguments than typical function calls in non-object-oriented languages. Thus, the calls are shorter, the function definitions are shorter and the function prototypes are shorter. This facilitates many aspects of program development.

Error-Prevention Tip 9.3

The fact that member function calls generally take either no arguments or substantially fewer arguments than conventional function calls in non-object-oriented languages reduces the likelihood of passing the wrong arguments, the wrong types of arguments or the wrong number of arguments.

Using Class Time

Once class Time has been defined, it can be used as a type in object, array, pointer and reference declarations as follows:

Time sunset; // object of type Time
Time arrayOfTimes[ 5 ], // array of 5 Time objects
Time &dinnerTime = sunset; // reference to a Time object
Time *timePtr = &dinnerTime, // pointer to a Time object

Figure 9.3 uses class Time. Line 12 instantiates a single object of class Time called t. When the object is instantiated, the Time constructor is called to initialize each private data member to 0. Then, lines 16 and 18 print the time in universal and standard formats to confirm that the members were initialized properly. Line 20 sets a new time by calling member function setTime, and lines 24 and 26 print the time again in both formats. Line 28 attempts to use setTime to set the data members to invalid valuesfunction setTime recognizes this and sets the invalid values to 0 to maintain the object in a consistent state. Finally, lines 33 and 35 print the time again in both formats.

Figure 9.3. Program to test class Time.

(This item is displayed on page 488 in the print version)

 1 // Fig. 9.3: fig09_03.cpp
 2 // Program to test class Time. 
 3 // NOTE: This file must be compiled with Time.cpp.
 4 #include 
 5 using std::cout;
 6 using std::endl;
 8 #include "Time.h" // include definition of class Time from Time.h
10 int main()
11 {
12 Time t; // instantiate object t of class Time
14 // output Time object t's initial values
15 cout << "The initial universal time is ";
16 t.printUniversal(); // 00:00:00
17 cout << "
The initial standard time is ";
18 t.printStandard(); // 12:00:00 AM
20 t.setTime( 13, 27, 6 ); // change time
22 // output Time object t's new values
23 cout << "

Universal time after setTime is ";
24 t.printUniversal(); // 13:27:06
25 cout << "
Standard time after setTime is ";
26 t.printStandard(); // 1:27:06 PM
28 t.setTime( 99, 99, 99 ); // attempt invalid settings
30 // output t's values after specifying invalid values
31 cout << "

After attempting invalid settings:"
32 << "
Universal time: ";
33 t.printUniversal(); // 00:00:00
34 cout << "
Standard time: ";
35 t.printStandard(); // 12:00:00 AM
36 cout << endl;
37 return 0;
38 } // end main
 The initial universal time is 00:00:00
 The initial standard time is 12:00:00 AM

 Universal time after setTime is 13:27:06
 Standard time after setTime is 1:27:06 PM

 After attempting invalid settings:
 Universal time: 00:00:00
 Standard time: 12:00:00 AM

Looking Ahead to Composition and Inheritance

Often, classes do not have to be created "from scratch." Rather, they can include objects of other classes as members or they may be derived from other classes that provide attributes and behaviors the new classes can use. Such software reuse can greatly enhance programmer productivity and simplify code maintenance. Including class objects as members of other classes is called composition (or aggregation) and is discussed in Chapter 10. Deriving new classes from existing classes is called inheritance and is discussed in Chapter 12.

Object Size

People new to object-oriented programming often suppose that objects must be quite large because they contain data members and member functions. Logically, this is truethe programmer may think of objects as containing data and functions (and our discussion has certainly encouraged this view); physically, however, this is not true.

Performance Tip 9.2

Objects contain only data, so objects are much smaller than if they also contained member functions. Applying operator sizeof to a class name or to an object of that class will report only the size of the class's data members. The compiler creates one copy (only) of the member functions separate from all objects of the class. All objects of the class share this one copy. Each object, of course, needs its own copy of the class's data, because the data can vary among the objects. The function code is nonmodifiable (also called reentrant code or pure procedure) and, hence, can be shared among all objects of one class.

Introduction to Computers, the Internet and World Wide Web

Introduction to C++ Programming

Introduction to Classes and Objects

Control Statements: Part 1

Control Statements: Part 2

Functions and an Introduction to Recursion

Arrays and Vectors

Pointers and Pointer-Based Strings

Classes: A Deeper Look, Part 1

Classes: A Deeper Look, Part 2

Operator Overloading; String and Array Objects

Object-Oriented Programming: Inheritance

Object-Oriented Programming: Polymorphism


Stream Input/Output

Exception Handling

File Processing

Class string and String Stream Processing

Web Programming

Searching and Sorting

Data Structures

Bits, Characters, C-Strings and structs

Standard Template Library (STL)

Other Topics

Appendix A. Operator Precedence and Associativity Chart

Appendix B. ASCII Character Set

Appendix C. Fundamental Types

Appendix D. Number Systems

Appendix E. C Legacy Code Topics

Appendix F. Preprocessor

Appendix G. ATM Case Study Code

Appendix H. UML 2: Additional Diagram Types

Appendix I. C++ Internet and Web Resources

Appendix J. Introduction to XHTML

Appendix K. XHTML Special Characters

Appendix L. Using the Visual Studio .NET Debugger

Appendix M. Using the GNU C++ Debugger


C++ How to Program
C++ How to Program (5th Edition)
ISBN: 0131857576
EAN: 2147483647
Year: 2004
Pages: 627

Similar book on Amazon

Flylib.com © 2008-2020.
If you may any questions please contact us: flylib@qtcs.net