Professional Documents
Culture Documents
Chapter XVII
Chapter XVII
17.1 Introduction
The question is short and simple. The answer is hardly short, and it is far from
simple. Do you remember how in the previous chapter the early data structure
definition was altered by the end of the chapter. Well, in this case I am not even
going to attempt an early OOP definition. OOP is defined by a set of significant
OOP components. Each one of these components is brand-new, and needs to be
explained as well. There is no clean way to take prior knowledge - prior
vocabulary - and prior experience, and toss it together into a definition.
Does this make OOP difficult to learn? Not really. Objects are pretty neat and
provide the programmer with some powerful tools for sophisticated programs.
Like many new concepts, OOP needs to be taken one small step at a time and
digested slowly. In this chapter a series of programs will be presented, and then
in the last section of the chapter an OOP definition will be presented.
In the eighties, and very much in the nineties, software became more and more
sophisticated. Consumers demanded more, faster, prettier, and constantly better
software. The complexity of software was difficult to manage with the traditional
approach of designing a program. A new approach was necessary to make
programs more robust, more manageable and certainly easier to alter.
Personally, I was confused for some time about this OOP business. I kept looking
for the difference and I did not find it. Sure there were many new, and powerful
features, but I did not see the dramatic change and difference that I expected.
Consider the following car analogy. Now a car is basically transportation. One
day somebody tells you that they have revolutionized transportation. You are
invited over and you see a nice looking car. This new car runs on water. It does
not pollute. It does not require an expensive source of fuel. Suddenly you have a
car that solves both the pollution problem and the energy problem. This is
terrific. But is this a new style of transportation? It is still a car with wheels,
doors and a steering wheel. It just happens to be a much better implementation of
the old type of car.
I personally maintain that a person, programmer, student, anybody who has been
creating programs according to well-established modular programming
techniques will embrace OOP. At the same time, they will not be doing anything
that is so radically different. They will be doing their old jobs better. Their
programs will be more reliable. The programs will be easier to update and it will
be easier to reach new levels of program complexity. In other words, your
programs will not pollute anymore and guzzle a lot of fuel, but it still is very
much a program.
This introduction is becoming tiresome. Teachers and students with prior OOP
knowledge are saying yes, that makes sense. But, you, the student who is trying
to learn this stuff, feels not one lick closer in understanding anything about OOP.
Basically, the time has come for... Show me the objects!.
The record data structure is only one chapter old, and now we are taking a closer
look at how C++ uses struct. There is more to this not-so-humble data structure.
We will start by repeating a program from the end of the last chapter. This is a
program that takes record information and displays a mailing address and a phone
listing. The ShowData function that displayed everything has been removed. It
is neither necessary, nor very realistic. This chapter will show a series of
programs that all accomplish the same task. Each program does it differently, but
the input and output is the same story. It is not necessary to waste a bunch of
paper to show the same output time and again. Basically, the program asks a
bunch of information and then displays a mailing label and a phone listing. So
when future programs say it has the same output as PROG1701.CPP you will
know what the story is.
Carlton Gutschick
7009 Orleans Court
Kensington, Md. 20795
With this program as a start, we will make very gradual changes, and slowly
bridge the gap to the object world. Do not worry, the bridge is sturdy. Just make
sure that you walk in a straight line, and do not look down.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
struct Worker
{
apstring FirstName;
apstring LastName;
apstring Street;
apstring CityStateZip;
apstring Phone;
};
void main()
{
clrscr();
Worker Employee;
EnterData(Employee);
MailAddress(Employee);
PhoneListing(Employee);
getch();
}
// PROG1702.CPP
// This program places the three functions that access
the Worker
// data type inside the Worker struct declaration.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
struct Worker
{
apstring FirstName;
apstring LastName;
apstring Street;
apstring CityStateZip;
apstring Phone;
void EnterData()
{
cout << "Enter First Name ===>> ";
getline(cin,FirstName);
cout << "Enter Last Name ===>> ";
getline(cin,LastName);
cout << "Enter Street ===>> ";
getline(cin,Street);
cout << "Enter City State Zip ===>> ";
getline(cin,CityStateZip);
cout << "Enter Phone Number ===>> ";
getline(cin,Phone);
}
void MailAddress()
{
cout << endl << endl;
cout << FirstName << " " << LastName << endl;
cout << Street << endl;
cout << CityStateZip << endl;
}
void PhoneListing()
{
void main()
{
clrscr();
Worker Employee;
Employee.EnterData();
Employee.MailAddress();
Employee.PhoneListing();
getch();
}
PROG1702.CPP OUTPUT
Same as PROG1701.CPP
When you try to run the program you will find that it compiles, and it also gives
the exact same output as the earlier program with the functions outside the data
structure. Play along with me. This looks strange, but you have just started to
walk on the bridge. Do not turn around and do not look down. Just keep
marching steadily across the ravine.
I do have a confession to make. The functions were not moved inside the struct
declaration exactly as they appeared before. Several significant changes were
made to allow this different approach to work.
Let us start by looking at the main function. The old function call shown in the
last chapter, and the first program in this chapter, calls the function and provides a
parameter to pass the required data structure information.
EnterData(Employee); Employee.EnterData();
After we have studied the difference in the function calls, we must now check the
manner in which the functions process the data. You have been told that the
programs produce the same result with both methods, but there is a difference in
the statement syntax.
cout << Employee.Street << endl; cout << Street << endl;
cout << Employee.CityStateZip; cout << CityStateZip;
The old approach starts with the record identifier, Employee, followed by a
period and then uses the field identifier to specify what record component is being
processed. With the new approach there is a serious lack of any type of record
identifier in sight. Odd? Not at all, because you are inside the record. Why
bother stating where Street is located when you are standing on the street in
question. So, we now have a large, messy looking struct declaration with a pile of
functions shoved inside. This is progress? Did somebody mention readability as
a desirable quality in a program? You are right. This is not what we desire and it
is just one small step. What did you learn from this step?
This time let us clean up the mess and still hang on to the same principles.
Program PROG1703.CPP also creates a data structure with functions inside the
declaration. The difference is that now only the function prototypes are used.
This approach gives an instantly cleaner look to the data structure. The function
implementations are elsewhere, but the record still contains the functions as
fields, because of the prototypes. This program will be divided into two sections.
First we examine the Worker declaration, and then we look at the functions.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
struct Worker
{
apstring FirstName;
apstring LastName;
apstring Street;
apstring CityStateZip;
apstring Phone;
void EnterData();
void MailAddress();
void PhoneListing();
};
void main()
{
clrscr();
Worker Employee;
Employee.EnterData();
Employee.MailAddress();
Employee.PhoneListing();
getch();
}
The main function is identical to the previous main function. Each one of the
functions is called by using the record identifier, Worker. A period separates the
record identifier from the field identifier that follows and calls each one of the
appropriate functions.
In the struct syntax you see normal, conventional function prototypes. The
placement is unconventional because the prototypes are inside the data structure
declaration, but the syntax of the function prototypes is the same, as if they were
declared in the usual global location, above the main function.
void Worker::MailAddress()
{
cout << endl << endl;
cout << FirstName << " " << LastName << endl;
cout << Street << endl;
cout << CityStateZip << endl;
}
void Worker::PhoneListing()
{
cout << endl << endl;
cout << LastName << " " << FirstName
<< " " << Street
<< " ......." << Phone << endl;
}
void Worker::MailAddress()
This kind of function heading makes the following statement: This is function
MailAddress, which is a member of the Worker type. The new operator :: is
called a scope resolution operator. The name is pretty exotic but it is logical.
You remember the concept of scope explained a few chapters back.
Scope indicates where an identifier is available, just like the jurisdiction analogy
of various law enforcement agencies we discussed. If scope is not clear, go back
to Chapter XIV. This new operator resolves the scope of the function identifier
that follows.
General syntax:
void struct-identifier::function-identifier
Example syntax:
void Worker::MailAddress()
We are slowly creating special data types. Data types that are different from the
ones we have used before. You have been looking at the C++ data structure,
struct, which stores not only information in various fields, but also stores the
function information that accesses the data. A data type with such unique
capabilities has to be special and you are getting suspicious. Is this maybe an
object? Bingo, we have been sneaking some new data structure into your mind
and this new data structure is none other than your-soon-to-be-new-friend, the
object. It is now time for a first definition of this new and all-important computer
science feature.
At this early object stage you have not seen any new capability yet. It is nice
enough to place functions inside records, but nothing has been accomplished that
has not been done before. In the last chapter you could print mailing labels and
phone listing with our program. That program contained a record of information
and a variety of functions that processed the required data. In this chapter we are
There is bunches more to learn about objects, but right now you need to be clear
on the first definition before you move on. Do note that this first definition is an
object definition and not object oriented programming. There is quite a
difference. It is easier to explain driving, after you have defined what a car is.
The next program example accomplishes the same old task once again, but it does
it very poorly. The EnterData function has been removed and information is
entered with program input statements in the main function. This is considered
bad programming style. The data in an object should be accessed indirectly with
a function, and not directly from anywhere in the program.
// PROG1704.CPP
// This program has removed the EnterData function and enters
// all Employee information in the main function.
// This is an example of very poor program design.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
struct Worker
{
apstring FirstName;
apstring LastName;
apstring Street;
apstring CityStateZip;
apstring Phone;
void MailAddress();
void PhoneListing();
};
void main()
{
clrscr();
Worker Employee;
Employee.MailAddress();
Employee.PhoneListing();
void Worker::MailAddress()
{
cout << endl << endl;
cout << FirstName << " " << LastName << endl;
cout << Street << endl;
cout << CityStateZip << endl;
}
void Worker::PhoneListing()
{
cout << endl << endl;
cout << LastName << " " << FirstName
<< " " << Street
<< " ......." << Phone << endl;
}
You know that this type of C++ syntax works. You just learned it in the last
chapter. So what is wrong and why must we use functions to access data in an
object? The reasoning for this is connected with the reason why objects exist in
the first place. The biggest challenge of any program is to make the program
work correctly, for all situations. That is a tall order and many programs,
including sophisticated professional software, fall short when the real world uses
the software in many different applications.
One reasons for this software problem is that program users do so many
unexpected things, and frankly, programmers can be pretty quirky too and display
some serious cerebral lapses. The intent with objects is to create data structures
that look after themselves. If we create a neat package of data and functions, it
becomes easier to make sure that the data is processed properly. For example, a
loop that displays data information may cause problems if the data is not present.
It is quite possible that some programmer who accesses the data directly is not
aware of a situation where some database of information is empty. On the other
hand, a local function inside the object, can be written to carefully check different
situations. Such a function only displays data when it is proper to do so without
any computer problems.
You are pleased and I am pleased that the functions inside an object do such a
good job protecting the objects data from harm. But what prevents intentional or
unintentional bad programming that goes ahead and accesses data directly
C++ has a nice feature that adds discipline to our objects. It is possible to indicate
segments of the data structure as private and other segments as public. Any
identifier placed in the private segment is only accessible by the object itself.
Outside program statements cannot access any of the private identifiers. Program
PROG1705.CPP demonstrates the proper syntax for the reserved words private
and public in a struct declaration.
The order is not significant. You may select to start with the private segment, or
the public segment. People have various reasons why they select one approach or
the other. The C++ compiler does not care.
// PROG1705.CPP
// This program adds private and public segments to the Worker
// data structure. It is no longer possible to access data
// fields from the main function, which in compile errors.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
struct Worker
{
private:
apstring FirstName;
apstring LastName;
apstring Street;
apstring CityStateZip;
apstring Phone;
public:
void MailAddress();
void PhoneListing();
};
void main()
{
clrscr();
Worker Employee;
cout << "Enter First Name ===>> ";
getline(cin,Employee.FirstName);
cout << "Enter Last Name ===>> ";
getline(cin,Employee.LastName);
cout << "Enter Street ===>> ";
getline(cin,Employee.Street);
cout << "Enter City State Zip ===>> ";
getline(cin,Employee.CityStateZip);
cout << "Enter Phone Number ===>> ";
getline(cin,Employee.Phone);
Employee.MailAddress();
Employee.PhoneListing();
void Worker::MailAddress()
{
cout << endl << endl;
cout << FirstName << " " << LastName << endl;
cout << Street << endl;
cout << CityStateZip << endl;
}
void Worker::PhoneListing()
{
cout << endl << endl;
cout << LastName << " " << FirstName << " "
<< Street << " ......." << Phone << endl;
}
PROG1705.CPP OUTPUT
Compiling PROG1705.CPP:
Error PROG1705.CPP 31: ’Worker::FirstName’ is not
accessible
Error PROG1705.CPP 33: ’Worker::FirstName’ is not
accessible
Error PROG1705.CPP 35: ’Worker::FirstName’ is not
accessible
Ironically, we were doing fine in the beginning of this chapter when we used an
EnterData function. We will return to that approach and our program compiles
nicely, even with private and public added inside the Worker record. The next
program demonstrates the proper way to access private data. Program
PROG1706.CPP provides proper protection of the Worker data information.
// PROG1706.CPP
// This program demonstrate the correct way use the data in
// Worker by using only accessing functions.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H
void main()
{
clrscr();
Worker Employee;
Employee.EnterData();
Employee.MailAddress();
Employee.PhoneListing();
getch();
}
void Worker::EnterData()
{
cout << "Enter First Name ===>> ";
getline(cin,FirstName);
cout << "Enter Last Name ===>> ";
getline(cin,LastName);
cout << "Enter Street ===>> ";
getline(cin,Street);
cout << "Enter City State Zip ===>> ";
getline(cin,CityStateZip);
cout << "Enter Phone Number ===>> ";
getline(cin,Phone);
}
void Worker::MailAddress()
{
cout << endl << endl;
cout << FirstName << " " << LastName << endl;
cout << Street << endl;
cout << CityStateZip << endl;
}
void Worker::PhoneListing()
{
cout << endl << endl;
cout << LastName << " " << FirstName << " "
<< Street << " ......." << Phone << endl;
}
The whole program will not be shown here. Rest assured that the program
compiles and works. Besides, all you need to do is take the previous program and
substitute struct with class.
// PROG1707.CPP
// This program is almost identical to the previous
program.
// The only difference is that the Worker type is now
implemented
// with the reserved word class in place of struct.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Worker
{
private:
apstring FirstName;
apstring LastName;
apstring Street;
apstring CityStateZip;
apstring Phone;
public:
void EnterData();
void MailAddress();
void PhoneListing();
};
void main()
{
clrscr();
Logical, pondering students have a basic question here. Objects with struct were
doing just dandy. Data and functions were sitting nice and cozy inside the record
and the job was accomplished. Now you are told that we can do the same job
with class. Fine, but why? What does class have that struct lacks?
The answer to the why-class question will be partially answered by looking at the
next program example. Program PROG1708.CPP has great similarities to the
previous program. There is only one small change. The reserved words private
and public have been removed from the Worker data type declaration. Try this
program and watch what happens.
// PROG1708.CPP
// This program demonstrates that in a class
declaration
// all fields are defaulted to private.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
class Worker
{
apstring FirstName;
apstring LastName;
apstring Street;
apstring CityStateZip;
apstring Phone;
void EnterData();
void MailAddress();
void PhoneListing();
};
void main()
{
clrscr();
Worker Employee;
Employee.EnterData();
void Worker::EnterData()
{
cout << "Enter First Name ===>> ";
getline(cin,FirstName);
cout << "Enter Last Name ===>> ";
getline(cin,LastName);
cout << "Enter Street ===>> ";
getline(cin,Street);
cout << "Enter City State Zip ===>> ";
getline(cin,CityStateZip);
cout << "Enter Phone Number ===>> ";
getline(cin,Phone);
}
void Worker::MailAddress()
{
cout << endl << endl;
cout << FirstName << " " << LastName << endl;
cout << Street << endl;
cout << CityStateZip << endl;
}
void Worker::PhoneListing()
{
cout << endl << endl;
cout << LastName << " " << FirstName << " "
<< Street << " ......." << Phone << endl;
}
PROG1708.CPP OUTPUT
Compiling PROG1708.CPP:
Error PROG1708.CPP 29: ’Worker::EnterData()’ is not
accessible
Error PROG1708.CPP 30: ’Worker::MailAddress()’ is not
accessible
Error PROG1708.CPP 31: ’Worker::PhoneListing()’ is not
accessible
Several languages were developed with OOP capability. Bjarne Strousop created
a new language based on C with two major improvements. He wanted to
eliminate some of C’s quirkier features and also add OOP. His original choice for
the new language was C Plus Classes. Classes in this case implies OOP
capability. Hang on just a little bit and the world class will make more sense.
Right now it is sufficient to regard class the same as an object.
The widespread use and popularity of C made it impossible to ignore. A new
language based on C, yet an improvement on C, needed to be backwardly
compatible. This meant that the new language was capable of compiling and
running any of the older programs. Without this backward compatibility,
commercial success of the new language would be doubtful.
This explains why today all the C features are alive and well in C++. Many of the
quirkier features have not been taught, but they exist. This brings us to the
manner of implementing objects. The existing struct was a natural choice for
storing data and functions. Old C programs already used struct for data only.
This capability had to be preserved for backward compatibility reasons.
Why not simply make all the information in struct private? Why bother with two
separate reserved words that almost do the same thing? Backward compatibility
once again. If struct had been changed to default all its members to private, old
C-style programs would not work properly. Data would not be accessible because
C programs do not use private and public segment inside struct.
We need to stop and take some inventory of some confusing vocabulary that is
being tossed around. We have records that are implemented with struct and
class. We also seem to have objects that are implemented with struct and class.
Furthermore, we talk about objects and we talk about classes. This is a time-out
section. We need to catch our breath and look around. The end of the last section
stated that information stored in a record implemented with struct defaults to
public. The same record information implemented with class defaults to private.
At the C++ program code level that seems to make sense.
Now we need to move on and tackle the object and class business. The
distinction is surprisingly simple. A class is a data type and an object is a
variable. Look at the main function below from the latest program. In this main
function the identifier Worker is the Class and the identifier Employee is the
object.
An object is a variable.
This distinction should not be confusing. You have for many weeks, and for
many chapters been aware of the difference between a type and a variable. The
only reason why there tends to be a confusion at this stage is because the type is
created by the programmer and it is not already available in C++.
int Number;
causes few problems. int is the data type, and Number is the variable. Now we
can also create our own type with typedef and have the following kind of
program statements.
In this example Integer is now a data type and Number is the variable. When we
increase the complexity by creating our own data structure with either struct or
class, we still have a data type.
So we have this class and object difference pretty straight. Part of the confusion
is that objects and Object Oriented Programming, as well as classes are talked
about in a generic sense. Technically speaking Kleenex is one brand (or instance)
of tissue, but we often talk about needing a Kleenex, even when the tissue brand
might be totally different.
But are you straight on struct and class? You know the default difference, but do
you realize that both struct and class create a CLASS. Yes that is right. The C+
+ reserved word class can be used to declare a class, and the C++ reserved word
struct can also be used to declare a class. If you think back to the previous
sample programs, you observed the evidence that struct and class both created
records that contained data and functions.
Once again this dual-class-creation possibility is not for the purpose of confusing
new computer science students. It is all part of the backward compatibility
emphasis. Furthermore, you will find that we can suggest a convenient way to
use both data structures in our C++ programs.
struct Location
{
apstring Street;
apstring CityStateZip;
};
class Worker
{
private:
apstring FirstName;
apstring LastName;
Location Address;
apstring Phone;
public:
void EnterData();
void MailAddress();
void PhoneListing();
};
The complete program follows to make sure that you realize the necessary
changes that have to be made. The nested Location type alters the required
syntax in the way you learned about nested records in the last chapter. It is not
// PROG1709.CPP
// This program uses one record, implemented with
struct,
// used by another data structure, implemented with
class.
#include <iostream.h>
#include <conio.h>
#include "APSTRING.H"
struct Location
{
apstring Street;
apstring CityStateZip;
};
class Worker
{
private:
apstring FirstName;
apstring LastName;
Location Address;
apstring Phone;
public:
void EnterData();
void MailAddress();
void PhoneListing();
};
void main()
{
clrscr();
Worker Employee;
Employee.EnterData();
Employee.MailAddress();
Employee.PhoneListing();
void Worker::EnterData()
{
cout << "Enter First Name ===>> ";
getline(cin,FirstName);
cout << "Enter Last Name ===>> ";
getline(cin,LastName);
cout << "Enter Street ===>> ";
getline(cin,Address.Street);
cout << "Enter City State Zip ===>> ";
getline(cin,Address.CityStateZip);
cout << "Enter Phone Number ===>> ";
getline(cin,Phone);
}
void Worker::MailAddress()
{
cout << endl << endl;
cout << FirstName << " " << LastName << endl;
cout << Address.Street << endl;
cout << Address.CityStateZip << endl;
}
void Worker::PhoneListing()
{
cout << endl << endl;
cout << LastName << " " << FirstName << " "
<< Address.Street << " ......." << Phone << endl;
}
I know exactly what is needed right now. Simplicity, not complexity. This
section will not throw a series of complex programs at you. Rather, we will
examine some extremely short, simple programs and along the way learn some
new OOP concepts.
You will get the best value from this section if you sit behind a computer and type
in each program. There will be ten stages. Each stage is not a new start. Some
small addition will need to be made to the growing program. There is a benefit in
typing each one of these stages on your computer. The slowness of the typing is
precisely what the doctor (or your teacher) ordered. You see the program
unfolding in front of your eyes and concepts sink in more clearly and thoroughly
at that speed.
Program DECK01.CPP could not be simpler. The program displays nothing, but
it does compile and gets us started with the minimal program statements for a
program with a user-created class.
// DECK01.CPP
// Stage #1 is the minimal class program that compiles.
class CardDeck
{
};
void main()
{
}
In Stage #2, the reserved words private and public are added. Right now these
words are meaningless since there is not anything that can be private or public.
The intention here is to create good habits. One good habit is to start with a
skeleton that alerts you to using private and public segments. This stage also
creates a little output, which makes the program more practical to execute.
Another important step in this program to observe is that we now have an object.
Note that identifier D is the object, and CardDeck is the class. The program
statement . . . . CardDeck D; . . . . is the instantiation of the object D.
// DECK02.CPP
// Stage #2 is more practical. It is still a minimal
// skeleton program, but it does have some output.
// This program "instantiates" an object D of the
CardDeck class.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
public:
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #2" << endl;
CardDeck D;
getch();
}
// DECK03.CPP
// Stage #3 adds the private data members to the class.
// These are the attributes of the CardDeck class.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #3" << endl;
CardDeck D;
getch();
}
Do not be surprised if constructor does not ring a bell at all. This is a totally new
concept that was not shown with any of the previous programs. Students familiar
with OOP may even have wondered about the omission of such an important class
Objects were meant to make programs more reliable. One common program
problem in times past, was that programs would crash because various data
structures had not receive the proper information. In many cases such crashes
could have been prevented if the data structure information was properly
initialized. It is the job of the constructor to properly initialize the object during
its instantiation. You can also say during the construction of the object. The
constructor is a special public function of a class, and the constructor function is
automatically called in the program statement that defines the object. In other
words, at the instantiation of the object, the constructor function is called and the
object is initialized. We need to look at two locations to understand this
constructor business. There is the constructor in the class declaration, and there is
the implementation of the constructor.
// DECK04.CPP
// Stage #4 adds a simple constructor function.
// The purpose of the constructor is to initialize the
class.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck(); // constructor
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #4" << endl;
CardDeck D;
}
CardDeck::CardDeck() // constructor
{
cout << endl << endl;
It is easy to identify the constructor in the class. The function heading of the
constructor is the name of the class itself without void and also without a return
type. Traditionally, the constructor is the first member function placed in the
public segment. The constructor must be in the public segment of the class.
The implementation of the constructor member function is also a little unusual.
Both the class identifier is CardDeck and the field identifier is CardDeck. This
says that the function CardDeck is located in the CardDeck class. This is all a
little weird, but it has to do with reliability and initialization. Using the class
name for this special member function means that the function will be
automatically called during the instantiation of the new object. You see, it is not
good enough to create a function that initializes an object. You have to make sure
that the function is called. Giving the function the same name as the class itself is
the way that C++ guarantees that the constructor function will be called.
You will also note that the initialization in the constructor function makes certain
assumptions. It is assumed that every card game in the program requires at least
one deck, every game has at least one player, there will be 52 cards in a deck and
at least one card is dealt for each hand. Most card games may require different
values. That is fine and such games can create appropriate functions to alter the
necessary values. For the sake of initialization the selection above is satisfactory.
Constructor Definition
It is time to add a member function to the class that does something. All the data
in the CardDeck class is private and cannot be accessed directly. We need a
member function that can access the data and show us the contents. Class
member functions are also called actions or methods. Think of attributes as
nouns and actions as verbs. Right now the only action we are interested in is
showing the value of each one of the attributes (or data members) in the
CardDeck class. This is done in a manner that you have seen in previous
programs.
// DECK05.CPP
// Stage #5 adds a public member function to display
the
// private data.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
void ShowData();
};
CardDeck::CardDeck()
{
cout << endl << endl;
cout << "CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
getch();
}
void CardDeck::ShowData()
{
cout << endl << endl;
cout << "SHOW DATA FUNCTION" << endl;
cout << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
cout << "CardsLeft: " << CardsLeft << endl;
cout << "CardsDealt: " << CardsDealt << endl;
getch();
}
This stage adds yet another special class feature called the destructor. This is
another public member function that is called automatically. This time the
function is not called when the object is created, but rather when the object is no
longer used by the program. There is a time when the object is destroyed. At that
time the special destructor function is called.
In later chapters there will be programs that use computer memory in a special
way. Such programs require special memory management and cleaning up tasks
for which the destructor is ideally suited. At this stage, it is important to realize
that the destructor exists.
The syntax of the destructor is very similar to the constructor. The name of the
class is the name of the destructor with a tilde ( ~ ) in front of the identifier.
Typically the destructor is placed directly below the constructor in the public
segment of the class.
Run the program, with the new destructor function added and determine exactly
where the destructor does its job. The output message of the destructor function
should give you the desired information.
// DECK06.CPP
// Stage #6 adds a destructor function.
// The purpose of the destructor is to de-initialize
the
// creation of the CardDeck object.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
~CardDeck();
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #5" << endl;
CardDeck D;
D.ShowData();
}
CardDeck::CardDeck()
{
cout << endl << endl;
cout << "CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
getch();
}
CardDeck::~CardDeck()
{
cout << endl << endl;
cout << "DESTRUCTOR CALL" << endl;
getch();
}
void CardDeck::ShowData()
{
cout << endl << endl;
cout << "SHOW DATA FUNCTION" << endl;
cout << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
cout << "CardsLeft: " << CardsLeft << endl;
cout << "CardsDealt: " << CardsDealt << endl;
getch();
}
So what exactly does one do with a card deck? Decks are shuffled, they are dealt,
they are cut, and used in numerous different actions that are demanded by various
different card games.
Right now we will keep it simple. Our small little class will be given the
opportunity to Shuffle the deck, Deal the cards and determine how many cards
are Left. Functions ShuffleCards and DealHand are void functions. Function
CheckCardsLeft returns an integer. This stage only adds the prototypes in the
CardDeck class. The program is not shown in its entirety.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
~CardDeck();
void ShowData();
void ShuffleCards();
void DealHand();
int CheckCardsLeft();
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #7" << endl;
CardDeck D;
D.ShowData();
}
Stage #8 implements the newly selected class actions, method, member functions,
whatever. Nothing new is shown. We now start to have a fairly complete
program. Many students may be surprised by the implementation of the members
functions. A quick peek at the ShuffleCards function shows little evidence of a
function that shuffles much of anything, let alone itself.
// DECK08.CPP
// Stage #8 implements the new member functions,
// ShuffleCards, DealHand and CheckCardsLeft.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
~CardDeck();
void ShowData();
void ShuffleCards();
void DealHand();
int CheckCardsLeft();
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #7" << endl;
CardDeck D;
D.ShowData();
D.ShuffleCards();
D.DealHand();
cout << "There are " << D.CheckCardsLeft()
<< " cards left in the deck " << endl;
}
CardDeck::~CardDeck()
{
cout << endl << endl;
cout << "DESTRUCTOR CALL" << endl;
getch();
}
void CardDeck::ShowData()
{
cout << endl << endl;
cout << "SHOW DATA FUNCTION" << endl;
cout << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
cout << "CardsLeft: " << CardsLeft << endl;
cout << "CardsDealt: " << CardsDealt << endl;
getch();
}
void CardDeck::ShuffleCards()
{
cout << endl << endl;
cout << "SHUFFLE CARDS FUNCTION" << endl;
getch();
}
void CardDeck::DealHand()
{
cout << endl << endl;
cout << "DEAL HAND FUNCTION" << endl;
int CardDeck::CheckCardsLeft()
{
cout << endl << endl;
cout << "CHECK CARDS LEFT FUNCTION" << endl;
getch();
return CardsLeft;
}
This stage is designed to show that constructors can have more versatility than
initialize class member data. It will demonstrate that a member function is
perfectly capable of calling another member function in the class. In the case of a
constructor this can have practical benefits. The improved constructor in this
program is not real clever, but it shows an important feature. Our CardDeck
class includes a ShuffleCards function. Shuffling is a practical feature for any
card game, but should the cards not be shuffled before any game even starts. This
is precisely what should be done by the constructor. The job of the constructor is
to set the proper stage. This includes not only initializing variables with some
values, but also calling appropriate functions, like ShuffleCards. Stage #9 will
just show a partial program to focus on the small change in the constructor.
// DECK09.CPP
// Stage #9 uses an improved constructor.
// In this program the constructor not only initializes
the data
// members, but also calls the ShuffleCards function to
insure
// that cards will be shuffled for any new object.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #9" << endl;
CardDeck D;
D.ShowData();
D.ShuffleCards();
D.DealHand();
cout << "There are " << D.CheckCardsLeft()
<< " cards left in the deck " << endl;
}
CardDeck::CardDeck()
{
cout << endl << endl;
cout << "CONSTRUCTOR CALL" << endl;
NumDecks = 1;
NumPlayers = 1;
CardsLeft = NumDecks * 52;
CardsDealt = 1;
ShuffleCards();
getch();
}
CardDeck D;
creates a new object D, and D has no parameters at all. Translation ..... call the
default constructor. C++ knows which constructor is the default constructor
because the constructor name is used in the function heading without any
parameters. This paragraph gives you a strong feeling that other constructors are
lurking around the corner. You are totally correct. After all, would it not be nice
to have a little say-so about the manner in which your objects are initialized.
Two, three chapters ago you learned about functions. One of the last function
features you learned was about overloading functions. You learned that the
same function name could be used with a variety of different function
implementations. C++ would sort out the details by matching up the appropriate
parameters and then decide which function to call.
Adding a second constructor means that we will overload the constructor. This is
no problem, as long as we give C++ a clear indication of our intentions. The
second constructor needs some parameters to help distinguish it from the first
constructor. Why do we need a second constructor? How about indicating how
many people are playing, and how card decks should be shuffled. This is
practical stuff and it would be nice if we could control that.
Program DECK10.CPP instantiates (does this term make sense now?) two
objects. The first object, D1, calls the default constructor. Notice there are no
parameters anywhere near D1. Same old constructor, nothing fancy. The second
object, D2, includes two parameters, D2(4,5). The 4 is passed to the number of
decks variable, and the 5 is passed to the number of players variable.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
int CardsDealt;
public:
CardDeck();
CardDeck(int,int);
~CardDeck();
void ShowData();
void ShuffleCards();
void DealHand();
int CheckCardsLeft();
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #10" << endl;
CardDeck D1;
D1.ShowData();
D1.ShuffleCards();
D1.DealHand();
cout << "There are " << D1.CheckCardsLeft()
<< " cards left in the deck " << endl;
CardDeck D2(4,5);
D2.ShowData();
D2.ShuffleCards();
D2.DealHand();
cout << "There are " << D2.CheckCardsLeft()
<< " cards left in the deck " << endl;
}
CardDeck::CardDeck()
{
CardDeck::~CardDeck()
{
cout << endl << endl;
cout << "DESTRUCTOR CALL" << endl;
getch();
}
void CardDeck::ShowData()
{
cout << endl << endl;
cout << "SHOW DATA FUNCTION" << endl;
cout << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
cout << "CardsLeft: " << CardsLeft << endl;
cout << "CardsDealt: " << CardsDealt << endl;
getch();
}
void CardDeck::DealHand()
{
cout << endl << endl;
cout << "DEAL HAND FUNCTION" << endl;
getch();
}
int CardDeck::CheckCardsLeft()
{
cout << endl << endl;
cout << "CHECK CARDS LEFT FUNCTION" << endl;
getch();
return CardsLeft;
}
Encapsulation Notes:
It is true that private data of a class should not be accessed by any function or
program statement that is not a member of the same class. That means that
modifying any private data is done my member functions of the same class. We
call such function modifier functions. Now we are really concerned that once
again data is only altered properly.
Please do not ask why function ShowData includes assignment statements that
alter NumDecks and NumCards. This is a simple example to demonstrate that,
even though the data is in the private segment, I can rather easily modify the
values with any member function.
// DECK11.CPP
// This program shows how a function, which should be
strictly an
// "accessor" function, can modify private data. This
type of
// modification should to be avoided.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
public:
CardDeck(int,int);
void ShowData();
};
void CardDeck::ShowData()
{
cout << endl << endl;
cout << "SHOW DATA FUNCTION" << endl;
cout << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
NumDecks = 1; // NONO
NumPlayers = 2; // STATEMENTS
cout << "Attribute values after new assignment
values" << endl;
cout << "NumDecks: " << NumDecks << endl;
cout << "NumPlayers: " << NumPlayers << endl;
getch();
}
DECK11.CPP OUTPUT
NumDecks: 4
There is a neat safety-valve that can be used to make sure that a function,
designated to be only an accessing function, will not modify any data. The
function can be declared as a const function. Placing the reserved word const at
the right side the of the member function heading will insure that data is only
accessed, and not modified in any way.
Program DECK12.CPP repeats the previous program with the small addition of
the word const added to the ShowData function. Run this program the first time
with the “offending assignment statement” commented out. The program will run
fine, as long as no attempt is made to alter any data.
// DECK12.CPP
// This program demonstrates how to deliberately
prevent an
// accessing function from modifying private data. The
accessing
// function needs to be declared as a "const" function.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
public:
CardDeck(int,int);
void ShowData() const;
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #12" << endl;
NumDecks: 4
NumPlayers: 5
Attribute values after new assignment values
NumDecks: 1
NumPlayers: 2
It is a different story when the comments are removed. This time the program
will not even compile and an error, like the one shown below, is generated.
Program does not compile and provides the following error message:
Compiling DECK12.CPP
Error DECK12.CPP 48: Cannot modify a const object
Error DECK12.CPP 49: Cannot modify a const object
The example of the previous program showed how a void function, like
ShowData, can be “locked in” to prevent unwanted data modification. The same
exact logic applies to return functions that are written to be strictly accessor
functions. The program is altered slightly and two return functions are now used
to display the data.
// DECK13.CPP
// This program demonstrates how to use return accessor
functions
// as const functions that cannot modify attributes.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
public:
int NumDecks;
int NumPlayers;
public:
CardDeck(int ND,int NP) { NumDecks = ND,
NumPlayers = NP; }
int GetDecks() const { return NumDecks; }
int GetPlayers() const { return NumPlayers; }
};
void main()
{
clrscr();
DECK13.CPP OUTPUT
Example:
You have learned in an earlier function chapter that functions can be overloaded.
It is possible to use the same function name and yet call different function
implementations. C++ manages to distinguish between the different functions by
looking at the function signatures. Different parameter types, and or different
quantities of parameters identify the desired function. This overloaded function
business was shown earlier in this chapter with the use of multiple constructors.
You will be pleased to know that C++ can overload operators besides functions.
The topic of overloaded operators is quite involved and in this chapter you will be
exposed to a brief introduction to give you a flavor of the concept and to make it
easier to study the concept in greater detail at some future date. Remember this is
an introductory, survey, chapter of object oriented programming.
Overloaded operators are not such an odd concept. You have used the plus
operator for quite some time in various contexts. Integer addition means that the
// DECK14.CPP
// This program adds the Increment members function,
which
// increases the number of decks by a specified number.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
public:
int NumDecks;
int NumPlayers;
public:
CardDeck(int ND,int NP) { NumDecks = ND,
NumPlayers = NP; }
int GetDecks() const { return NumDecks; }
int GetPlayers() const { return NumPlayers; }
void Increment(int N) { NumDecks += N; }
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #14" << endl;
CardDeck D(3,6);
cout << endl;
cout << "There are " << D.GetDecks() << " card
decks." << endl;
cout << "There are " << D.GetPlayers() << " card
players."
<< endl;
D.Increment(2);
cout << "After calling Increment function" << endl;
cout << "There are " << D.GetDecks() << " card
DECK14.CPP OUTPUT
There are no big surprises in program DECK14.CPP, but the stage is now set for
adding an overloaded operator. Our motivation is do exactly what was done in
the previous program with function Increment. The big difference is that we do
not want the usual dot.function notation like D.Increment(2). Our desire is
to use a statement, like D+=2 and this will increment in the precise same manner.
C++ allows this type of programming with the reserved word operator followed
by the operator that needs to be overloaded. For this chapter you will only be
learning how to overload an arithmetic assignment operator, like +=. The magic
of this special reserved word is that the operator following the word operator
performs the action that is designated in the body of the function. For instance,
the operator+= function below is shown implemented two ways. In both cases
the value of NumDecks is incremented by the value of N.
void CardDeck::operator+=(int N)
{
NumDecks += N;
}
Pay very close attention to the main function of program DECK15.CPP. The
main function calls the overloaded operator in two different ways. First, you will
see the familiar dot.function notation with the statement D.operator+=(1);
// DECK15.CPP
// This program uses the overloaded operator+= function
in
// place of the Increment function.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
public:
int NumDecks;
int NumPlayers;
public:
CardDeck(int ND,int NP) { NumDecks = ND,
NumPlayers = NP; }
int GetDecks() const { return NumDecks; }
int GetPlayers() const { return NumPlayers; }
void operator+=(int N) { NumDecks += N; }
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #15" << endl;
CardDeck D(3,6);
cout << endl;
cout << "There are " << D.GetDecks() << " card
decks." << endl;
cout << "There are " << D.GetPlayers() << " card
players."
<< endl;
cout << endl;
D.operator+=(1);
cout << "After using D.operator+=(1);" << endl;
cout << "There are " << D.GetDecks() << " card
decks." << endl;
D += 2;
DECK15.CPP OUTPUT
Again, keep in mind that there are many other operators that can be overloaded
and there is much to be learned in this overloaded operator area. Study this
program example, absorb the logic and the syntax, and you will not be so
surprised when overloaded operators returns with serious reinforcements.
void CardDeck::operator+=(int N)
{
NumDecks += N;
}
D.operator+=(5); or D+=5;
This chapter is just going to be full of goodies that will have greater meaning at a
later stage. Even the last section of the object oriented introduction will hold true
to that philosophy. In this section you will learn that there is another way to pass
information to private data. Start by looking at program DECK16.CPP and
observe anything that looks different from materials that has already been shown.
// DECK16.CPP
// This program demonstrates how to pass use the
"intializer list"
// in the constructor to assign values to private data.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
public:
CardDeck(int ND,int NP);
int GetDecks() const { return NumDecks; }
int GetPlayers() const { return NumPlayers; }
};
void main()
{
clrscr();
cout << "CARD DECK PROGRAM STAGE #16" << endl;
CardDeck D(3,6);
cout << endl;
cout << "There are " << D.GetDecks() << " card
decks." << endl;
cout << "There are " << D.GetPlayers() << " card
players."
<< endl;
getch();
}
DECK16.CPP OUTPUT
Careful observes will notice that the constructor shows some odd looking syntax.
The execution of the program is fine, but the constructor is just a little bit on the
weird side. In this program the purpose of the constructor is to initialize values
for NumDecks and NumPlayers. A constructor, like the one shown below, will
perform that job quite nicely.
I am sure that you are not pleased that a nice, simple constructor is now altered to
some strange looking function. It is a style of constructor that uses the so called
initializer list. By using a colon after the function heading, and following this
colon with some special program statement, it is possible to pass information to
the data members of an object with the conventional assignment approach. This
initializer list approach is shown below.
I can just hear all the why, why, why questions popping up. Excellent question,
and at this level there is no justification for using the initializer list. There is some
value because the initializer list processes faster than the assignment statements,
but that is not much motivation to come up with another approach. Have a little
patience because the very next chapter will show some program examples that
involves constructors that must use the initializer list approach.