First | Next | Previous | Last | Glossary | About

# Introducing Classes and Objects in C++ - Part 1

- Introduction
- Review of data structures
- The Abstract Data Type
- Some work for you
- Classes and Objects

## Introduction

So far your experience in implementing programs has been to combine two distinctive approaches, one analyses the processes involved, the other analyses the data structures involved. Using a structured approach you identify the processes and data structures for the various program entities and combine the process and data models to build a complete program or suite of programs.

There are other ways of course and in recent years we have seen the growth of the object-oriented approach. This approach doesn't view programming solutions as separate process and data models but as an object model. The object is the processes and data structures.

There is a natural progression from a structured programming approach to building programs to an object-oriented approach.

In this introduction I will follow that progression and review program implementation in structured programming terms, and then introduce the notion of the object. This way you should be able to see that the progression from process-oriented and data-oriented modelling to object-oriented modelling is a natural and logical progression.

## Review of data structures

First we will review some programming material so that we can see OOD from the perspective of things you should have done before. We start with:

- type declarations
- records

In computer programming a data type declaration is effectively a template or plan for variables.

In C and C++, apart from using the existing simple and structured types, a types can be declared with the typedef reserved word:

    typedef int Integer_Array[10];

this type can now be used by variable declarations:

    Integer_Array x,y,z;

A common use of type declarations is in the specification of complex, structured data types. These are data types based on simple data types or other structured data types:

    #include <fstream>
    #include <string>
    typedef char TNameStr[20];
    typedef char TAddressStr[50];
    typedef char TCourseStr[4];
    typedef char TFileNameStr[127];
    typedef struct
     { TNameStr    Name1,Name2;
       TAddressStr Address;
       TCourseStr  Course;
     } TStudent;
| The word typedef means that type declarations follow. TStudent is the type of the record; Name1, Name2, Address and Course are fields.
    main ()
    {
      TStudent      NewStudent, OldStudent;
      ifstream      StudentFile;
      TFileNameStr  StFileName;

      strcpy(StFileName,"STUDENTS.DAT");
      StudentFile.open(StFileName);
    // .. do some work
      StudentFile.close();
    }
| NewStudent and OldStudent are instances of the type. Open a file containing student records, do some work and close the file.

We can also declare records of records:

    typedef struct
     { TNameStr    EmployerName;
       TAddressStr EmployerAddress;
     } TEmployee;

    typedef struct
     { TStudent  StudentData;
       TEmployee EmployeeData;
     } TTrainee;
| In this example TTrainee inherits the fields of TStudent and TEmployer.

We could sketch the student structure and program as shown in Fig 1. It consists of the student record structure, the program (things to do with the student structure) and the data file.

We haven't yet defined what can be done with the student record and will look at that next. The sketch introduces the notion of modelling a program without necessarily using flowcharts, Nassi-Schneiderman charts, structure charts etc.

## The Abstract Data Type

Although a data type has been declared for students we still need to determine and specify what can be done with student records ie with instances of the type TStudent. The kinds of things we can do might include:

- create a student record
- write a student record to a file
- read a student record from a file
- validate a student record according to some validation rules
- modify a student record
- delete a student record
- sort student records

and similarly we need to specify what can be legitimately done with the fields of a student record. For instance we don't want to permit student names to contain invalid characters and we might use validation rules here.

The combination of the data type and the operations that can be performed on or with the data type is known as an ADT or Abstract Data Type. It is called abstract because it is an attempt to model a real-world entity and the behaviours that are associated with that entity, and, it should be independent of any specific implementation. We can look at this in terms of something other than a student record, for example a TV set.

The TV set we are modelling has the attributes:

- an ON/OFF switch
- volume control
- channel selector
- colour control
- brightness control
- contrast control

Now that we have some properties we can sketch our model TV. The top of the diagram is a title for the model. The centre section shows the data fields and the lower section the behaviours or processes that operate on the data. We will use this diagram and variations on it quite often to depict the ADT. For now to continue the practical view we should look at a program which implements the TV ADT.

Our TV data model might be something like the structure shown to the right. It consists of a simple struct containing some of the kinds of properties that a TV would usually. |
    typedef enum toggleswitch {OFF = 0, ON = 1};
    typedef struct
     { toggleswitch OnOffSwitch;
       int VolumeLevel,
           ChannelNumber,
           Colour,
           Brightness,
           Contrast;
     } TTelevision;

Here is a simple program which demonstrates how to use the TV ADT.

    #include <iostream>

    #define MAXLEVEL 100
    #define MINLEVEL 0
    typedef enum toggleswitch{OFF = 0, ON = 1};
    typedef struct
     { toggleswitch OnOffSwitch;
       int VolumeLevel,
           ChannelNumber,
           Colour,
           Brightness,
           Contrast;
     } TTelevision;
| This is the TV structure.
    void OnOff(TTelevision &);
    toggleswitch IsOn(TTelevision &);
    void InitTV(TTelevision &);
    void AdjustVolume(TTelevision &, int);
    int GetVolume(TTelevision &);
| These are the TV functions. The structure and functions together make up the ADT.
    int main()
    { TTelevision Sony;
      int count = 0;

      InitTV(Sony);
      OnOff(Sony);
      if (IsOn(Sony))
       cout << "Sony TV is now ON\n";
      else
       cout << "Sony TV is now OFF\n";
      cout << "volume level is "
           << GetVolume(Sony) << endl;
      AdjustVolume(Sony, 5);
      cout << "volume level is "
           << GetVolume(Sony) << endl;
      OnOff(Sony);
      if (IsOn(Sony))
       cout << "Sony TV is now ON\n";
      else
       cout << "Sony TV is now OFF\n";
     return 0;
    }
| The main function has one instance of TTelevision. This is initialised to a known state, the TV is then turned on and its on/off state and volume level are displayed. The volume is set to 5 and displayed again. Finally the TV is turned off, there was nothing worth watching.
    void InitTV(TTelevision &TV)
    {
      TV.OnOffSwitch    = OFF;
      TV.VolumeLevel    = 0;
      TV.ChannelNumber  = 0;
      TV.Colour         = 0;
      TV.Brightness     = 0;
      TV.Contrast       = 0;
    }
| An important operation is to initialise our model TV to some known state, in this case OFF and all controls set to 0. Hand over a reference to a TV and set the initial state.
    void OnOff(TTelevision &TV)
    {
     if (TV.OnOffSwitch == ON)
      TV.OnOffSwitch = OFF;
     else
     if (TV.OnOffSwitch == OFF)
      TV.OnOffSwitch = ON;
    }
| Hand over a reference to a TV and if the TV is ON turn it OFF, if it is OFF then turn it ON.
    toggleswitch IsOn(TTelevision &TV)
    { return TV.OnOffSwitch; }
| Hand over a reference to a TV and return the current on/off state. We need this so that we can avoid attempting to do something with the model when it is OFF.
    void AdjustVolume(TTelevision &TV, int Level)
    { if (IsOn(TV))
       if ((Level < MAXLEVEL) &&
           (Level > MINLEVEL))
        TV.VolumeLevel = Level;
    }
| Hand over a reference to a TV and a new level for the volume control. Set the volume to the new level.
    int GetVolume(TTelevision &TV)
    {  return TV.VolumeLevel;
    }
| Accepts a reference to a TV and returns the current volume level.

Most structured programming languages provide the kind of support that is necessary for ADT's, ie:

- powerful data typing,
- parameter passing by value and reference,
- support for include files and libraries of functions
- rules of scope to determine the visibility of identifiers

There are some problems tho' with ADT's defined in this way. What do we do when we have a TV which has additional controls, e.g. TV/AV, Stereo, Sound Muting? We would either have to modify the original data type or create a new data type. If we create a new data type then all the operations for the old type will have to be duplicated for the new, we can't use the old operations since the parameters will be different, our ADT isn't readily reusable. There is also a modelling problem: In the model the operations exist independently of the structure and this doesn't reflect the real world. The separation of data and operations also leads to some problems with the coupling of operations, that is how operations access and use data. We may find that for a given ADT some operations share global data and this is a weakness since one operation may inadvertently affect another operation. A second problem that occurs in languages which impose rules of scope is that an identifier declared with the name X in a procedure can render a global identifier X inaccessible until the procedure is finished. If we attempt to avoid the use of global data, which we should, and pass all data as parameters or parameter lists we face other difficulties. Assume that we want to minimise the number of variables being used in a program. We might decide to modify the procedure OnOff and rather than pass the whole record as a parameter we just pass the relevant field. We have reduced the amount of data and therefore any risks it might be exposed to by passing one field instead of six but weakened the ADT by weakening the close cohesion between the data and the operations.

The answer to many of these difficulties is simple and elegant, the data type and the operations are combined or encapsulated into a single thing. Now the ADT can almost seamlessly model the real world, the ADT enters the world of Object Oriented Programming. We can take our TV example and make it look something like this:

Example - using a struct

##

##

###

###

###

###

    typedef enum toggleswitch {OFF = 0, ON = 1};

    typedef struct
     { toggleswitch OnOffSwitch;
       int Volumuages. Now if we need to extend our TV ADT we
    have a number of options. We could simply add data fields and procedures/functions to
    it, at least there is only one thing to modify; or we could create another ADT
    which inherited the characteristics of the original in much the same that the TTrainee
    record inherited the fields of TStudent and TEmployer.

    Some work for you

    I have developed the Example - using a struct into something like a working example which you
    can get here.

    Get a copy of the example and make the following changes:

    Add functions to implement changes to the contrast, brightness and colour.
    Modify the ShowTV() function to show the state of all teh controls.

    Classes and Objects

    Classes and objects along with encapsulation and inheritance are part of the
    language of OOP systems. What do these words mean?

    A class has properties or members and these can consist of data fields and methods.

    A class is to object oriented programming as a record type is to non-object
    oriented programming, what we can call procedural programming. An object is to OOP
    what a record variable is to PP. Just as a record variable is an instance of a
    record type so an object is an instance of a class. The difference
    between a class and a record type is that the class not only contains the data
    fields for an object but also the operations or methods for the object. The class
    is a template for an object.

    Encapsulation

    Encapsulation offers more than just lumping methods and data together, encapsulation also
    makes it possible to make some properties of the class inaccessible to the outside
    world and other properties accessible. By making some parts of the class
    inaccessible, that is by hiding them we can make the class more robust, the private
    part of the class is only accessible via the member functions.  A benefit that
    is claimed for a well-designed class is that it offers a higher degree of utility
    or re-usability than traditional functions written in procedural languages. Along
    with encapsulation, the second great strength of classes is inheritance, that is,
    the ability to define a class in terms of other classes and it is from
    inheritance that classes gain their greater utility. A class can inherit the data
    fields and methods of a parent class and then add some of it's own.

    Inheritance

    The base class is also known as a parent class or a superclass, the descendent class is also know as a child class or a subclass. A chain of classes has ancestor classes and descendent classes. A parent class can have many different child classes each of which is different. A child class can have more than one parent class, called multiple inheritance, and classes generally  exist as class hierarchies. Base classes are also known as server classes and subclasses as client classes.

    The diagrams here show some of these characteristics of class inheritance
    arrangements.

    Inheritance and Polymorphism

    Polymorphism is a very useful feature of classes by which a base class enables subclasses to each carry out
    an apparently similar method in different ways. Here is an example:

    Assume that we are designing a sytem that models different shapes and our system can deal with circles,
    squares, rectangles and triangles. Each shape has an area but the way an area is calculated differs
    according to shape.

    The base class, TShape, has a data field called area and a method called calc_area. We then design subclasses
    of  TShape, called TCircle, TSquare, TRectangle and TTriangle and each of these has a method called
    calc_area.

    If the base class and the subclass both have methods with the same name it might appear that there will
    be difficulties resolving which calc_area will actually be used by an instance of one of the
    subclasses. It turns out that object-oriented systems take care of this by providing virtual methods and
    you will see more of these later. Be aware for now that we can deal with circumstances like those shown
    in the figure.

    Messages

    We make an object do something by sending it a message and, essentially, message is a call to
    one of the member functions or methods of the object.

    First

    Next

    Previous

    Last

    Glossary

    About

    Copyright ©  1999 - 2001
    David Beech