Working with Classes - Introducing C++ - C++ All-in-One For Dummies (2009)

C++ All-in-One For Dummies (2009)

Book I

Introducing C++

Chapter 7: Working with Classes

In This Chapter

Understanding objects and classes and the difference between the two

Becoming familiar with member functions and variables in a class

Making parts of a class public, private, and protected

Using constructors and destructors

Building hierarchies of classes

Back in the early 1990s, the big buzzword in the computer world was object-oriented. For anything to sell, it had to be object-oriented, whatever that meant. Programming languages were object-oriented. Software applications were object-oriented. Computers were object-oriented. Refrigerators were object-oriented. What did that all mean? Nothing. It was simply a catchphrase that was cool at the time.

Those days are gone, and now we can explore what object-oriented really means and how you can use it to organize your C++ programs. In this chapter, we introduce object-oriented programming and show how you can do it in C++. Although people disagree on the strict definition of object-oriented, in this book it means programming with objects and classes.

Understanding Objects and Classes

Consider a pen, a regular, old pen. One of us actually has a pen on our desk. Here’s what we can say about it:

Ink Color: Black

Shell Color: Light gray

Cap Color: Black

Style: Ballpoint

Length: Six inches

Brand: Office Depot

Ink Level: 50 percent full

Capability #1: Write on paper

Capability #2: Break in half

Capability #3: Run out of ink

Now, look around for other things. We see a printer. Let us describe that:

Kind: Laser

Brand: Lexmark

Model: E260

Ink Color: Black

Case Color: Cream

Input trays: Two

Output trays: Two

Connection: USB

Capability #1: Reads print job requests from the computer

Capability #2: Prints on sheets of paper

Capability #3: Prints a test page

Capability #4: Needs the toner cartridge replaced when empty

We’re just describing the things we see. We’re giving dimensions, color, model, brand. And we’re also describing what the things can do. The pen can break in half and run out of ink. The printer can take print jobs, print pages, and have its cartridge replaced.

When we describe what the things can do, we’re carefully writing it from the perspective of the thing itself, not from the perspective of the person using the thing. A good way to name the capability is to test it by preceding it with the words “I can” and see if it makes sense. Thus, because “I can write on paper” works from the perspective of a pen, we chose write on paper for one of the pen’s capabilities.

imageInstead of saying the word thing, we will say the word object. The two meanings are the same: An object is just a thing. Anything, really. A book. A dirty plate. A stack of writeable CD-ROMs. These are all objects.

But is seeing all the objects in the universe possible, or are some of those objects hidden? Certainly some objects are physical, like atoms or the dark side of the moon, and we can’t see them. But other objects are abstract. For example, we have a checking account. What is a checking account, exactly?

Using enumerations

We think that the number 12 is a good representation of the color blue, and the number 86 is a good representation of the color red. Purple? That’s 182. Beige? That’s getting up there — it’s 1047. Yes, this sounds kind of silly. But let’s suppose you want to create a variable that holds the color blue. Using the standard types of integers, floating-point numbers, characters, and letters, you don’t have a lot of choices. In the old days, people would just pick a number to represent each color and store that number in a variable. Or, you could have saved a string, as in blue. But C++ presents a better alternative. It’s called an enumeration. Remember that for each type, there’s a whole list of possible values. An integer, for example, can be a whole number within a particular range. (This range varies between computers, but it’s usually pretty big.) Strings can be any characters, all strung together. But what if you want a value called blue? Or red? Or even beige? Then you need enumerations. (Hurray!) This line creates an enumeration type:

enum MyColor {blue, red, green, yellow, black, beige};

You now have a new type called MyColor, which you can use the same way you can use other types, such as int or double or string. For example, you can create a variable of type MyColor and set its value to one of the values in the curly braces:

MyColor inkcolor = blue;

MyColor shellcolor = black;

The variable inkcolor is type MyColor, and its value is blue. The variable shellcolor is also of type MyColor, and its value is black.

Can you point to it? Can you drop it, throw it? You can throw your checkbook across the room and, if you’re brave, can even try to get into the room where the main computer is holding your checking account. But is the checking account something physical you can touch? No. It’s abstract.

Classifying classes and objects

When we pick up a pen, we can ask somebody, “What type of thing is this an instance of?” Most people would probably say, “a pen.” In computer programming, instead of using type of thing, we say class. This thing in my hand belongs to the pen class.

Now if we point to the thing parked out in the driveway, and ask you, “What class does that belong to?,” you will answer, “class Car.” Of course, you could be more specific. You may say that the thing belongs to class 1999 Ford Taurus.

Class names and class files

In Listings 7-3 and 7-4, nearby in this chapter, we chose filenames that match the name of our class. That’s usually the way we like to do it: When we create a class, we put the class definition in a header file of the same name as the class but with an .h extension. And we put the class member function code in a source-code file of the same name as the class but this time with a .cpp extension. We usually like our filenames capitalized the same as the class name; thus, we called the files Pen.h and Pen.cpp. Naming the files the same as classes has lots of advantages that can help you in your quest to become a millionaire. First, you automatically know the name of the header file you need to include if you want to use a certain class. Second, it provides a general consistency, which is always good in life, whether dealing with programming or pancake syrup. And finally, if we see a header file, we know what class is probably inside it.

When we show you the pen, we are asking you what class this object belongs to. If we then pick up another pen, we’re showing you another example of the same class. One class, several examples. If we stand next to a busy street, we see many examples of the class called car. Or we may see many examples of the class Ford Explorer, a few instances of the class Volkswagen Beetle, and so on. It depends on how you classify those things roaring down the road. But regardless, we likely see several examples of any given class.

So when you organize things, you specify a class, which is the type of object. And when you’re ready, you can start picking out examples (or instances) of the class. Each class may have several instances. Some classes have only one instance. That’s a singleton class. For example, at any given time, the class United States President would have one instance.

Describing member functions and data

If we choose a class, we can describe some characteristics. However, because we’re only describing the class characteristics, we don’t actually specify them. We may say the pen has an ink color, but we don’t actually say what color. That’s because we don’t yet have an example of the class pen. We have only the class itself. When we finally find an example, it may be one color, or it may be another. So, if we’re describing a class called pen, we may list the following characteristics:

Ink Color

Shell Color

Cap Color

Style

Length

Brand

Ink Level

We don’t specify ink color, shell color, length, or any of these. We’re listing only general characteristics for all instances of the class pen. That is, every pen has these characteristics. But the actual values for these characteristics might vary from instance to instance. One pen may have a different ink color from another, but both might have the same brand. Nevertheless, they are both separate instances of the class pen.

After we actually have an instance of class pen, we can give the specifics for the characteristics. For example, Table 7-1 lists the characteristics of three actual pens.

Table 7-1 Specifying Characteristics of Instances of Class Pen

Characteristic

First Pen

Second Pen

Third Pen

Ink Color

Blue

Red

Black

Shell Color

Grey

Red

Grey

Cap Color

Blue

Black

Black

Style

Ballpoint

Felt-tip

Ballpoint

Length

5.5 inches

5 inches

6 inches

Brand

Office Depot

Superrite

Easy-Ink

Ink Level

30%

60%

90%

In Table 7-1, the first column holds the names of the characteristics. The second column holds values for those characteristics for the first pen. The third column holds the values of characteristics for the second pen, and the final column holds the values for the third pen.

All the pens in the class share characteristics. But the values for these characteristics may differ from pen to pen. When we build a new pen (assuming that we could do such a thing), we would follow the list of characteristics, giving the new pen its own values. We may make the shell purple with yellow speckles, or we may make it transparent. But we would give it a shell that has some color, even if that color is transparent.

In Table 7-1, we didn’t list capabilities. But all these pens have the same capabilities:

Capability #1: Write on paper

Capability #2: Break in half

Capability #3: Run out of ink

Unlike characteristics, these capabilities don’t change from instance to instance. They are the same for each class.

image In computer programming, capabilities are member functions. That’s because you’ll be writing functions to perform these, and they are part of a class. The characteristics are member variables, because they are variables that are part of the class.

imageWhen you describe classes to build a computer program using a class, you are modeling. In the preceding examples, we modeled a class called Pen. In the following section, we implement this model by writing a program that mimics a pen.

imageIf you work with enums (the code form of enumerations), you need to decide what to name your new type. For example, you can choose MyColor or MyColors. Many people, when they write a line such as enum MyColor {blue, red, green, yellow, black, beige};, make the name plural (MyColors) because this is a list of colors. We make it singular, as in MyColor. When you declare a variable, it makes more sense: MyColor inkcolor; would mean that inkcolor is a color — not a group of colors.

Implementing a class

To implement a class in C++, you use the word class. We know it’s profound. And then you add the name of the class, such as Pen. You then add an open brace, list your member variables and member functions, and end with a closing brace.

imageMost people capitalize the first letter of a class name in C++, and if their class name is a word, they don’t capitalize the remaining letters. Although you don’t have to follow this rule, many people do. You can choose any name for a C++ class provided it is not a C++ word; it consists only of letters, digits, and underscores; and it does not start with a number.

The code in Listing 7-1 shows a class in C++, which we put inside a header file called Pen.h. (See Minibook I, Chapter 5, for information on how to put code in a header file.) Take a look at the header file, and you can see how we implemented the different characteristics. The characteristics of a header file are just like variables: They have a type and a name. And we implemented the capabilities simply as functions. But all this stuff goes inside curly brackets and is preceded by a class header. The header gives the name of the class. And, oh yes, the word publicis stuck in there, and it has a colon after it. We explain the word public in “Accessing members,” later in this chapter. By itself, this code isn’t very useful, but we put it to use in Listing 7-2, a program that you can compile and run.

Listing 7-1: Pen.h Contains the Class Description for Pen

#ifndef PEN_H_INCLUDED

#define PEN_H_INCLUDED

using namespace std;

enum Color

{

blue,

red,

black,

clear

};

enum PenStyle

{

ballpoint,

felt_tip,

fountain_pen

};

class Pen

{

public:

Color InkColor;

Color ShellColor;

Color CapColor;

PenStyle Style;

float Length;

string Brand;

int InkLevelPercent;

void write_on_paper(string words)

{

if (InkLevelPercent <= 0)

{

cout << “Oops! Out of ink!” << endl;

}

else

{

cout << words << endl;

InkLevelPercent = InkLevelPercent - words.length();

}

}

void break_in_half()

{

InkLevelPercent = InkLevelPercent / 2;

Length = Length / 2.0;

}

void run_out_of_ink()

{

InkLevelPercent = 0;

}

};

#endif // PEN_H_INCLUDED

image When you write a class, you always end it with a semicolon. Write that down on a sticky note and hang it on the refrigerator. The effort spent in doing this will be well worth avoiding the frustration of wondering why your code won’t compile.

image In a class definition, you describe the characteristics and capabilities (that is, supply the member variables and member functions, respectively).

Note in Listing 7-1, earlier in this chapter, that the member functions access the member variables. However, we said that these variables don’t have values yet, because this is just a class, not an instance of a class. How can that be? When you create an instance of this class, you can give values to these member variables. Then you can call the member functions. And here’s the really great part: You can make a second instance of this class and give it its own values for the member variables. Yes, the two instances will each have their own sets of member variables. And when you run the member functions for the second instance, these functions operate on the member variables for the second instance. Isn’t C++ smart?

Now take a look at Listing 7-2. This is a source file that uses the header file in Listing 7-1. In this code, we make use of the Pen class.

Listing 7-2: main.cpp Contains Code That Uses the Class Pen

#include <iostream>

#include “Pen.h”

using namespace std;

int main()

{

Pen FavoritePen;

FavoritePen.InkColor = blue;

FavoritePen.ShellColor = clear;

FavoritePen.CapColor = black;

FavoritePen.Style = ballpoint;

FavoritePen.Length = 6.0;

FavoritePen.Brand = “Pilot”;

FavoritePen.InkLevelPercent = 90;

Pen WorstPen;

WorstPen.InkColor = blue;

WorstPen.ShellColor = red;

WorstPen.CapColor = black;

WorstPen.Style = felt_tip;

WorstPen.Length = 3.5;

WorstPen.Brand = “Acme Special”;

WorstPen.InkLevelPercent = 100;

cout << “This is my favorite pen” << endl;

cout << “Color: “ << FavoritePen.InkColor << endl;

cout << “Brand: “ << FavoritePen.Brand << endl;

cout << “Ink Level: “ << FavoritePen.InkLevelPercent << “%” << endl;

FavoritePen.write_on_paper(“Hello I am a pen”);

cout << “Ink Level: “ << FavoritePen.InkLevelPercent << “%” << endl;

return 0;

}

There are two variables of class Pen: FavoritePen and WorstPen. To access the member variables of these objects, we type the name of the variable, a dot (or period), and then the member variable name. For example, to access the InkLevelPercent member of WorstPen, we type:

WorstPen.InkLevelPercent = 100;

Remember, WorstPen is the variable name, and this variable is an object. It is an object or instance of class Pen. This object has various member variables, including InkLevelPercent.

You can also run some of the member functions that are in these objects. In the code, we called

FavoritePen.write_on_paper(“Hello I am a pen”);

This called the function write_on_paper for the object FavoritePen. Take a look at the code for this function, which is in the header file, Listing 7-1:

void write_on_paper(string words)

{

if (InkLevelPercent <= 0)

{

cout << “Oops! Out of ink!” << endl;

}

else

{

cout << words << endl;

InkLevelPercent = InkLevelPercent - words.length();

}

}

This function uses the variable called InkLevelPercent. But InkLevelPercent is not declared in this function. The reason is that InkLevelPercent is part of the object and is declared in the class. Suppose you call this method for two different objects, as in the following:

FavoritePen.write_on_paper(“Hello I am a pen”);

WorstPen.write_on_paper(“Hello I am another pen”);

The first of these lines calls write_on_paper for the FavoritePen object; thus, inside the code for write_on_paper, the InkLevelPercent refers to InkLevelPercent for the FavoritePen object. It looks at and possibly decreases the variable for that object only. But WorstPen has its own InkLevelPercent member variable, separate from that of FavoritePen. So in the second of these two lines, write_on_paper accesses and possibly decreases the InkLevelPercent that lives inside WorstPen.

In other words, each object has its own InkLevelPercent. When you call write_on_paper, the function modifies the member variable based on which object you are calling it with. The first line calls it with FavoritePen. The second calls it with WorstPen. When you run this program, you see the following output:

This is my favorite pen

Color: 0

Brand: Pilot

Ink Level: 90%

Hello I am a pen

Ink Level: 74%

Now notice something about the color line. Here’s the line of code that writes it:

cout << “Color: “ << FavoritePen.InkColor << endl;

We’re writing the InkColor member for FavoritePen. But what type is InkColor? It’s the new enumerated type we created called Color. But something is wrong. It printed 0. Yet here’s the line where we set it:

FavoritePen.InkColor = blue;

We set it to blue, not 0. Unfortunately, that’s the breaks with using enum. You can use it in your code, but under the hood, it just stores numbers. And when we print it, we get a number. Well, that stinks. The compiler chooses the numbers for you, and it starts the first in the enum list as0, the second as 1, then 2, then 3, and so on. Thus, blue is stored as 0, red as 1, black as 2, and clear as 3. But, as we always say (because we’re forever the optimists), fear not! People have found a way to create a new class that handles the enum for you (that is, it wraps around the enum), and then you can print what you really want: blue, red, black, and clear. Take a look at Minibook I, Chapter 8 for tips on how to do this astounding feat.

image Remember that you can create several objects (also called instances) of a single class. Each object gets its own member variables, which you declare in the class. To access the members of an object, you use a period, or dot.

Separating member function code

When you work with functions, you can either make sure that the code to your function is positioned before any calls to the function, or you can use a forward reference, also called a function prototype. We talk about this handy little feature in Minibook I, Chapter 4.

The string class

If you’ve been reading the previous chapters of Minibook I and trying the programs, you have seen the string type. Now for the big secret: string is actually a class. When you create a variable of type string, you are creating an object of class string. That’s why, to use thestring functions, you first type the variable name, a dot, and then the function name: You are really calling a member function for the string object that you created. Similarly, when you work with pointers to strings, instead of a dot you can use the -> notation to access the member functions. (See “Using classes and pointers,” later in this chapter, for more information.) When working with newer versions of C++, the string class is part of the std namespace, which is why you add using namespace std; to the beginning of your code. If you use an older version of C++, the string class appears as part of the string file. In this case, you include <string> to provide the necessary header files to declare the string class.

When you work with classes and member functions, you have a similar option. Most C++ programmers prefer to keep the code for their member functions outside the class definition. The class definition contains only function prototypes, or, at least, mostly function prototypes. If the function is one or two lines of code, people may leave it in the class definition.

When you use a function prototype in a class definition, you write the prototype by ending the function header with a semicolon where you would normally have the open brace and code. If your member function looks like

void break_in_half()

{

InkLevelPercent = InkLevelPercent / 2;

Length = Length / 2.0;

}

a function prototype would look like

void break_in_half();

Yes, it’s true: To type this, we just copied the first line of the function, put the cursor at the end, pressed backspace a couple times, and typed a semicolon. We’re telling you that not to brag about our prowess with the keyboard when writing books but rather because that’s how we do it when we actually write code. That way, we can be assured that the two lines match. Ah, the beauty of computers. Imagine how hard it would be to write a computer program without the help of computers.

Now after you have the function prototype in the class, you write the function again outside the class definition. However, you need to doctor it up just a bit. In particular, you need to throw in the name of the class, so that the compiler knows which class this function goes with.

The following is the same function we described earlier but souped-up with the class information:

void Pen::break_in_half()

{

InkLevelPercent = InkLevelPercent / 2;

Length = Length / 2.0;

}

You would put this after your class definition. And you would want to put this inside one of your source-code files if your class definition is in a header file.

imageYou can use the same function name in different classes. Like variables in different functions, function names are totally separate things. Although you don’t want to go overboard on duplicating your function names, if you feel a need to, you can certainly do it without a problem.

Listings 7-3 and 7-4 show the modified version of the Pen class, which originally appeared earlier in this chapter in Listing 7-1. You can use these two files together with Listing 7-2, which did not change.

Listing 7-3: Using Member Function Prototypes with the Modified Pen.h file

#ifndef PEN_H_INCLUDED

#define PEN_H_INCLUDED

using namespace std;

enum Color

{

blue,

red,

black,

clear

};

enum PenStyle

{

ballpoint,

felt_tip,

fountain_pen

};

class Pen

{

public:

Color InkColor;

Color ShellColor;

Color CapColor;

PenStyle Style;

float Length;

string Brand;

int InkLevelPercent;

void write_on_paper(string words);

void break_in_half();

void run_out_of_ink();

};

#endif // PEN_H_INCLUDED

Listing 7-4: Containing the Member Functions for Class Pen in the New Pen.cpp File

#include <iostream>

#include “Pen.h”

using namespace std;

void Pen::write_on_paper(string words)

{

if (InkLevelPercent <= 0)

{

cout << “Oops! Out of ink!” << endl;

}

else

{

cout << words << endl;

InkLevelPercent = InkLevelPercent - words.length();

}

}

void Pen::break_in_half()

{

InkLevelPercent = InkLevelPercent / 2;

Length = Length / 2.0;

}

void Pen::run_out_of_ink()

{

InkLevelPercent = 0;

}

All the functions from the class are now in a separate source (.cpp) file. The header file now just lists prototypes and is a little easier for us humans to scan through. And for the source file, we included the header file at the top. That’s required; otherwise, the compiler won’t know thatPen is a class name, and it will get confused (as it so easily can).

The parts of a class

Here is a summary of the parts of a class and the different ways classes can work together.

Class: A class is a type. It includes characteristics and capabilities. Characteristics describe the class, and capabilities describe its behavior.

Object (or instance): An object is an example of a class. Or, to put it another way, an object’s type is the class. If you like analogies, the object Fred is to the Human class as 17 is to int.

Class definition: The class definition describes the class. It starts with the word class, then has the name of the class, and then an open brace and closing brace. Inside the braces are the members of the class.

Member variable: A member variable is the C++ version of a characteristic in a class. You list the member variables inside the class. Each instance of the class gets its own copy of each member variable.

Member function: A member function is the C++ version of a capability of a class. Like member variables, you list the member functions inside the class. When you call a member function for a particular instance, the function accesses the member variables for the instance.

When you divide the class, you put part in the header file and part in the source-code file. The following list describes what goes where:

Header file: Put the class definition in the header file. You can include the function code inside the class definition itself if it’s a short function. Most people prefer not to put any function code longer than a line or two in the header or don’t put any function code in the header. You may want to name the header file the same as the class but with an .h or .hpp extension. Thus, the class Pen, for instance, might be in the file Pen.h.

Source file: If your class has member functions, and you did not put the code in the class definition, you need to put the code in a source file. When you do, precede the function name with the class name and two colons. (Do not put any spaces between the two colons, but you can put spaces on either side of the pair of colons.) If you named the header file the same as the class, you probably want to name the source file the same as the class as well but with a .cpp or .cc extension.

Working with a Class

Many handy tricks are available for working with classes. In this section, we explore several clever ways of working with classes, starting with the way you can hide certain parts of your class from other functions that are accessing them.

Accessing members

When you work with an object in real life, there are often parts of the object that you interact with and other parts that you don’t. For example, when we use the computer, we type on the keyboard but don’t open the box and poke around with a wire attached to a battery. For the most part, the stuff inside is off-limits except when we’re upgrading it.

In object terminology, we use the words public and private to refer to characteristics and capabilities. When you design a class, you might want to make some member variables and functions freely accessible by users of the class. You may want to keep other classes tucked away.

First, let us explain what we mean by users of the class. When the main function of your program creates an instance of a class and calls one of its member functions, main is a user of the class. If you have a function called FlippityFlop, and it creates an instance of your class and does a few things to the instance, like change some its member variables, FlippityFlop is a user of your class. In short, a user is any function that accesses your class.

If you’re designing a class, it’s possible that you want only these users calling certain member functions. Other member functions you may want to keep hidden away, to be called only by other member functions within the class. Suppose you’re writing a class called Oven. This class includes a method called Bake, which takes a number as a parameter representing the desired oven temperature. Now you may also have a member function called TurnOnHeatingElement and one called TurnOffHeatingElement.

Here’s how it would work. The Bake method starts out calling TurnOnHeatingElement. Then it keeps track of the temperature, and when the temperature is correct, it calls TurnOffHeatingElement.

Now would you want somebody walking in the kitchen and calling the TurnOnHeatingElement function without touching any of the dials, only toleave the room as the oven gets hotter and hotter with nobody watching it? No. You allow the users of the class to call only Bake. The other two member functions, TurnOnHeatingElement and TurnOffHeatingElement, are reserved for use only by the Bake function.

You bar users from calling functions by making specific functions private. Functions that you want to allow access to you make public.

After you have such a class designed, if you write a function (not a member function) that has an object and you try to call one of an object’s private member functions, you get a compiler error when you try to compile it. The compiler won’t allow you to call it.

Listing 7-5 shows a sample Oven class and a main that uses it. Take a look at the class definition. It has two sections: one private and the other public. After the class definition, we put the code for the functions. The two private functions don’t do much other than print a message. (Although they’re also free to call other private functions in the class.) The public function, Bake, calls each of the private functions, because it’s allowed to.

Listing 7-5: Using the Public and Private Words to Hide Parts of Your Class

#include <iostream>

using namespace std;

class Oven

{

private:

void TurnOnHeatingElement();

void TurnOffHeatingElement();

public:

void Bake(int Temperature);

};

void Oven::TurnOnHeatingElement()

{

cout << “Heating element is now ON! Be careful!” << endl;

}

void Oven::TurnOffHeatingElement()

{

cout << “Heating element is now off. Relax!” << endl;

}

void Oven::Bake(int Temperature)

{

TurnOnHeatingElement();

cout << “Baking!” << endl;

TurnOffHeatingElement();

}

int main()

{

Oven fred;

fred.Bake(875);

return 0;

}

When you run this program, you see some messages:

Heating element is now ON! Be careful!

Baking!

Heating element is now off. Relax!

Nothing too fancy here. Now if you tried to include a line in your main such as the one in the following code, where you call a private function

fred.TurnOnHeatingElement();

you see an error message telling you that you can’t do it because the function is private. In CodeBlocks, we see this message:

error: `void Oven::TurnOnHeatingElement()’ is private

imageWhen you design your classes, consider making all the functions private by default, and then only make those public that you want users to have access to. Some people, however, prefer to go the other way around: Make them all public, and only make those private that you are sure you don’t want users to access. There are good arguments for either way; however, we prefer to make public only what must be public. That way, we minimize the risk of some other program, that’s using your class, messing things up by calling things the programmer doesn’t really understand.

imageYou don’t necessarily need to list the private members first followed by the public members. You can put the public members first if you prefer. Some people put the public members at the top so they see them first. That makes sense. Also, you can have more than one private section and more than one public section. For example, you can have a public section, a private section, and then another public section, as in the following code:

class Oven

{

public:

void Bake(int Temperature);

private:

void TurnOnHeatingElement();

void TurnOffHeatingElement();

public:

void Broil();

};

But we recommend having only one public section and only one private section (or no private sections). This minimalism keeps your code neater.

Using classes and pointers

As with any variable, you can have a pointer variable that points to an object. As usual, the pointer variable’s type must match the type of the class. This creates a pointer variable that points to a Pen instance:

Pen *ptrMyPen;

The variable ptrMyPen is a pointer, and it can point to an object of type Pen. The variable’s own type is pointer to Pen, or in C++ notation, Pen *.

image A line of code like Pen *ptrMyPen; creates a variable that serves as a pointer to an object. But this line, by itself, does not actually create an instance. By itself, it points to nothing. To create an instance, you have to call new. This is a common mistake among C++ programmers; sometimes people forget to call new and wonder why their programs crash.

After you create the variable ptrMyPen, you can create an instance of class Pen, and point ptrMyPen to it using the new keyword like so:

ptrMyPen = new Pen;

Or you can combine both Pen *ptrMyPen; and the preceding line:

Pen *ptrMyPen = new Pen;

Now you have two variables: You have the actual object, which is unnamed and sitting on the heap (see Minibook I, Chapter 6, for more information on pointers and heaps). You also have the pointer variable, which points to the object: two variables working together.

Because the object is out on the heap, the only way to access it is through the pointer. To access the members through the pointer, you use a special notation that is a minus sign followed by a greater-than sign, which looks like an arrow, as in the following line:

ptrMyPen->InkColor = red;

This goes through the pointer to set the InkColor of the object to red.

image Get used to working with pointers and using the pointer notation for accessing the members of an object. It’s not just a programming language; it’s a way of life!

imageAlthough we like to begin a pointer variable’s name with ptr, we sometimes forgo that when we’re working with objects. Most object work involves objects on the heap, so you are always accessing objects through pointers. In our minds, we connect the two into one, and we feel like the pointer variable is the object, so we don’t use the ptr prefix.

If we decide not to start our pointer variable names with ptr, the previous lines of code would look like this instead:

Pen *MyPen = new Pen;

MyPen->InkColor = red;

As with other variables you created with new, after you are finished using an object, you should call delete. To do so, start with the word delete and then the name of the object, as in this:

delete MyPen;

image Store a 0 in the pointer after you delete it (which really means delete the object it’s pointing to). When you call delete on a pointer to an object, you are deleting the object itself, not the pointer. If you don’t store a 0 in the pointer, it still points to where the object used to be.

Listing 7-6 shows a process of declaring a pointer, creating an object and pointing to it, accessing the object’s members through the pointer, deleting the object, and clearing the pointer back to 0.

Listing 7-6: Managing an Object’s Life

#include <iostream>

#include “Pen.h”

using namespace std;

int main()

{

Pen *MyPen;

MyPen = new Pen;

MyPen->InkColor = red;

cout << MyPen->InkColor << endl;

delete MyPen;

MyPen = 0;

return 0;

}

imageTable 7-2 reiterates the process (steps) shown in Listing 7-6 in a more formal way. We call Table 7-2 “Steps to Using Objects,” rather than something more specific such as “Using Objects with Pointers,” because the majority of your work with objects will be through pointers. Therefore, this is the most common way of using pointers.

Table 7-2 Steps to Using Objects

Step

Sample Code

Action

1

Pen *MyPen;

Declares the pointer

2

MyPen = new Pen;

Calls new to create the object

3

MyPen->InkColor = red;

Accesses the members of the object through the pointer

4

delete MyPen;

Deletes the object

5

MyPen = 0;

Clears the pointer

Now that you have an overview of the process through Listing 7-6 and understand the basics through Table 7-2, let’s formalize the procedure. The following steps describe precisely how to work with pointers and objects:

1. Declare the pointer.

The pointer must match the type of object you are going to work with, except the pointer’s type name in C++ is followed by an asterisk, *.

2. Call new, passing the class name, and store the results of new in the pointer.

You can combine Steps 1 and 2 into a single step.

3. Access the object’s members through the pointer with the shorthand notation ->.

You could dereference the pointer and put parentheses around it, but everyone uses the shorthand notation.

4. When you are finished with the pointer, call delete.

This step frees the object from the heap. Remember that this does not delete the pointer itself, although programmers usually say that they’re deleting the pointer.

5. Clear the pointer by setting it to 0.

If your delete statement is at the end of the program, you don’t need to clear the pointer to 0.

Passing objects to functions

When you write a function, normally you base your decision about using pointers on whether or not you want to change the original variables passed into the function. Suppose you have a function called AddOne, and it takes an integer as a parameter. If you want to modify the original variable, you can use a pointer (or you can use a reference). If you don’t want to modify the variable, just pass the variable by value as it’s called.

The following prototype represents a function that can modify the variable passed into it:

void AddOne(int *number);

And this prototype represents a function that cannot modify the variable passed into it:

void AddOne(int number);

With objects, you can do something similar. For example, this function takes a pointer to an object and can, therefore, modify the object:

void FixFlatTire(Car *mycar);

But what do you suppose this would do:

void FixFlatTire(Car mycar);

Based on what we said previously, most likely the function gets its own Car instance that cannot be modified. That’s correct, but consider that for a moment: The function gets its own instance. In other words, every time you call this function, it creates an entirely new instance of classCar. This instance would be a duplicate of class Car — except that it wouldn’t be the same instance. Just a copy of it.

When you work with objects, a copy is not always a sure thing. What if the object has member variables that are pointers to other objects? Will the copy get copies of those pointers, which in turn point to those same other objects? Or does this object’s members point to its own other objects? Are those objects copies or the originals?

imageAlways pass objects as pointers. Don’t pass objects directly into functions. Yes, it risks bad code changing the object, but careful C++ programmers want the actual object, not a copy. That outweighs the risk of an accidental change. This chapter explains how to prevent accidental changes by using the const parameters.

So just do this:

void FixFlatTire(Car *mycar);

If you like references, you are welcome to do this:

void FixFlatTire(Car &mycar);

But don’t just pass the object. It’s messy and not nice.

Because your function receives its objects as pointers, you continue accessing them by using the -> notation. For example, the function FixFlatTire may do this:

void FixFlatTire(Car *mycar)

{

mycar->RemoveTire();

mycar->AddNewTire();

}

Or, if you prefer references, you would do this:

void FixFlatTire2(Car &mycar)

{

mycar.RemoveTire();

mycar.AddNewTire();

}

In this code, because you’re dealing with a reference, you access the object’s members using the dot rather than the -> notation.

imageAnother reason to use only pointers and references as parameters for objects is that a function that takes an object as a parameter usually wants to change the function. Such changes require pointers or references. When you don’t want the function to modify the object, use const, which is covered in the following section.

Using const parameters in functions

If a function takes an object as a parameter and you’re passing it by using a pointer or reference but don’t want the function modifying the object, use the word const in the function header. If you insert const before the type in the parameter list, the compiler does not let the function code modify the object. Such code causes a compiler error.

The const word is useful because you generally don’t want to pass an object directly. That involves copying the object, which is messy. Instead, you normally pass by using a pointer or reference, which would allow you to change the object. If you put the word const before the parameter, the compiler will not allow you to change the parameter. In Listing 7-7, we have inserted const before the parameter. The function can look at the object but can’t change it.

Listing 7-7: The Inspect Function Is Not Allowed to Modify Its Parameter

#include <iostream>

#include “Pen.h”

using namespace std;

void Inspect(const Pen *Checkitout)

{

cout << Checkitout->Brand << endl;

}

int main()

{

Pen *MyPen = new Pen();

MyPen->Brand = “Spy Plus Camera”;

Inspect(MyPen);

return 0;

}

Now suppose that you tried to change the object in the Inspect function. You may have put a line in that function like this:

Checkitout->Length = 10.0;

If you try this, the compiler issues an error. In CodeBlocks, we get

error: assignment of data-member `Pen::Length’ in read-only structure

image If you have multiple parameters, you can mix const and non-const. If you go overboard, this can be confusing. The following line shows two parameters that are const and another that is not. The function can modify only the members of the object called one.

void Inspect(const Pen *Checkitout, Spy *one, const Spy *two);

Using the this pointer

Consider a function called OneMoreCheeseGone. It’s not a member function, but it takes an object of instance Cheese as a parameter. Its prototype looks like this:

void OneMoreCheeseGone(Cheese *Block);

This is just a simple function with no return type. It takes an object pointer as a parameter. For example, after you eat a block of cheese, you can call:

OneMoreCheeseGone(MyBlock);

Now consider this: If you have an object on the heap, it has no name. You access it through a pointer variable that points to it. But what if the code is currently executing inside a member function of an object? How do you refer to the object itself?

C++ has a secret variable that exists inside every member function: this. It’s a pointer variable. The this variable always points to the current object. So if code execution is occurring inside a member function and you want to call OneMoreCheeseGone, passing in the current object (or block of cheese), you would pass this.

Listing 7-8 demonstrates this. The this listing has four main parts. First is the definition for the class called Cheese. The class contains a couple of member functions.

Next is the function OneMoreCheeseGone along with a global variable that it modifies. This function subtracts one from the global variable and stores a string in a member variable, status, of the object passed to it.

Next come the actual member functions for class Cheese. (We put these functions after the OneMoreCheeseGone function because they call it. If we used a function prototype as a forward reference for OneMoreCheeseGone, the order wouldn’t matter.)

Finally we have main, which creates two new instances of Cheese. Then it sets the global variable to 2, which keeps track of the number of blocks left. Next, it calls the eat function for the asiago cheese and rot for the limburger cheese. And then it prints the results of everything that happened: It displays the Cheese count, and it displays the status variable of each object.

Listing 7-8: Passing an Object from Inside Its Member Functions by Using the this Variable

#include <iostream>

using namespace std;

class Cheese

{

public:

string status;

void eat();

void rot();

};

int CheeseCount;

void OneMoreCheeseGone(Cheese *Block)

{

CheeseCount--;

Block->status = “Gone”;

};

void Cheese::eat()

{

cout << “Eaten up! Yummy” << endl;

OneMoreCheeseGone(this);

}

void Cheese::rot()

{

cout << “Rotted away! Yuck” << endl;

OneMoreCheeseGone(this);

}

int main()

{

Cheese *asiago = new Cheese();

Cheese *limburger = new Cheese();

CheeseCount = 2;

asiago->eat();

limburger->rot();

cout << endl;

cout << “Cheese count: “ << CheeseCount << endl;

cout << “asiago: “ << asiago->status << endl;

cout << “limburger: “ << limburger->status << endl;

return 0;

}

When you run the program in Listing 7-8, you see this output:

Eaten up! Yummy

Rotted away! Yuck

Cheese count: 0

asiago: Gone

limburger: Gone

The first line is the result of calling asiago->eat(), which prints one message. The second line is the result of calling limburger->rot(), which prints another message.

The third line is simply the value in the variable CheeseCount. This variable was decremented once each time the computer called the OneMoreCheeseGone function. Because the function was called twice, CheeseCount went from 2 to 1 to 0.

The final two lines show the contents of the status variable in the two objects. (The OneMoreCheeseGone function had stored the string Gone in these variables.)

Take a careful look at the OneMoreCheeseGone function. It operated on the current object that came in as a parameter by setting its status variable to the string Gone. Where did the parameter come from? The member function eat called it, passing the object itself by using the thispointer. The member function rot also called it, again passing the object itself via the this pointer.

Overloading member functions

You may want a member function in a class to handle different types of parameters. For example, you might have a class called Door and a member function called GoThrough. You might want the GoThrough function to take as parameters an object of class Dog, an object of classHuman, or an object of class Cat. Depending on which class is entering, you might want to change the GoThrough function’s behavior.

A way to handle this is by overloading the GoThrough function. C++ lets you design a class that has multiple member functions that are all named the same. However, the parameters must differ between these functions. With the GoThrough function, one version will take a Human, another a Dog, and another a Cat.

Go through the code in Listing 7-9 and notice the GoThrough functions. There are three of them. Now look at main. It creates four different objects — a cat, a dog, a human, and a door. It then sends each creature through the door.

Listing 7-9: Overloading Functions in a Class

#include <iostream>

using namespace std;

class Cat

{

public:

string name;

};

class Dog

{

public:

string name;

};

class Human

{

public:

string name;

};

class Door

{

private:

int HowManyInside;

public:

void Start();

void GoThrough(Cat *acat);

void GoThrough(Dog *adog);

void GoThrough(Human *ahuman);

};

void Door::Start()

{

HowManyInside = 0;

}

void Door::GoThrough(Cat *somebody)

{

cout << “Welcome, “ << somebody->name << endl;

cout << “A cat just entered!” << endl;

HowManyInside++;

}

void Door::GoThrough(Dog *somebody)

{

cout << “Welcome, “ << somebody->name << endl;

cout << “A dog just entered!” << endl;

HowManyInside++;

}

void Door::GoThrough(Human *somebody)

{

cout << “Welcome, “ << somebody->name << endl;

cout << “A human just entered!” << endl;

HowManyInside++;

}

int main()

{

Door entrance;

entrance.Start();

Cat *SneekyGirl = new Cat;

SneekyGirl->name = “Sneeky Girl”;

Dog *LittleGeorge = new Dog;

LittleGeorge->name = “Little George”;

Human *me = new Human;

me->name = “Jeff”;

entrance.GoThrough(SneekyGirl);

entrance.GoThrough(LittleGeorge);

entrance.GoThrough(me);

delete SneekyGirl;

delete LittleGeorge;

delete me;

return 0;

}

The program allows them to enter like humans. The beginning of this program declares three classes, Cat, Dog, and Human, each with a name member. Next is the Door class. A private member, HowManyInside, tracks how many beings have entered. Then we have a public function called Start, which activates the door. Finally, the class contains the overloaded functions. They all have the same name and the same return type. You can have different return types, but they must differ by parameters. These do; one takes a Cat pointer; one takes a Dogpointer; and one takes a Human pointer.

Next is the code for the member functions. The first function, Start, is easy to activate. It sets HowManyInside to 0. The next three functions are overloaded. They do similar things, but they write slightly different messages. Each takes a different type.

Then we have main, which creates a Door instance. We didn’t make this a pointer (just to show that you can mix pointers with stack variables in a program). After creating the Door instance, we called its Start function. Next, we created three creature instances: one Cat, one Dog, and one Human. We also set the name member variables for each.

Then we call the entrance.GoThrough function. The first time we pass a Cat, then we pass a Dog, and then we pass a Human. (Sounds painful.) Because you can see the Door class, you know that we’re calling three different functions that happened to be all named the same. But when we’re using the class, we consider them all one function that happens to accept either a Cat, a Dog, or a Human. That’s the goal of overloading: to create what feels like versions of the one function.

Starting and Ending with Constructors and Destructors

You can add two special functions to your class that let you provide special startup and shutdown functionality. These are called a constructor and a destructor. The following sections provide the secret details about these nifty functions.

Starting with constructors

When you create a new instance of a class, you may want to do some basic setup on the object. Suppose you have a class called Apartment, with a private member variable called NumberOfOccupants and a member function called ComeOnIn. The code for ComeOnIn adds 1 toNumberOfOccupants.

When you create a new instance of Apartment, you probably want to start NumberOfOccupants at 0. The best way to do this is by adding a special member function, a constructor, to your class. This member function has a line of code such as

NumberOfOccupants = 0;

Whenever you create a new instance of the class Apartment, the computer first calls this constructor for your new object, thereby setting NumberOfOccupants to 0.

Think of the constructor as an initialization function: The computer calls it when you create a new object.

To write a constructor, you add it as another member function to your class, and you make it public. You name the constructor the same as your class. For the class Apartment, we would name our constructor Apartment. The constructor has no return type, not even void. You can have parameters in a constructor; see “Adding parameters to constructors,” later in this chapter.

Listing 7-10, later in this section, shows a sample constructor along with a destructor, which we cover in the next section.

Ending with destructors

When you delete an instance of a class, you might want some cleanup code to straighten things out before the object goes off to the classroom in the sky. For example, your object may have member variables that are pointers to other objects. You may want to delete those other objects.

You put cleanup code in a special function called a destructor. A destructor is a finalization function that the computer calls before it deletes your object.

The destructor function gets the same name as the class, except it has a tilde, ~, at the beginning of it. (The tilde is usually in the upper-left corner of the keyboard.) For a class called Squirrel, the destructor would be ~Squirrel. The destructor does not have a return type, not evenvoid, because you can’t return anything from a destructor (the object is gone, after all). You just start with the function name and no parameters.

The next section, “Sampling constructors and destructors,” shows an example that uses both constructors and destructors.

imageConstructors and destructors are a way of life for C++ programmers. Nearly every class has a constructor, and many also have a destructor.

Sampling constructors and destructors

Listing 7-10 uses a constructor and destructor. This program involves two classes, the main one called Squirrel that demonstrates the constructor and destructor, and one called Walnut, which is used by the Squirrel class.

The Squirrel class has a member variable called MyDinner that is a pointer to a Walnut instance. The Squirrel constructor creates an instance of Walnut and stores it in the MyDinner variable. The destructor deletes the instance of Walnut.

In main, we create two instances of Squirrel. Each instance gets its own Walnut to eat. Each Squirrel creates its Walnut when it starts and deletes the Walnut when the Squirrel is deleted.

Listing 7-10: Initializing and Finalizing with Constructors and Destructors

#include <iostream>

using namespace std;

class Walnut

{

public:

int Size;

};

class Squirrel

{

private:

Walnut *MyDinner;

public:

Squirrel();

~Squirrel();

};

Squirrel::Squirrel()

{

cout << “Starting!” << endl;

MyDinner = new Walnut;

MyDinner->Size = 30;

}

Squirrel::~Squirrel()

{

cout << “Cleaning up my mess!” << endl;

delete MyDinner;

}

int main()

{

Squirrel *Sam = new Squirrel;

Squirrel *Sally = new Squirrel;

delete Sam;

delete Sally;

return 0;

}

Notice in this code that the constructor has the same name as the class, Squirrel. The destructor also has the same name, but with a tilde, ~, tacked on to the beginning of it. Thus, the constructor is Squirrel and the destructor is ~Squirrel.

When you run this program, you can see the following lines, which were spit up by the Squirrel in its constructor and destructor. (You see two lines of each because we created two squirrels.)

Starting!

Starting!

Cleaning up my mess!

Cleaning up my mess!

If our Walnut class also had a constructor and destructor, and we made the MyDinner member an actual variable in the Squirrel class rather than a pointer, the computer would create the Walnut instance after it creates the Squirrel instance but before it calls the Squirrelconstructor. It then deletes the Walnut instances when it is deleting the Squirrel instance, after it finishes calling the ~Squirrel destructor. It would do this for each instance of Squirrel, so that each Squirrel gets its own Walnut, as before.

Constructors and destructors with stack variables

In Listing 7-10, we created the two Squirrels on the heap by using pointers and calling

Squirrel *Sam = new Squirrel;

Squirrel *Sally = new Squirrel;

But we could have made them on the stack by just declaring them without pointers:

Squirrel Sam;

Squirrel Sally;

You can do this, and the program will run fine, provided you remove the delete lines. You do not delete stack variables. The computer calls the destructor when the main function ends. That’s the general rule with objects on the stack: They are created when you declare them, and they stay until the function ends.

Adding parameters to constructors

Like other functions, constructors allow you to include parameters. When you do, you can use these parameters in constructors in your initialization process. To use them, you list the arguments inside parentheses when you create the object.

image Although int has a constructor, it is not actually a class. However, the runtime library (that big mass of code that gets put in with your program by the linker) includes a constructor and destructor that you can use when calling new for an integer.

Suppose that you want the Squirrel class to have a member variable called name. Although you could create an instance of Squirrel and then set its name variable, you can specify the name directly by using a constructor.

The constructor’s prototype would look like this:

Squirrel(string StartName);

Then, you would create a new instance like so:

Squirrel *Sam = new Squirrel(“Sam”);

The constructor is expecting a string, so you pass a string when you create the object.

Listing 7-11 shows a program that includes all the basic elements of a class with a constructor that accepts parameters.

Listing 7-11: Placing Parameters in Constructors

#include <iostream>

using namespace std;

class Squirrel

{

private:

string Name;

public:

Squirrel(string StartName);

void WhatIsMyName();

};

Squirrel::Squirrel(string StartName)

{

cout << “Starting!” << endl;

Name = StartName;

}

void Squirrel::WhatIsMyName()

{

cout << “My name is “ << Name << endl;

}

int main()

{

Squirrel *Sam = new Squirrel(“Sam”);

Squirrel *Sally = new Squirrel(“Sally”);

Sam->WhatIsMyName();

Sally->WhatIsMyName();

delete Sam;

delete Sally;

return 0;

}

In main, we passed a string into the constructors. In the code for the constructor, we’re taking the string parameter called StartName and copying it to the member variable called Name. In the WhatIsMyName function, we write it to the console.

image You cannot include parameters in a destructor. The C++ language does not allow it.

Building Hierarchies of Classes

When you start going crazy describing classes, you usually discover hierarchies of classes. For example, you might say you have a class Vehicle. But we might say, we can divide your class Vehicle into classes Car, Pickup Truck, Tractor Trailer, and SUV.

Then you might say that you can take the Car class and divide it into such classes as Station Wagon, Four-door Sedan, and Two-door Hatchback.

Or we could divide Vehicle into car brands, such as Ford, Honda, and Toyota. Then we could divide the class Toyota into models, such as Tercel, Celica, Camry, and Corolla.

You can create similar groupings of objects for the other class hierarchies; your decision depends on how you categorize things and how the hierarchy is used.

In the hierarchy, class Vehicle is at the top. This class has characteristics that you find in every brand or model of vehicles. For example, all vehicles have wheels. How many they have varies, but it doesn’t matter at this point because classes don’t have specific values for the characteristics.

Each brand has certain characteristics that might be unique to it, but each has all the characteristics of class Vehicle. That’s called inheritance. The class Toyota, for example, has all the characteristics found in Vehicle. And the class Celica has all the characteristics found inToyota, which includes those inherited from Vehicle.

Creating a hierarchy in C++

In C++, you can create a hierarchy of classes. When you take one class and create a new one under it, such as creating Toyota from Vehicle, you are deriving a new class.

To derive a class from an existing class, you write the new class as you would any other class, but you extend the header after the class name with a colon, :, the word public, and then the class you’re deriving from, as in the following class header line:

class Toyota : public Vehicle {

When you do so, the class you create (Toyota) inherits the member variables and functions from the previous class (Vehicle). For example, if Vehicle has a public member variable called NumberOfWheels and a public member function called Drive, the class Toyota has these members, although you didn’t write the members in Toyota.

Listing 7-12 shows a class inheritance. We started with a class called Vehicle, and we derived a class called Toyota. In main, we created an instance of Toyota, and we called two member functions for the instance, MeAndMyToyota and Drive. The definition of the Toyotaclass does not show a Drive function. The Drive function was inherited from the Vehicle class. You can call this function like a member of the Toyota class because in many ways it is.

Listing 7-12: Deriving One Class from Another

#include <iostream>

using namespace std;

class Vehicle

{

public:

int NumberOfWheels;

void Drive()

{

cout << “Driving, driving, driving...” << endl;

}

};

class Toyota : public Vehicle

{

public:

void MeAndMyToyota()

{

cout << “Just me and my Toyota!” << endl;

}

};

int main()

{

Toyota MyCar;

MyCar.MeAndMyToyota();

MyCar.Drive();

return 0;

}

When you run this program, you see the output from two functions:

Just me and my Toyota!

Driving, driving, driving...

imageSome people use the term parent class for the first class in a hierarchy and child for the one that is derived. However, these are not the best terms because some people use them to mean that one class has an instance of another class as a member variable. In that case, the parent class has as a member the child class. A better term is base class and derived class. You derive a class from the base class. The result is a derived class.

Understanding types of inheritance

When you create a class, member functions can access both public and private member variables and functions. Users of the class can access only the public member variables and functions. But when you derive a new class, the picture changes. The derived class cannot access the private members in its own class. Private members are reserved for a class itself and not for any derived class.

When members need to be accessible by derived classes, there’s a specification you can use beyond public and private: protected.

image Protected members and private members work the same way, but derived classes can access only protected members, not private members. Users can’t access either class.

image We avoid private members unless we know that we won’t derive classes from a member. When we’ve derived classes from other people’s classes with private unprotected members, we couldn’t add all the cool features we wanted. My derived class required access to those private members, so we had to mess up the original code to modify the original class. If the original programmer had used protected members, our derived class could access the members without changing the original code!