Copy Link
Add to Bookmark
Report

Coronado Enterprises C++ TUTOR: Chapter 5

Chapter 5: ENCAPSULATION

As mentioned in Chapter 1, object oriented programming will seem very unnatural to a programmer with a lot of procedural programming experience. This chapter is the beginning of the definition of object oriented programming, and we will study the topic of encapsulation which is a "divide and conquer" technique. As we stated earlier, there are a lot of new terms used with object oriented programming. Don't be intimidated by the new terminology, we will study the terms one at a time in a meaningful order.

Encapsulation is the process of forming objects which we will discuss throughout this chapter. An encapsulated object is often called an abstract data type and it is what object oriented programming is all about. Without encapsulation, which involves the use of one or more classes, there is no object oriented programming. Of course there are other topics concerning object oriented programming, but this is the cornerstone.


WHY BOTHER WITH ENCAPSULATION?

We need encapsulation because we are human, and humans make errors.
When we properly encapsulate some code, we actually build an impenetrable wall to protect the contained code from accidental corruption due to the silly little errors that we are all prone to make. We also tend to isolate errors to small sections of code to make them easier to find and fix. We will have a lot more to say about the benefits of encapsulation as we progress through the tutorial.


NO INFORMATION HIDING

The program named OPEN.CPP is a really stupid program because it does next to nothing, but it will be the beginning point for our discussion of encapsulation, otherwise known as informationf hiding. Information hiding is an important part of object oriented programming and you should have a good grasp of what it is by the time we finish this chapter.

A very simple structure is defined in lines 4 through 6 which contains a single int type variable within the structure. This is sort of a silly thing to do but it will illustrate the problem we wish to overcome in this chapter. Three variables are declared in line 10, each of which contains a single int type variable and each of the three variables are available anywhere within the main function. Each variable can be assigned, incremented, read, modified, or have any number of operations performed on it. A few of the operations are illustrated in lines 13 through 21 and should be self explanatory to anyone with a little experience with the C programming language.

An isolated local variable named piggy is declared and used in the same section of code to illustrate that there is nothing magic about this code.

Study this simple program carefully because it is the basis for beginning our study of encapsulation. Be sure to compile and execute this program, then we will go on to the next example program.

OPEN.CPP

  
// Chapter 5 - Program 1
#include <iostream.h>

struct one_datum {
int data_store;
};

main()
{
one_datum dog1, dog2, dog3;
int piggy;

dog1.data_store = 12;
dog2.data_store = 17;
dog3.data_store = -13;
piggy = 123;

cout << "The value of dog1 is " << dog1.data_store << "\n";
cout << "The value of dog2 is " << dog2.data_store << "\n";
cout << "The value of dog3 is " << dog3.data_store << "\n";
cout << "The value of piggy is " << piggy << "\n";
}




// Result of execution
//
// The value of dog1 is 12
// The value of dog2 is 17
// The value of dog3 is -13
// The value of piggy is 123

INFORMATION HIDING

Examine the program named CLAS.CPP for our first example of a program with a little information hiding contained in it. This program is identical to the last one except for the way it does a few of its operations. We will take the differences one at a time and explain what is happening here. Keep in mind that this is a trivial program and the safeguards built into it are not needed for such a simple program but are used here to illustrate how to use these techniques in a larger much more complicated program.

The first difference is that we have a class instead of a structure beginning in line 4 of this program. The only difference between a class and a structure is that a class begins with a private section whereas a structure has no private section automatically defined. The keyword class is used to declare a class as illustrated here.

The class named one_datum is composed of the single variable named data_store and two functions, one named set() and the other named get_value(). A more complete definition of a class is a group of variables and one or more functions that can operate on that data. Stay with us, we will tie this all together in a meaningful and useful way very soon.

CLAS.CPP

  
// Chapter 5 - Program 2
#include <iostream.h>

class one_datum {
int data_store;
public:
void set(int in_value);
int get_value(void);
};

void one_datum::set(int in_value)
{
data_store = in_value;
}

int one_datum::get_value(void)
{
return data_store;
}

main()
{
one_datum dog1, dog2, dog3;
int piggy;

dog1.set(12);
dog2.set(17);
dog3.set(-13);
piggy = 123;

// dog1.data_store = 115; This is illegal in C++
// dog2.data_store = 211; This is illegal in C++

cout << "The value of dog1 is " << dog1.get_value() << "\n";
cout << "The value of dog2 is " << dog2.get_value() << "\n";
cout << "The value of dog3 is " << dog3.get_value() << "\n";
cout << "The value of piggy is " << piggy << "\n";
}




// Result of execution
//
// The value of dog1 is 12
// The value of dog2 is 17
// The value of dog3 is -13
// The value of piggy is 123

WHAT IS A PRIVATE SECTION?

A private section of a class is a section of data which cannot be accessed outside of the class, it is hidden from any outside access. Thus, the variable named data_store which is a part of the object (an object will be defined completely later) named dog1 declared in line 23 is not available for use anywhere in the main program. It is as if we have built a "brick wall" around the variables to protect them from accidental corruption by outside programming influences. It seems a little dumb to declare a variable in the main program that we cannot use, but that is exactly what we did.

Figure 5-1 is a graphical representation of the class with its "brick wall" built around the data to protect it. You will notice the small peep holes we have opened up to allow the user to gain access to the functions. The peep holes were opened by declaring the functions in the public section of the class.

WHAT IS A PUBLIC SECTION?

A new keyword, public, is introduced in line 6 which states that anything following this keyword can be accessed from outside of this class. Because the two functions are defined following the keyword public, they are both public and available for use in the calling function or any other function that is within the scope of the calling function. This opens two small peepholes in the solid wall of protection. You should keep in mind that the private variable is not available to the calling program. Thus, we can only use the variable by calling one of the two functions defined as a part of the class. These are called member functions because they are members of the class.

Since we have declared two functions, we need to define them by saying what each function will actually do. This is done in lines 11 through 19 where they are each defined in the normal way, except that the class name is prepended onto the function name and separated from it by a double colon. These two function definitions are called the implementation of the functions. The class name is required because we can use the same function name in other classes and the compiler must know with which class to associate each function implementation.

One of the key points to be made here is that the private data contained within the class is available within the implementation of the member functions of the class for modification or reading in the normal manner. You can do anything with the private data within the function implementations which are a part of that class, but the private data of other classes is hidden and not available within the member functions of this class. This is the reason we must prepend the class name to the function names of this class when defining them.

It would be well to mention at this point that it is legal to include variables and functions in the private part and additional variables and functions in the public part. In most practical situations, variables are included in only the private part and functions are included in only the public part of a class definition. Occasionally, variables or functions are used in the other part. This sometimes leads to a very practical solution to a particular problem, but in general, the entities are used only in the places mentioned.

In C++ we have three scopes of variables, local, file and class.
Local variables are localized to a single function and file variables are available anywhere in a file following their definition. A variable with class scope is available anywhere within the scope of a class and nowhere else.

You must be very confused by this point since we have given a lot of rules but few reasons for doing all of this. Stay with us and you will soon see that there are very practical reasons for doing all of this.


MORE NEW TERMINOLOGY

As with most new technologies, developers seem to delight in making up new names for all aspects of their new pet. Object oriented programming is no different, so we must learn new names for some of our old familiar friends if we are going to learn how to effectively use it. To help you learn this new programming terminology, we will list a few of them here and begin using them in the text to get you used to seeing and using them.

  • A class is a grouping of data and methods (functions). A class is very much like a type as used in ANSI-C, it is only a pattern to be used to create a variable which can be manipulated in a program.
  • An object is an instance of a class, which is similar to a variable defined as an instance of a type. An object is what you actually use in a program since it has values and can be changed.
  • A method is a function contained within the class. You will find the functions used within a class referred to as methods.
  • A message is the same thing as a function call. In object oriented programming, we send messages instead of calling functions. For the time being, you can think of them as identical. Later in this tutorial we will see that they are in fact slightly different.

With all the new terminology, we will continue our study of the program named CLAS.CPP and show you how to use the class. We can now say that we have a class composed of one variable and two methods. The methods operate on the variable contained in the class when they receive messages to do so. In this tutorial we will use the terms object and variable interchangeably because both names are very descriptive of what the object really is.

This is a small point but it could be easily overlooked. Lines 7 and 8 of this program are actually the prototypes for the two methods, and is our first example of the use of a prototype within a class. This is the reason we spent so much time on prototypes in the last chapter. You will notice line 7 which says that the method named set requires one parameter of type int and returns nothing, hence the return type is void. The method named get_value() however, according to line 8, has no input parameters but returns an int type value to the caller.

SENDING A MESSAGE

Following all of the definitions in lines 1 through 19, we finally come to the program where we actually use the class. In line 23 we declare three objects of the class one_datum and name the objects dog1, dog2, and dog3. Each object contains a single data point which we can set through use of one method or read its value through use of the other method, but we cannot directly set or read the value of the data point because it is hidden within the "block wall" around the class. In line 26, we send a message to the object named dog1 instructing it to set its internal value to 12, and even though this looks like a function call, it is properly called sending a message to a method. Remember that the object named dog1 has a method associated with it called set() that sets its internal value to the actual parameter included within the message. You will notice that the form is very much like the means of accessing the elements of a structure. You mention the name of the object with a dot connecting it to the name of the method. In a similar manner, we send a message to each of the other two objects dog2 and dog3 to set their values to those indicated.

Lines 31 and 32 have been commented out because the operations are illegal since the variable named data_store is private and not available to the code outside of the object itself. It should be obvious, but it will be pointed out that the data contained within the object named dog1 is not available within the methods of dog2 or dog3 because they are different objects. These rules are all devised to help you develop better code more quickly and you will soon see how they help.

The other method defined for each object is used in lines 34 through 36 to illustrate how it can be used. In each case, another message is sent to each object and the returned result is output to the monitor via the stream library.

USING A NORMAL VARIABLE

There is another variable named piggy declared and used throughout this example program that illustrates that a normal variable can be intermixed with the objects and used in the normal manner. The use of this variable should pose no problem to you, so after you understand the program, be sure to compile and execute it. It would be a good exercise for you to remove the comments from lines 31 and 32 to see what kind of error message your compiler issues.

This program illustrates information hiding but it will not be clear to you that it really does anything worthwhile until we study the next two programs. Be sure to compile and execute this program before continuing on to the next example program.

A PROGRAM WITH PROBLEMS

Examine the program named OPENPOLE.CPP for an example of a program with a few serious problems that will be overcome in the next example program by using the principles of encapsulation.

We have two structures declared, one being a rectangle and the other being a pole. The data fields should be self explanatory with the exception of the depth of the flagpole which is the depth it is buried in the ground, the overall length of the pole is therefore the sum of the length and the depth.

Based on your experience with ANSI-C, you should have no problem understanding exactly what this program is doing, but you may be a bit confused at the meaning of the result found in line 38 where we multiply the height of the square with the width of the box. This is perfectly legal to do in ANSI-C or C++, but the result has no earthly meaning because the data are for two different entities. Likewise, the result calculated in line 40 is even sillier because the product of the height of the square and the depth of the flagpole has absolutely no meaning in any real world physical system we can think up.

Wouldn't it be neat if we had a way to prevent such stupid things from happening in a large production program. If we had a good program that defined all of the things we can do with a square and another program that defined everything we could do with a pole, and if the data could be kept mutually exclusive, we could prevent these silly things from happening.

It should come as no real surprise to you that the next program will do just those things for us and do it in a very elegant way. Before proceeding on to the next example program, you should compile and execute this one even though it displays some silly results.


OPENPOLE.CPP

  
// Chapter 5 - Program 3
#include <iostream.h>

int area(int rec_height, int rec_width);

struct rectangle {
int height;
int width;
};

struct pole {
int length;
int depth;
};

int area(int rec_height, int rec_width) //Area of a rectangle
{
return rec_height * rec_width;
}


main()
{
rectangle box, square;
pole flag_pole;

box.height = 12;
box.width = 10;
square.height = square.width = 8;

flag_pole.length = 50;
flag_pole.depth = 6;

cout << "The area of the box is " <<
area(box.height, box.width) << "\n";
cout << "The area of the square is " <<
area(square.height, square.width) << "\n";
cout << "The funny area is " <<
area(square.height, box.width) << "\n";
cout << "The bad area is " <<
area(square.height, flag_pole.depth) << "\n";
}




// Result of execution
//
// The area of the box is 120
// The area of the square is 64
// The funny area is 80
// The bad area is 48


OBJECTS PROTECT DATA

Examine the program named CLASPOLE.CPP as an example of data protection in a very simple program.

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 initialize the values of the objects created and the other method to return the area of the object. The two methods are defined in lines 12 through 21 in the manner described earlier in this chapter. 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 33 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 36 through 38 are commented out for that reason and the messages are sent to the objects in lines 40 and 41 to tell them to initialize themselves to the values input as parameters. The flag_pole is initialized 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 50 through 53 have been commented out. Once again, it is difficult to see the utility of this in such a simple program. In a large program, using the compiler to enforce the rules can pay off in a big way.

Figure 5-2 is a graphical illustration of the two objects available for use within the calling program. 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 chapter, 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.

CLASPOLE.CPP

  
// Chapter 5 - Program 4
#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;
};



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";
}




// Result of execution
//
// The area of the box is 120
// The area of the square is 64

HAVE YOU EVER USED THIS TECHNIQUE BEFORE?

A good example of the use of this technique is in the file commands you have been using with ANSI-C. The data in the file is 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 is 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 in the monitor and keyboard handling routines. You are prevented from getting into the workings of them and corrupting them accidentally, or on purpose if you have such a bent, but once again, you are provided with all of the data interfaces that you really need.

Suppose you are developing a program to analyze 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 color. 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 his programs in such a way that he too can practice information hiding and reduce the debugging time.

WHAT DOES THIS COST?

It should be clear that this technique will cost you something in efficiency because every access to the elements of the object will require the time and inefficiency of a call to a function, or perhaps I should be more proper and refer to it as a method. The time saved in building a large program, however, could easily be saved in debug time when it comes time to iron out the last few bugs. This is because a program made up of objects that closely match the application are much easier to understand than a program that does not.

This is obviously such a small program that it is silly to try to see any gain with this technique. In a real project however, it could be a great savings if one person developed all of the details of the rectangle, programmed it, and made it available to you to simply use. This is exactly what has been done for you if you consider the video monitor an object. There is a complete set of preprogrammed and debugged routines you can use to make the monitor do anything you wish it to do, all you have to do is study the interface to the routines and use them, expecting them to work. As we mentioned earlier, it is impossible for you to multiply the size of your monitor screen by the depth of the flag pole because that information is not available to you to use in a corruptible way.

After you understand some of the advantages of this style of programming, be sure to compile and execute this program.

CONSTRUCTORS AND DESTRUCTORS

The file named CONSPOLE.CPP introduces constructors and destructors and should be examined at this time.

This example program is identical to the last example 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 14 through 18. 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 uninitialized variable. When the object named box is declared in line 46, 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 49 and 50. Likewise, when the square is declared in line 46, the values of the height and the width of the square are each initialized 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 initialized in lines 58 and 59 to new values and processing continues. Since we have a constructor that does the initialization, 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 characterized 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 11 and defined in lines 31 through 35. 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. In like manner, if a destructor is defined for such a variable, it will execute following the completion of execution of the main program. This will not adversely affect your programs, but it is interesting to make note of.

CONSPOLE.CPP

  
// Chapter 5 - Program 5
#include <iostream.h>

class rectangle { // A simple class
int height;
int width;
public:
rectangle(void); // with a constuctor,
int area(void); // two methods,
void initialize(int, int);
~rectangle(void); // and a destructor
};

rectangle::rectangle(void) // constuctor
{
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;
};



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";
}




// Result of execution
//
// The area of the box is 36
// The area of the square is 36
// The area of the box is 120
// The area of the square is 64


OBJECT PACKAGING

Examine the file named BOXES1.CPP for an example of how not to package an object for universal use. This packaging is actually fine for a very small program but is meant to illustrate to you how to split your program up into smaller more manageable files when you are developing a large program or when you are part of a team developing a large system. The next three example programs in this chapter will illustrate the proper method of packaging a class.

This program is very similar to the last one with the pole structure dropped and the class named box. The class is defined in lines 4 through 12, the implementation of the class is given in lines 15 through 34, and the use of the class is given in lines 37 through 50. With the explanation we gave about the last program, the diligent student should have no problem understanding this program in detail.

BOXES1.CPP

  
// Chapter 5 - Program 6
#include <iostream.h>

class box {
int length;
int width;
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void) {return (length * width);}
~box(void); //Destructor
};


box::box(void) //Constructor implementation
{
length = 8;
width = 8;
}


// 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;
}


box::~box(void) //Destructor
{
length = 0;
width = 0;
}


main()
{
box small, medium, large; //Three boxes to work with

small.set(5, 7);
// Note that the medium box uses the values
// supplied by the constructor
large.set(15, 20);

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";

}




// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300


INLINE IMPLEMENTATION

The method in line 10 contains the implementation for the method as a part of the declaration because it is very simple, and because it introduces another new topic which you will use often in C++ programming. When the implementation is included in the declaration, it will be assembled inline wherever this function is called leading to much faster code. This is because there is no overhead to accomplish the call to the method. In some cases this will lead to code that is both smaller and faster. This is yet another illustration of the efficiency built into the C++ programming language.

Compile and execute this program in preparation for our study of the next three examples which are a repeat of this program in a slightly different form.


THE CLASS HEADER FILE

If you examine BOX.H carefully, you will see that it is only the class definition. No details are given of how the various methods are implemented except of course for the inline method named get_area(). This gives the complete definition of how to use the class with no implementation details. You would be advised to keep a hardcopy of this file available as we study the next two files. You will notice that it contains lines 4 through 12 of the previous example program named BOXES1.CPP.

This is called the class header file and cannot be compiled or executed.

BOX.H

  
// Chapter 5 - Program 7

class box {
int length;
int width;
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void) {return (length * width);}
~box(void); //Destructor
};




// Result of execution
//
// This header file cannot be compiled or executed


THE CLASS IMPLEMENTATION FILE

Examine the file named BOX.CPP for the implementation of the methods declared in the class header file. Notice that the class header file is included into this file in line 2 which contains all of the prototypes for its methods. The code from lines 15 through 34 of BOXES1.CPP is contained in this file which is the implementation of the methods declared in the class named box.

This file can be compiled but it cannot be executed because there is no main entry point which is required for all ANSI-C or C++ programs. When it is compiled, the object code will be stored in the current directory and available for use by other programs. It should be noted here that the result of compilation is usually referred to as an object file because it contains object code. This use of the word object has nothing to do with the word object as used in object oriented programming. It is simply a matter of overloading the use of the word. The practice of referring to the compiled result as an object file began long before the method of object oriented programming was ever considered.

The separation of the definition and the implementation is a major step forward in software engineering. The definition file is all the user needs in order to use this class effectively in a program. He needs no knowledge of the actual implementation of the methods. If he had the implementation available, he may study the code and find a trick he could use to make the overall program slightly more efficient, but this would lead to nonportable software and possible bugs later if the implementor changed the implementation without changing the interface. The purpose of object oriented programming is to hide the implementation in such a way that the implementation can not affect anything outside of its own small and well defined boundary or interface.

You should compile this implementation file now and we will use the result with the next example program.

BOX.CPP

  
// Chapter 5 - Program 8
#include "box.h"


box::box(void) //Constructor implementation
{
length = 8;
width = 8;
}


// 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;
}


box::~box(void) //Destructor
{
length = 0;
width = 0;
}




// Result of execution
//
// This implementation file cannot be executed


USING THE BOX OBJECT

Examine the file named BOXES2.CPP and you will find that the class we defined previously is used within this file. In fact, these last three programs taken together are identical to the program named BOXES1.CPP studied earlier.

The BOX.H file is included here, in line 3, since the definition of the box class is needed to declare three objects and use their methods. You should have no trouble seeing that this is a repeat of the previous program and will execute in exactly the same way. There is a big difference in BOXES1.CPP and BOXES2.CPP as we will see shortly.

A very important distinction must be made at this point. We are not merely calling functions and changing the terminology a little to say we are sending messages. There is an inherent difference in the two operations. Since the data for each object is tightly bound up in the object, there is no way to get to the data except through the methods and we send a message to the object telling it to perform some operation based on its internally stored data. However, whenever we call a function, we take along the data for it to work with as parameters since it doesn't contain its own data.

Be sure to compile and execute this program, but when you come to the link step, you will be required to link this program along with the result of the compilation when you compiled the class named box. The file is probably named BOX.OBJ that must be linked with this file. You may need to consult the documentation for your C++ compiler to learn how to do this. Even if it seems to be a lot of trouble to learn how to link several files together, it will be worth your time to do so now because we will be linking several more multifile C++ programs in the remainder of this tutorial.

If you are using Turbo C++, this is your first opportunity to use a project file. If you are using Zortech C++ or one of the other implementations, you can use the "make" facility included with your compiler. Regardless of which C++ compiler you are using, it would pay you to stop and learn how to use the multifile technique provided with your compiler because you will need to use it several times before the end of this tutorial. The nature of C++ tends to drive the programmer to use many files for a given programming project and you should develop the habit early.

BOXES2.CPP

  
// Chapter 5 - Program 9
#include <iostream.h>
#include "box.h"

main()
{
box small, medium, large; //Three boxes to work with

small.set(5, 7);
// Note that the medium box uses the values
// supplied by the constructor
large.set(15, 20);

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";

}




// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300

INFORMATION HIDING

The last three example programs illustrate a method of information hiding that can have a significant impact on the quality of software developed for a large project. Since the only information the user of the class really needs is the class header, that is all he needs to be given. The details of implementation can be kept hidden from him to prevent him from studying the details and possibly using a quirk of programming to write some rather obtuse code. Since he doesn't know exactly what the implementor did, he must follow only the definition given in the header file. This can have a significant impact on a large project. As mentioned earlier, accidental corruption of data is prevented also.

Another reason for hiding the implementation is economic. The company that supplied you with your C++ compiler gave you many library functions but did not supply the source code to the library functions, only the interface to each function. You know how to use the file access functions but you do not have the details of implementation, nor do you need them. Likewise a class library industry can develop which supplies users with libraries of high quality, completely developed and tested classes, for a licensing fee of course. Since the user only needs the interface defined, he can be supplied with the interface and the object (compiled) code for the class and can use it in any way he desires. The suppliers source code is protected from accidental or intentional compromise and he can maintain complete control over it.

It is very important that you understand the principles covered in this chapter before proceeding on to the next chapter. If you feel you are a little weak in any of the areas covered here, you should go over them again before proceeding on. A point that should be made here that may be obvious to you, is that it requires some amount of forethought to effectively use classes.


ABSTRACT DATA TYPES

We mentioned the abstract data type at the beginning of this chapter and again briefly midway through, and it is time to describe it a little more completely. An abstract data type is a group of data, each of which can store a range of values, and a set of methods or functions that can operate on that data. Since the data are protected from any outside influence, it is protected and said to be encapsulated. Also, since the data is somehow related, it is a very coherent group of data that may be highly interactive with each other, but with little interaction of its class outside the scope.

The methods, on the other hand, are coupled to the outside world through the interface, but there are a limited number of contacts with the outside world and therefore a weak coupling with the outside. The object is therefore said to be loosely coupled to the outside world. Because of the tight coherency and the loose coupling, ease of maintenance of the software is greatly enhanced. The ease of maintenance may be the greatest benefit of object oriented programming.

It may bother you that even though the programmer may not use the private variables directly outside of the class, they are in plain sight and he can see what they are and can probably make a good guess at exactly how the class is implemented. The variables could have been hidden completely out of sight in another file, but because the designers of C++ wished to make the execution of the completed application as efficient as possible, the variables were left in the class definition where they can be seen but not used.

FRIEND FUNCTIONS

A function outside of a class can be defined to be a friend function by the class which gives the friend free access to the private members of the class. This in effect, opens a small hole in the protective shield of the class, so it should be used very carefully and sparingly. There are cases where it helps to make a program much more understandable and allows controlled access to the data. Friend functions will be illustrated in some of the example programs later in this tutorial. It is mentioned here for completeness of this section. A single isolated function can be declared as a friend, as well as members of other classes, and even entire classes can be given friend status if needed in a program. Neither a constructor nor a destructor can be a friend function.

THE struct IN C++

The struct is still useable in C++ and operates just like it does in ANSI-C with one addition. You can include methods in a structure that operate on data in the same manner as in a class, but all methods and data are automatically defaulted to be public in a structure. Of course you can make any of the data or methods private by defining a private section within the structure. The structure should be used only for constructs that are truly structures. If you are building even the simplest objects, you are advised to use classes to define them.

A VERY PRACTICAL CLASS

The examples of encapsulation used in this chapter have all been extremely simple in order to illustrate the mechanics of encapsulation. Since it would be expedient to study a larger example the date class is given below for your instruction. The date class is a complete nontrivial class which can be used in any program to get the current date and print it as an ASCII string in any of four predefined formats. It can also be used to store any desired date and format it for display.

Examine the file named DATE.H which is the header file for the date class. This file is so well commented that we don't have much else to say about it. If you understand the principles covered in this chapter you should have no problem understanding this class. The first thing that is new to you is the reserved word protected which is used in line 12. We will define this word in a couple of chapters. Until then, pretend that it means the same thing as private and you will be close enough for this present example. The code in lines 8 and 9 along with line 55 will be explained shortly. For the present time, simply pretend those lines of code are not there. Also the keyword static as used in lines 16 and 17 will be explained later.

DATE.H

  
// Chapter 5 - Program 10

// This date class is intended to illustrate how to write a non-
// trivial class in C++. Even though this class is non-trivial,
// it is still simple enough for a new C++ programmer to follow
// all of the details.

#ifndef DATE_H
#define DATE_H

class date {
protected:
int month; // 1 through 12
int day; // 1 through max_days
int year; // 1500 through 2200
static char out_string[25]; // Format output area
static char format; // Format to use for output

// Calculate how many days are in any given month
// Note - This is a private method which can be called only
// from within the class itself
int days_this_month(void);

public:
// Constructor - Set the date to the current date and set
// the format to 1
date(void);

// Set the date to these input parameters
// if return = 0 ---> All data is valid
// if return = 1 ---> Something out of range
int set_date(int in_month, int in_day, int in_year);

// Get the month, day, or year of the stored date
int get_month(void) { return month; };
int get_day(void) { return day; };
int get_year(void) { return year; };

// Select the desired string output format for use when the
// get_date_string is called
void set_date_format(int format_in) { format = format_in; };

// Return an ASCII-Z string depending on the stored format
// format = 1 Aug 29, 1991
// format = 2 8/29/91
// format = 3 8/29/1991
// format = 4 29 Aug 1991 Military time
// format = ? Anything else defaults to format 1
char *get_date_string(void);

// Return Jan Feb Mar Apr etc.
char *get_month_string(void);
};

#endif


You should spend the time necessary to completely understand this class header, with the exception of the new things added, before going on to the implementation for this class.

The file named DATE.CPP is the implementation for the date class and once again, there is nothing unusual or difficult about this code.
It uses very simple logic to store and format the date in a usable manner. You should study this code until you understand it completely before going on to the next example which will use the date class in a main program.

DATE.CPP

  
// Chapter 5 - Program 11

// This file contains the implementation for the date class.

#include <stdio.h> // Prototype for sprintf
#include <time.h> // Prototypes for the current date
#include "date.h"

char date::format; // This defines the static data member
char date::out_string[25]; // This defines the static string

// Constructor - Set date to current date, and
// set format to the default of 1
date::date(void)
{
time_t time_date;
struct tm *current_date;

time_date = time(NULL); // DOS system call
current_date = localtime(&time_date); // DOS system call
month = current_date->tm_mon + 1;
day = current_date->tm_mday;
year = current_date->tm_year + 1900;
format = 1;
}


// Set the date to these input parameters
// if return = 0 ---> All data is valid
// if return = 1 ---> Something out of range
int date::set_date(int in_month, int in_day, int in_year)
{
int temp = 0;
int max_days;
// The limits on the year are purely arbitrary
if (in_year < 1500) { // Check that the year is between
year = 1500; // 1500 and 2200
temp = 1;
} else {
if (in_year > 2200) {
year = 2200;
temp = 1;
} else
year = in_year;
}

if(in_month < 1) { // Check that the month is between
month = temp = 1; // 1 and 12
} else {
if (in_month > 12) {
month = 12;
temp = 1;
} else
month = in_month;
}

max_days = days_this_month();
if (in_day < 1) { // Check that the day is between
day = temp = 1; // 1 and max_days
} else {
if (in_day > max_days) {
day = max_days;
temp = 1;
} else
day = in_day;
}

return temp;
}



static char *month_string[13] = {" ", "Jan", "Feb", "Mar", "Apr",
"May", "Jun", "Jul", "Aug",
"Sep", "Oct", "Nov", "Dec"};

// Return Jan Feb Mar Apr etc.
char *date::get_month_string(void)
{
return month_string[month];
}



// Return an ASCII-Z string depending on the stored format
// format = 1 Aug 29, 1991
// format = 2 8/29/91
// format = 3 8/29/1991
// format = 4 29 Aug 1991 Military time
// format = ? Anything else defaults to format 1
char *date::get_date_string(void)
{
switch (format) {
// This printout assumes that the year will be
// between 1900 and 1999
case 2 : sprintf(out_string, "%02d/%02d/%02d",
month, day, year - 1900);
break;

case 3 : sprintf(out_string, "%02d/%02d/%04d",
month, day, year);
break;

case 4 : sprintf(out_string, "%d %s %04d",
day, month_string[month], year);
break;

case 1 : // Fall through to the default case
default : sprintf(out_string, "%s %d, %04d",
month_string[month], day, year);
break;
}
return out_string;
}



int days[13] = {0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};

// Since this is declared in the private part of the class
// header is is only available for use within the class.
// It is hidden from use outside of the class.
int date::days_this_month(void)
{
if (month != 2)
return days[month];

if (year % 4) // Not leap year
return 28;
if (year % 100) // It is leap year
return 29;
if (year % 400) // Not leap year
return 28;
return 29; // It is leap year
}


The very simple program named USEDATE.CPP is a main program that uses the date class to list the current date and another date on the monitor. Once again, you should have no problem understanding this program so nothing more will be said about it.

You should spend the time necessary to understand these three files because they are the starting point for a practical track in the next few chapters. This class will be used in conjunction with others to illustrate single and multiple inheritance. Even though you do not understand all of the details of these files, spend enough time that you are comfortable with the structure and the major points of them.

USEDATE.CPP

  
// Chapter 5 - Program 12
// This is a very limited test of the date class

#include <iostream.h>
#include "date.h"

void main(void)
{
date today, birthday;

birthday.set_date(7, 21, 1960);
cout << "Limited test of the date class\n";
cout << "Today is " << today.get_date_string() << "\n";
cout << "Birthday is " << birthday.get_date_string() << "\n";

today.set_date_format(4);
cout << "Today is " << today.get_date_string() << "\n";
cout << "Birthday is " << birthday.get_date_string() << "\n";
}


// Result of execution

// Limited test of the date class
// Today is Jan 20, 1992
// Birthday is Jul 21, 1960
// Today is 20 Jan 1992
// Birthday is 21 Jul 1960


We will continue our discussion of encapsulation in the next chapter.


PROGRAMMING EXERCISES

  1. Add a method to CLAS.CPP which will supply the square of the stored value. Include some code in the main program to read and display the squared values.
  2. Continuing with CLAS.CPP, add a constructor to initialize the stored value to 10 and add a few lines of code to the main program to display the values immediately following the object definition.
  3. Add an output statement to the rectangle constructor of the program named CONSPOLE.CPP and another to the destructor to prove to yourself that they really are called by the system when we said they are.
  4. Write a more comprehensive program to use the date class presented at the end of this chapter.
  5. Write a name class which is somewhat similar to the date class which can store any name in three parts and return the full name in any of several different formats such as the following;


John Paul Doe
J. P. Doe
Doe, John Paul
and any other formats you desire.

If this is carefully planned, it could be useful to you someday.

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT