C++ Classes

An Introduction to Object Oriented Programming


C++ - A new way of organizing code

The C language is a procedural programming language. That is, you write a number of procedures (functions) each of which may call other functions to accomplish the objectives of the program. Each function may have some local variables defined within it and there may be some global variables available to all functions. In C it is considered very good programming practice to organize your code into modules. That is all related functions are organized into a single source code file. There are examples of this type of modular programming in the Code Vault (stack, queue, ordered linked list, and binary tree code examples).

The C++ programming language is an extension of the C language. It requires you to apply the good programming practices of modularity, data encapsulation, and data abstraction. C++ is called an object oriented language because you organize all of the code in your program into modules which are called objects. Unlike C, in which you can create modules of code just by isolating all the functions of a module into a single source code file, C++ provides new commands, syntax, and data structures that makes an object (module) a separate and distinct entity. Object oriented programming then becomes a process of creating these objects and defining how the objects interact with each other.

What is an Object?

The "objects" used to create a C++ program are called classes. A class is organized almost exactly like a data structure in C. In a data structure you can group together a number of related variables of different data types. When you then declare a data structure of that type (example: struct simple S1;) you can treat the data structure as a single entity or object. A class contains any number of variables just like a data structure, but it also contains all of the functions that work on that data.

To define a class you must create two files. The first is a header file, called the interface or specification file. This interface file contains the definition of the class, using a syntax similar to that for defining a data structure. It contains all of the class variables and the prototypes for the functions that are part of the class. The second file, called the implementation file contains all of the functions that are members of that class. BTW: the variables in a class are referred to as member variables and the functions in a class are referred to as member functions.

A simple example: The MyString Class

Here is a simple interface file which defines a class to be used to implement a string.
//-------------------------------------------------------------------------------------
// File: MyString.h
// Purpose: Demonstration of defining a simple class of a dynamic length string.
//--------------------------------------------------------------------------
#ifndef MYSTRING_H
#define MYSTRING_H
#include <string.h>
#include <iostream.h>

class MyString
{
    private:
        char *theString;

    public:
        MyString();
        MyString(char *str);
        ~MyString();
        void clearString();
        void setString(char *str);
        char *getString();
        int  StringLength();
        void PrintString();
        bool StringEqual(MyString *otherString) const;
        bool operator==(MyString *otherString) const; };
#endif				// end if class defined
Some things to notice in this header file:
  1. #ifndef MYSTRING_H -- It is possible for several different files to #include this header file. If this happens you will get an error from the compiler saying the class has been "redefined". To prevent this and ensure that the class is only defined once during compilation you can enclose the entire header file contents inside of a #ifndef...#endif pair. Just after the #ifndef preprocessor directive include a #define which defines the constant checked by the #ifndef. The first time this header is included the constant will not have been defined so the #ifndef check will succeed and the rest of the file will be processed including the #define. The next time the header gets included the constant will already be defined and the #ifndef will fail. The constant which is defined is usually the name of the header file, in all caps, with the dot replaced by an underbar character. Thus the constant used for this header file, MyString.h is MYSTRING_H.

  2. The organization of the class definition is almost the same as defining a data structure with the keyword class substituted for the keyword struct.

  3. In the class definition you first list all of the class member variables, just like you do in a data structure definition. After that you list all of the function prototypes. In C++ classes you can also assign to the variables and functions an access scope. Variables and functions labeled as private: can only be accessed by functions within the class itself. Variables and functions labeled as public: can be accessed by any function outside of the class. Labeling a function as private: in C++ is similar to declaring a function in a C module as static. By default all functions are public and all variables are private. When a private: label is used, everything after that becomes private, until the public: label is used again.
    Member variables and functions can also be labeled as protected:. This means that only the class or its sub-classes can access that variable. Sub-classes and inheritance are discussed below.

  4. MyString(); This function, with the exact same name as the class and with no return type is called the class constructor. When you create (the correct term is instantiate) an object (the correct term is an instance) of this class type this function is called automatically. In this function you can do any initialization required. This is where the theString pointer the MyString class would be initialized to NULL.

    MyString(char *str); This function is also a class constructor. When you instantiate an instance of MyString you can use the keyword new in either of the following ways:

    MyString *str1 = new MyString();

    or

    MyString *str1 = new MyString("This is a new string.");

    If you use the first syntax a new MySring object is created with its theString pointer set to NULL. If you use the second syntax a new MyString object is created with its theString pointer pointing to a new string containing the characters "This is a new string.". You would think that, since both functions have the same name, that this would comfuse the compiler. But, the compiler is able to determine which constructor to call by looking at the arguments.

    This ability to have two, or more, functions with the same name, but different arguments, in the same class is called function overloading.

  5. ~MyString(); This function, with the exact same name as the class, but with a tilde ('~') in front of the name, and with no return type is called the class destructor. When you destroy the instance of a class (using the delete keyword) this function gets called automatically before the memory for the class instance is deallocated. In this function you free up any other memory allocated by the class. In the case of the MyString class this function would free the memory dynamically allocated for the character array holding the string data.

Below is the implementation file in which the functions that make up the class are defined. Note that the class name and two colons (MyString::) preceeds the name of each function. This is the way C++ distinguishes which functions go in this class. Note also the use of the cout (<<) and cin (>>) operators from iostream.
//--------------------------------------------------------------------------
// File: MyString.cpp
// Purpose: Demonstration of implementing a simple class
//--------------------------------------------------------------------------
#include "MyString.h"

//--------------------------------------------------------
// Default class constructor
// Initialize the string pointer to null
//--------------------------------------------------------
MyString::MyString()
{
    theString = NULL;
}
//--------------------------------------------------------
// Class constructor
// Initialize the string to the str argument
//--------------------------------------------------------
MyString::MyString(char *str)
{
    theString = new char[strlen(str) + 1]; // Allocate memory for new string
    strcpy(theString, str);  // Copy string argument into new memory space.
}

//--------------------------------------------------------
// Class destructor
// Initialize the string to the str argument
//--------------------------------------------------------
MyString::~MyString()
{
    delete theString;
}

//--------------------------------------------------------
// Clear the current string and reset theString to null
//--------------------------------------------------------
void MyString::clearString()
{
    delete theString;
    theString = NULL;
}

//--------------------------------------------------------
// Reset the current string to a new value
//--------------------------------------------------------
void MyString::setString(char *str)
{
    clearString();  // Clear the current string, if any
    theString = new char[strlen(str) + 1]; // Allocate memory for new string
    strcpy(theString, str);  // Copy string argument into new memory space.
}

//--------------------------------------------------------
// Return the number of chars in the current string
//--------------------------------------------------------
int MyString::StringLength()
{
    return strlen(theString);
}

//--------------------------------------------
// Get a copy of the current string
//--------------------------------------------
char *MyString::getString()
{
    char *newString = new char[strlen(theString) + 1];
    strcpy(newString, theString);  
    return newString;
}

//--------------------------------------------------------
// Print the string
//--------------------------------------------------------
void MyString::PrintString()
{
    cout << theString;
}

//--------------------------------------------------------
// Determine if the string is equal to the arg string.
//--------------------------------------------------------
bool MyString::StringEqual(MyString *otherString)
{
    char *temp = otherString->getString();
    if(strcmp(theString, temp) == 0)
    {
        delete temp;  // Clear temporary memory
        return true;
    }
    else
    {
        delete temp; // Clear temporary memory
        return false;
    }
}

//--------------------------------------------------------
// Determine if the string is equal to the arg string.
// Overload the == operator
//--------------------------------------------------------
bool MyString::operator==(MyString otherString)
{
    if(strcmp(theString, temp) == 0)
    {
        delete temp;  // Clear temporary memory
        return true;
    }
    else
    {
        delete temp; // Clear temporary memory
        return false;
    }
}
And a simple test of the MyString class...

//-----------------------------------------------------
// File: TestMyString.cpp
// Purpose: Demonstration program using a simple class
//-----------------------------------------------------
#include "MyString.h"
#include <iostream.h>
int main(int argc, char **argv)
{
    MyString *myString = new MyString("This is a test");
    MyString *otherString = new MyString("This is a new string.");

    cout << "My string is " << myString->StringLength() 
        << " characters long.\n";
    cout.flush();
    cout << "My string is: ";
    myString->PrintString();
    cout << "\n";
    cout.flush();
    cout << "\nTesting StringEqual() by comparing known different strings\n";
    if(!myString->StringEqual(otherString))
        cout << "   Strings not equal\n";
    cout.flush();
    // Change myString to = otherString
    myString->setString("This is a new string."); 
    cout << "Testing operator == with if(myString == otherString) with known equal strings\n";
    if((*myString) == (*otherString)) // Note we have to use the structures not pointers
        cout << "   Strings are equal\n";
    cout.flush();
    cout << "\nSay goodbye to the strings.\n";
    delete myString;
    delete otherString;
    cout.flush();
    return 0;
}

Inheritance

When you define a class in C++ you can also define sub-classes of that class. A sub-class of a class inherites all of the variables and functions from it's parent class, just as if those had been defined in the sub-class. In the following example a class called CShape is created which contains a number of variables and functions that would be common to a set of classes used to draw different shapes in a C++ drawing program. After defining the CShape class a number of sub-classes can be derived from CShape to define specific shapes like CRectangle and CCircle

//--------------------------------------------------------------------------------------------
//  File: CShape.h  --  Definition of a general shape class
//--------------------------------------------------------------------------------------------
#ifndef CSHAPE_H
#define CSHAPE_H		// This insures we don’t inadvertently try to redefine the class
// Include other headers needed to define Pattern, RGBColor, Rect, etc.

class CShape
{
    // Member variables, also called instance variables -- These are public by default 
    protected:                    // protected means these are private, but can be seen in
        Pattern    penPat;        //        any sub-classes derived from Cshape
        Pattern    fillPat;
        RGBColor   penColor;
        RGBColor   fillColor;
        int        xPos;
        int        yPos;
        Rect       enclosingRect

	// Member functions also called methods
    public:
        CShape( void );         // Initialize the object 
        ~CShape( void );        // Destroy the object 
        virtual void Draw();    // A virtual function doesn't exist in this class, but requires
                                //    all sub-classes to define it.
        void SetEnclosingRect(int x1, int y1, int x2, int y2);
        void Move(int deltaX, int deltaY);
        void SetPenPat(Pattern pPat);
        void SetFillPat(Pattern fPat);
        void SetPenColor(RGBColor pColor);
        void SetFillColor(RGBColor fColor);
};
#endif


//-------------------------------------------------------------------------------------------- // File: cShape.cpp -- Definition of a general shape class //-------------------------------------------------------------------------------------------- #include <CShape.h> // Constructor for the CShape object CShape::CShape() { // Initialize variables penPat = PLAIN; fillPat = PLAIN; penColor = BLACK; fillColor = BLACK; xPos = yPos = 0; SetEnclosingRect(0, 0, 0, 0); } // Destructor for the CShape object ~CShape::CShape() { // Nothing to do here } void CShape::SetEnclosingRect(int x1, int y1, int x2, int y2) { enclosingRect.left = x1; enclosingRect.top = y1; enclosingRect.right = x2; enclosingRect.bottom = y2; } //--------- Other functions prototyped in cshape.h would be defined below ----------------

Now, given the parent class, CShape, you can define sub-classes of CShape.
//-----------------------------------------------------------------------------------------
//  File: CRectangle.h  --  Definition of a rectangle class derived from the CShape class
//-----------------------------------------------------------------------------------------
#ifndef CRECTANGLE_H
#define CRECTANGLE_H	

#include <CShape.h>

class CRectangle:CShape    // Note that ":CShape" marks this as a sub-class of CShape
{
    // If any additional variables specific to a rectangle class are needed define them here.

    // Member functions
    public:
        CRectangle();              // Initialize the object (Constructor)
        ~CRectangle();             // Destroy the object (Destructor)
        void Draw();               // Draw the object
        // CRectangle has inherited all the functions from CShape.  
        // If additional functions were needed that were specific to a CRectangle they 
        //    would  be prototyped here.  Note: they do not have to be declared as
        //    virtual in the parent class if they will only be used by CRectangle.
};
#endif

//-----------------------------------------------------------------------------------------
//  File: CCircle.h  --  Definition of a circle class derived from the CShape class
//-----------------------------------------------------------------------------------------
#ifndef CCIRCLE_H
#define CCIRCLE_H	

#include <CShape.h>

class CCircle:CShape    // Note that ":CShape" marks this as a sub-class of CShape
{
    private:
        int    centerX; // CCircle inherits all of the variables from it's parent class.
        int    centerY; // In addition CCircle defines these three variables that are
        int    radius;  // needed to define a circle.

    // Member functions
    public:
        CCircle();        // Initialize the object (Constructor)
        ~CCircle();       // Destroy the object (Destructor)
        void Draw();      // Draw the object
    // CCircle has inherited all the functions from CShape.  
    // If additional functions were needed that were specific to a CCircle they 
    //    would  be prototyped here.  Note: they do not have to be declared as
    //    virtual in the parent class if they will only be used by CCircle.
};
#endif

//---------------------------------------------------------------------------------------------
//  File: ClassDemo.cpp  --  A simple application using CShape and CRectangle
//---------------------------------------------------------------------------------------------

#include <stdio.h>
// Other required headers are included here.  This includes platform specific graphics
// files such as the one to define RED and BLUE.
#include "CRectangle.h"   // Note that both CRectangle.h and CCircle.h #include CShape.h
#include "CCircle.h"      // so if you didn't have the #ifndef...#endif pair around the contents
                          // you would get a "redefined class" error here.

int main(void)
{
	CShape	*theRect;
	CShape	*theCircle;

	theRect = new CRectangle();
	theCircle = new CCircle();

	// Draw a rectangle
	theRect->SetEnclosingRect(10,10, 100,100);     // function in base class
	theRect->SetPenColor(RED);                     // function in base class
	theRect->Draw();                               // function in Rectangle class

	// Draw a circle
	theCircle->SetEnclosingRect(100,100, 200,200);   // function in base class
	theCircle->SetPenColor(BLUE);                    // function in base class
	theCircle->Draw();                               // function in Rectangle class

	// Do some other stuff here

	delete theRect;                // Destroy the rectangle object
	delete theCircle;              // Destroy the circle object
}