Copy Link
Add to Bookmark
Report

Coronado Enterprises C++ TUTOR: Chapter 6

Chapter 6: MORE ENCAPSULATION


The purpose of this chapter is to illustrate how to use some of the traditional aspects of C or C++ with classes and objects. Pointers to an object as well as pointers within an object will be illustrated. Arrays embedded within an object, and an array of objects will be illustrated. Since objects are simply another C++ data construct, all of these things are possible and can be used if needed.

In order to have a systematic study, we will use the program named BOXES1.CPP from the last chapter as a starting point and we will add a few new constructs to it for each example program. You will recall that it was a very simple program with the class definition, the class implementation, and the main calling program all in one file. This was selected as a starting point because we will eventually make changes to all parts of the program and it will be convenient to have it all in a single file for illustrative purposes. It must be kept in mind however that the proper way to use these constructs is to separate them into the three files as was illustrated in BOX.H, BOX.CPP, and BOXES2.CPP in the last chapter. This allows the implementor of box to supply the user with only the interface, namely BOX.H. Not giving him the implementation file named BOX.CPP, is practicing the technique of information hiding.

As we have said many times, it seems silly to break up such a small program into three separate files, and it is sort of silly. The last chapter of this tutorial will illustrate a program large enough to require dividing the program up into many separate files.


AN ARRAY OF OBJECTS


Examine the file named OBJARRAY.CPP for our first example of an array of objects. This file is nearly identical to the file named BOX1.CPP until we come to line 44 where an array of 4 boxes are declared.

Recalling the operation of the constructor you will remember that each of the four box objects will be initialized 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 initializing parameters, but we will in the next program.) This is an efficiency consideration since it would probably be an error to initialize 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 49 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 50 and the operation of that code should be clear to you. The other method is called in the output statement in lines 57 and 58 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 49 and is still available for use in line 56 since we have not yet left the enclosing block which begins in line 43 and extends to line 65.


OBJARRAY.CPP

  
// Chapter 6 - Program 1
#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);
}


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




// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300
// The array box area is 64
// The array box area is 110
// The array box area is 120
// The array box area is 130
// The extra data value is 1
// The extra data value is 2
// The extra data value is 3
// The extra data value is 4
// The extra data value is 5

DECLARATION AND DEFINITION OF A VARIABLE


An extra variable was included for illustration, the one named extra_data in line seven. Since the keyword static is used to modify this variable in line 7, it is an external variable and only one copy of this variable will ever exist. All seven objects of this class share a single copy of this variable which is global to the objects defined in line 44.

The variable is actually only declared here which says it will exist somewhere, but it is not defined. A declaration says the variable will exist and gives it a name, but the definition actually defines a place to store it somewhere in the computers memory space. By definition, a static variable can be declared in a class header but it cannot be defined there, so it is defined somewhere outside of the header, usually in the implementation file. In this case it is defined in line 16 and can then be used throughout the class.

Line 23 of the constructor sets the single global variable to 1 each time an object is declared. Only one assignment is necessary so the other six are actually wasted code. To illustrate that there is only one variable shared by all objects of this class, the method to read its value also increments it. Each time it is read in lines 60 through 64, it is incremented and the result of the execution proves that there is only a single variable shared by all objects of this class. You will also note that the method named get_extra() is defined within the class declaration so it will be assembled into the final program as inline code.

Be sure you understand this program and especially the static variable, then compile and execute it to see if you get the same result as listed at the end of the program.


A STRING WITHIN AN OBJECT


Examine the program named OBJSTRNG.CPP for our first example of an object with an embedded string. Actually, the object does not have an embedded string, it has an embedded pointer, but the two work so closely together that we can study one and understand both.

You will notice that line 7 contains a pointer to a string named line_of_text. The constructor contains an input parameter which is a pointer to a string which will be copied to the string named line_of_text within the constructor. We could have defined the variable line_of_text as an actual array in the class, then used strcpy() to copy the string into the object and everything would have worked the same, but we will leave that as an exercise for you at the end of this chapter. It should be pointed out that we are not limited to passing a single parameter to a constructor. Any number of parameters can be passed, as will be illustrated later.

You will notice that when the three boxes are declared this time, we supply a string constant as an actual parameter with each declaration which is used by the constructor to assign the string pointer some data to point to. When we call get_area() in lines 48 through 53, we get the message displayed and the area returned. It would be prudent to put these operations in separate methods since there is no apparent connection between printing the message and calculating the area, but it was written this way to illustrate that it can be done. What this really says is that it is possible to have a method that has a side effect, the message output to the monitor, and a return value, the area of the box. However, as we discussed in chapter 4 when we studied DEFAULT.CPP, the order of evaluation is sort of funny, so we broke each line into two lines.

After you understand this program, compile and execute it.


OBJSTRNG.CPP

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

class box {
int length;
int width;
char *line_of_text;
public:
box(char *input_line); //Constructor
void set(int new_length, int new_width);
int get_area(void);
};


box::box(char *input_line) //Constructor implementation
{
length = 8;
width = 8;
line_of_text = input_line;
}


// 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)
{
cout << line_of_text << "= ";
return (length * width);
}


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

small.set(5, 7);
large.set(15, 20);

cout << "The area of the ";
cout << small.get_area() << "\n";
cout << "The area of the ";
cout << medium.get_area() << "\n";
cout << "The area of the ";
cout << large.get_area() << "\n";

}




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

AN OBJECT WITH AN INTERNAL POINTER


The program named OBJINTPT.CPP is our first example program with an embedded pointer which will be used for dynamic allocation of data.

In line 7 we declare a pointer to an integer variable, but it is only a pointer, there is no storage associated with it. The constructor therefore allocates an integer type variable on the heap for use with this pointer in line 21. It should be clear to you that the three objects created in line 45 each contain a pointer which points into the heap to three different locations. Each object has its own dynamically allocated variable for its own private use. Moreover each has a value of 112 stored in its dynamically allocated data because line 22 stores that value in each of the three locations, once for each call to the constructor.

In such a small program, there is no chance that we will exhaust the heap, so no test is made for unavailable memory. In a real production program, it would be expedient to test that the value of the returned pointer is not NULL to assure that the data actually did get allocated.

The method named set() has three parameters associated with it and the third parameter is used to set the value of the new dynamically allocated variable. There are two messages passed, one to the small box and one to the large box. As before, the medium box is left with its default values.

The three areas are displayed followed by the three stored values in the dynamically allocated variables, and we finally have a program that requires a destructor in order to be completely proper. If we simply leave the scope of the objects as we do when we leave the main program, we will leave the three dynamically allocated variables on the heap with nothing pointing to them. They will be inaccessible and will therefore represent wasted storage on the heap. For that reason, the destructor is used to delete the variable which the pointer named point is referencing as each object goes out of existence. In this case, lines 37 and 38 assign values to variables that will be automatically deleted. Even though these lines of code really do no good, they are legal statements.

Actually, in this particular case, the variables will be automatically reclaimed when we return to the operating system because all program cleanup is done for us at that time. If this were a function that was called by another function however, the heap space would be wasted. This is an illustration of good programming practice, that of cleaning up after yourself when you no longer need some dynamically allocated variables.

One other construct should be mentioned once again, that of the inline method implementations in line 11 and 12. As we mentioned in chapter 5 and repeated earlier in this chapter, inline functions can be used where speed is of the utmost in importance since the code is assembled inline rather than by actually making a method call. Since the code is defined as part of the declaration, the system will assemble it inline, and a separate implementation for these methods is not needed. If the inline code is too involved, the compiler is allowed to ignore the inline request and will actually assemble it as a separate method, but it will do it invisibly to you and will probably not even tell you about it.

Remember that we are interested in using information hiding and inline code prevents hiding of the implementation, putting it out in full view. Many times you will be more interested in speeding up a program than you are in hiding a trivial implementation. Since most inline methods are trivial, feel free to use the inline code construct.

Be sure to compile and execute this program.


OBJINTPT.CPP

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

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


box::box(void) //Constructor implementation
{
length = 8;
width = 8;
point = new int;
*point = 112;
}


// This method will set a box size to the input parameters
void box::set(int new_length, int new_width, int stored_value)
{
length = new_length;
width = new_width;
*point = stored_value;
}


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


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

small.set(5, 7, 177);
large.set(15, 20, 999);

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";
cout << "The small box stored value is " <<
small.get_value() << "\n";
cout << "The medium box stored value is " <<
medium.get_value() << "\n";
cout << "The large box stored value is " <<
large.get_value() << "\n";
}




// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300
// The small box stored value is 177
// The medium box stored value is 112
// The large box stored value is 999


A DYNAMICALLY ALLOCATED OBJECT


Examine the file named OBJDYNAM.CPP for our first look at a dynamically allocated object. This is not any different than any other dynamically allocated object, but an example is always helpful.

In line 39 we declare a pointer to an object of type box and since it is only a pointer with nothing to point to, we dynamically allocate an object for it in line 44, with the object being created on the heap just like any other dynamically allocated variable. When the object is created in line 44, the constructor is called automatically to assign values to the two internal storage variables. Note that the constructor is not called when the pointer is declared since there is nothing to initialize. It is called when the object is allocated.

Reference to the components of the object are handled in much the same way that structure references are made, through use of the pointer operator as illustrated in lines 50 through 52. Of course you can use the pointer dereferencing method without the arrow such as (*point).set(12, 12); as a replacement for line 51 but the arrow notation is much more universal and should be used. Finally, the object is deleted in line 54 and the program terminates. If there were a destructor for this class, it would be called as part of the delete statement to clean up the object prior to deletion.

You have probably noticed by this time that the use of objects is not much different from the use of structures. Be sure to compile and execute this program after you have studied it thoroughly.


OBJDYNAM.CPP

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


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


// This method will calculate and return the area of a box instance
int box::get_area(void)
{
return (length * width);
}


main()
{
box small, medium, large; //Three boxes to work with
box *point; //A pointer to a box

small.set(5, 7);
large.set(15, 20);

point = new box; // Use the defaults supplied by the constructor

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

cout << "The new box area is " << point->get_area() << "\n";
point->set(12, 12);
cout << "The new box area is " << point->get_area() << "\n";

delete point;
}




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

AN OBJECT WITH A POINTER TO ANOTHER OBJECT


The program named OBJLIST.CPP contains an object with an internal reference to another object of its own class. This is the standard structure used for a singly linked list and we will keep the use of it very simple in this program.

The constructor contains the statement in line 21 which assigns the pointer the value of NULL to initialize the pointer. This is a good idea for all of your programming, don't allow any pointer to point off into space, but initialize all pointers to something. By assigning the pointer within the constructor, you guarantee that every object of this class will automatically have its pointer initialized. It will be impossible to overlook the assignment of one of these pointers.

Two additional methods are declared in lines 12 and 13 with the one in line 13 having a construct we have not yet mentioned in this tutorial. This method returns a pointer to an object of the box class. As you are aware, you can return a pointer to a struct in standard C, and this is a parallel construct in C++. The implementation in lines 48 through 51 returns the pointer stored within the object. We will see how this is used when we get to the actual program.

An extra pointer named box_pointer is declared in the main program for use later and in line 66 we make the embedded pointer within the small box point to the medium box, and in line 67 we make the embedded pointer within the medium box point to the large box. We have effectively generated a linked list with three elements. In line 69 we make the extra pointer point to the small box. Continuing in line 70 we use it to refer to the small box and update it to the value contained in the small box which is the address of the medium box. We have therefore traversed from one element of the list to another by sending a message to one of the objects. If line 70 were repeated exactly as shown, it would cause the extra pointer to refer to the large box, and we would have traversed the entire linked list which is only composed of three elements.

OBJLIST.CPP

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

class box {
int length;
int width;
box *another_box;
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void);
void point_at_next(box *where_to_point);
box *get_next(void);
};


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


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


// This method causes the pointer to point to the input parameter
void box::point_at_next(box *where_to_point)
{
another_box = where_to_point;
}


// This method returns the box the current one points to
box *box::get_next(void)
{
return another_box;
}


main()
{
box small, medium, large; //Three boxes to work with
box *box_pointer; //A pointer to a box

small.set(5, 7);
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";

small.point_at_next(&medium);
medium.point_at_next(&large);

box_pointer = &small;
box_pointer = box_pointer->get_next();
cout << "The box pointed to has area " <<
box_pointer->get_area() << "\n";
}




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


ANOTHER NEW KEYWORD this


Another new keyword is available in C++, the keyword this. The word this is defined within any object as being a pointer to the object in which it is contained. It is implicitly declared as;

 class_name *this; 

and is initialized to point to the object for which the member function is invoked. This pointer is most useful when working with pointers and especially with a linked list when you need to reference a pointer to the object you are inserting into the list. The keyword this is available for this purpose and can be used in any object. Actually the proper way to refer to any variable within a list is through use of the predefined pointer this, by writing this->variable_name, but the compiler assumes the pointer is used, and we can simplify every reference by omitting the pointer. Use of the keyword this is not illustrated in a program at this point, but will be used in one of the larger example programs later in this tutorial.

You should study this program until you understand it completely then compile and execute it in preparation for our next example program.

A LINKED LIST OF OBJECTS


The next example program in this chapter is named OBJLINK.CPP and is a complete example of a linked list written in object oriented notation.

This program is very similar to the last one. In fact it is identical until we get to the main program. You will recall that in the last program the only way we had to set or use the embedded pointer was through use of the two methods named point_at_next() and get_next() which are listed in lines 40 through 51 of the present program. We will use these to build up our linked list then traverse and print the list. Finally, we will delete the entire list to free the space on the heap.

In lines 56 to 58 we declare three pointers for use in the program. The pointer named start will always point to the beginning of the list, but temp will move down through the list as we create it. The pointer named box_pointer will be used for the creation of each object. We execute the loop in lines 61 through 69 to generate the list where line 62 dynamically allocates a new object of the box class and line 63 fills it with nonsense data for illustration. If this is the first element in the list, the start pointer is set to point to this element, but if elements already exist, the last element in the list is assigned to point to the new element. In either case, the temp pointer is assigned to point to the last element of the list, in preparation for adding another element if there is another element to be added.

In line 72, the pointer named temp is pointed to the first element and it is used to increment its way through the list by updating itself in line 75 during each pass through the loop. When temp has the value of NULL, which it gets from the last element of the list, we are finished traversing the list.

Finally, we delete the entire list by starting at the beginning and deleting one element each time we pass through the loop in lines 79 through 84.

A careful study of the program will reveal that it does indeed generate a linked list of ten elements, each element being an object of class box. The length of this list is limited by the practicality of how large a list we desire to print out, but it could be lengthened to many thousands of these simple elements provided you have enough memory available to store them all.

Once again, the success of the dynamic allocation is not checked as it should be in a correctly written program. Be sure to compile and execute this example program.


OBJLINK.CPP

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

class box {
int length;
int width;
box *another_box;
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void);
void point_at_next(box *where_to_point);
box *get_next(void);
};


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


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


// This method causes the pointer to point to the input parameter
void box::point_at_next(box *where_to_point)
{
another_box = where_to_point;
}


// This method returns the box that this one points to
box *box::get_next(void)
{
return another_box;
}


main()
{
box *start = NULL; // Always points to the start of the list
box *temp; // Working pointer
box *box_pointer; // Used for box creation

// Generate the list
for (int index = 0 ; index < 10 ; index++ ) {
box_pointer = new box;
box_pointer->set(index + 1, index + 3);
if (start == NULL)
start = box_pointer; // First element in list
else
temp->point_at_next(box_pointer); // Additional element
temp = box_pointer;
}

// Print the list out
temp = start;
do {
cout << "The area is " << temp->get_area() << "\n";
temp = temp->get_next();
} while (temp != NULL);

// Delete the list
temp = start;
do {
temp = temp->get_next();
delete start;
start = temp;
} while (temp != NULL);
}




// Result of execution
//
// The area is 3
// The area is 8
// The area is 15
// The area is 24
// The area is 35
// The area is 48
// The area is 63
// The area is 80
// The area is 99
// The area is 120

NESTING OBJECTS


Examine the program named NESTING.CPP for an example of nesting classes which results in nested objects. A nested object could be illustrated with your computer in a rather simple manner. The computer itself is composed of many items which work together but work entirely differently, such as a keyboard, a disk drive, and a power supply. The computer is composed of these very dissimilar items and it is desireable to discuss the keyboard separately from the disk drive because they are so different. A computer class could be composed of several objects that are dissimilar by nesting the dissimilar classes within the computer class.

If however, we wished to discuss disk drives, we may wish to examine the characteristics of disk drives in general, then examine the details of a hard disk, and the differences of floppy disks. This would involve inheritance because much of the data about both drives could be characterized and applied to the generic disk drive then used to aid in the discussion of the other three. We will study inheritance in the next three chapters, but for now we will look at the embedded or nested class.

This example program contains a class named box which contains an object of another class embedded within it in line 16, the mail_info class. This object is available for use only within the class implementation of box because that is where it is defined. The main program has objects of class box defined but no objects of class mail_info, so the mail_info class cannot be referred to in the main program. In this case, the mail_info class object is meant to be used internally to the box class and one example is given in line 21 where a message is sent to the label.set() method to initialize the variables. Additional methods could be used as needed, but these are given as an illustration of how they can be called.

Of prime importance is the fact that there are never any objects of the mail_info class declared directly in the main program, they are inherently declared when the enclosing objects of class box are declared. Of course objects of the mail_info class could be declared and used in the main program if needed, but they are not in this example program. In order to be complete, the box class should have one or more methods to use the information stored in the object of the mail_info class. Study this program until you understand the new construct, then compile and execute it.

If the class and the nested classes require parameter lists for their respective constructors an initialization list can be given. This will be discussed and illustrated later in this tutorial.

NESTING.CPP

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

class mail_info {
int shipper;
int postage;
public:
void set(int in_class, int in_postage)
{shipper = in_class; postage = in_postage; }
int get_postage(void) {return postage;}
};

class box {
int length;
int width;
mail_info label;
public:
void set(int l, int w, int s, int p) {
length = l;
width = w;
label.set(s, p); }
int get_area(void) {return length * width;}
};


main()
{
box small, medium, large;

small.set(2, 4, 1, 35);
medium.set(5, 6, 2, 72);
large.set(8, 10, 4, 98);

cout << "The area is " << small.get_area() << "\n";
cout << "The area is " << medium.get_area() << "\n";
cout << "The area is " << large.get_area() << "\n";
}




// Result of Execution
//
// The area is 8
// The area is 30
// The area is 80

OPERATOR OVERLOADING


The example file named OPOVERLD.CPP contains examples of overloading operators. This allows you to define a class of objects and redefine the use of the normal operators. The end result is that objects of the new class can be used in as natural a manner as the predefined types. In fact, they seem to be a part of the language rather than your own add-on.

In this case we overload the + operator and the * operator, with the declarations in lines 10 through 12, and the definitions in lines 16 through 40. The methods are declared as friend functions so we can use the double parameter functions as listed. If we did not use the friend construct, the function would be a part of one of the objects and that object would be the object to which the message was sent. Including the friend construct allows us to separate this method from the object and call the method with infix notation. Using this technique, it can be written as object1 + object2 rather than object1.operator+(object2). Also, without the friend construct we could not use an overloading with an int type variable for the first parameter because we can not send a message to an integer type variable such as int.operator+(object). Two of the three operator overloadings use an int for the first parameter so it is necessary to declare them as friend functions.

There is no upper limit to the number of overloadings for any given operator. Any number of overloadings can be used provided the parameters are different for each particular overloading.

The header in line 16 illustrates the first overloading where the + operator is overloaded by giving the return type followed by the keyword operator and the operator we wish to overload. The two formal parameters and their types are then listed in the parentheses and the normal function operations are given in the implementation of the function in lines 18 through 21. The observant student will notice that the implementation of the friend functions are not actually a part of the class because the class name is not prepended onto the method name in line 16. There is nothing unusual about this implementation, it should be easily understood by you at this point. For purposes of illustration, some silly mathematics are performed in the method implementation, but any desired operations can be done.

The biggest difference occurs in line 56 where this method is called by using the infix notation instead of the usual message sending format. Since the variables small and medium are objects of the box class, the system will search for a way to use the + operator on two objects of class box and will find it in the overloaded operator+ method we have just discussed. The operations within the method implementation can be anything we need them to be, and they are usually much more meaningful than the silly math included here.

In line 58 we ask the system to add an int type constant to an object of class box, so the system finds the other overloading of the + operator beginning in line 25 to perform this operation. Also in line 60 we ask the system to use the * operator to do something to an int constant and an object of class box, which it satisfies by finding the method in lines 34 through 40. Note that it would be illegal to attempt to use the * operator the other way around, namely large * 4 since we did not define a method to use the two types in that order. Another overloading could be given with reversed types, and we could use the reverse order in a program.

You will notice that when using operator overloading, we are also using function name overloading since some of the function names are the same.

When we use operator overloading in this manner, we actually make our programs look like the class is a natural part of the language since it is integrated into the language so well. C++ is therefore an extendible language and can be molded to fit the mechanics of the problem at hand.

OPOVERLD.CPP

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

class box {
int length;
int width;
public:
void set(int l, int w) {length = l; width = w;}
int get_area(void) {return length * width;}
friend box operator+(box a, box b); // Add two boxes
friend box operator+(int a, box b); // Add a constant to a box
friend box operator*(int a, box b); // Multiply a box by a constant
};


box operator+(box a, box b) // Add two boxes' widths together
{
box temp;
temp.length = a.length;
temp.width = a.width + b.width;
return temp;
}


box operator+(int a, box b) // Add a constant to a box
{
box temp;
temp.length = b.length;
temp.width = a + b.width;
return temp;
}


box operator*(int a, box b) // Multiply a box by a constant
{
box temp;
temp.length = a * b.length;
temp.width = a * b.width;
return temp;
}


main()
{
box small, medium, large;
box temp;

small.set(2, 4);
medium.set(5, 6);
large.set(8, 10);

cout << "The area is " << small.get_area() << "\n";
cout << "The area is " << medium.get_area() << "\n";
cout << "The area is " << large.get_area() << "\n";

temp = small + medium;
cout << "The new area is " << temp.get_area() << "\n";
temp = 10 + small;
cout << "The new area is " << temp.get_area() << "\n";
temp = 4 * large;
cout << "The new area is " << temp.get_area() << "\n";
}




// Result of Execution
//
// The area is 8
// The area is 30
// The area is 80
// The new area is 20
// The new area is 28
// The new area is 1280

OPERATOR OVERLOADING CAVEATS


Each new topic we study has its pitfalls which must be warned against and the topic of operator overloading seems to have the record for pitfalls since it is so prone to misuse and has several problems. The overloading of operators is only available for classes, you cannot redefine the operators for the predefined simple types. This would probably be very silly anyway since the code could be very difficult to read if you changed some of them around.

The logical and (&&) and the logical or (||) operators can be overloaded for the classes you define, but they will not operate as short circuit operators. All members of the logical construction will be evaluated with no regard concerning the outcome. Of course the normal predefined logical operators will continue to operate as short circuit operators as expected, but not the overloaded ones.

If the increment (++) or decrement (--) operators are overloaded, the system has no way of telling whether the operators are used as preincrement or postincrement (or predecrement or postdecrement) operators. Which method is used is implementation dependent, so you should use them in such a way that it doesn't matter which is used.

Be sure to compile and execute OPOVERLD.CPP before continuing on to the next example program.


FUNCTION OVERLOADING IN A CLASS


Examine the program named FUNCOVER.CPP for an example of function name overloading within a class. In this program the constructor is overloaded as well as one of the methods to illustrate what can be done.

This file illustrates some of the uses of overloaded names and a few of the rules for their use. You will recall that the function selected is based on the number and types of the formal parameters only. The type of the return value is not significant in overload resolution.

In this case there are three constructors. The constructor which is actually called is selected by the number and types of the parameters in the definition. In line 77 of the main program the three objects are declared, each with a different number of parameters and inspection of the results will indicate that the correct constructor was called based on the number of parameters.

In the case of the other overloaded methods, the number and type of parameters is clearly used to select the proper method. You will notice that one method uses a single integer and another uses a single float type variable, but the system is able to select the correct one. As many overloadings as desired can be used provided that all of the parameter patterns are unique.

You may be thinking that this is a silly thing to do but it is, in fact, a very important topic. Throughout this tutorial we have been using an overloaded operator and you haven't been the least confused over it. It is the cout operator which operates as an overloaded function since the way it outputs data is a function of the type of its input variable or the field we ask it to display. Many programming languages have overloaded output functions so you can output any data with the same function name.

Be sure to compile and execute this program.

FUNCOVER.CPP

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

class many_names {
int length;
int width;
public:
many_names(void); // Constructors
many_names(int len);
many_names(int len, int wid);
void display(void); // Display functions
void display(int one);
void display(int one, int two);
void display(float number);
};



many_names::many_names(void)
{
length = 8;
width = 8;
}



many_names::many_names(int len)
{
length = len;
width = 8;
}



many_names::many_names(int len, int wid)
{
length = len;
width = wid;
}



void many_names::display(void)
{
cout << "From void display function, area = " <<
length * width << "\n";
}



void many_names::display(int one)
{
cout << "From int display function, area = " <<
length * width << "\n";
}



void many_names::display(int one, int two)
{
cout << "From two int display function, area = " <<
length * width << "\n";
}



void many_names::display(float number)
{
cout << "From float display function, area = " <<
length * width << "\n";
}



main()
{
many_names small, medium(10), large(12, 15);
int gross = 144;
float pi = 3.1415, payroll = 12.50;

small.display();
small.display(100);
small.display(gross,100);
small.display(payroll);

medium.display();
large.display(pi);
}




// Result of execution
//
// From void display function, area = 64
// From int display function, area = 64
// From two int display function, area = 64
// From float display function, area = 64
// From void display function, area = 80
// From float display function, area = 180

SEPARATE COMPILATION


Separate compilation is available with C++ and it follows the identical rules as given for ANSI-C separate compilation. As expected, separately compiled files can be linked together. However, since classes are used to define objects, the nature of C++ separate compilation is considerably different from that used for ANSI-C. This is because the classes used to create the objects are not considered as external variables, but as included classes. This makes the overall program look different from a pure ANSI-C program. Your programs will take on a different appearance as you gain experience in C++.

ANOTHER PRACTICAL EXAMPLE


Once again we come to the practical part of this lesson where we study a practical class that can actually be used in a program but is still simple enough for the student to completely understand.

In the last chapter we studied the date class and in this chapter we will study a simple time class. You should begin by studying the file named TIME.H which will look very similar to the date class header. The only major difference in this class from the date class is the overloaded constructors and methods. The program is a very practical example that illustrates very graphically that many constructor overloadings are possible.

The implementation for the time class is given in the file named TIME.CPP. Once again, the code is very simple and you should have no problem understanding this example in its entirety. It should be pointed out that three of the four overloadings actually call the fourth so that the code did not have to be repeated four times. This is a perfectly good coding practice and illustrates that other member functions can be called from within the implementation.

The example program named USETIME.CPP is a very simple program that uses the time class in a very rudimentary way as an illustration for you.
You should be able to understand this program in a very short time. It will be to your advantage to completely understand the practical example programs given at the end of the last chapter and the end of this chapter. As mentioned above, we will use the time class and the date class as the basis for both single and multiple inheritance in the next three chapters.


TIME.H

  
// Chapter 6 - Program 10

// This is probably the minimum usable time class, but is intended as
// an illustration of a class rather than to build an all inclusive
// class for future use. Each student can develop his own to suit
// his own taste.

#ifndef TIME_H
#define TIME_H

class time_of_day {
protected:
int hour; // 0 through 23
int minute; // 0 through 59
int second; // 0 through 59
static char format; // Format to use for output
static char out_string[25]; // Format output area

public:
// Constructor - Set time to current time and format to 1
time_of_day(void);
time_of_day(int H) {hour = H; minute = 0; second = 0; };
time_of_day(int H, int M) {hour = H; minute = M; second = 0; };
time_of_day(int H, int M, int S) {hour = H;
minute = M; second = S; };

// Set the time to these input values
// return = 0 ---> data is valid
// return = 1 ---> something is out of range
int set_time(void);
int set_time(int hour_in);
int set_time(int hour_in, int minute_in);
int set_time(int hour_in, int minute_in, int second_in);

// Select string output format
void set_time_format(int format_in) { format = format_in; };

// Return an ASCII-Z string depending on the stored format
// format = 1 13:23:12
// format = 2 13:23
// format = 3 1:23 PM
char *get_time_string(void);

};

#endif


TIME.CPP

  
// Chapter 6 - Program 11

#include <stdio.h> // For the sprintf function
#include <time.h> // For the time & localtime functions
#include "time.h" // For the class header


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


// Constructor - Set time to current time
// and format to 1
time_of_day::time_of_day(void)
{
time_t time_date;
struct tm *current_time;

time_date = time(NULL);
current_time = localtime(&time_date);
hour = current_time->tm_hour;
minute = current_time->tm_min;
second = current_time->tm_sec;

format = 1;
}


// Set the time to these input values
// return = 0 ---> data is valid
// return = 1 ---> something out of range
int time_of_day::set_time(void) {return set_time(0, 0, 0); };
int time_of_day::set_time(int H) {return set_time(H, 0, 0); };
int time_of_day::set_time(int H, int M) {return set_time(H, M, 0); };
int time_of_day::set_time(int hour_in, int minute_in, int second_in)
{
int error = 0;

if (hour_in < 0) {
hour_in = 0;
error = 1;
} else if (hour_in > 59) {
hour_in = 59;
error = 1;
}
hour = hour_in;

if (minute_in < 0) {
minute_in = 0;
error = 1;
} else if (minute_in > 59) {
minute_in = 59;
error = 1;
}
minute = minute_in;

if (second_in < 0) {
second_in = 0;
error = 1;
} else if (second_in > 59) {
second_in = 59;
error = 1;
}
second = second_in;

return error;
}



// Return an ASCII-Z string depending on the stored format
// format = 1 13:23:12
// format = 2 13:23
// format = 3 1:23 PM
char *time_of_day::get_time_string(void)
{
switch (format) {
case 2 : sprintf(out_string, "%2d:%02d", hour, minute);
break;

case 3 : if (hour == 0)
sprintf(out_string, "12:%02d AM", minute);
else if (hour < 12)
sprintf(out_string, "%2d:%02d AM", hour, minute);
else if (hour == 12)
sprintf(out_string, "12:%02d PM", minute);
else
sprintf(out_string, "%2d:%02d PM",
hour - 12, minute);
break;

case 1 : // Fall through to default so the default is also 1
default : sprintf(out_string, "%2d:%02d:%02d",
hour, minute, second);
break;
}

return out_string;
}


USETIME.CPP

  
// Chapter 6 - Program 12

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

void main(void)
{
date today;
time_of_day now, lunch(12, 15);

cout << "This program executed on " << today.get_date_string() <<
" at " << now.get_time_string() << "\n";

cout << "We are planning lunch at " << lunch.get_time_string() <<
" tomorrow.\n";

lunch.set_time(13);
cout << "We decided to move lunch to "<< lunch.get_time_string()
<< " due to a late meeting.\n";
}


// Result of execution

// This program executed on Jan 20, 1991 at 10:34:16
// We are planning lunch at 12:15:00 tomorrow.
// We decided to move lunch to 13:00:00 due to a late meeting.


WHAT SHOULD BE THE NEXT STEP?


At this point you have learned enough C++ to write meaningful programs and it would be to your advantage to stop studying and begin using the knowledge you have gained. Because C++ is an extension to ANSI-C, it can be learned in smaller pieces than would be required if you are learning a completely new language. You have learned enough to study and completely understand the example program given in chapter 12, the Flyaway adventure game. You should begin studying this program now.

One of your biggest problems is learning to think in terms of object oriented programming. It is not a trivial problem if you have been programming in procedural languages for any significant length of time. However, it can be learned by experience, so you should begin trying to think in terms of classes and objects immediately. Your first project should use only a small number of objects and the remainder of code can be completed in standard procedural programming techniques. As you gain experience, you will write more of the code for any given project using classes and objects but every project will eventually be completed in procedural code.

After you have programmed for a while using the techniques covered up to this point in the tutorial, you can continue on to the next few chapters which will discuss inheritance and virtual functions.


PROGRAMMING EXERCISES


  1. Modify OBJDYNAM.CPP to make the objects named small and medium pointers, then dynamically allocate them prior to using them.
  2. Modify the loop in line 61 of OBJLINK.CPP so that the loop will store 1000 elements in the list before stopping. You will probably wish to remove the printout from line 74 so the program will stop in a reasonable time. You may also get an integer overflow indicated by wrong answers if you send a message to get_area() with such large numbers. That will depend upon your compiler. You should also add a test to assure that the memory did not become exhausted after each dynamic allocation.
  3. Write a program that uses both the date and time classes in a meaningful manner. No answer will be given in the ANSWERS directory for this exercise since it is so straight forward. These classes can be used in all of your future C++ programs to time stamp the time and date of execution.

← 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