C++ Class Part 5

From EDM2
Jump to: navigation, search
C++ Class / Part
1 2 3 4 5 6

By Terry Norton

Welcome to Lesson 5 of our C++ class for beginners. This lesson is pretty big covering Chapters 9-12. In this lesson we finally study Classes and Objects (Chapters 10, 11, 12), the heart of Object Oriented Programming (OOP), at least to me.

Chapter 9 - Memory Models

You'll notice I didn't quite get the name for Chapter 9 correct. I left out namespaces. The only reason I did this is because OpenWatcom doesn't support this feature yet and I didn't want to study material we can't use at this time. But, if you are instead using the gcc compiler, then you can use namespaces. Since this class is semi-officially using the OpenWatcom compiler, I am just going to delay studying namespaces until a later time, or when every one of us is using gcc instead of OpenWatcom. OK, let's continue.

Objectives for Chapter 9

By the semi-end of Chapter 9, you should understand the following concepts:

  • Breaking a program into separate files and assembling a C++ program
  • Storage Classes and how they allow finer control of variables in a program
  • Scope and linkage rules

I'm going to temporarily jump ahead to page 352. I want to present a point about compilers and linking before getting into separate file compiling. On page 352 is a note titled

Real World Note: Multiple Library Linking

When reading programming newsgroups and forums, I always wondered a lot what all this recompiling talk was about concerning libraries. If libraries were already compiled, what the heck was the purpose of recompiling them. Sure seemed like a bunch of extra work for nothing. Of course I was (am) very ignorant about compilers. It was the "name mangling" done by the compiler which makes recompiling necessary. Remember I told you in Chapter 8 to keep this stuff in mind? This note explains this pretty good.

OK, back to the beginning of Chapter 9.

Separate Compilation

NOTE:

The book keeps referring to Listing 7.11. This is incorrect, it should be Listing 7.12

Reviewing Listing 7.12, the program describes (declares) two structures. One is called polar and the second is called rect. Remember, this creates two new types: rect and polar, just like int and char are types.

Separating the code into a file that contains main(), and another file that contains the functions <tt>rect_to_polar()</tt> and show_polar(), means each file would need to have the structures polar and rect declared in each file since each file uses the polar type and the rect type.

Look in main() and you see variables created: a rect type called rplace, and a polar type called pplace.

Look in the rect_to_polar() declaration or definition and you see the rect type as a parameter. In the definition you see an answer variable of type polar being created.

Look in the show_polar() declaration or definition and you see the polar type as a parameter.

Having to declare these structures in both files makes it difficult if you ever need to change these structures because they are in more than one file, not good. The structures should only be declared in one place.

This is where the header file enters the picture. Place the structure declarations in yet another file called a header file. Then use #include to include the header file them in both of our code files. Now if you ever need to modify the structures, do so in the header file. We have been using header files already, mainly the iostream header file like this:

#include

All we're doing now is making our own header files and including them in our source code.

NOTE:

Be sure to read the note on page 350 called Header File Management. This explains how header files are, or aren't, included during compile time. You'll also notice the line

#define COORDIN_H_</tt>

If you ever wondered where the name COORDIN_H_ comes from, it's just a name made up by the programmer. Take a look at any of the header files included with your compiler and you'll countless examples.

Storage Duration, Scope, and Linkage

I won't add anything here. The book explains this well. However, remembering all this info will make your head swim. I'd say this is a reference section of the book. If you need the info, look it up as you need it. It will eventually be remembered as you use it more often.

Chapter 10 - Objects and Classes

Here we are, finally. This is what we've been waiting to learn. All the previous material, learning syntax, general concepts, pointers, references, functions, variables, etc., has all been for using objects to accomplish a task. There are several chapters dealing with objects and classes because that's what C++ is all about.

Objectives for Chapter 10

  • Object-oriented programming concepts of abstraction, encapsulation, and data hiding
  • How these OOP concepts benefit the programmer and user
  • C++ Classes: the mechanics of declaring and defining them
  • const member functions and the protection of data and functions
  • Public and private Class members, both data and functions
  • Class scope to understand the visibility of Class members
  • Creating objects and how their methods are used
  • Class constructors and destructors for creating and releasing class objects
  • Self identity and the this pointer to Class objects
  • Using arrays of objects to represent and work with collections of objects
  • Abstract data types to represent more general objects

Procedural and Object-Oriented Programming

I already expressed my own views about procedural and OOP in earlier lessons, so no since repeating myself.

Abstraction and Classes

I tried reading this section and in the end, sat there scratching my head. Say what? Abstraction is what? I finally went looking on the web for a definition a little more definite for a beginning C++ programmer. Believe it or not, most of the results of my search weren't any better, but I am able to tell you, in just a few sentences, what the big deal is all about.

Abstraction is the process of taking away or removing characteristics from "something" in order to reduce it to a set of essential characteristics. Objects we want to create only have to contain the data we need to accomplish the task. The object that remains is a representation of the original, with unwanted detail omitted.

Abstraction, sure sounds like a difficult way to describe something that is real life is very simple, but I wasn't consulted when words were chosen as definitions for C++.

In the Listing 10.1, stocks.cpp, there are certain things we need to know about a stock such as the share price (value), how many we own. Things we don't need to know about a stock is when was the stock certificate printed, what color of ink was used to print it, what kind of paper is it, etc. All these things, plus more, are all part of a stock, the original object. But in a stock program we need only a tiny portion of what a stock is, so what we have is an abstract of a stock.

Type

We just covered this in the last chapter while dealing with structures. When we defined a structure, we created a new type for our enjoyment and use. Just like int and char are types, they just happen to be supplied as part of the programming language.

Here it comes:

The Class (yeah!!!)

This is the Holy Grail of C++. We create our Classes, like we created structures (sorta), so suit our need to describe what the objects we want to make. Remember, a Class is like the manual that tells you how to make a car. The manual isn't a car, it defines what a car will be when we do decide to make one, the object.

The example in the book is a stock. I'm sure there must be some laws that say what a stock certificate is and what it has to include. However, we aren't going to make stocks with a program, we just want to use some of its characteristics. So once again, we will use an abstract of a stock, just using the parts we need, like its value.

Like a structure, the Class defines the data an object can hold. Unlike a structure, a Class can also define the functions that are allowed to work with the data, and those functions are also part of the object. One nice, neat little package. The only way your program can (should) access the data in an object is through its own contained functions, also called class methods or member functions. Actually, you can get at the data if you really want without member functions (class methods), but that's not a good idea. But it does show that you're the boss and can do whatever you darn well please.

I would bet the word method was chosen so programmers would know the difference between a function that is part of a Class and its objects, and a function you have elsewhere in your program that isn't part of some Class. Now that makes sense to me. I'm sure I just have some sort of mental block about the word abstraction, but I sure you can handle it much easier than I can.

Public Interface is something you will hear a lot, so let's see what it means, especially since you have to define it for a Class. The Public Interface (or just Interface) lets the world know the data an object can store, and the methods (functions) available to access or work with the data. The Interface is all a user of the Class needs to know. They don't need to know the specific details of how the methods actually do their work. Take a car for example. You are familiar with the Interface of a car, such as how to make it go. All you need to know is that pushing on the gas pedal makes the car go. Do you need to know how the pedal connects to the engine, or whether it's connected to a carburetor, or to a fuel injection valve, or to an alien anti-grav unit?

Implementing Class Member Functions

This whole section is very well explained, so I won't add any confusion. I will just point out that on page 405, second to last sentence:

"Calling a member function is what OOP languages term sending a message."

And I'll bet you thought the whole idea of OOP would be hard. The only thing hard about C++ is learning the basic rules of syntax, but that's no different than learning any kind of language, even English. Syntax is one of those things that you learn more as you use it. If you can't remember something, look it up, no big deal. Also, once you know how objects are these clever, encapsulated entities, using objects makes programming so much easier compared to procedural programming. Yes, C is required for things like device drivers, but for user applications, OOP is great. Did I ever mention I like object-oriented programming?

Using a Class

Using a Class is almost the same as using a Struct (structure), which we covered in the last lesson. I included, to save you some typing, and compiled Listing 10.3, stocks.cpp. I made the changes so it would compile using OpenWatcom. In the main() function I had to change ios_base:: to ios::. I also include some extra comments in case a little review is necessary about pointers and strings.

// stocks.cpp
#include <iostream>
#include <string>    // cstring doesn't exist with OW
class Stock
{
 private:
  char company[30];
  int shares;
  double share_val;
  double total_val;
  void set_tot() { total_val = shares * share_val; }
 public:
  void acquire(const char * co, int n, double pr);
  void buy(int num, double price);
  void sell(int num, double price);
  void update(double price);
  void show();
}; 

void Stock::acquire(const char * co, int n, double pr) // Note the pointer parameter
{
 strncpy(company, co, 29);      // truncate co to fit if needed
 company[29] = '\0';            // make the end of the string the NULL character
 if (n < 0)
 {
  cerr << "Number of shares can't be negative; "
   << "shares set to 0.\n";
  shares = 0;
 }
 else
  shares = n;
 share_val = pr;
 set_tot();
}

void Stock::buy(int num, double price)
{
 if (num < 0)
 {
  cerr << "Number of shares purchased can't be negative. "
   << "Transaction aborted.\n";
 }
 else
 {
  shares += num;
  share_val = price;
  set_tot();
 }
}

void Stock::sell(int num, double price)
{
 if (num < 0)
 {
  cerr << "Number of shares purchased can't be negative. "
   << "Transaction aborted.\n";
 }
 else if (num > shares)
 {
  cerr << "You can't sell more than you have! "
   << "Transaction is aborted.\n";
 }
 else
 {
  shares -= num;
  share_val = price;
  set_tot();
 }
}

void Stock::update(double price)
{
 share_val = price;
 set_tot();
}

void Stock::show()
{
cout << "Company: " << company
  << " Shares: " << shares << "\n"
  << " Share Price: $" << share_val
  << " Total Worth: $" << total_val << "\n";
}

int main()
{
 Stock stock1;                           // Create a Stock object called stock1
                                         // Remember, Stock is a Type
stock1.acquire("NanoSmart", 20, 12.50);  // Literal string, see page 273 to refresh
                                         // how the first element of a string is actually
                                         // a pointer (address) for this array
 cout.setf(ios::fixed);                  // #.## format
 cout.precision(2);                      // #.## format
 cout.setf(ios::showpoint);              // #.## format
 stock1.show();                          // Send a message to the stock1 object
 stock1.buy(15, 18.25);                  // Send a message...
 stock1.show();                          // Send a message...
 stock1.sell(400, 20.00);                // Send a message...
 stock1.show();                          // Send a message...
 cin.get();
 return 0;
}

Now that you have some code you can copy and paste, doing the other code listings will be a bit easier. You'll just need to make necessary modification.

Our Story to Date

Read this section and understand it. This is the key to using Classes and their objects. Everything discussed here we just did in Listing 10.3. The author switched to using a different Class name, Bozo, and an object named Bozetta, to illustrate his point, so if you don't like Bozo, then substitute Stock instead, the object we just created, stock1.

Class Constructors and Destructors

I can remember being really confused about Constructors when I was learning Object REXX, but it is quite simple. I just have a bad habit of thinking things are harder than they are.

The constructor, a Class function, is automatically run (called) when the object is created. The best use of a constructor that I used with Object REXX was reading stored data from a file. When my program would start, if there was data in a file, a new object would be created which made the constructor function read the data from the file to initialize the object's variables. Objects kept being created until all the data from the file had been retrieved.

How does the object know which member function to run when it's created? The function is named the same as the Class the object is created from. For our stock program, since the Class is the Stock type, the constructor is named Stock().

The Default Constructor

The constructor that is called when you don't provide any Constructor. Note these points:

  • The compiler provides one if you don't provide any constructors.
  • If you do provide any constructors at all, the compiler will NOT automatically provide a default constructor, so also code in a default constructor with either:
    No arguments at all like this:
 Stock();

or with arguments with default values assigned, something like this:

Stock(const char * co = "Error", init n = 0, double pr = 0.0)

Just to muddy the waters a little bit, even if you only code in the default constructor in your declaration like this, Stock();, you can still set the initial values for the member variables in the function definition file something like this:

Stock::Stock()         // this is the default constructor, it also has a block of code
 {
  strcpy(company, "no name");
  shares = 0;
  share_val = 0.0;
  total_val = 0.0;
 }

This is the preferred way to set up a default constructor. Read the tip on page 414.

Improving the Stock Class

Not much to add here except notice the next three titles of the subsections:

  • The Header File
  • The Implementation File
  • The Client File

The program is separated into specific files.

Knowing Your Objects: The this Pointer

The this pointer is simply the address of an object. In Listing 10.5, &stock1 would be the address of stock1. Well, you can also see the address of stock1 using the this pointer while calling a stock1 function. You can test this for yourself with some minor changes to the code. Here is the change I made in main():

 cout << "Using constructors to create new objects\n";
 Stock stock1("NanoSmart", 12, 20.0);            // Syntax 1
 cout << "The address of stock1 is: " << &stock1 << "\n";
 stock1.show();                                  // Send a message to the stock1 object
 Stock stock2 = Stock ("Boffo Objects", 2, 2.0); // Syntax 2
 cout << "The address of stock2 is: " << &stock2 << "\n";
 stock2.show();                                  // Send a message to the stock1 object


and here's the change to show():

void Stock::show() {

 cout << "Company: " << company
  << " Shares: " << shares << "\n"
  << " Share Price: $" << share_val
  << " Total Worth: $" << total_val << "\n";
 cout << "This address is: " << this << "\n";

}

Here's the relevant part of the output when I run Listing 10.5:

Using constructors to create new objects Constructor using NanoSmart called
The address of stock1 is: 0x42ec4    <-- address of stock1
Company: NanoSmart Shares: 12
Share Price: $20.00 Total Worth: $240.00
This address is: 0x42ec4      <-- address of stock1 while in show()
Constructor using Boffo Objects called
The address of stock2 is: 0x42efc    <-- address of stock2
Company: Boffo Objects Shares: 2
Share Price: $2.00 Total Worth: $4.00
This address is: 0x42efc      <-- address of stock2 while in show()

If you want to run this yourself, you may have to add a cin.get() midway in the main() code, otherwise the output may scroll off the screen and you won't see the first part of the output.

An Array of Objects

This section shows the program creating 4 objects, but without any names. This was another mental block I had when learning object REXX. How do you deal with the objects if they aren't named. It was like dealing with ghosts. How do you work with something you can't see? Well, I did get over this eventually. What I learned is that a name doesn't do anything to help working with objects.

Take the stocks program for example. At first the examples provide names for each object, like stock1 and stock2. But that's not what's important. In Listing 10.9, usestok2.cpp, none of the objects have names, so it is kind of like they exist in never-never land, but yet there's no problem working with them. Why? Because everything you need to know about an object is contained in it. We work with the data contained in the object, not some name we might want to give it.

OK, so we work with the data, how do you work with a specific object then? You will discover that just about anything you do with data, whether you do procedural programming or OOP, will involve some kind of sorting, searching, selecting, filtering, whatever. For instance, you might have stored a bunch of data in an array, but you only want to see data that meets certain criteria, so you program some filter, run the data through the filter and display the result in a list box. From there the user can make further selections.

You can store objects in an array. Actually you'll eventually learn to store objects in several available container class objects. An object is a container of stored data as well. All data gets stored somewhere no matter how you program. So when you want to display certain data, contained objects, in a list box, it won't be the objects the user sees, it's the data from the object that gets displayed. To get the list box filled with the particular data you want, you'll run some sort of filter on one or more of the objects data members, which you retrieve by sending each object the same message.

Please note the last sentence on page 431:
"One thing to note is how most of the work goes into designing the class. Once that's done, writing the program itself is rather simple."

I love object programming, did I ever mention that?

Chapter 11 - Working With Classes

Objectives for Chapter 11

By the end of Chapter 11, you should understand the following concepts:

  • Operator overloading, the details of its use, and why it is a useful tool
  • Overloading of +, -, and * operators
  • Overloading of ostream objects, &lgt;&lgt; in particular
  • Using friend functions to extend the flexibility of Class definitions
  • State members used to add intelligence to Classes
  • Generating random numbers and the use of the system clock as a seed
  • Decisions a compiler makes in converting data and how to give it guidance
  • Conversion functions to explicitly define conversions involving objects
  • The difference between explicit and implicit conversions

Operator Overloading

A note before starting with operator overloading. This isn't anything that's required for creating Classes and using objects. Operator overloading can make coding easier once you know the material. You can accomplish your programming tasks without using overloading, however, the rest of the book's examples are going to make heavy use of operator overloading, so in a word, learn it now.

The built-in types, such as int, can make use of operators such as "+", "-", etc. But the Class types you create aren't able to use them by default. C++ does provide a way that you can use them with operator overloading.

I'm not saying you can't use C++ operators in your Class' code to do the usual things like adding or multiplying numbers with the built-in types, like adding two int types together. However if you create a Class to make time types (time objects), you aren't allowed to just add together two time types together because your compiler hasn't been told it can do such a thing. Operator overloading simply means we are going to tell our C++ compiler that a time object can be added to another time object and not produce a compiling error, and we're going to tell the operator(s) how to do it.

Let's see if I can summarize what's taking place. Let's use an overloaded + and A = B + C, which means we are adding two Time objects, B & C and assigning the result to another Time object, A. You define operator+() as a Time Class member function. So now when the compiler sees the + operator it checks all the types it's allowed to work with and behold, it finds that we defined in our Time Class definition that it can add two Time object together, and how to do it. So it sees that Time object B is the first object, so it calls the member function, operator+() in the B object. The compiler sees that the operator+() function requires an argument, so it looks at the expression A = B + C and finds that C is the other operand, so the C object is used as the argument for the B member function, operator+(). You notice that the C object has the same operator+() function, but in this expression it isn't called. B was first in the expression A = B + C, so it's member function was called. C is then just the argument for B's function.

OK, that's one way to add two objects, Time objects in this case. But there's another way that doesn't use a member function. To do it another way we need a Friend.

Introducing Friends(More overloading)

To keep a linear thought process going here, only read the first two paragraphs of this section about Friends, then go to page 466 and read the section:

Overloaded Operators: Member Versus Nonmember Functions

Now to continue with the second way to do the expression A = B + C. By defining a function in the Time Class as a friend, we're making it a nonmember function. That means the compiler knows there's a way to add two Time objects together because the definition for the operator+() is in the Time Class, but it's not calling a member function to do it. Since it's not a member function, the compiler doesn't call the operator+() like this:

B.operator+()  // this is for a member function only, not what we're doing here 

Instead, the compiler can see that the two operand in A = B + C are B and C, and that they can indeed be added together, but operator+() is just another function. In other words, you don't have to send any object a message to use it. When the compiler sees the + operator, it has to look at the types being added together, then go look to see if the types being added are defined somewhere as being addable. Sure enough, it finds that there's a definition in the Time Class that allows them to be added. But this time the operator+() takes two arguments, both of the operands B & C.

Since this time it's not a member function, both B & C objects need to access their data explicitly. That means the code for operator+() has to send messages to both objects to get the required data.

OK, now go back to page 458 and finish reading the Friend section.

A Common Kind of Friend: Overloading the << Operator

Don't burn the First Version of Overloading too deep into your brain because the Second Version of Overloading is the keeper. Also catch the note on page 464 about using friend in the Class declaration only.

I want to take the time to make sure there isn't a fuzzy spot in your brain concerning functions that return a value, such as an int value, and an object, such as the cout object.

When a function returns a value, that's easy to understand, but returning an object may drive you crazy. What do you mean return an object? An object is a particular type just like int is a particular type. An object will usually contain data and functions.

cout is an object that has a bunch of overloaded functions in it that outputs some data, usually to the screen. Each member function in the cout Class has a different signature, just like we can overload functions simply by using the same name for a function but with each argument list having different arguments, the signature of a function.

When we do something like:

 cout << "Send this text to the screen" << a_variable;  // a cout statement  

the literal string is just an argument for one of the member functions in cout. The compiler knows this is a literal string and selects the function that takes a literal string as an argument. Also in the member function is a return statement. When the function in cout finishes sending the string to the screen, the function exits with a return statement that returns itself, the cout object.

Since a cout object has been returned, the next part, << a_variable is now sent to a function in cout that accepts a variable as an argument. The compiler sees it's a variable, so the proper function is selected once again and the value that's stored at a_variable is sent to the screen.

Had the function that processed the literal string not returned the cout object, the a_variable would not have been processed because the << operator only knows how to send what follows it to a member function that's in the cout object. The compiler would have just produced an error.

So when a function returns an object, it's just providing you with this encapsulated chunk of code that you can send a message to. That's the purpose of an object, to have it do some sort of work for you.

The rest of the chapter about conversion is pretty straight forward about types being converted when an object is created. Constructors arguments accepts a specific type, but the compiler can automatically convert one type to another, such as an int to a double. Or using conversion functions to go from some object type to a built-in type.

The rest of the chapter discusses conversion. It's pretty straight forward.

Chapter 12 - Classes and Dynamic Memory Allocation

Objectives for Chapter 12

By the end of Chapter 12, you should understand the following concepts:

  • Using dynamic memory with new and delete and Classes
  • How to use Classes to make the free store (heap) to hold data
  • static Class members to hold data about all the objects in a Class
  • Constructors and destructors to manage dynamic memory
  • Freeing dynamic memory versus freeing memory used by an object
  • Default member functions provided by C++ and what can go wrong
  • Deep copying in constructors and assignment operators
  • Deleting memory multiple times - why it is a problem and how to avoid it
  • Effectively using pointer to objects
  • How a queue simulation works and implementing Classes with dynamic memory
  • Nesting structures and Classes within a Class (OpenWatcom doesn't allow this)

Dynamic Memory and Classes

The review of new and delete is very well done. I will not add anything to confuse what the book already explains. At the very end of this review section is mentioned a new constructor called the copy constructor, so be sure to follow the explanation for Listing 12.3, vegnews.cpp.

Yes, these examples compile and run with OpenWatcom, however, run this program from the commandline. If you place cin.get(); before the return statement, and run from OpenWatcom, you'll miss the last few lines of output since some of the object destructors don't get called until the program ends.

The Copy Constructor

The last chapter talked about conversion, specifically how something such as an int type being assigned to a Class type becomes a Class type. Constructors handle the conversion as we define them. But what happens when conversion isn't necessary? What happens when you assign a Class type to the same Class type? There is no conversion since they're the same type. It's a straight forward copy of one object to another, and the constructors we have defined so far don't handle copying. In the StringBad situation, a copy constructor wasn't defined, so when a new StringBad type was created from data contained in another StringBad type, a default copy constructor was used that the compiler automatically created. If we want the num_strings static variable to be incremented, we need to define our own copy constructor.

Just to make sure you understand, the copy constructor is called when you copy an object to a "newly" created object. The assignment operator, discussed next, is used when you assign one object to an "existing" object.

The Assignment Operator

This is almost exactly the same as the copy constructor problem. If your program will be assigning the same type objects, you will also need to define an assignment operator.

To get the StringBad program to work correctly, add the copy constructor and assignment operator function to the header file and the class methods source file. The header file will look like this:

// strngbad.h - - corrected string class definition
 #include <iostream>
 #ifndef STRNGBAD_H_
 #define STRNGBAD_H_
 class StringBad
 {
  private:
   char * str;
   int len;
   static int num_strings;
  public:
   StringBad(const char * s);                   //pointer to string
   StringBad & operator=(const StringBad & st); //assignment operator
   StringBad(const StringBad & st);             //copy constructor
   StringBad();                                 //default constructor
   ~StringBad();                                //destructor
   friend ostream & operator<<(ostream & os, const StringBad & s);
 };
 #endif

The New, Improved String Class

Many more overloaded operators are added to the Class definition to make dealing with objects so easy.

NOTE: In Listing 12.4, string1.h, it appears that OpenWatcom 1.0 doesn't support static class constants, so the statement:

static const in CINLIM = 80;

produced a compiler error message. The fix for this is provided on page 527:

enum {CINLIM = 80};  //should be 80, not 90  

NOTE: On page 531 is another compatibility note. OpenWatcom does need to use the second test in the if statement. I tried both tests separately to see if they would both work. The !cin did not work for me. When using just this test, an empty line did not result in the program breaking out of the for loop.

The book explains the overloaded operators very well. Also, on page 532 - 533 is a good review. However, I can't leave this section without a brief comment. If you've already got the hang of what's going on here, that's great, but I want to make sure the beginners have the big picture. Believe me, the more I get into this material the more enlightened I'm becoming as well.

All these overloaded operators may seem way too confusing, but the concept is simple. As you are aware, you can do things with int types quite easily, such as add, subtract, divide, compare, etc. The reason you can do these things is because someone else did the programming and included the necessary files so the compiler knows how to handle int types. cout and cin also know how to handle all the types that are built-in, like int and char, etc. How? Because someone else did the programming so that the cout and cin objects know how to handle these types.

It's very obvious the compiler won't know how to handle something it doesn't know about. When you make your own Classes, you are creating a new type the compiler has never seen. So you can't just add these new types together, nor can cout deal with them either. We are presently creating our own version of the String class. We have been using it instead of the built-in version, but the compiler has no idea what to do with our version.

We have been teaching the compiler how to handle our version of a String Class. We taught it to use the cout object when we defined the << operator in the Class definition. In Listing 12.6 there was a String object created called name. Go to line 14 and you see cout << name. We told the compiler that when it sees a cout object and a String object with << in the middle to go find the operator<<() function in our String Class definition, defined in Listing 12.5, and to take the data at the str pointer and send it to the screen. This is the same thing somebody else did so that an int type could be sent to the screen.

So when we tell the compiler how to handle our objects with the available operators, it's called operator overloading. All we did was add another definition to the compiler's dictionary of knowledge.

Using Pointers to Objects

In Listing 12.6, sayings1.cpp, we created an array of String objects, sayings[]. To keep track of the String object containing the first string alphabetically, and the String object containing the shortest string in our array of String objects, we simply stored the array index value in two int variables called first and shortest.

Now we are going to change how we do this by using pointers instead of int variables. This means that instead of storing some index values of the sayings[] array for the corresponding String objects, we are storing the memory addresses for the String objects in two pointers.

NOTE: In Listing 12.7, sayings2.cpp, remember to change the #include statements to <stdlib> instead of <stdlib>, and

A Queue Class

C++ already comes with containers, however, here we create our own just so you can see how this particular kind of container works. Containers are pretty neat, they hold the objects we create. It's not any different that a container you have in your house called a cupboard. The cupboard container holds all your dish and glass objects.

There are several kinds of containers we can use, depending on how you want to store and retrieve your objects (data). The Queue container has the feature that you put your objects into one end of a Queue, and retrieve then from the other end. Picture the Queue as a tube. The first object you put into one end of the tube will be the first object that you can retrieve from the other end.

If you wish, think of a container as a small database, it simply stores information. The information a container holds is the references (addresses in memory) of objects.

Note: It appears that OpenWatcom doesn't support nested structures and classes. To get the Queue program to compile, I had to declare the Node structure outside of the Queue class, like this:

 typedef Customer Item;
 struct Node { Item item; struct Node * next; };   <--- Node declaration
 class Queue
 {...

I will include the three files for this example on the V.O.I.C.E. forum, under lesson 5. Look for queue.zip.

So what did this Queue example show? The objects and their data are created in the computer's memory. A Queue node holds the address of one object. The node also holds the address of the next node in the Queue, which holds the address of another object in the Queue. This simply repeats for all the nodes in the Queue container.

Phew! Big lesson, but well worth the effort. Have you noticed that defining Classes where most of the C++ effort is located for a program? You will find, like I did with Object REXX, that the supporting code you create is mainly for sending messages to objects to get data. This is where objects are no nice. As you develop a program you may discover that your objects may need to contain some data you hadn't planned on. No big deal. Just add the new data member to the Class definition, and the member functions to access the data. I love object programming. Did I mention this before?