• No results found

Higher-Level Abstraction and the C++ Class Type

Data Design and Implementation

HUMAN ANATOMY

2. To specify a place from which a value is to be retrieved: value = numbers[4];

2.3 Higher-Level Abstraction and the C++ Class Type

In the last section, we examined C++’s built-in data types from the logical view, the application view, and the implementation view. Now we shift our focus to data types that are needed in a program but not pro-

vided by the programming language.

The classtype is a construct in which the members of the class can be both functions and data; that is, the data members and the code that manipulates them are bound together within the class itself. Because the data are bound together with the operations, we can use one object to build another object; in other words, a data member of an object can be another object.

When we design an abstract data type, we want to bind the operations of the data type with the data that are being manipulated. The class is the perfect mechanism to implement an abstract data type because it enforces encapsulation. The class acts like the case around a watch that prevents us from accessing the works. The case is provided by the watchmaker, who can easily open it when repairs become necessary.

Classes are written in two parts, the specification and the implementation. The spec- ification, which defines the interface to the class, is like the face and knobs on a watch.

The specification describes the resources that the class can supply to the program.

Resources supplied by a watch might include the value of the current time and opera- tions to set the current time. In a class, the resources include data and operations on the data. The implementation section provides the implementation of the resources defined in the specification; it is like the inside of the watch.

Significant advantages are derived from separating the specification from its imple- mentation. A clear interface is important, particularly when a class is used by other members of a programming team or is part of a software library. Any ambiguity in an interface may result in problems. By separating the specification from its implementa- tion, we are given the opportunity to concentrate our efforts on the design of a class without needing to worry about implementation details.

Another advantage of this separation is that we can change the implementation at any time without affecting the programs that use the class (clientsof the class). We can make changes when a better algorithm is discovered or the environment in which the program is run changes. For example, suppose we need to control how text is displayed on screen. Text control operations might include moving the cursor to a particular location and setting text characteristics such as bold, blink, and underline. The algo- rithms required for controlling these characteristics usually differ from one computer system to another. By defining an interface and encapsulating the algorithms as

Class An unstructured type that encapsulates a fixed number of data components with the functions that manipulate them; the predefined operations on an instance of a class are whole assignment and component access

Client Software that declares and manipulates objects (instances) of a particular class

3Your system may use extensions different from .hand .cppfor these files—for example, .hppor .hxx(or no extension at all) for header files and .cxx, .c, or .Cfor implementation files.

member functions, we can easily move our program to a different system simply by rewriting the implementation. We do not have to change the rest of the program.

Because the class is such an important construct, we review its syntax and seman- tics in the next section. Most of you will be familiar with this material. Indeed, we used a class in Chapter 1.

Class Specification

Although the class specification and implementation can reside in the same file, the two parts of a class are usually separated into two files: The specification goes into a header

file (.h extension), and the implementation goes into a file with the same name but a

.cpp extension. This physical separation of the two parts of a class reinforces the logi- cal separation.3

We describe the syntax and semantics of the class type within the context of defin- ing an abstract data type Date.

// Declare a class to represent the Date ADT. // This is file DateType.h.

class DateType {

public:

void Initialize(int newMonth, int newDay, int newYear);

int YearIs() const; // Returns year

int MonthIs() const; // Returns month

int DayIs() const; // Returns day

private: int year; int month; int day; };

The data members of the class are year, month, and day. The scope of a class

includes the parameters on the member functions, so we must use names other than month, year, and day for our formal parameters. The data members are marked pri-

vate, which means that although they are visible to the human user, they cannot be

accessed by client code. Private members can be accessed only by the code in the imple- mentation file.

The member functions of the class are Initialize, YearIs, MonthIs, and DayIs.

They are marked public, which means that client code can access these functions.

Initializeis a constructor operation; it takes values for the year, month, and day and stores these values into the appropriate data members of an object (an instance of the class).4YearIs, MonthIs, and DayIsare accessor functions; they are member functions

that access the data members of the class. The const beside the accessor function

names guarantees that these functions do not change any of the data members of the objects to which they are applied.

Scope Rules in C++

The rules of C++ that govern who knows what, where, and when are called scope rules. Three main categories of scope exist for an identifier in C++: class scope, local scope, and global scope. Class scope refers to identifiers declared within a class declaration. Local scope is the scope of an identi- fier declared within a block (statements enclosed within {}). Global scope is the scope of an iden- tifier declared outside all functions and classes.

• All identifiers declared within a class are local to the class (class scope).

• The scope of a formal parameter is the same as the scope of a local variable declared in the outermost block of the function body (local scope).

• The scope of a local identifier includes all statements following the declaration of the identifier to the end of the block in which it is declared; it includes any nested blocks unless a local identifier of the same name is declared in a nested block (local scope).

• The name of a function that is not a member of a class has global scope. Once a global func- tion name has been declared, any subsequent function can call it (global scope).

• When a function declares a local identifier with the same name as a global identifier, the local identifier takes precedence (local scope).

• The scope of a global variable or constant extends from its declaration to the end of the file in which it is declared, subject to the condition in the last rule (global scope).

• The scope of an identifier does not include any nested block that contains a locally declared identifier with the same name (local identifiers have name precedence).

4At the implementation level from here on, we use the word objectto refer to a class object, an instance of a class type.

Class Implementation

Only the member functions of the class DateTypecan access the data members, so we

must associate the class name with the function definitions. We do so by inserting the

class name before the function name, separated by the scope resolution operator (::).

The implementation of the member functions goes into the file DateType.cpp. To

access the specifications, we must insert the file DateType.h by using an #include

directive.

// Define member functions of class DateType. // This is file DateType.cpp.

#include "DateType.h" // Gain access to specification of class void DateType::Initialize

(int newMonth, int newDay, int newYear) // Post: year is set to newYear.

// month is set to newMonth. // day is set to newDay. {

year = newYear; month = newMonth; day = newDay; }

int DateType::MonthIs() const

// Accessor function for data member month. {

return month; }

int DateType::YearIs() const

// Accessor function for data member year. {

return year; }

int DateType::DayIs() const

// Accessor function for data member day. {

return day; }

A client of the class DateTypemust have an #include "DateType.h" directive for the specification (header) file of the class. Note that system-supplied header files are

enclosed in angle brackets (<iostream>), whereas user-defined header files are

enclosed in double quotes. The client then declares a variable of type DateTypejust as it would any other variable.

#include "DateType.h" DateType today; DateType anotherDay;

Member functions of a class are invoked in the same way that data members of a struct are accessed—with the dot notation. The following code segment initializes two objects of type DateTypeand then prints the dates on the screen:

today.Initialize(9, 24, 2003); anotherDay.Initialize(9, 25, 2003);

cout << " Today is " << today.MonthIs() << "/" << today.DayIs() << "/" << today.YearIs() << endl;

cout << " Another date is " << anotherDay.MonthIs() << "/"

<< anotherDay.DayIs() << "/" << anotherDay.YearIs() << endl;

Member Functions with Object Parameters

A member function applied to a class object uses the dot notation. What if we want a member function to operate on more than one object—for example, a function that com- pares the data members of two instances of the class? The following code compares two instances of the class DateType:

enum RelationType {LESS, EQUAL, GREATER};

// Prototype of member function in the specification file. RelationType ComparedTo(DateType someDate);

// Compares self with someDate.

// Implementation of member function in the implementation file. RelationType DateType::ComparedTo(DateType aDate)

// Pre: Self and aDate have been initialized.

// Post: Function value = LESS, if self comes before aDate. // = EQUAL, if self is the same as aDate. // = GREATER, if self comes after aDate. {

if (year < aDate.year) return LESS;

else if (year > aDate.year) return GREATER;

else if (month < aDate.month) return LESS;

else if (month > aDate.month) return GREATER;

else if (day < aDate.day) return LESS;

else if (day > aDate.day) return GREATER;

else return EQUAL; }

In this code,yearrefers to theyeardata member of the object to which the function is applied; aDate.year refers to the data member of the object passed as a parameter. The object to which a member function is applied is calledself. In the function definition, the data members of self are referenced directly without using dot notation. If an object is passed as a parameter, the parameter name must be attached to the data member being accessed using dot notation. As an example, look at the following client code:

switch (today.ComparedTo(anotherDay)) {

case LESS :

cout << "today comes before anotherDay"; break;

case GREATER :

cout << "today comes after anotherDay"; break;

case EQUAL :

cout << "today and anotherDay are the same"; break;

}

Now look back at the ComparedTo function definition. In that code, year in the

function refers to the yearmember of today, and aDate.yearin the function refers to the yearmember of anotherDay, the actual parameter to the function.

Why do we use LESS, GREATER, and EQUAL when COMES_BEFORE, COMES_AFTER,

and SAMEwould be more meaningful in the context of dates? We use the more general

words here, because in other places we use functions of type RelationTypewhen com-

paring numbers and strings.

Difference Between Classes and Structs

In C++, the technical difference between classes and structs is that, without the use of the

reserved wordspublicandprivate, member functions and data are private by default

in classes and public by default in structs. In practice, structs and classes are often used differently. Because the data in a struct is public by default, we can think of a struct as a

passive data structure. The operations that are performed on a struct are usually global functions to which the struct is passed as a parameter. Although a struct may have mem- ber functions, they are seldom defined. In contrast, a class is an active data structure where the operations defined on the data members are member functions of the class.

In object-oriented programming, an object is viewed as an active structure with control residing in the object through the use of member functions. For this reason, the C++ class type is used to represent the concept of an object.