|
|
In this laboratory assignment we will consider the following:
A. Object-Oriented Programming
B. Data Encapsulation
C. C++ Classes
D. C++ Class Syntax
Previously, we have viewed software development using functional (or procedural)
oriented programming. In this approach, software is developed by decomposing
the solution to a problem into functions. These functions represent
the step-by-step procedures, called algorithms, which solve the problem.
Usually data structures (like arrays or strings) are a secondary concern
and are selected after the project has been broken down into functional modules.
There are other approaches to software development. One such approach
is called object-oriented programming (OOP).
In the OOP approach, software objects are used. A software object is considered to be a self-contained computing entity with its own data and its associated operations or methods. In real life, we deal with physical objects all the time. When we sit in our chair (an object) at our desk (an object), we might consult our calendar (an object) to see what we must do today. Then we might make a few calls using our phone (an object) after consulting our phone book (an object) to look up numbers and so on.
Object-oriented programming (OOP) mimics real life situations by building software using software objects. The idea is to use software objects which closely correspond to real objects in the application. Thus in our example above, we could visualize both the calendar and the phone book and possibly the desk as software objects. Probably "today" would be a date object as well.
In the object-oriented approach to programming, a problem is decomposed
into a well-defined set of objects that can be used to model the application.
Once the objects are determined, consideration is given to exactly what data
each will encapsulate and what operations are needed for the data.
Finally the relationship among the objects is established.
OOP has several advantages. Listed below are a few advantages taken from Wang [1].
Simplicity: Because software objects model real world objects in the application domain, the complexity of the program is reduced and the program structure is more clear and simple.
Reusability: Objects can be reused in different programs. A table-building object, for instance, can be used in any program that requires a table of some sort. Thus, programs can be built from prefabricated and pretested components in a fraction of the time required to build new programs from scratch.
Maintainability: Objects can be maintained separately, making locating and fixing problems and adding "bells and whistles" easy.
Several languages have been developed which are OOP languages. One such language is Smalltalk. It is considered to be a pure object-oriented language, a language in which everything is an object. All actions in a pure object-oriented language are produced by passing messages. A message is a request for an object to perform one of its operations on some of its data.. Other languages like C++ are called hybrid languages because they support a combination of object-oriented and traditional procedure-oriented programming.
C++ was designed by Bjarne Stroustrup of AT&T Bell Labs in the early 1980s. It was designed to incorporate the object-oriented paradigm into the C programming language. C++ has become a predominant OOP language because it provides a well-designed set of language constructs which fully support OOP programming. Secondly, it is an extension of the popular, standardized ANSI C.
In this lab, we will introduce C++ objects and start using them in our
problem solving. However, we will not truly use the object-oriented
approach to programming in this class. We will still predominantly use
procedure-oriented programming and we will only use objects to represent the
data structures which we will need in our applications. We will gradually
work our way from the procedural oriented approach to the object-oriented
approach in this and subsequent classes.
Consider an object most of us use -- an automobile. We know that each automobile has a certain model type, was built in a certain year, was made by a particular manufacturer, has a steering wheel, an ignition, is either straight shift or automatic and so on. This set of features can be thought of as "data" features describing an automobile object. The automobile object also has certain functions or "operations" which it should perform. When we "start it", the motor is supposed to start. When we put it in "drive", it is supposed to move forward. When we put it in "reverse", it is supposed to go backwards. It doesn't matter what kind of an automobile we have, we can hopefully depend on certain basic functions and features (data) being built in. This wrapping together of data features and functions is an example of encapsulation. This situation is analogous to the concept of data encapsulation which we use in computing. Using this concept we view the data which composes the data structure and the functions (also called methods or operations) on these data items as "encapsulated" or bound together in a single program unit.
The operations may require certain inputs and certain assumptions to obtain the desired results. For example, to "start" the car, we assume it is in the correct gear. Then we must insert the correct key in the ignition and turn the key to obtain the results we want--namely the automobile will start. If we use the wrong key or don't have the car in the correct gear, the automobile won't start. Similarly in computing, the operations which are bound with an object may have certain inputs and assumptions which must be provided before the correct results will be obtained.
We consider the automobile as a machine whose internal workings are not normally visible. When we insert the correct key in the ignition and turn it, the car starts. What goes on inside the machine is unknown and unimportant as long as the car starts. We use this concept in computing also. The process of "hiding" implementation details from a higher-level module is called information hiding. When we use an object, we are normally concerned with how to interface with the object (the public interface) rather than with the internal workings of the object. The public interface of an object is composed of the collection of functions and features which allow us to use the object effectively. This public interface completely defines how to use the object (similar to a manual which comes with our automobile describing how to operate and maintain the vehicle). The public interface to the car object includes details on "starting" the car, putting the car in "drive", and in "reverse". The internal (private) data or functions in an object are unavailable to the public. This would be like disabling the hood release so we would not be able to access internal parts of an automobile (which would be a good idea in some cases). An object organizes data and related functions (methods) into a black box, which hides the internal data structures, representations, and procedures from outside view.
As previously described, objects are used to closely model actual objects in a real problem to be solved. In C++, the class construct is used to define an object. Using the class, the object's data and associated operations can be collected (encapsulated) into one unit.
Consider a "clock" object. We might use this object to maintain
and display the time of day. We could use this object in any software
in which the time of day was necessary. Of course, the clock object
consists of the data and operations(methods) associated with the data.
Thus a clock might be viewed as:
|
hour minute second am/pm |
(in the range 1-12) (in the range 0-59) (in the range 0-59) (0 if am, 1 if pm) |
|
setClock displayStandard displayMilitary incrementClock |
(i.e. 1:30:45 pm) (i.e. 13:30:45) |
We could set a clock, say to 1:30:45 pm, by passing the method setClock arguments for the hour, minute, second and whether it is am or pm. The actual representation of the data is hidden from the user. Sending the message displayStandard would cause the time to be displayed in the form 1:30:45 pm. Sending the message incrementClock would add one second to the clock time to make it change to 1:30:46 pm.
Now we will learn how to implement the "clock" object using C++. This is accomplished with the C++ class construct. The class consists of two sections of code, a definition section, and an implementation section. Below is the definition section for a class called Clock.
//The Clock class definition starts here. class Clock { public: //function prototypes of member functions (methods) //Set the clock to the specified time void setClock (int h, int m, int s, int aOrP); //Display the time in standard notation void displayStandard(); //Display the time in military notation void displayMilitary(); //Increment the time by one second void incrementClock(); private: //declarations of data members that are private int hr, //an hour in the range 1 - 12 min, //a minute in the range 0 - 59 sec, //a second in the range 0 - 59 amPm; //is the time AM or PM }; //The Clock class definition ends with the closing brace and semicolon.
As you can see, the definition section begins with the line that starts with the C++ reserved word class and consists of everything between the opening brace, {, up to and including the semicolon after the closing brace, }. This definition section includes a public and a private section. The public section describes the public interface of the class (remember the car manual). The public interface includes variables, types, constants, and function prototypes that a programmer needs to know to use the class. The private section includes variables, constants, data types, and function prototypes which are to be hidden (not to be used) by other program components except those in the class definition. Whether in the public or private section, variables and constants are referred to as data members and functions are referred to as function members.
The definition section for a class is usually placed in a .h file and is included in appropriate program files when the class definition is used. The Clock class is in the file inlab6a.h in the 2170 account. Copy this file to your account and examine it. This file can be included in a program file with the #include directive:
#include "inlab6a.h"
This file will need to be included in the implementation section of the class which will be discussed next and it will also need to be included in any main program that utilizes the class. The implementation section is usually contained in a different file other than the file containing the main function. Thus both the implementation file and the main function file must have the #include directive. However, if an application program includes this header file more than once, the compiler issues an error message that there is an attempt to redefine a type. Thus we surround the definition with preprocessor-directives as follows:
//Eliminate the problem of multiple inclusions #ifndef CLOCK_H #define CLOCK_H //the class definitions come here . . . #endif
Before inlab6a.h is included initially, the constant CLOCK_H is not defined. Thus the #define directive causes the constant CLOCK_H and the class Clock to become defined. For subsequent inclusions, the preprocessor notes that CLOCK_H has a definition and does not pass the remainder of the file to the compiler so the Clock class is not redefined.
In addition to the definition section, an implementation section must be developed which defines each function included in the class definition. The implementation section for a class is usually placed in a separate file and the Clock class implementation can be found in the file inlab6a.cc.
The implementation of the setClock function is as follows:
//Function: setClock() //Purpose: This function assigns the time specified by the user. // No error checking is done to check for illegal dates. // Arguments include the hour (h), minute (m), second(s) // and aOrP (0 means am and 1 means pm). void Clock::setClock(int h, int m, int s, int aOrP) //IN IN IN IN { //hr, min, sec and amPm are private data members hr = h; min = m; sec = s; amPm = aOrP; }
As you can see from this definition, we are just defining the function (method), setClock, which appears in the definition section of the Clock class. Note that the method has direct access to the private data members of the class. Functions outside the implementation file do not! The main difference between functions in the implementation file and functions outside the implementation file is the function heading. Consider the setClock heading.
void Clock::setClock(int h, int m, int s, int aOrP)
The operator :: is used and is preceded by the name of the class in which setClock() is a method. The operator :: is called the scope resolution operator. It is possible for several different classes to have member functions with the same name. The scope resolution operator is like the dot operator that was introduced (and we will see again shortly) in the lab on structs. The scope resolution operator is used to tell the compiler to what class a member function belongs. Thus when Clock:: precedes the name of the function setClock, we automatically know that setClock is a member function in the class Clock.
Now, how do we use this class definition and implementation in a program? The class definition defines the interface to an object and what data should be in the object. To declare and use objects of type Clock, consider the following simple main function:
#include <iostream> #include "inlab6a.h" using namespace std; int main() { //Declare two Clock objects Clock myClock, newClock; //Set the time of myClock to 12:45:30 am. myClock.setClock(12, 45, 30, 0); //Set the time of the newClock object to 2:15:30 pm. newClock.setClock (2, 15, 30, 1); //Display the time of myClock in both standard and //military format cout << "The time on the Digital Clock (in standard form) is: " myClock.displayStandard(); cout << "\nThe time in military format is: "; myClock.displayMilitary(); //Set the two times to the same time myClock = newClock; return 0; }
Notice that the dot operator is used to select a particular member function to invoke. This is how "messages" are passed to an object in C++. Thus,
sends the setClock message to the object myClock.myClock.setClock(12,45,30,0);
In the above function, notice the last line before the return statement. This line is valid and copies all data fields from the object NewClock to the object myClock. It is valid and accomplishes what we desire in this example because we have simple data types making up the two objects.
Note that nowhere in our main function do we try to access the data variables hr, sec, and min which were defined in the Clock class. In fact, we could not do so because these data members were placed in the private section of the class which means that only members of the class can access them. Thus we could not incorporate the following statements in the main function:
//This is an invalid member access myClock.hr = 2;
Run the program (the executable is contained in inlab6a). Read the program and make sure you understand the code. What does the program do?
aCC inlab6a.cc main6a.cc -o inlab6a