Previous Section
 < Free Open Study > 
Next Section


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 provided by the programming language.

The class type 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.

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

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 specification, 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 operations 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 implementation. 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 implementation, 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 (clients of 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 algorithms required for controlling these characteristics usually differ from one computer system to another. By defining an interface and encapsulating the algorithms as 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 semantics 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 logical separation.[3]

We describe the syntax and semantics of the class type within the context of defining 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 private, 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 implementation 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. Initialize is 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).[4] YearIs, MonthIs, and DayIs are 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.

C++: Scope Rules in C++
Start example

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 identifier declared within a block (statements enclosed within {}). Global scope is the scope of an identifier 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 function 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).

End example

Class Implementation

Only the member functions of the class DateType can 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 DateType must 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 DateType just 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 DateType and 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()  << end1;
cout  << " Another date is "  << anotherDay.MonthIs()  << "/"
      << anotherDay.DayIs()  << "/"  << anotherDay.YearIs()  << end1;

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 compares 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, year refers to the year data 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 called self. 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:

Self The object to which a member function is applied

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 year member of today, and aDate.year in the function refers to the year member of anotherDay, the actual parameter to the function.

Why do we use LESS, GREATER, and EQUAL when COMES_BEFORE, COMES_AFTER, and SAME would be more meaningful in the context of dates? We use the more general words here, because in other places we use functions of type RelationType when comparing 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 words public and private, 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 member 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.

[3]Your system may use extensions different from .h and .cpp for these files-for example, .hpp or .hxx (or no extension at all) for header files and .cxx, .c, or .c for implementation files.

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



Previous Section
 < Free Open Study > 
Next Section
Converted from CHM to HTML with chm2web Pro 2.85 (unicode)