Classes extend the built-in capabilities of C++ to assist you in representing and solving complex, real-world problems.
Today, you will learn
• What classes and objects are
• How to define a new class and create objects of that class
• What member functions and member data are
• What constructors are and how to use them
At one point, C, the predecessor to C++, was the world’s most popular programming language for commercial software development. It was used for creating operating systems (such as the Unix operating system), for real-time programming (machine, device, and electronics control), and only later began to be used as a language for programming conventional languages. Its intent was to provide an easier and safer way to program down close to the hardware.
C was developed as a middle ground between high-level business application languages such as COBOL and the pedal-to-the-metal, high-performance, but difficult-to-use Assembler language. C was to enforce “structured” programming, in which problems were “decomposed” into smaller units of repeatable activities called procedures and data was assembled into packages called structures.
But research languages such as Smalltalk and CLU had begun to pave a new direction—object-orientation—which combined the data locked away in assemblies like structures with the capabilities of procedures into a single unit: the object.
The world is filled with objects: cars, dogs, trees, clouds, flowers. Objects. Each object has characteristics (fast, friendly, brown, puffy, pretty). Most objects have behavior (move, bark, grow, rain, wilt). You don’t generally think about a car’s specifications and how those specifications might be manipulated. Rather, a car is thought about as an object that looks and acts a certain way. And the same should be true with any real-world object that is brought into the domain of the computer.
The programs being written early in the twenty-first century are much more complex than those written at the end of the twentieth century. Programs created in procedural languages tend to be difficult to manage, hard to maintain, and expensive to extend. Graphical user interfaces, the Internet, digital and wireless telephony, and a host of new technologies have dramatically increased the complexity of our projects at the same time that consumer expectations for the quality of the user interface are rising.
Object-oriented software development offers a tool to help with the challenges of software development. Though there are no silver bullets for complex software development, object-oriented programming languages build a strong link between the data structures and the methods that manipulate that data and have a closer fit to the way humans (programmers and clients) think, improving communication and improving the quality of delivered software. In object-oriented programming, you no longer think about data structures and manipulating functions; you think instead about objects as if they were their real-world counterparts: as things that look and act a certain way.
C++ was created as a bridge between object-oriented programming and C. The goal was to provide object-oriented design to a fast, commercial software development platform, with a special focus on high performance. Next, you’ll see more about how C++ meets its objectives.
Programs are usually written to solve real-world problems, such as keeping track of employee records or simulating the workings of a heating system. Although it is possible to solve complex problems by using programs written with only numbers and characters, it is far easier to grapple with large, complex problems if you can create representations of the objects that you are talking about. In other words, simulating the workings of a heating system is easier if you can create variables that represent rooms, heat sensors, thermostats, and boilers. The closer these variables correspond to reality, the easier it is to write the program.
You’ve already learned about a number of variable types, including unsigned
integers and characters. The type of a variable tells you quite a bit about it. For example, if you declare Height
and Width
to be unsigned short
integers, you know that each one can hold a number between 0 and 65,535, assuming an unsigned short
integer is two bytes. That is the meaning of saying they are unsigned
integers; trying to hold anything else in these variables causes an error. You can’t store your name in an unsigned short
integer, and you shouldn’t try.
Just by declaring these variables to be unsigned short
integers, you know that it is possible to add Height
to Width
and to assign the result to another number.
The type of these variables tells you
• Their size in memory
• What information they can hold
• What actions can be performed on them
In traditional languages such as C, types were built in to the language. In C++, the programmer can extend the language by creating any type needed, and each of these new types can have all the functionality and power of the built-in types.
• Structs
and the functions that operate on them aren’t cohesive wholes; functions can only be found by reading the header files for the libraries available and looking for those with the new type as a parameter.
• Coordinating the activities of groups of related functions on the struct
is harder because anything in the struct
can be changed at any time by any piece of program logic. There is no way to protect struct
data from interference.
• The built-in operators don’t work on structs
—it does not work to add two structs
with a plus sign (+
), even when that might be the most natural way to represent the solution to a problem (for instance, when each struct
represents a complex piece of text to be joined together).
You make a new type in C++ by declaring a class. A class is just a collection of variables—often of different types—combined with a set of related functions.
One way to think about a car is as a collection of wheels, doors, seats, windows, and so forth. Another way is to think about what a car can do: It can move, speed up, slow down, stop, park, and so on. A class enables you to encapsulate, or bundle, these various parts and various functions into one collection, which is called an object.
Encapsulating everything you know about a car into one class has a number of advantages for a programmer. Everything is in one place, which makes it easy to refer to, copy, and call on functions that manipulate the data. Likewise, clients of your class—that is, the parts of the program that use your class—can use your object without worrying about what is in it or how it works.
A class can consist of any combination of the variable types and also other class types. The variables in the class are referred to as the member variables or data members. A Car
class might have member variables representing the seats, radio type, tires, and so forth.
Member variables, also known as data members, are the variables in your class. Member variables are part of your class, just as the wheels and engine are part of your car.
A class can also contain functions called member functions or methods. Member functions are as much a part of your class as the member variables. They determine what your class can do.
The member functions in the class typically manipulate the member variables. For example, methods of the Car
class might include Start()
and Brake()
. A Cat
class might have data members that represent age and weight; its methods might include Sleep()
, Meow()
, and ChaseMice()
.
Declaring a class tells the compiler about the class. To declare a class, use the class
keyword followed by the class name, an opening brace, and then a list of the data members and methods of that class. End the declaration with a closing brace and a semicolon. Here’s the declaration of a class called Cat
:
class Cat
{
unsigned int itsAge;
unsigned int itsWeight;
void Meow();
};
Declaring this class doesn’t allocate memory for a Cat
. It just tells the compiler what a Cat
is, what data members it contains (itsAge
and itsWeight
), and what it can do (Meow()
). Although memory isn’t allocated, it does let the compiler know how big a Cat
is—that is, how much room the compiler must set aside for each Cat
that you will create. In this example, if an integer is four bytes, a Cat
is eight bytes big: itsAge
is four bytes, and itsWeight
is another four bytes. Meow()
takes up only the room required for storing information on the location of Meow()
. This is a pointer to a function that can take four bytes on a 32-bit platform.
As a programmer, you must name all your member variables, member functions, and classes. As you learned on Day 3, “Working with Variables and Constants,” these should be easily understood and meaningful names. Cat
, Rectangle
, and Employee
are good class names. Meow()
, ChaseMice()
, and StopEngine()
are good function names because they tell you what the functions do. Many programmers name the member variables with the prefix “its,” as in itsAge
, itsWeight
, and itsSpeed
. This helps to distinguish member variables from nonmember variables.
Other programmers use different prefixes. Some prefer myAge
, myWeight
, and mySpeed
. Still others simply use the letter m
(for member), possibly with an underscore (_
) such as mAge
or m_age
, mWeight
or m_weight
, or mSpeed
or m_speed
.
Some programmers like to prefix every class name with a particular letter—for example, cCat
or cPerson
—whereas others put the name in all uppercase or all lowercase. The convention that this book uses is to name all classes with initial capitalization, as in Cat
and Person
.
Similarly, many programmers begin all functions with capital letters and all variables with lowercase. Words are usually separated with an underscore—as in Chase_Mice
—or by capitalizing each word—for example, ChaseMice
or DrawCircle
.
The important idea is that you should pick one style and stay with it through each program. Over time, your style will evolve to include not only naming conventions, but also indentation, alignment of braces, and commenting style.
Note
It’s common for development companies to have house standards for many style issues. This ensures that all developers can easily read one another’s code. Unfortunately, this extends to the companies that develop operating systems and libraries of reusable classes, which usually means that C++ programs must work with several different naming conventions at once.
Caution
As stated before, C++ is case sensitive, so all class, function, and variable names should follow the same pattern so that you never have to check how to spell them—was it Rectangle
, rectangle
, or RECTANGLE
?
After you declare a class, you can then use it as a new type to declare variables of that type. You declare an object of your new type the same as you declare an integer variable:
This code defines a variable called GrossWeight
, whose type is an unsigned
integer. It also defines Frisky
, which is an object whose class (or type) is Cat
.
You never pet the definition of a cat; you pet individual cats. You draw a distinction between the idea of a cat and the particular cat that right now is shedding all over your living room. In the same way, C++ differentiates between the class Cat
, which is the idea of a cat, and each individual Cat
object. Thus, Frisky
is an object of type Cat
in the same way that GrossWeight
is a variable of type unsigned int
.
An object is an individual instance of a class.
After you define an actual Cat
object—for example,
Cat Frisky;
you use the dot operator (.
) to access the members of that object. Therefore, to assign 50
to Frisky
’s Weight
member variable, you would write
Frisky.itsWeight = 50;
In the same way, to call the Meow()
function, you would write
Frisky.Meow();
When you use a class method, you call the method. In this example, you are calling Meow()
on Frisky
.
In C++, you don’t assign values to types; you assign values to variables. For example, you would never write
The compiler would flag this as an error because you can’t assign 5
to an integer. Rather, you must define an integer variable and assign 5
to that variable. For example,
This is a shorthand way of saying, “Assign 5
to the variable x
, which is of type int
.” In the same way, you wouldn’t write
The compiler would flag this as an error because you can’t assign 5
to the age part of a class called Cat
. Rather, you must define a specific Cat
object and assign 5
to that object. For example,
Try this experiment: Walk up to a three-year-old and show her a cat. Then say, “This is Frisky. Frisky knows a trick. Frisky, bark.” The child will giggle and say, “No, silly, cats can’t bark.”
If you wrote
the compiler would say, “No, silly, Cat
s can’t bark.” (Your compiler’s wording will probably look more like “[531] Error: Member function Bark not found in class Cat”.) The compiler knows that Frisky
can’t bark because the Cat
class doesn’t have a Bark()
method. The compiler wouldn’t even let Frisky
meow if you didn’t define a Meow()
function.
Additional keywords are often used in the declaration of a class. Two of the most important are public
and private
.
The private
and public
keywords are used with members of a class—both data members and member methods. Private members can be accessed only within methods of the class itself. Public members can be accessed through any object of the class. This distinction is both important and confusing. All members of a class are private, by default.
To make this a bit clearer, consider an example from earlier:
class Cat
{
unsigned int itsAge;
unsigned int itsWeight;
void Meow();
};
In this declaration, itsAge
, itsWeight
, and Meow()
are all private because all members of a class are private by default. Unless you specify otherwise, they are private. If you create a program and try to write the following within main
(for example):
the compiler flags this as an error. In effect, by leaving these members as private, you’ve said to the compiler, “I’ll access itsAge
, itsWeight
, and Meow()
only from within member functions of the Cat
class.” Yet, here, you’ve accessed the itsAge
member variable of the Boots
object from outside a Cat
method. Just because Boots
is an object of class Cat
, that doesn’t mean that you can access the parts of Boots
that are private (even though they are visible in the declaration).
This is a source of endless confusion to new C++ programmers. I can almost hear you yelling, “Hey! I just said Boots
is a Cat
. Why can’t Boots
access his own age?” The answer is that Boots
can, but you can’t. Boots
, in his own methods, can access all his parts—public and private. Even though you’ve created a Cat
, that doesn’t mean that you can see or change the parts of it that are private.
The way to use Cat
so that you can access the data members is to make some of the members public:
class Cat
{
public:
unsigned int itsAge;
unsigned int itsWeight;
void Meow();
};
In this declaration, itsAge
, itsWeight
, and Meow()
are all public. Boots.itsAge=5
from the previous example will compile without problems.
Note
The keyword public
applies to all members in the declaration until the keyword private
is encountered—and vice versa. This lets you easily declare sections of your class as public or private.
Listing 6.1 shows the declaration of a Cat
class with public member variables.
Listing 6.1. Accessing the Public Members of a Simple Class
Frisky is a cat who is 5 years old.
Line 6 contains the keyword class
. This tells the compiler that what follows is a declaration. The name of the new class comes after the keyword class
. In this case, the name is Cat
.
The body of the declaration begins with the opening brace on line 7 and ends with a closing brace and a semicolon on line 11. Line 8 contains the keyword public
followed by a colon, which indicates that everything that follows is public until the keyword private
or the end of the class declaration.
Lines 9 and 10 contain the declarations of the class members itsAge
and itsWeight
.
Line 13 begins the main()
function of the program. Frisky
is defined on line 15 as an instance of a Cat
—that is, as a Cat
object. On line 16, Frisky
’s age is set to 5
. On lines 17 and 18, the itsAge
member variable is used to print out a message about Frisky
. You should notice on lines 16 and 18 how the member of the Frisky
object is accessed. itsAge
is accessed by using the object name (Frisky
in this case) followed by period and then the member name (itsAge
in this case).
Note
Try commenting out line 8 and try to recompile. You will receive an error on line 16 because itsAge
will no longer have public access. Rather, itsAge
and the other members go to the default access, which is private access.
As a general rule of design, you should keep the data members of a class private. Of course, if you make all of the data members private, you might wonder how you access information about the class. For example, if itsAge
is private, how would you be able to set or get a Cat
object’s age?
To access private data in a class, you must create public functions known as accessor methods. Use these methods to set and get the private member variables. These accessor methods are the member functions that other parts of your program call to get and set your private member variables.
A public accessor method is a class member function used either to read (get) the value of a private class member variable or to set its value.
Why bother with this extra level of indirect access? Why add extra functions when it is simpler and easier to use the data directly? Why work through accessor functions?
The answer to these questions is that accessor functions enable you to separate the details of how the data is stored from how it is used. By using accessor functions, you can later change how the data is stored without having to rewrite any of the other functions in your programs that use the data.
If a function that needs to know a Cat
’s age accesses itsAge
directly, that function would need to be rewritten if you, as the author of the Cat
class, decided to change how that data is stored. By having the function call GetAge()
, your Cat
class can easily return the right value no matter how you arrive at the age. The calling function doesn’t need to know whether you are storing it as an unsigned
integer or a long
, or whether you are computing it as needed.
This technique makes your program easier to maintain. It gives your code a longer life because design changes don’t make your program obsolete.
In addition, accessor functions can include additional logic, for instance, if a Cat
’s age is unlikely to be more than 100, or its weight is unlikely to be 1000. These values should probably not be allowed. An accessor function can enforce these types of restrictions as well as do other tasks.
Listing 6.2 shows the Cat
class modified to include private member data and public accessor methods. Note that this is not a listing that can be run if it is compiled.
Listing 6.2. A Class with Accessor Methods
1: // Cat class declaration
2: // Data members are private, public accessor methods
3: // mediate setting and getting the values of the private data
4:
4: class Cat
5: {
6: public:
7: // public accessors
8: unsigned int GetAge();
9: void SetAge(unsigned int Age);
10:
11: unsigned int GetWeight();
12: void SetWeight(unsigned int Weight);
13:
14: // public member functions
15: void Meow();
16:
17: // private member data
18: private:
19: unsigned int itsAge;
20: unsigned int itsWeight;
21: };
This class has five public methods. Lines 8 and 9 contain the accessor methods for itsAge
. You can see that on line 8 there is a method for getting the age and on line 9 there is one for setting it. Lines 11 and 12 contain similar accessor methods for itsWeight
. These accessor functions set the member variables and return their values.
The public member function Meow()
is declared on line 15. Meow()
is not an accessor function. It doesn’t get or set a member variable; it performs another service for the class, printing the word “Meow.”
The member variables themselves are declared on lines 19 and 20.
To set Frisky
’s age, you would pass the value to the SetAge()
method, as in
Cat Frisky;
Frisky.SetAge(5); // set Frisky’s age using the public accessor
Later today, you’ll see the specific code for making the SetAge
and the other methods work.
Declaring methods or data private enables the compiler to find programming mistakes before they become bugs. Any programmer worth his consulting fees can find a way around privacy if he wants to. Stroustrup, the inventor of C++, said, “The C++ access control mechanisms provide protection against accident—not against fraud” (ARM, 1990).
class class_name
{
// access control keywords here
// class variables and methods declared here
};
class Cat
{
public:
unsigned int Age;
unsigned int Weight;
void Meow();
};
Cat Frisky;
Frisky.Age = 8;
Frisky.Weight = 18;
Frisky.Meow();
As you’ve seen, an accessor function provides a public interface to the private member data of the class. Each accessor function, along with any other class methods that you declare, must have an implementation. The implementation is called the function definition.
A member function definition begins similarly to the definition of a regular function. First, you state the return type that will come from the function, or void if nothing will be returned. This is followed by the name of the class, two colons, the name of the function, and then the function’s parameters. Listing 6.3 shows the complete declaration of a simple Cat
class and the implementation of its accessor function and one general class member function.
Listing 6.3. Implementing the Methods of a Simple Class
Meow.
Frisky is a cat who is 5 years old.
Meow.
Lines 5–13 contain the definition of the Cat
class. Line 7 contains the keyword public
, which tells the compiler that what follows is a set of public members. Line 8 has the declaration of the public accessor method GetAge()
. GetAge()
provides access to the private member variable itsAge
, which is declared on line 12. Line 9 has the public accessor function SetAge()
. SetAge()
takes an integer as an argument and sets itsAge
to the value of that argument.
Line 10 has the declaration of the class method Meow()
. Meow()
is not an accessor function. Here it is a general method that prints the word “Meow” to the screen.
Line 11 begins the private section, which includes only the declaration on line 12 of the private member variable itsAge
. The class declaration ends with a closing brace and semicolon on line 13.
Lines 17–20 contain the definition of the member function GetAge()
. This method takes no parameters, and it returns an integer. Note on line 17 that class methods include the class name followed by two colons and the function name. This syntax tells the compiler that the GetAge()
function you are defining here is the one that you declared in the Cat
class. With the exception of this header line, the GetAge()
function is created the same as any other function.
The GetAge()
function takes only one line; it returns the value in itsAge
. Note that the main()
function cannot access itsAge
because itsAge
is private to the Cat
class. The main()
function has access to the public method GetAge()
.
Because GetAge()
is a member function of the Cat
class, it has full access to the itsAge
variable. This access enables GetAge()
to return the value of itsAge
to main()
.
Line 25 contains the definition of the SetAge()
member function. You can see that this function takes one integer value, called age
, and doesn’t return any values, as indicated by void
. SetAge()
takes the value of the age
parameter and assigns it to itsAge
on line 29. Because SetAge()
is a member of the Cat
class, it has direct access to the private member variable itsAge
.
Line 36 begins the definition, or implementation, of the Meow()
method of the Cat
class. It is a one-line function that prints the word “Meow” to the screen, followed by a new line. Remember that the
character prints a new line to the screen. You can see that Meow
is set up just like the accessor functions in that it begins with the return type, the class name, the function name, and the parameters (none in this case).
Line 43 begins the body of the program with the familiar main()
function. On line 45, main()
declares an object called Frisky
of type Cat
. Read a different way, you could say that main()
declares a Cat
named Frisky
.
On line 46, the value 5
is assigned to the itsAge
member variable by way of the SetAge()
accessor method. Note that the method is called by using the object name (Frisky
) followed by the member operator (.
) and the method name (SetAge()
). In this same way, you can call any of the other methods in a class.
Note
The terms member function and method can be used interchangeably.
Line 47 calls the Meow()
member function, and line 48 prints a message using the GetAge()
accessor. Line 50 calls Meow()
again. Although these methods are a part of a class (Cat
) and are being used through an object (Frisky
), they operate just like the functions you have seen before.
Two ways exist to define an integer variable. You can define the variable and then assign a value to it later in the program. For example:
Or, you can define the integer and immediately initialize it. For example,
Initialization combines the definition of the variable with its initial assignment. Nothing stops you from changing that value later. Initialization ensures that your variable is never without a meaningful value.
How do you initialize the member data of a class? You can initialize the member data of a class using a special member function called a constructor. The constructor can take parameters as needed, but it cannot have a return value—not even void
. The constructor is a class method with the same name as the class itself.
Whenever you declare a constructor, you’ll also want to declare a destructor. Just as constructors create and initialize objects of your class, destructors clean up after your object and free any resources or memory that you might have allocated (either in the constructor, or throughout the lifespan of the object). A destructor always has the name of the class, preceded by a tilde (~
). Destructors take no arguments and have no return value. If you were to declare a destructor for the Cat
class, its declaration would look like the following:
~Cat();
Many types of constructors are available; some take arguments, others do not. The one that takes no arguments is called the default constructor. There is only one destructor. Like the default constructor, it takes no arguments.
It turns out that if you don’t create a constructor or a destructor, the compiler provides one for you. The constructor that is provided by the compiler is the default constructor.
The default constructor and destructor created by the compiler don’t have arguments. In addition, they don’t appear to do anything! If you want them to do something, you must create your own default constructor or destructor.
What good is a constructor that does nothing? In part, it is a matter of form. All objects must be “constructed” and “destructed,” and these do-nothing functions are called as a part of the process of constructing and destructing.
To declare an object without passing in parameters, such as
you must have a constructor in the form
Cat();
When you define an object of a class, the constructor is called. If the Cat
constructor took two parameters, you might define a Cat
object by writing
Cat Frisky (5,7);
In this example, the first parameter might be its age and the second might be its weight. If the constructor took one parameter, you would write
Cat Frisky (3);
In the event that the constructor takes no parameters at all (that is, that it is a default constructor), you leave off the parentheses and write
Cat Frisky;
This is an exception to the rule that states all functions require parentheses, even if they take no parameters. This is why you are able to write
Cat Frisky;
This is interpreted as a call to the default constructor. It provides no parameters, and it leaves off the parentheses.
Note that you don’t have to use the compiler-provided default constructor. You are always free to write your own default constructor—that is, a constructor with no parameters. You are free to give your default constructor a function body in which you might initialize the object. As a matter of form, it is always recommended that you define a constructor, and set the member variables to appropriate defaults, to ensure that the object will always behave correctly.
Also as a matter of form, if you declare a constructor, be certain to declare a destructor, even if your destructor does nothing. Although it is true that the default destructor would work correctly, it doesn’t hurt to declare your own. It makes your code clearer.
Listing 6.4 rewrites the Cat
class to use a nondefault constructor to initialize the Cat
object, setting its age to whatever initial age you provide, and it demonstrates where the destructor is called.
Listing 6.4. Using Constructors and Destructors
Meow.
Frisky is a cat who is 5 years old.
Meow.
Now Frisky is 7 years old.
Listing 6.4 is similar to Listing 6.3, except that line 9 adds a constructor that takes an integer. Line 10 declares the destructor, which takes no parameters. Destructors never take parameters, and neither constructors nor destructors return a value—not even void.
Lines 19–22 show the implementation of the constructor. It is similar to the implementation of the SetAge()
accessor function. As you can see, the class name precedes the constructor name. As mentioned before, this identifies the method, Cat()
in this case as a part of the Cat
class. This is a constructor, so there is no return value—not even void. This constructor does, however, take an initial value that is assigned to the data member, itsAge
, on line 21.
Lines 24–26 show the implementation of the destructor ~Cat()
. For now, this function does nothing, but you must include the definition of the function if you declare it in the class declaration. Like the constructor and other methods, this is also preceded by the class name. Like the constructor, but differing from other methods, no return time or parameters are included. This is standard for a destructor.
Line 57 contains the definition of a Cat
object, Frisky
. The value 5
is passed in to Frisky
’s constructor. No need exists to call SetAge()
because Frisky
was created with the value 5
in its member variable itsAge
, as shown on line 60. On line 62, Frisky
’s itsAge
variable is reassigned to 7
. Line 64 prints the new value.
const
Member FunctionsYou have used the const
keyword to declare variables that would not change. You can also use the const
keyword with member functions within a class. If you declare a class method const
, you are promising that the method won’t change the value of any of the members of the class.
To declare a class method constant, put the keyword const
after the parentheses enclosing any parameters, but before the semicolon ending the method declaration. For example,
void SomeFunction() const;
This declares a constant member method called SomeFunction()
that takes no arguments and returns void. You know this will not change any of the data members within the same class because it has been declared const
.
Accessor functions that only get values are often declared as constant functions by using the const
modifier. Earlier, you saw that the Cat
class has two accessor functions:
void SetAge(int anAge);
int GetAge();
SetAge()
cannot be const
because it changes the member variable itsAge
. GetAge()
, on the other hand, can and should be const
because it doesn’t change the class at all.
GetAge()
simply returns the current value of the member variable itsAge
. Therefore, the declaration of these functions should be written like this:
void SetAge(int anAge);
int GetAge() const;
If you declare a function to be const
, and the implementation of that function changes the object by changing the value of any of its members, the compiler flags it as an error. For example, if you wrote GetAge()
in such a way that it kept count of the number of times that the Cat
was asked its age, it would generate a compiler error. This is because you would be changing the Cat
object when the method was called.
It is good programming practice to declare as many methods to be const
as possible. Each time you do, you enable the compiler to catch your errors instead of letting your errors become bugs that will show up when your program is running.
Clients are the parts of the program that create and use objects of your class. You can think of the public interface to your class—the class declaration—as a contract with these clients. The contract tells how your class will behave.
In the Cat
class declaration, for example, you create a contract that every Cat
’s age can be initialized in its constructor, assigned to by its SetAge()
accessor function, and read by its GetAge()
accessor. You also promise that every Cat
will know how to Meow()
. Note that you say nothing in the public interface about the member variable itsAge
; that is an implementation detail that is not part of your contract. You will provide an age (GetAge()
) and you will set an age (SetAge()
), but the mechanism (itsAge
) is invisible.
If you make GetAge()
a const
function—as you should—the contract also promises that GetAge()
won’t change the Cat
on which it is called.
C++ is strongly typed, which means that the compiler enforces these contracts by giving you a compiler error when you violate them. Listing 6.5 demonstrates a program that doesn’t compile because of violations of these contracts.
Caution
Listing 6.5 does not compile!
Listing 6.5. A Demonstration of Violations of the Interface
As it is written, this program doesn’t compile. Therefore, there is no output.
This program was fun to write because so many errors are in it.
Line 10 declares GetAge()
to be a const
accessor function—as it should be. In the body of GetAge()
, however, on line 32, the member variable itsAge
is incremented. Because this method is declared to be const
, it must not change the value of itsAge
. Therefore, it is flagged as an error when the program is compiled.
On line 12, Meow()
is not declared const
. Although this is not an error, it is poor programming practice. A better design takes into account that this method doesn’t change the member variables of Cat
. Therefore, Meow()
should be const
.
Line 58 shows the creation of a Cat
object, Frisky
. Cat
now has a constructor, which takes an integer as a parameter. This means that you must pass in a parameter. Because no parameter exists on line 58, it is flagged as an error.
Note
If you provide any constructor, the compiler will not provide one at all. Thus, if you create a constructor that takes a parameter, you then have no default constructor unless you write your own.
Line 60 shows a call to a class method, Bark()
. Bark()
was never declared. Therefore, it is illegal.
Line 61 shows itsAge
being assigned the value 7
. Because itsAge
is a private data member, it is flagged as an error when the program is compiled.
Each function that you declare for your class must have a definition. The definition is also called the function implementation. Like other functions, the definition of a class method has a function header and a function body.
The definition must be in a file that the compiler can find. Most C++ compilers want that file to end with .c
or .cpp
. This book uses .cpp
, but check your compiler to see what it prefers.
Note
Many compilers assume that files ending with .c
are C programs, and that C++ program files end with .cpp
. You can use any extension, but .cpp
minimizes confusion.
You are free to put the declaration in this file as well, but that is not good programming practice. The convention that most programmers adopt is to put the declaration into what is called a header file, usually with the same name but ending in .h
, .hp
, or .hpp
. This book names the header files with .hpp
, but check your compiler to see what it prefers.
For example, you put the declaration of the Cat
class into a file named Cat.hpp
, and you put the definition of the class methods into a file called Cat.cpp
. You then attach the header file to the .cpp
file by putting the following code at the top of Cat.cpp
:
#include "Cat.hpp"
This tells the compiler to read Cat.hpp
into the file, the same as if you had typed in its contents at this point. Be aware that some compilers insist that the capitalization agree between your #include
statement and your file system.
Why bother separating the contents of your .hpp
file and your .cpp
file if you’re just going to read the .hpp
file back into the .cpp
file? Most of the time, clients of your class don’t care about the implementation specifics. Reading the header file tells them everything they need to know; they can ignore the implementation files. In addition, you might very well end up including the .hpp
file into more than one .cpp
file.
Note
The declaration of a class tells the compiler what the class is, what data it holds, and what functions it has. The declaration of the class is called its interface because it tells the user how to interact with the class. The interface is usually stored in an .hpp
file, which is referred to as a header file.
The function definition tells the compiler how the function works. The function definition is called the implementation of the class method, and it is kept in a .cpp
file. The implementation details of the class are of concern only to the author of the class. Clients of the class—that is, the parts of the program that use the class—don’t need to know, and don’t care, how the functions are implemented.
Just as you can ask the compiler to make a regular function inline, you can make class methods inline. The keyword inline
appears before the return type. The inline implementation of the GetWeight()
function, for example, looks like this:
You can also put the definition of a function into the declaration of the class, which automatically makes that function inline. For example,
Note the syntax of the GetWeight()
definition. The body of the inline
function begins immediately after the declaration of the class method; no semicolon is used after the parentheses. Like any function, the definition begins with an opening brace and ends with a closing brace. As usual, whitespace doesn’t matter; you could have written the declaration as
Listings 6.6 and 6.7 re-create the Cat
class, but they put the declaration in Cat.hpp
and the implementation of the functions in Cat.cpp
. Listing 6.7 also changes the accessor functions and the Meow()
function to inline.
Listing 6.6. Cat
Class Declaration in Cat.hpp
Listing 6.7. Cat
Implementation in Cat.cpp
Meow.
Frisky is a cat who is 5 years old.
Meow.
Now Frisky is 7 years old.
The code presented in Listing 6.6 and Listing 6.7 is similar to the code in Listing 6.4, except that three of the methods are written inline in the declaration file and the declaration has been separated into Cat.hpp
(Listing 6.6).
GetAge()
is declared on line 6 of Cat.hpp
, and its inline implementation is provided. Lines 7 and 8 provide more inline functions, but the functionality of these functions is unchanged from the previous “outline” implementations.
Line 4 of Cat.cpp
(Listing 6.7) shows #include "Cat.hpp"
, which brings in the listings from Cat.hpp
. By including Cat.hpp
, you have told the precompiler to read Cat.hpp
into the file as if it had been typed there, starting on line 5.
This technique enables you to put your declarations into a different file from your implementation, yet have that declaration available when the compiler needs it. This is a very common technique in C++ programming. Typically, class declarations are in an .hpp
file that is then #include
d into the associated .cpp
file.
Lines 18–29 repeat the main
function from Listing 6.4. This shows that making these functions inline doesn’t change their performance.
It is not uncommon to build up a complex class by declaring simpler classes and including them in the declaration of the more complicated class. For example, you might declare a wheel class, a motor class, a transmission class, and so forth, and then combine them into a car class. This declares a has-a relationship. A car has a motor, it has wheels, and it has a transmission.
Consider a second example. A rectangle is composed of lines. A line is defined by two points. A point is defined by an x-coordinate and a y-coordinate. Listing 6.8 shows a complete declaration of a Rectangle
class, as might appear in Rectangle.hpp
. Because a rectangle is defined as four lines connecting four points, and each point refers to a coordinate on a graph, you first declare a Point
class to hold the x- and y-coordinates of each point. Listing 6.9 provides the implementation for both classes.
Listing 6.8. Declaring a Complete Class
Listing 6.9. Rect.cpp
1: // Begin Rect.cpp
2: #include "Rectangle.hpp"
3: Rectangle::Rectangle(int top, int left, int bottom, int right)
4: {
5: itsTop = top;
6: itsLeft = left;
7: itsBottom = bottom;
8: itsRight = right;
9:
10: itsUpperLeft.SetX(left);
11: itsUpperLeft.SetY(top);
12:
13: itsUpperRight.SetX(right);
14: itsUpperRight.SetY(top);
15:
16: itsLowerLeft.SetX(left);
17: itsLowerLeft.SetY(bottom);
18:
19: itsLowerRight.SetX(right);
20: itsLowerRight.SetY(bottom);
21: }
22:
23:
24: // compute area of the rectangle by finding sides,
25: // establish width and height and then multiply
26: int Rectangle::GetArea() const
27: {
28: int Width = itsRight-itsLeft;
29: int Height = itsTop - itsBottom;
30: return (Width * Height);
31: }
32:
33: int main()
34: {
35: //initialize a local Rectangle variable
36: Rectangle MyRectangle (100, 20, 50, 80 );
37:
38: int Area = MyRectangle.GetArea();
39:
40: std::cout << "Area: " << Area << "
";
41: std::cout << "Upper Left X Coordinate: ";
42: std::cout << MyRectangle.GetUpperLeft().GetX();
43: return 0;
44: }
Area: 3000
Upper Left X Coordinate: 20
Lines 3–14 in Rectangle.hpp
(Listing 6.8) declare the class Point
, which is used to hold a specific x- and y-coordinate on a graph. As written, this program doesn’t use Points
much; however, other drawing methods require Points
.
Some compilers report an error if you declare a class named Rectangle
. This is usually because of the existence of an internal class named Rectangle
. If you have this problem, simply rename your class to myRectangle
.
Within the declaration of the class Point
, you declare two member variables (itsX
and itsY
) on lines 12 and 13. These variables hold the values of the coordinates. As the x-coordinate increases, you move to the right on the graph. As the y-coordinate increases, you move upward on the graph. Other graphs use different systems. Some windowing programs, for example, increase the y-coordinate as you move down in the window.
The Point
class uses inline accessor functions declared on lines 7–10 to get and set the x and y points. The Points
class uses the default constructor and destructor. Therefore, you must set their coordinates explicitly.
Line 17 begins the declaration of a Rectangle
class. A Rectangle
consists of four points that represent the corners of the Rectangle
.
The constructor for the Rectangle
(line 20) takes four integers, known as top
, left
, bottom
, and right
. The four parameters to the constructor are copied into four member variables (Listing 6.9), and then the four Points
are established.
In addition to the usual accessor functions, Rectangle
has a function GetArea()
declared on line 43. Instead of storing the area as a variable, the GetArea()
function computes the area on lines 28 and 29 of Listing 6.9. To do this, it computes the width and the height of the rectangle, and then it multiplies these two values.
Getting the x-coordinate of the upper-left corner of the rectangle requires that you access the UpperLeft
point and ask that point for its x value. Because GetUpperLeft()
is a method of Rectangle
, it can directly access the private data of Rectangle
, including itsUpperLeft
. Because itsUpperLeft
is a Point
and Point
’s itsX
value is private, GetUpperLeft()
cannot directly access this data. Rather, it must use the public accessor function GetX()
to obtain that value.
Line 33 of Listing 6.9 is the beginning of the body of the actual program. Until line 36, no memory has been allocated, and nothing has really happened. The only thing you’ve done is tell the compiler how to make a point and how to make a rectangle, in case one is ever needed.
On line 36, you define a Rectangle
by passing in values for top
, left
, bottom
, and right
.
On line 38, you make a local variable, Area
, of type int
. This variable holds the area of the Rectangle
that you’ve created. You initialize Area
with the value returned by Rectangle
’s GetArea()
function. A client of Rectangle
could create a Rectangle
object and get its area without ever looking at the implementation of GetArea()
.
Rectangle.hpp
is shown in Listing 6.8. Just by looking at the header file, which contains the declaration of the Rectangle
class, the programmer knows that GetArea()
returns an int
. How GetArea()
does its magic is not of concern to the user of class Rectangle
. In fact, the author of Rectangle
could change GetArea()
without affecting the programs that use the Rectangle
class as long as it still returned an integer.
Line 42 of Listing 6.9 might look a little strange, but if you think about what is happening, it should be clear. In this line of code, you are getting the x-coordinate from the upper-left point of your rectangle. In this line of code, you are calling the GetUpperLeft()
method of your rectangle, which returns to you a Point
. From this Point
, you want to get the x-coordinate. You saw that the accessor for an x-coordinate in the Point
class is GetX()
. Line 42 simply puts the GetUpperLeft()
and GetX()
accessors together:
MyRectangle.GetUpperLeft().GetX();
This gets the x-coordinate from the upper-left point coordinate that is accessed from the MyRectangle
object.
A very close cousin to the keyword class
is the keyword struct
, which is used to declare a structure. In C++, a struct
is the same as a class, except that its members are public by default. You can declare a structure exactly as you declare a class, and you can give it the same data members and functions. In fact, if you follow the good programming practice of always explicitly declaring the private and public sections of your class, no difference will exist whatsoever.
Try re-entering Listing 6.8 with these changes:
• On line 3, change class Point
to struct Point
.
• On line 17, change class Rectangle
to struct Rectangle
.
Now run the program again and compare the output. No change should have occurred.
You’re probably wondering why two keywords do the same thing. This is an accident of history. When C++ was developed, it was built as an extension of the C language. C has structures, although C structures don’t have class methods. Bjarne Stroustrup, the creator of C++, built upon structs
, but he changed the name to class
to represent the new, expanded functionality, and the change in the default visibility of members. This also allowed the continued use of a vast library of C functions in C++ programs.
Today, you learned how to create new data types using classes. You learned how to define variables of these new types, which are called objects.
A class can have data members, which are variables of various types, including other classes. A class can also include member functions—also known as methods. You use these member functions to manipulate the member data and to perform other services.
Class members, both data and functions, can be public or private. Public members are accessible to any part of your program. Private members are accessible only to the member functions of the class. Members of a class are private by default.
It is good programming practice to isolate the interface, or declaration, of the class in a header file. You usually do this in a file with an .hpp
extension and then use it in your code files (.cpp
) using an include
statement. The implementation of the class methods is written in a file with a .cpp
extension.
Class constructors can be used to initialize object data members. Class destructors are executed when an object is destroyed and are often used to free memory and other resources that might be allocated by methods of the class.
Q How big is a class object?
A A class object’s size in memory is determined by the sum of the sizes of its member variables. Class methods take up just a small amount of memory, which is used to store information on the location of the method (a pointer).
Some compilers align variables in memory in such a way that two-byte variables actually consume somewhat more than two bytes. Check your compiler manual to be certain, but at this point you do not need to be concerned with these details.
Q If I declare a class Cat
with a private member itsAge
and then define two Cat
objects, Frisky
and Boots
, can Boots
access Frisky
’s itsAge
member variable?
A Yes. Different instances of a class can access each other’s nonpublic data. In other words, if Frisky
and Boots
are both instances of Cat
, Frisky
’s member functions can access Frisky
’s data and Boots
’s data.
Q Why shouldn’t I make all the member data public?
A Making member data private enables the client of the class to use the data without being dependent on how it is stored or computed. For example, if the Cat
class has a method GetAge()
, clients of the Cat
class can ask for the Cat
’s age without knowing or caring if the Cat
stores its age in a member variable or computes its age on-the-fly. This means the programmer of the Cat
class can change the design of the Cat
class in the future without requiring all of the users of Cat
to change their programs as well.
Q If using a const
function to change the class causes a compiler error, why shouldn’t I just leave out the word const
and be certain to avoid errors?
A If your member function logically shouldn’t change the class, using the keyword const
is a good way to enlist the compiler in helping you find mistakes. For example, GetAge()
might have no reason to change the Cat
class, but your implementation has this line:
if (itsAge = 100) cout << "Hey! You’re 100 years old
";
Declaring GetAge()
to be const
causes this code to be flagged as an error. You meant to check whether itsAge
is equal to 100, but instead you inadvertently assigned 100 to itsAge
. Because this assignment changes the class—and you said this method would not change the class—the compiler is able to find the error.
This kind of mistake can be hard to find just by scanning the code. The eye often sees only what it expects to see. More importantly, the program might appear to run correctly, but itsAge
has now been set to a bogus number. This causes problems sooner or later.
Q Is there ever a reason to use a structure in a C++ program?
A Many C++ programmers reserve the struct
keyword for classes that have no functions. This is a throwback to the old C structures, which could not have functions. Frankly, it is confusing and poor programming practice. Today’s methodless structure might need methods tomorrow. Then, you’ll be forced either to change the type to class
or to break your rule and end up with a structure with methods. If you need to call a legacy C function that requires a particular struct, then you would have the only good reason to use one.
Q Some people working with object-oriented programming use the term “instantiation.” What is this?
A Instantiation is simply a fancy word for the process of creating an object from a class. A specific object defined as being of the type of a class is a single instance of a class.
The Workshop provides quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you’ve learned. Try to answer the quiz and exercise questions before checking the answers in Appendix D, and be certain you understand the answers before continuing to tomorrow’s lesson, where you will learn more about controlling the flow of your program.
1. What is the dot operator, and what is it used for?
2. Which sets aside memory—a declaration or a definition?
3. Is the declaration of a class its interface or its implementation?
4. What is the difference between public and private data members?
5. Can member functions be private?
6. Can member data be public?
7. If you declare two Cat
objects, can they have different values in their itsAge
member data?
8. Do class declarations end with a semicolon? Do class method definitions?
9. What would the header be for a Cat
function, Meow
, that takes no parameters and returns void?
10. What function is called to initialize a class?
1. Write the code that declares a class called Employee
with these data members: itsAge
, itsYearsOfService
, and itsSalary
.
2. Rewrite the Employee
class declaration to make the data members private, and provide public accessor methods to get and set each of the data members.
3. Write a program with the Employee
class that makes two employees; sets their itsAge
, itsYearsOfService
, and itsSalary
; and prints their values. You’ll need to add the code for the accessor methods as well.
4. Continuing from Exercise 3, write the code for a method of Employee
that reports how many thousands of dollars the employee earns, rounded to the nearest 1,000.
5. Change the Employee
class so that you can initialize itsAge
, itsYearsOfService
, and itsSalary
when you create the employee.
6. BUG BUSTERS: What is wrong with the following declaration?
class Square
{
public:
int Side;
}
7. BUG BUSTERS: Why isn’t the following class declaration very useful?
class Cat
{
int GetAge() const;
private:
int itsAge;
};
8. BUG BUSTERS: What three bugs in this code should the compiler find?
class TV
{
public:
void SetStation(int Station);
int GetStation() const;
private:
int itsStation;
};
int main()
{
TV myTV;
myTV.itsStation = 9;
TV.SetStation(10);
TV myOtherTv(2);
}
3.142.135.249