Objects protect data
The next program [CLASPOLE.CPP] is an example of data protection
in a very simple program.
#include <iostream.h>
class rectangle { // A simple class
int height;
int width;
public:
int area(void); // with two methods
void initialize(int, int);
};
int rectangle::area(void) //Area of a rectangle
{
return height * width;
}
void rectangle::initialize(int init_height, int init_width)
{
height = init_height;
width = init_width;
}
struct pole {
int length;
int depth;
};
void main()
{
rectangle box, square;
pole flag_pole;
// box.height = 12;
// box.width = 10;
// square.height = square.width = 8;
box.initialize(12, 10);
square.initialize(8, 8);
flag_pole.length = 50;
flag_pole.depth = 6;
cout << "The area of the box is " << box.area() << "\n";
cout << "The area of the square is " << square.area() << "\n";
// cout << "The funny area is " <<
// area(square.height, box.width) << "\n";
// cout << "The bad area is " <<
// area(square.height, flag_pole.depth) << "\n";
}
In this program the rectangle is changed to a class with the same two
variables (which are now private) and two methods to handle the
private data. One method is used to initialise the values of the objects
created and the other method to return the area of the object. The two
methods are defined in lines 7 to 17 in the manner described previously.
The pole is left as a structure to illustrate that the two can be used
together and that C++ is truly an extension of ANSI-C.
In line 24 we declare two objects, once again named box and square,
but this time we cannot assign values directly to their individual
components because they are private elements of the class. Lines 26 to
28 are commented out for that reason and the messages are sent to the
objects in lines 29 and 30 to tell them to initialise themselves to the
values input as parameters. The flag_pole is initialised in the same
manner as in the previous program. Using the class in this way prevents
us from making the silly calculations we did in the last program. The
compiler is now being used to prevent the erroneous calculations. The
end result is that the stupid calculations we did in the last program are
not possible in this program so lines 35 to 38 have been commented
out. Once again, it is difficult to see the utility of this in such a simple
program, but in a large program, using the compiler to enforce the rules
can really pay off.
Even though the square and the box are both objects of class rectangle,
their private data is hidden from each other such that neither can
purposefully or accidentally change the others data. This is the abstract
data type mentioned earlier in this section, a model with an allowable
set of variables for data storage and a set of allowable operations that
can be performed on that stored data. The only operations that can be
performed on the data are those defined by the methods which prevents
many kinds of erroneous or silly operations. Encapsulation and data
hiding bind the data and procedures, or methods, tightly together and
limit the scope and visibility of each. Once again, we have the "divide
and conquer" technique in which an object is separated from the rest of
the code and carefully developed in complete isolation from it. Only
then is it integrated into the rest of the code with a few very simple
interfaces.
A good example of the use of this technique is in file access in ANSI-C.
The data in the file are only available through the predefined functions
provided by your compiler writer. You have no direct access to the
actual data because it is impossible for you to address the actual data
stored on the disk. The data are therefore private data, as far as you are
concerned, but the available functions are very much like methods in
C++. There are two aspects of this technique that really count when
you are developing software. First, you can get all of the data you really
need from the file system because the interface is complete, but
secondly, you cannot get any data that you do not need. You are
prevented from getting into the file handling system and accidentally
corrupting some data stored within it. You are also prevented from
using the wrong data because the functions available demand a serial
access to the data.
Another example is monitor and keyboard handling routines. You are
prevented from getting into the workings of them and corrupting them
accidentally (or even on purpose), but once again, you are provided
with all of the data interfaces you need.
Suppose you are developing a program to analyse some characteristics
of flagpoles. You would not wish to accidentally use some data
referring to where the flagpole program was stored on your hard disk as
the height of the flagpole, nor would you wish to use the cursor
position as the flagpole thickness or colour. All code for the flagpole is
developed alone, and only when it is finished, is it available for external
use. When using it, you have a very limited number of operations which
you can do with the class. The fact that the data is hidden from you
protects you from accidentally doing such a thing when you are
working at midnight to try to meet a schedule. Once again, this is
referred to as information hiding and is one of the primary advantages
of object oriented programming over procedural techniques.
Based on the discussion given above you can see that object-oriented
programming is not really new, since it has been used in a small
measure for as long as computers have been popular. The newest
development, however, is in allowing the programmer to partition her
programs in such a way that she too can practice information hiding and
reduce the debugging time.
The next program [CONSPOLE.CPP] introduces
constructors anddestructors
.#include <iostream.h>
class rectangle { // A simple class
int height;
int width;
public:
rectangle(void); // with a constructor,
int area(void); // two methods,
void initialize(int, int);
~rectangle(void); // and a destructor
};
rectangle::rectangle(void) // constructor
{
height = 6;
width = 6;
}
int rectangle::area(void) // Area of a rectangle
{
return height * width;
}
void rectangle::initialize(int init_height, int init_width)
{
height = init_height;
width = init_width;
}
rectangle::~rectangle(void) // destructor
{
height = 0;
width = 0;
}
struct pole {
int length;
int depth;
};
void main()
{
rectangle box, square;
pole flag_pole;
cout << "The area of the box is " << box.area() << "\n";
cout << "The area of the square is " << square.area() << "\n";
// box.height = 12;
// box.width = 10;
// square.height = square.width = 8;
box.initialize(12, 10);
square.initialize(8, 8);
flag_pole.length = 50;
flag_pole.depth = 6;
cout << "The area of the box is " << box.area() << "\n";
cout << "The area of the square is " << square.area() << "\n";
// cout << "The funny area is " <<
// area(square.height, box.width) << "\n";
// cout << "The bad area is " <<
// area(square.height, flag_pole.depth) << "\n";
}
This program is identical to the last except that a constructor has been
added as well as a destructor. The constructor always has the same
name as the class itself and is declared in line 8 then defined in lines 11
to 15. The constructor is called automatically by the C++ system when
the object is declared and can therefore be of great help in preventing
the use of an uninitialised variable. When the object named box is
declared in line 36, the constructor is called automatically by the
system. The constructor sets the values of height and width each to 6 in
the object named box. This is printed out for reference in lines 38 and
39. Likewise, when the square is declared in line 36, the values of the
height and the width of the square are each initialised to 6 when the
constructor is called automatically.
A constructor is defined as having the same name as the class itself. In
this case both are named rectangle. The constructor cannot have a
return type associated with it since it is not permitted to have a user
defined return type. It actually has a predefined return type, a pointer to
the object itself, but we will not be concerned about this until much
later in this tutorial. Even though both objects are assigned values by
the constructor, they are initialised in lines 45 and 46 to new values and
processing continues. Since we have a constructor that does the
initialisation, we should probably rename the method named initialize( )
something else but it illustrates the concept involved here.
The destructor is very similar to the constructor except that it is called
automatically when each of the objects goes out of scope. You will
recall that automatic variables have a limited lifetime since they cease to
exist when the enclosing block in which they were declared is exited.
When an object is about to be automatically deallocated, its destructor,
if one exists, is called automatically. A destructor is characterised as
having the same name as the class but with a tilde prepended to the
class name. A destructor has no return type.
A destructor is declared in line 9 and defined in lines 26 to 29. In this
case the destructor only assigns zeros to the variables prior to their
being deallocated, so nothing is really accomplished. The destructor is
only included for illustration of how it is used. If some blocks of
memory were dynamically allocated within an object, a destructor
should be used to deallocate them prior to losing the pointers to them.
This would return their memory to the free store for further use later in
the program.
It is interesting to note that if a constructor is used for an object that is
declared prior to the main program, otherwise known as globally, the
constructor will actually be executed prior to the execution of the main
program. Similarlry, if a destructor is defined for such a variable, it will
execute after the main program has been executed. This will not
adversely affect your programs, but it is worth noting.
An array of objects
Examine the next program [OBJARRAY.CPP] for an example of an
array of objects. This file is nearly identical to the file named
BOX1.CPP until we come to line 33 where an array of 4 boxes is
declared.
#include <iostream.h>
class box {
int length;
int width;
static int extra_data; // Declaration of extra_data
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void);
int get_extra(void) {return extra_data++;}
};
int box::extra_data; // Definition of extra_data
box::box(void) //Constructor implementation
{
length = 8;
width = 8;
extra_data = 1;
}
// This method will set a box size to the two input parameters
void box::set(int new_length, int new_width)
{
length = new_length;
width = new_width;
}
// This method will calculate and return the area of a box
instance
int box::get_area(void)
{
return (length * width);
}
void main()
{
box small, medium, large, group[4]; //Seven boxes to work with
small.set(5, 7);
large.set(15, 20);
for (int index = 1 ; index < 4 ; index++)
//group[0] uses default
group[index].set(index + 10, 10);
cout << "The small box area is " << small.get_area() << "\n";
cout << "The medium box area is " << medium.get_area() << "\n";
cout << "The large box area is " << large.get_area() << "\n";
for (index = 0 ; index < 4 ; index++)
cout << "The array box area is " <<
group[index].get_area() << "\n";
cout << "The extra data value is " << small.get_extra() << "\n";
cout << "The extra data value is " << medium.get_extra() << "\n";
cout << "The extra data value is " << large.get_extra() << "\n";
cout << "The extra data value is " << group[0].get_extra()
<< "\n";
cout << "The extra data value is " << group[3].get_extra()
<< "\n";
}
Recalling the operation of the constructor, you will remember that each
of the four box objects will be initialised to the values defined within the
constructor since each box will go through the constructor as they are
declared. In order to declare an array of objects, a constructor for that
object must not require any parameters. (We have not yet illustrated a
constructor with initialising parameters, but we will in the next
program.) This is an efficiency consideration since it would probably be
an error to initialise all elements of an array of objects to the same
value. We will see the results of executing the constructor when we
compile and execute the file later.
Line 36 defines a for loop that begins with 1 instead of the normal
starting index for an array leaving the first object, named group[0], to
use the default values stored when the constructor was called. You will
observe that sending a message to one of the objects uses the same
construct as is used for any object. The name of the array followed by
its index in square brackets is used to send a message to one of the
objects in the array. This is illustrated in line 36 and the operation of
that code should be clear to you. The other method is called in the
output statement in lines 39 to 41 where the area of the four boxes in
the group array are listed on the monitor.
Another fine point should be pointed out. The integer variable named
index is declared in line 36 and is still available for use in line 38 since
we have not yet left the enclosing block which begins in line 32 and
extends to line 51.
Variable Decleration and Definition, Dynamically allocate object, and Pointer to object.
Links: Home C Programming Guide C++ programming Guide