Professional Documents
Culture Documents
26.1 Introduction
Each one of the OOP chapters will be relatively short, and focus on one particular
aspect of OOP. This approach is a little artificial. Each chapter may almost
appear that other features of OOP do not exist. This approach is intentional and
by now you must realize that I prefer teaching with a focused approach on one
particular concept at one time.
This chapter will focus on encapsulation. The next chapter will look at all the
details of constructors and destructors. When all these individual concept
chapters are done, we put everything together and observe a more global picture.
Now, it is not possible to look at individual concepts strictly in a vacuum. Each
program example still needs all the minimal program items necessary to make the
program compile and execute. The point is that the focus of how to do some
features a variety of ways is concentrated in one chapter. So this chapter will put
member data and member functions under a microscope. Did you forget what a
member function is? No problem . . . there are lots of examples in this chapter to
remind you. Furthermore, because this is the first OOP chapter, a general
introduction to object oriented programming will be provided as well.
This chapter also covers a brief topic of include files and preprocessors. You will
find that the topic fits nicely with member functions, as it explains how to
organize different components of your program and where to place them.
Encapsulation
Polymorphism
Inheritance
Encapsulation
Encapsulation is what you learned and practiced back in Chapter XVII. In that
chapter we took a data structure that was minding its own business storing data,
and placed the functions, which manipulated the data, inside the data structure
declaration. OOP uses neat packages of data (often called attributes), combined
with the functions that act upon the data (often called actions or methods). This
package of attributes and actions is the main character of this story, the object.
Another word for package can be container or capsule. Placing everything inside
a capsule is encapsulation.
Encapsulation Definition
Polymorphism
Polymorphism Definition
Inheritance
Inheritance will be quite a different story. The OOP features, encapsulation and
polymorphism could be explained by various examples that you had seen in prior
chapters. Inheritance is a totally new concept, that will be briefly mentioned and
then allowed to rest until a complete chapter on the subject will describe the
details of inheritance. Go back to your Geometry days. For many students this is
not a long distance trip to the past. One important feature of Geometry is its use
of prior material. After a quadrilateral has been defined, its definition can be used
to simplify the definition of a parallelogram. All the features of a parallelogram
Inheritance Definition
You have used the “include file concept” for some time with a library of utility
functions. Right now you need to review, and understand this include business in
a more thorough manner and truly understand what is involved. For starters look
at program PROG2601.CPP, which will be used for a variety of demonstrations.
This program calls three functions, each of which basically announces its
presence. There is also a small Skip function that is used by the other functions.
// PROG2601.CPP
// This program demonstrates a few simple functions
using
// prototypes before the main function and the function
// implementations below the main function body.
#include <iostream.h>
#include <conio.h>
void main()
{
clrscr();
Function1();
Function2();
Function3();
getch();
}
void Function1()
{
Skip(2);
cout << "THIS IS FUNCTION 1" << endl;
}
void Function2()
{
Skip(2);
cout << "THIS IS FUNCTION 2" << endl;
}
void Function3()
{
Skip(2);
cout << "THIS IS FUNCTION 3" << endl;
}
PROG2601.CPP OUTPUT
THIS IS FUNCTION 1
THIS IS FUNCTION 2
THIS IS FUNCTION 3
There were no big surprises in this first program example. Everything was done
in a straightforward manner. The second program example will now take the first
// PROG2602.CPP
// This program demonstrates using two include files.
// One is a header file of prototypes and the other is
the
// implementation file of function definitions.
#include "INCLUDE2.H"
void main()
{
clrscr();
Function1();
Function2();
Function3();
getch();
}
#include "INCLUDE2.CPP"
// INCLUDE2.H
// Include file for PROG2602.CPP
// This file contains the function prototypes.
#include <iostream.h>
#include <conio.h>
#include Preprocessor
// INCLUDE2.CPP
// Include file for PROG2602.CPP
// This file contains the function declarations of the
// prototypes in the INCLUDE2.H header file.
void Skip(int N)
{
int K;
for (K=1; K<=N; K++)
cout << endl;
}
void Function1()
{
Skip(2);
cout << "THIS IS FUNCTION 1" << endl;
}
void Function2()
{
Skip(2);
cout << "THIS IS FUNCTION 2" << endl;
}
void Function3()
{
Skip(2);
cout << "THIS IS FUNCTION 3" << endl;
}
THIS IS FUNCTION 1
THIS IS FUNCTION 2
THIS IS FUNCTION 3
The next program example shows a slight variation of the previous program,
which used two #include preprocessors in the main driving program. Program
PROG2603.CPP shows only one #include statement in the main driving
program. Only the header file, INCLUDE3.H, is included in the main program.
The program is identical in result to the previous program. The same result is
achieved by having an #include statement in the header file that compiles the
function implementation at the correct location.
// PROG2603.CPP
// This program is almost identical to the previous
program.
// In this version only the INCLUDE3.H (header) file is
included.
// The INCLUDE3.CPP file of function definitions is
included
// by the header file.
#include "INCLUDE3.H"
void main()
{
clrscr();
Function1();
Function2();
Function3();
getch();
}
#include <iostream.h>
#include <conio.h>
#include "INCLUDE3.CPP"
Notice how the header file includes INCLUDE3.CPP at the end of the file, below
the function prototypes. This will result that the implementation file will be
compiled at that location. Is this wrong? After all, you have been accustomed to
a system of prototypes, followed by the main function, which is then followed by
the function implementations.
If you understand this include business correctly, it will result in the main
function coming dead last now in the sequence. This sequence is still perfectly
correct. As long as the function prototypes come before the function
implementations and the main function everything is in order.
// INCLUDE3.CPP
// Include file for PROG2603.CPP
// This file contains the function declarations of the
// prototypes in the INCLUDE3.H header file.
void Skip(int N)
{
int K;
for (K=1; K<=N; K++)
cout << endl;
}
void Function2()
{
Skip(2);
cout << "THIS IS FUNCTION 2" << endl;
}
void Function3()
{
Skip(2);
cout << "THIS IS FUNCTION 3" << endl;
}
PROG2603.CPP OUTPUT
THIS IS FUNCTION 1
THIS IS FUNCTION 2
THIS IS FUNCTION 3
Perhaps you are quite confused about just exactly what program statements go
where, and why. You have now gone quite some time in a well established ritual
sequence of prototypes, main function, function implementations. Apparently
now you are told that this is not really so significant anymore and the sequence
can be altered. It is very important that you clearly understand what is flexible in
a program sequence and what is not. In other words, some sequencing is done
because it is required by C++ syntax, and other sequencing is done because of
convention. Which is which?
The rules in the summary box above seem to disagree with the earlier program
that placed the function implementations above the main function. Now if you
really paid close attention you will note that the program did follow the sequence
rules. C++ does not require that the main function comes before the function
implementations. That is just convention for ease of viewing the program. Now
if source code is placed in an include file, the viewing of the main program source
code is not altered. In other words, with all this include business, the first
program statements to be seen are those in the main function. All other source
code is placed in one or more include files.
This page is very significant because writing a program requires that syntactical
program language rules are obeyed. At the same time, program writing should
follow conventions that are established for ease of program development. Include
files can be tricky in this respect because major sections of program code are no
longer in direct view. You must have a clear picture, in your head or on paper, of
the complete source code sequence. This becomes especially important when the
main program includes a file, which in turn includes another file.
It is possible that you never quite understood why all this include file stuff was
done. You did start using include files quite some time ago, but it is common for
students to develop certain program routines without appreciating the purpose for
such routines. A few reminders will follow here to help explain why this business
is used in the first place.
It may be simpler to think of a program that does not use include files. Imagine a
sizable program of 5000 lines of source code. This is small in the software
application world, but quite large for the average high school or college student.
At 25 lines per screen, a program of this size will use up 200 monitor screens.
OK, suppose you are not concerned about huge amounts of tedious code that must
be scrolled past to get to the desired location. Are there still other reasons? You
bet, and one of them is ease of debugging. Your program may contain routines
that have been solidly tested in the past, and there may be sections in the program
that are finished and likewise have undergone thorough testing. It is very
convenient to take this code and remove it from view. The code is finished and
only occupies space. Perhaps a desk analogy helps here. A student, a teacher, an
engineer, a businessman or any other person uses a desk to do work. The desk is
cluttered with papers that need to be finished, edited, graded, whatever. Now
does it not make sense that groups of papers that are finished are grouped
together, placed in a file and placed somewhere away from the desk? Leave the
desk as empty as possible with only those papers left that still require attention.
This will be an odd section. A concept will be mentioned here, but it will not be
explained. I am talking about compiled libraries. Understanding this business
requires that we back-up and look what happens when we eagerly try to compile
our programs. You have probably noticed that a variety of files are created that
you never remembered saving. Files with *.OBJ endings pop up all over the
place. There is a tendency to speak of compiling a program, but there is more
than compiling that is happening. When you press <F9> or any other appropriate
key combination or mouse clicks to get your program ready, a sequence of
processes follows. This sequence more than translates your C++ source code into
binary machine code that can be executed.
Is there an advantage to all this compiled library stuff? Yes, and the advantage is
simply speed. Code that has been thoroughly tested can go one step further than
to be placed in an include file, it can be compiled into an binary code object file.
This step will avoid the need to recompile the code a second, or multiple more,
time(s) during program development. For small programs this is all of no
significant consequence. Very large programs pay a considerable penalty in
speed if unnecessary compiling is repeated over and over again. Just imagine a
one million line program in its final stage. A team of programmers is finishing
the final 10,000 lines of code and 990,000 lines of code are recompiled each time
that the final stage is tested.
The compiled libraries provided by your C++ software also serve a portability
purpose. The C++ language is meant to be portable with many different
computing environments. One major problem is input/output (I/O) routines. A
program can be portable across various platforms and each platform carries its
own library of input/output libraries to be linked with the program.
Now this is all lovely and here comes the bad news. You will not be shown how
to create a compiled library. Any attempt to do that in this chapter will probably
be pretty sad. The whole process of creating a compiled library is not only
different with different C++ software companies, it is also different between
different versions of the same compiler. With some C++ versions the compiled
library process is very easily accomplished. Users of such software wonder what
the fuzz is about. There are also other C++ packages that make creating compiled
libraries quite difficult. Any attempt to explain the process here would be
extremely system dependent without guarantees that it will work.
Compiling source code files with include files, as shown in this chapter, is very
reliable and works across all the different C++ versions. The speed of today’s
modern computers is also such that compiling is very fast and the penalty of
recompiling finished routines is only a problem when the programs becomes quite
large. I have personally worked with a C++ program in the neighborhood of
8,000 lines and found that the compile time was not objectionable at all. This
does not mean that you should not use compiled libraries. By all means check the
Include files can create a headache all of their own. It was mentioned that you
need to have a picture in your head about the organization of your program, or
better yet, a written plan that displays the global flow of your program logic.
With the best intentions it will not take long before it really becomes confusing if
some support file of library routines has been included or not. This complexity
grows when you create compiled libraries that require the inclusion of all support
files to create an object file. This brings up a curious problem. What happens if
the same file is included more than once? How does the compiler react to such an
event. Program PROG2604.CPP intentionally includes the INCLUDE4.H1
header file twice. After the program source code, the output example shows how
the compiler reacts to this type of programming.
// PROG2604.CPP
// This program demonstrates the problems that can
occur when
#include <iostream.h>
#include <conio.h>
#include "INCLUDE4.H1"
#include "INCLUDE4.H2"
void main()
{
clrscr();
Function1();
Function2();
Function3();
getch();
}
// INCLUDE4.H1
// This include file is used by PROG2604.CPP
// This file contains the Skip function that will used
by
// all the other functions.
#include <iostream.h>
#include <conio.h>
void Skip(int N)
{
int K;
for (K=1; K<=N; K++)
cout << endl;
}
The INCLUDE4.H1 file is a very short file that contains a few include statements
and a Skip function. Note that the main driver program includes IOSTREAM.H
and so does the INCLUDE4.H1 file. In this program you see multiple examples
of including the same file at different locations. Will the compiler accept this?
#include "INCLUDE4.H1"
void Function1();
void Function2();
void Function3();
#include "INCLUDE4.CPP"
// INCLUDE4.CPP
// Include file for PROG2604.CPP
// This file contains the function declarations of the
// prototypes in the INCLUDE4.H2 header file.
void Function1()
{
Skip(2);
cout << "THIS IS FUNCTION 1" << endl;
}
void Function2()
{
Skip(2);
cout << "THIS IS FUNCTION 2" << endl;
}
void Function3()
{
Skip(2);
cout << "THIS IS FUNCTION 3" << endl;
}
PROG2604.CPP OUTPUT
The error message is more significant than you may think. The message states
that Skip already has been defined. This statement is certainly appropriate but the
INCLUDE4.H1 is included after IOSTREAM.H. What is the point? There is
no indication that there is any problem with the IOSTREAM.H file, yet that file
is included twice as well. You may think that the compiler stopped when it first
encountered the problem and therefore you do not see a later message about
IOSTREAM.H. That is a good theory but the compiler is extremely sequential
in reporting problems that it encounters and IOSTREAM.H is included first and
should be the first candidate for some duplicate identifier type of error message.
Are you drawing the correct conclusion that something was done to the
IOSTREAM.H file that prevented the problem, and we need to do something of
the same nature to our included files? This is a wise conclusion. Now how can
this type of problem be solved?
Consider the following situation in a high school. Some emergency has happened
and the school principals are visiting every class to talk to students. There is not
enough time to create an organized schedule of who visits which class. At the
same time classes should not be unnecessarily interrupted. At the conclusion of a
visit each principal tapes a note card on the visited classroom door. Any other
principal who observes the card, knows that the class has been visited. Such a
system is very simple, yet very effective. It is possible to use a similar system
with C++ by “marking” a file with a system that can determine if the file has been
included or not. C++ provides some special purpose preprocessors to accomplish
this task. Program PROG2605.CPP uses these special processors. Look at the
program first, and satisfy yourself that the previous problem has now been solved.
The special features of this program will be explained afterwards.
// PROG2605.CPP
// This program cures the problem of the previous
program.
// The include file now uses the #ifndef preprocessor
to
// alert the compiler that the file has already been
compiled.
#include <iostream.h>
#include <conio.h>
#include "INCLUDE5.H1"
#include "INCLUDE5.H2"
// INCLUDE5.H1
// This include file is used by PROG2605.CPP
// This file uses the #ifndef preprocessor to prevent
compiling
// errors, due to multiple Skip function definitions.
#ifndef _INCLUDE5_H1
#define _INCLUDE5_H1
#include <iostream.h>
#include <conio.h>
void Skip(int N)
{
int K;
for (K=1; K<=N; K++)
cout << endl;
}
#endif
// INCLUDE5.H2
// This file contains the prototypes of PROG2605.CPP
// It includes the INCLUDE5.H1 file with the Skip
function,
// as well as the INCLUDE5.CPP file of function
definitions.
#include "INCLUDE5.H1"
void Function1();
void Function2();
void Function3();
#include "INCLUDE5.CPP"
void Function1()
{
Skip(2);
cout << "THIS IS FUNCTION 1" << endl;
}
void Function2()
{
Skip(2);
cout << "THIS IS FUNCTION 2" << endl;
}
void Function3()
{
Skip(2);
cout << "THIS IS FUNCTION 3" << endl;
}
PROG2605.CPP OUTPUT
THIS IS FUNCTION 1
THIS IS FUNCTION 2
THIS IS FUNCTION 3
#ifndef QWERTY
#define QWERTY
void HappyFunction()
{
cout << ”I am happy” << endl;
#endif
What does all of this mean? The first encounter with the statement ifndef
QWERTY starts out with QWERTY not defined. If the program segment is
included in a file, this will mean that all the program statements will be compiled.
Do notice that immediately after the #ifndef follows another preprocessor, which
is #define. This processor will insure that QWERTY becomes defined. This
change of status with QWERTY means that the next time the if statement is
false. Now just like any other conditional statement, the program block following
the if is only executed when the conditional statement is true. Now let us look at
the INCLUDE5.H1 file and see just what that means.
// INCLUDE5.H1
// This include file is used by PROG2605.CPP
// This file uses the #ifndef preprocessor to prevent
compiling
// errors, due to multiple Skip function definitions.
#ifndef _INCLUDE5_H1
#define _INCLUDE5_H1
#include <iostream.h>
#include <conio.h>
void Skip(int N)
{
int K;
for (K=1; K<=N; K++) cout << endl;
}
#endif
The main program PROG2605.CPP includes INCLUDE5.H1. The first time
that the file is included, the ifndef statement is false. Remember that any
identifier may be used, but by convention the identifier is the name of the file
preceded by an underscore character. Since _INCLUDE5_H1 is not defined, the
program block after the if and the endif is entered.
After this short file, the compiler continues on its merry way and comes on the
statement #include INCLUDE5.H2, which happens to have its own statement
that is #include INCLUDE5.H1. The compiler now makes a second visit to the
humble INCLUDE5.H1 file. But now there is a difference. _INCLUDE5_H1 is
now defined, which makes the #ifndef statement false, and the compiler skips to
the #endif to continue. In this case that means the end of the file, and the result is
that the Skip function is not encountered a second time.
Should every file have an #ifndef statement to avoid duplicate inclusion? Perhaps
this is a good idea, but in practice you will find the preprocessors with the header
files only. The logic of this convention is that header files should pull in the
implementation files - especially when compiled libraries are used - and this only
happens when the header file is compiled. In other words, any attempt to include
the header file a second time will fail because of the #ifndef protection. This in
turn prevents the implementation file to be included a second time.
#ifndef _UTILITY_H
#define _UTILITY_H
#endif
As you can see, the include file business has its share of complexity. There is not
one thou shalt do it only this way approach. There are a variety of approaches
that will work correctly. Knowing what works and does not work requires that
you understand the proper program sequence requirements, and then make sure
that your include files obey the sequence needed by your program.
You may find it interesting to look at the header files IOSTREAM.H and
CONIO.H, provided by your compiler. These files will have considerable
complexity, but you will notice that the conditional preprocessors are used. Even
the very short BOOL.H file uses conditional preprocessors. Short files that may
be included at many stages of a program are excellent candidates for accidental
inclusion at more than one location. Make sure to protect these files. I will finish
this section by showing you the brief BOOL.H file. Note that once again the
identifier that is used is based on the name of the file. Just keep in mind that this
name could be AARDVARK and there is not any requirement that this name has
anything to do with the external filename.
#ifndef _BOOL_H
#define _BOOL_H
#endif
This section will be review. Everything shown here was already presented in an
earlier chapter. Now you will probably not require this review, but you know not
every student is as clever and as cerebrally gifted as you are.
Your first concern with OOP is encapsulation, which is the creation of a data type
that combines the attributes (data) with the actions that act upon the data
(member functions). The main purpose of this chapter is to investigate the
various features of the member functions that are part of an object.
// PROG2606.CPP
// This program reviews the basic syntax of creating a class
// with member functions defined after the main function.
#include <iostream.h>
#include <conio.h>
class Car
{
void main()
{
clrscr();
Car Jeep;
Jeep.GetData();
Jeep.ShowData();
getch();
}
Car::Car()
{
cout << endl << endl;
cout << "CAR IS CREATED" << endl;
Year = 0;
Value = 0;
}
Car::~Car()
{
cout << endl << endl;
cout << "CAR IS FINISHED" << endl;
getch();
}
void Car::GetData()
{
cout << endl << endl;
cout << "Enter car year ===>> ";
cin >> Year;
cout << "Enter car value ===>> ";
cin >> Value;
}
void Car::ShowData()
{
cout << endl << endl;
cout << "CAR YEAR: " << Year << endl;
cout << "CAR VALUE: " << Value << endl;
}
PROG2606.CPP OUTPUT
CAR IS CREATED
CAR IS FINISHED
Different parts of the program will be used to review vocabulary and syntax that
you need to know when you create and use objects. The details of the program
will not get a whole lot of explanation. If this review does not make much sense,
you may need to go back to the earlier chapter where this object oriented
programming material was first introduced.
There is some confusion about the word object. We talk about object oriented
programming, and we also talk about creating objects. Yet a new object data type
is declared with the class reserved word. This confusion is due to the fact that the
word object is used to describe the style of programming and the features that are
used. Object has both a generic meaning and a specific meaning. It is a little like
Kleenex, which can mean tissue, and it can also mean a brand of tissue. It is not
that big a deal. Just make sure that you use the program features correctly and
you will be fine.
Class Syntax
Unless functions are very short, normally only the prototypes of the member
functions are placed inside the class declaration. The syntax of member functions
is almost the same as any other function, except for the function heading. The
heading must be different so that the compiler recognizes the function as a
member of a class and not some global function implementation.
The scope resolution operator, which is a C++ operator with two adjacent
colons ( :: ), is used in the declarations of member functions to identify where the
function belongs. Do not use the scope resolution operator if a complete function
declaration is placed inside a class. The compiler has no difficulty determining
that a function declaration is part of a class when a function, in its entirety, is
positioned inside a class. It is precisely when a function definition sits by itself
somewhere in the program source code that the compiler must have some means
to identify the function. The compiler must know that this is not some global
function swinging in a breeze, but additional information for a class positioned
earlier in the program source code.
Member Function Syntax
void Car::GetData()
{
cout << endl << endl;
cout << "Enter car year ===>> ";
cin >> Year;
cout << "Enter car value ===>> ";
cin >> Value;
}
Most classes contain some special functions that have their own name, the
constructor and the destructor. It is not the purpose of this chapter to provide
details for constructors and destructors. That topic will be the focus of the next
chapter. In this chapter, constructors and destructors will be handled in a very
minimal manner. Constructors and destructors are functions inside a class, so
they certainly are member functions. However, as a rule when member functions
of a class are mentioned, they are kept separate from the special constructor and
destructor functions.
The constructor and destructor are quickly recognized because the function
identifier has the same name as the class identifier. Furthermore, constructor and
destructor function headings do not have any data type or void preceding the
function name. The destructor has a tilde ( ~ ) placed in front of its identifier.
public:
Car();
~Car();
Car::~Car()
{
cout << endl << endl;
cout << "CAR IS FINISHED" << endl;
getch();
}
Declaring a user-created class with the reserved word class, private, public and
all the necessary data and functions does not create an object. All the information
necessary to construct a new object is now available. It remains for the definition
of an object to breathe life into your new creation. This creation or construction
class-identifier object-identifier;
void main()
{
clrscr();
Car Jeep;
Jeep.GetData();
Jeep.ShowData();
}
I have mentioned before that OOP has its share of vocabulary. Whether all this
vocabulary is good or bad is a dead issue. The vocabulary exists and it gets used
in the computer science community. Students who are not familiar with the
vocabulary can be bewildered, and totally unaware that a simple concept is
discussed with a complicated word.
Attributes:
state
data members
member variables
Actions:
behaviors
operations
member functions
methods
services
Member functions are actions that act on the attributes of an object. There are so
many member functions that a classification has been established that indicates
the nature of the member functions and how they behave. All functions in a class
are member functions, but they do not all behave in the same way.
Member Function Categories
Constructors
Member functions that create objects of a class.
Accessors
Member functions that access the attributes of an
object without changing data values.
Modifiers
Member functions that access the attributes of
objects and alter their values.
This chapter started by showing you how to handle a variety of include file
situations. Include files are used heavily in object oriented programming. Right
now we need to look at the manner that you will normally encounter program
source code that uses object oriented programming.
One major goal of computer science is information hiding. Perhaps this seems
like an odd goal. Why hide information? Information hiding actually is very
desirable and allows people to function within their capabilities. The complexity
of today’s technology is such that extremely few people are familiar with all the
exact details of every technology aspect. Do you know precisely how a single
pixel finds its way on the monitor with a given color, at a given location? Do you
really care how that happens?
There is also another issue at hand that ties right in with the include files we
mentioned earlier. Suppose now that you do understand all the exact details that
are used in a computer program. Do you want to be surrounded by all those
details all the time? Program development requires concentrated focus and this
focus should not be cluttered by unnecessary details. In other words, when you
write a program that makes heavy use of mathematical concepts, you will focus
totally on complicated math functions when you write them. Perhaps these
functions are written with the help of some reference material, or perhaps you use
another person who is better in math than you are.
This information hiding business can be done very nicely with object oriented
programming. Using a class requires that you are familiar with the behavior of an
object. What does the object do? What is the interface to the object? A well
written class will not allow any access to the attributes of the class. This means
that your concern is with the actions of the class that control what goes and does
not go. Using member functions requires an understanding of the function
preconditions, postconditions and parameters. Knowing the complete function
implementation is not necessary.
So what does all of this mean when you work with a class? It means that our
driving program should include the header file. This header file normally uses the
suffix *.H and provides the interface to a class. The header file contains the class
declaration and usually any required information that is necessary to use the class,
such as the preconditions and postconditions of member functions.
// PROG2607.CPP
// This program demonstrates the conventional approach
of
// including the header file, CAR.H, which contains the
class
// declaration. The member functions are in CAR.CPP.
#include <iostream.h>
#include <conio.h>
#include "CAR.H" // the interface header file of
Car
The main program provides little information. You can see that Jeep is
instantiated as an object of the Car class and then two member functions are
called. It is not possible to get much understanding from this file about the Car
class. We will need to go elsewhere to get better information. Take a look at the
CAR.H file, which should provide the information to use the Car class properly.
// CAR.H
// This is the header file included by PROG2607.CPP
// that provides the interface to the Car class.
#ifndef _CAR_H
#define _CAR_H
class Car
{
private:
int Year; // stores year that the car was
built
double Value; // stores the resale value of
the car
public:
Car(); // constructor initializes
private data
~Car(); // destructor
void GetData(); // places entered values in
private data
void ShowData(); // displays private data values
};
#include "CAR.CPP"
#endif
You will not always see the approach of including the implementation file at the
end of the header file. Frequently, the implementation file is a compiled object
file that is included in a special program project. I mentioned earlier that this
topic varies wildly across different C++ version and the demonstrated approach
works nicely with every compiler that you may use.
// CAR.CPP
// This file contains the functions implementations of
the Car
// class declared in CAR.H and used by program
PROG2607.CPP.
Car::Car()
{
cout << endl << endl;
cout << "CAR IS CREATED" << endl;
Year = 0;
Value = 0;
}
Car::~Car()
{
cout << endl << endl;
cout << "CAR IS FINISHED" << endl;
getch();
}
void Car::GetData()
{
cout << endl << endl;
cout << "Enter car year ===>> ";
cin >> Year;
cout << "Enter car value ===>> ";
cin >> Value;
}
void Car::ShowData()
{
cout << endl << endl;
PROG2607.CPP OUTPUT
CAR IS CREATED
CAR IS FINISHED
The previous example involved one class. It is entirely possible that some library
involves a variety of classes with some common purpose. In such a case your
header file may contain the declarations of more than one class. At the same time
the implementation file will include details for member functions of more than
one class. Program PROG2608.CPP shows how to handle a header file and an
implementation file for such a situation.
// PROG2608.CPP
// This program creates two classes with a member
function
// to display the value of the Data field.
#include <iostream.h>
#include <conio.h>
#include "ONETWO.H"
void main()
{
clrscr();
One ObjectOne;
Two ObjectTwo;
ObjectOne.ShowOne();
ObjectTwo.ShowTwo();
getch();
}
// ONETWO.H
// This file is the header file included in
PROG2608.CPP
class One
{
private:
int DataOne;
public:
One(); // constructor initializes
DataOne to 1
void ShowOne(); // member function that displays
class Two
{
private:
int DataTwo;
public:
Two(); // constructor initializes
DataTwo to 2
void ShowTwo(); // member function that displays
DataTwo
};
#include "ONETWO.CPP"
// ONETWO.CPP
// This is the implementation file of classes One and
Two
One::One()
{
DataOne = 1;
}
Two::Two()
{
DataTwo = 2;
}
void One::ShowOne()
{
cout << endl;
cout << "ShowOne Data: " << DataOne << endl;
}
void Two::ShowTwo()
{
cout << endl;
cout << "ShowTwo Data: " << DataTwo << endl;
}
ShowOne Data: 1
ShowTwo Data: 2
You are pleased to see a section on understanding the this pointer. Your pleasure
no doubt is increased by the fact that you have never even heard such a pointer
mentioned, or forgotten that it was mentioned, and pray tell what does this weird
pointer have to do with object oriented programming? Well hang on because you
are about to understand something mysterious that has been going on.
Since the first introduction of objects you have been shown that the compiler
knows where to find member data. Most students do not find this all that strange,
because you are confident the syntax of providing a class identifier with a scope
resolution operator and function identifier solves the problem. A function like:
The secret is accomplished by a special pointer that keeps track of the object
currently being used. Program PROG2609.CPP uses a simple Animal class to
demonstrate what the this pointer is about. Different features of the program will
be explained inside the program with comments.
#include <iostream.h>
#include <conio.h>
class Animal
{
private:
int Data1;
float Data2;
public:
Animal(int,float);
void ShowData();
};
// The Animal class has data members, Data1 and Data2, to store an integer and a
real number.
// The class has a constructor and a ShowData member function.
void main()
{
clrscr();
Animal Aardvark(1111,3.14);
Animal Wallaby(2222,6.28);
cout << "Aardvark address: " << &Aardvark << endl;
Aardvark.ShowData();
cout << endl;
cout << "Wallaby address: " << &Wallaby << endl;
Wallaby.ShowData();
getch();
}
// The main function displays the memory addresses of the Aardvark and Wallaby
objects.
void Animal::ShowData()
{
cout << "this address: " << this << endl;
cout << "Data1: " << Data1 << endl;
cout << "Data2: " << Data2 << endl;
cout << "(*this).Data1: " << (*this).Data1 <<
endl;
cout << "this->Data2: " << this->Data2 <<
endl;
}
// The ShowData function displays some pretty weird stuff. This function will be
// explained in detail after the program output display. You will see that various
// addresses are displayed. Check the output to see how these values compare
with
// the output provided by the main function.
An explanation of the bizarre ShowData function will need to wait until after you
have studied the program output. In particular, note that the memory address of
the Aardvark object and the elusive this pointer are the same. At this stage, look
at that, absorb the fact that there is some relationship between the new object and
some odd, special reserved identifier, called this.
PROG2609.CPP OUTPUT
void Animal::ShowData()
{
cout << "this address: " << this << endl;
cout << "Data1: " << Data1 << endl;
cout << "Data2: " << Data2 << endl;
cout << "(*this).Data1: " << (*this).Data1 <<
endl;
cout << "this->Data2: " << this->Data2 <<
endl;
}
Let us start at the first weird statement, which outputs the value of the this
identifier. Observe that there is not an ampersand & in front of the identifier, yet
the output is a memory address. Displaying a memory address without
specifically asking for memory with the & operator indicates that the identifier
must be a pointer. Remember that a pointer stores the location of a memory
address.
It is also very significant that the this pointer displays the same memory address
as the address of the Aardvark object in the main function. The same is true of
the Wallaby object. This pretty much indicates a very strong connection between
Aardvark and this, as well as Wallaby and this.
The next two statements display the values of Data1 and Data2 in a straight
forward manner. No new concepts are shown here. The output of the data will be
compared shortly.
Now with the * operator we can dereference the this pointer and view its
contents. But it is not that simple. Memory locations are not just addresses but
addresses of certain data types and this points to a class with various fields.
Displaying the contents of a class, like a struct, can only be done by specifying
individual fields. So in the statement above we output the contents located at the
memory of the this pointer, but only the Data1 field.
It appears that this gets around, because you see the same relationship with the
this pointer and the Wallaby object. The address of the object that is currently
accessed has the same address as the this pointer.
Well C++ just had to make your life a little more complicated by providing a
second method of dereferencing. You can also use the -> operator after a
pointer identifier to indicate that the pointer is dereferenced and follow it with the
specific field identifier whose value is accessed.
It is quite possible that you understood every statement on a line by line basis, but
you are not seeing the big picture. Inside a member function you can access
Now comes the secret. The this pointer is used everywhere. You do not see it,
but this is there with any object access. You can actually include the this pointer
in a statement like I showed you. However, if this is left out, the compiler will
assume its presence for you and puts it there automatically.
You can use the this pointer and dereference any contents
of its memory address with two methods. These two methods
are shown with two program statements that both have
the same exact meaning.
It is time for another small case study. In this case study you will investigate the
process of creating a List class as the class is developed through five stages. The
majority of steps shown in this case study do not present new information. It is
meant as a review and reinforcement of the principles discussed so far in this
chapter. The final stage will demonstrate how to use a member function that
requires information to be passed by parameter.
The first step of the List class is to set the stage with minimal syntax that
compiles. This program does compile, and it does run, even if there are neither
attributes nor actions in the List class declaration.
// PROG2610
// First stage of the List class
// The minimal program that does compile.
#include <iostream.h>
#include <conio.h>
class List
{
private:
public:
};
PROG2610.CPP OUTPUT
#include <iostream.h>
#include <conio.h>
class List
{
private:
int Array[100];
int Size;
public:
List();
};
void main()
{
clrscr();
cout << "LIST CLASS STAGE 2" << endl;
List L;
List::List()
{
Size = 100;
int K;
for (K=0; K<Size; K++)
Array[K] = 0;
cout << endl;
cout << "List object is constructed" << endl;
}
PROG2611.CPP OUTPUT
// PROG2612.CPP
// Third stage of the List class
// This stage adds the RandomList, DisplayList and
SortList
// member functions stubs.
#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
class List
{
private:
void main()
{
clrscr();
cout << "LIST CLASS STAGE 3" << endl;
List L;
L.RandomList();
L.SortList();
L.DisplayList();
getch();
}
List::List()
{
cout << endl << endl;
Size = 1000;
int K;
for (K=0; K<Size; K++)
Array[K] = 0;
cout << "LIST OBJECT IS CONSTRUCTED" << endl;
}
void List::RandomList()
{
cout << endl << endl;
cout << "RANDOM LIST FUNCTION" << endl;
}
void List::DisplayList()
{
cout << endl << endl;
cout << "DISPLAY LIST FUNCTION" << endl;
}
void List::SortList()
{
cout << endl << endl;
cout << "SORT LIST FUNCTION" << endl;
}
// PROG2613.CPP
// Fourth stage of the List class
// This stage implements the member functions
// RandomList, DisplayList and SortList.
#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include "BOOL.H"
class List
{
private:
int Array[1000];
int Size;
public:
List();
void RandomList();
void DisplayList();
void SortList();
};
void List::RandomList()
{
cout << endl << endl;
cout << "RANDOM LIST FUNCTION" << endl;
cout << endl;
cout << "Enter size of the list [1..1000] ===>> ";
cin >> Size;
int K;
for (K=0; K<Size; K++)
Array[K] = random(9000) + 1000; // [1000..9999]
range
}
void List::DisplayList()
{
cout << endl << endl;
cout << "DISPLAY LIST FUNCTION" << endl;
cout << endl;
int K;
for (K=0; K<Size; K++)
{
cout << Array[K] << " ";
if (K%10 == 9)
cout << endl;
}
}
PROG2614.CPP OUTPUT
DISPLAY FUNCTION
The int parameter provides the number that the Search function is expected to
find. If the number is found, the location (index) of the number in the array is
returned, otherwise a value of -1 is returned.
The complete program is shown. The majority of the code you have seen before,
but the few extra pages required to duplicate the code is meant to make studying
the case study easier by seeing the context of the new stage.
// PROG2614.CPP
// Fifth stage of the List class case study
// This stage adds the Search member function.
// Search introduces a member function with a
parameter.
#include <iostream.h>
#include <conio.h>
#include <stdlib.h>
#include "BOOL.H"
class List
{
private:
int Array[1000];
int Size;
public:
List();
void RandomList();
void DisplayList();
void SortList();
int Search(int Item);
};
void main()
{
clrscr();
cout << "LIST CLASS STAGE 5" << endl;
List L;
L.RandomList();
L.SortList();
L.DisplayList();
SearchItem(L);
getch();
}
void SearchItem(List L)
{
cout << endl << endl;
cout << "FUNCTION SEARCH ITEM" << endl;
cout << endl;
int Item;
int Index;
cout << "Enter search item ===>> ";
cin >> Item;
cout << endl;
Index = L.Search(Item);
if (Index == -1)
cout << Item << " was not found in the list " <<
endl;
else
cout << Item << " is located at index " << Index
<< endl;
List::List()
{
cout << endl << endl;
Size = 1000;
int K;
for (K=0; K<Size; K++)
Array[K] = 0;
cout << "List object is constructed" << endl;
}
void List::RandomList()
{
cout << endl << endl;
cout << "RANDOM LIST FUNCTION" << endl;
cout << endl;
cout << "Enter size of the list [1..1000] ===>> ";
cin >> Size;
int K;
for (K=0; K<Size; K++)
Array[K] = random(9000) + 1000; // [1000..9999]
range
}
void List::DisplayList()
{
cout << endl << endl;
cout << "DISPLAY LIST FUNCTION" << endl;
cout << endl;
int K;
for (K=0; K<Size; K++)
{
cout << Array[K] << " ";
if (K%10 == 9)
cout << endl;
}
}
void List::SortList()
{
cout << endl << endl;
PROG2614.CPP OUTPUT
DISPLAY FUNCTION
The const reserved word has been used several times. It was first introduced as a
means to assign a value to an identifier that cannot be altered. The term const is
literal and means constant value. You have also seen const used with parameter
passing. It is possible to make a reference parameter const. The benefit of this
feature is to allow passing by reference of some large data structure that saves
both copy time and memory space. Yet at the same time const prevents altering
the parameter. It was shown to be a special way of passing parameters that
combined the advantages of both value and reference parameters.
Our hardworking const is not finished yet, and this time you will see how const
also can be used with the member functions of a class. If you suspect that once
again something will not change, you are correct.
However, do wait for one brief program. The const issue is the purpose of this
section. First a program will be shown that demonstrates how very short, one-
line, member functions are frequently used in a class declaration. In the case of
using a short function, it is quite common that you do not use a function
prototype, followed by the complete implementation coded elsewhere. Short
functions can quite conveniently be completed inside the class declaration without
causing any clutter and confusion. It is true that this approach may violate the
idea of information hiding, but my goal is to present different approaches right
now and not philosophy about what is best. These same short member functions
will then be used in the next program example with const.
// PROG2615.CPP
// This program demonstrates the practice of placing
complete
// functions inside a class declaration when such
functions
// are very small.
#include <iostream.h>
#include <conio.h>
void main()
{
clrscr();
CardDeck D;
cout << "Number of decks: " << D.GetDecks() <<
endl;
cout << "Number of players: " << D.GetPlayers() <<
endl;
cout << "Number of cards: " << D.GetCards() <<
endl;
getch();
}
CardDeck::CardDeck()
{
NumDecks = 2;
NumPlayers = 4;
CardsLeft = NumDecks * 52;
}
PROG2615.CPP OUTPUT
Number of decks: 2
Number of players: 4
Number of cards: 104
When you run the program the first time, everything compiles and works fine.
Each member function is a classic example of an accessor member function
which gets values without trying to modify any data. The const provides extra
protection for functions that should not be altering data. Now change the
comments and try to compile the program with the first GetDecks commented
out. Uncomment the second CardDeck, and now the program will not compile.
The result is shown in the second program output below.
// PROG2616.CPP
// This program demonstrates the common practice of
making
// member functions "constant" functions to avoid
altering
// any private data.
#include <iostream.h>
#include <conio.h>
class CardDeck
{
private:
int NumDecks;
int NumPlayers;
int CardsLeft;
public:
CardDeck();
int GetDecks() const { return NumDecks; }
// int GetDecks() const { NumDecks++; return
NumDecks; }
int GetPlayers() const { return NumPlayers; }
int GetCards() const { return CardsLeft; }
};
CardDeck::CardDeck()
{
NumDecks = 2;
NumPlayers = 4;
CardsLeft = NumDecks * 52;
}
PROG2616.CPP OUTPUT #1
Number of decks: 2
Number of players: 4
Number of cards: 104
PROG2616.CPP OUTPUT #2
The program has no program execution, and generates the following error
message:
Compiling PROG2515.CPP:
Error PROG2616.CPP 19: Cannot modify a const object