Classes and Object-Oriented Programming

Objects

Classical programs are composed of data items and functions that operate on the data. Although considered as separate entities, the data has no inherent meaning without the functions that operate on it. Object-oriented programming extends this concept by encapsulating the data and functions into single entities, known as objects.

Objects extend the standard record (struct) data structure. While records contain only multiple data items, objects can contain multiple data items as well as functions that operate on those data items.

Object-orientation provides the programmer with a better programming paradigm than the classical approach for many reasons, most importantly being the built-in extensibility and mantainability of object-oriented code.

Object-oriented programs differ vastly from classical programs. Classical programming defines data and functions that operate on the data in a certain sequence to provide the solution to a problem. Object-orientation first creates a model of the real world. The real world can be thought of as a number of physical objects, which interact with one another. These physical objects can then be modelled on computer and the program can facilitate the interaction. Generally, the information of attributes of the physical objects are modelled as data items and the actions performed by/on the objects are the functions.

Like all other data, objects need to be associated with a data type. Since different objects have different properties, each needs its own data type (unless the data type is generalised to cater for various objects).

Classes

The type of an object is known as a class. Before an object can be created, its class must be declared and fully defined. Classes are simply the data types, the data itself is an object.

Once a class has been created, objects are declared using exactly the same syntax for standard data types. In fact, the computer does not distinguish between standard data types and classes, making it possible to customise the programming environment through the introduction of well-defined popular data structures in the form of classes.

Syntax:
class <class name>
{
   <data members>
   .
   .
   <functions>
   .
   .
};
Example:
class ComplexNumber
{
   int real, imaginary;
   void Output ();
};
ComplexNumber aNumber;

After a class declaration, all its member functions must be defined. If they are defined outside the class declaration, then each function name must be prefixed by the name of the class and two colons.

Example:
void ComplexNumber::Output ()
{
   printf ("%d %d", real, imaginary);
}

Operations with Objects

Since objects are just an extension of records, their fields are accessed with the "." operator. Data items are analogous to records. Functions can be called with the same syntax. In addition to global variables, functions also have access to the data items contained within the current object (the one from which the function is being called).

Example:
aNumber.real = 0;
aNumber.imaginary = 1;
aNumber.Output ();

Protection

Object-oriented programming promotes data hiding in order to facilitate easier abstraction of data. Data hiding is when the data items of an object cannot be used directly by the program - instead they must be accessed indirectly through member functions. In C++, this is achieved by protection attributes being associated with each data item of function in a class.

There are three protection attributes: private, public and protected. private means that all the following members of the class can only be accessed from within member functions. public means that the following members can be accessed from anywhere in the program. protected means that the members are private but can be accessed from derived classes as well.

Example:
class ComplexNumber
{
private:
   int real, imaginary;
public:
   void Output ();
};

After the above example code has been declared, it will be illegal to attempt accessing the real or imaginary variables contained within a ComplexNumber object. Instead, member functions will have to be inserted to access these variables.

By default, all member functions and data items are private.

Constructors and Destructors

A constructor is a special member function that is called automatically whenever an object is created. This function must have the same name as the class and no return type. Parameters, if any, are passed when declaring objects.

Example:
class ComplexNumber
{
public:
   int real, imaginary;
   ComplexNumber ( int r, int i );
   void Output ();
};
ComplexNumber aNumber (1, 2);

Destructors are special functions that are called whenever an object is destroyed. Destructors are declared as functions with the same name as the class, but prefixed by a tilde. Destructors cannot have any parameters.

Whereas there may be mutliple constructors to initialise objects in different ways, there can only be one destructor in any class.

See example DS02.CPP for object orientation of complex number.

See example DS03.CPP for object orientation of matrices.

Terminology

Encapsulation is when data and functions are put together to form single units called objects.

Data Hiding is when the data members of an object are hidden from the programmer, by use of protection attributes, thereby forcing indirect access through function members.

Members refer to the data items and functions contained within a class/object.

Methods refer to the functions contained within a class/object.

Objects are self-contained composite programmatic entities, containing both data items and the methods that operate on them.

Classes are the type definitions for objects.

Abstraction of Data is achieved when the physical storage is no longer important to the programmer using the data - the data is accessed indirectly through a well-defined and portable interface.

Object-Oriented Streams

Classical programs in C++ use streams for input and output. These streams are buffers which synchronise input and output between the program and the i/o devices. This compensates for the speed differences, making it transparent to the programmer.

Object-oriented programs allocate an object to each form of input or output - all i/o is then performed via these objects.

cout is the standard output object. To use cout, an expression must be formed with the output values on the rhs and cout on the lhs, separated with a "<<". Since this is an expression, it has a return value, being cout once again. Thus it is possible to use this return value for another output, eventually resulting in a chain of variables or constants being output with a single statement. Any data type can be used with the cout object.

Example:
cout << "Hello " << "World" << anInteger << aChar;

cin is the standard input object. The syntax is identical, except that the operator used in this case is ">>". The operators for both streams are obvious since the arrows point in the direction in which data flows. In the case of output, the data goes to the output stream, and in the case of input, it comes from there and is stored in variables.

Example:
cin >> anInteger >> aString;

There are also streams to do input and output to/from files. These are simply defined as classes (fstream) and objects must be created in order to use them.

cin and cout, being objects, also have methods to format and control the streams. For example, the width method specifies the minimum width for output formating and precision specifies the decimal precision for floating point numbers.

Example DS04.CPP illustrates the usage of cout and cin.

Example DS05.CPP reads in a file using fstream.

Modularisation

Although part and parcel of standard C, modularisation of programs is especially relevant to object-orientation since objects are self-contained reusable units.

Simple programs are compiled, linked with the default libraries and then executed. Larger programs invariably mean a bigger source file and recompiling a massive program, which is mostly correct, for every little change is not desirable. To avoid this, large programs are written as a series of small files, each containing a part of the program. These are individually compiled and linked together only when the program is ready for execution.

The biggest problem with such splitting up of the code is that each module would not know of the existance of the other modules. Thus, references to functions defined in other modules would not be possible. To solve this problem, the function headers for each called function are included at the top of each module. In practice, all the function headers for a module are normally listed in a "header" file with the extension ".H". Then the module wishing to use one of those functions simply includes the relevant header file at the top of its code.

Header files must never contain code, only declarations. Even variables must not be defined in a header files. They should be declared as extern so that compiler knows they exist but does not allocate storage for them.

Example:
#include "module2.h"

Each module will compile properly and all the modules will have to be linked to produce a complete program. Most C compilers provide either a project file or a makefile for this purpose. All the modules in the program are listed in this file so that the compiler knows what to link together when necessary.

In terms of object-orientation, each object should ideally be stored in a separate module. The class definition can be stored in a source code file and the declaration in a header file. Any other module that wishes to use the object should then include its header file, which contains only the class declaration. This promotes portability since objects can be shared among different programs.