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:
-
#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.
-
The organization of the class definition is almost the same as defining a
data structure with the keyword class substituted for the keyword
struct.
-
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.
-
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.
-
~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
}