C++ tutorial (chapter 1- SIMPLE THINGS)
Chapter 1
SIMPLE THINGS
As we begin the study of C++ and object oriented programming, a few
comments are in order to help you get started. Since the field of
object oriented programming is probably new to you, you will find
that there is a significant amount of new terminology for you to
grasp. This is true of any new endeavor and you should be warned
not to be intimidated by all of the new concepts. We will add a
few new topics in each chapter and you will slowly grasp the entire
language.
Chapters one through four of this tutorial will concentrate on the
non object oriented programming additions to C++. We will not
begin the discussion of any object oriented programming techniques
until chapter five.
EVEN COMMENTS ARE IMPROVED IN C++
// Chapter 1 - Program 1
#include <iostream.h> /* This is the stream definition file */
void print_it(const int data_value);
main()
{
const int START = 3; // The value of START cannot be changed
const int STOP = 9; // The value of STOP cannot be changed
volatile int CENTER = 6; /* The value of CENTER may be changed
by something external to this
program. */
int index; /* A normal C variable */
for (index = START ; index < STOP ; index++)
print_it(index);
} /* End of program */
void print_it(const int data_value)
{
cout << "The value of the index is " << data_value << "\n";
}
// Result of execution
//
// The value of the index is 3
// The value of the index is 4
// The value of the index is 5
// The value of the index is 6
// The value of the index is 7
// The value of the index is 8
Examine the file named CONCOM.CPP for an example ==============
of several new things in C++. We will take the CONCOM.CPP
new constructs one at a time beginning with the ==============
comments.
A comment begins with the double slash "//", starts anywhere on a
line, and runs to the end of that line where it is automatically
terminated. The old method of comment definition used with ANSI-
C can also be used with C++ as illustrated in lines 11 through 14,
among other places in this program. The new method is the
preferred method of comment definition because it is impossible to
inadvertently comment out several lines of code. This can be done
by forgetting to include the end of comment notation when using the
older C method of comment notation. Good programming practice
would be to use the new method for all comments and reserve the old
method for use in commenting out a section of code during debugging
since the two methods can be nested.
It would be well to caution you at this point however, that you
should not use comments when the same sense of program definition
can be obtained by using meaningful names for variables, constants,
and functions. The careful selection of variable and function
names can make nearly any code self documenting and you should
strive to achieve this in your code.
Page 1-1
Chapter 1 - Simple Things
THE KEYWORDS const AND volatile
_________________________________________________________________
There are two new keywords used in lines 9 through 11 which were
not part of the original K&R definition of C, but are part of the
ANSI-C standard. The keyword const is used to define a constant.
In line 9 the constant is of type int, it is named START, and is
initialized to the value 3. The compiler will not allow you to
accidentally or purposefully change the value of START because it
has been declared a constant. If you had another variable named
STARTS, the system would not allow you to slightly misspell STARTS
as START and accidentally change it. The compiler would give you
an error message so you could fix the error. Since it is not
permissible to change the value of a constant, it is imperative
that you initialize it when it is declared so it will have a useful
value. The compiler does not require you to initialize it however,
and will not issue an error message if you do not.
You will note that the keyword const is also used in the function
header in line 21 to indicate that the formal parameter named
data_value is a constant throughout the function. Any attempt to
assign a new value to this variable will result in a compile error.
This is a small thing you can add to your programs to improve the
compilers ability to detect errors for you.
The keyword volatile is also part of the ANSI-C standard but was
not included in the original K&R definition of C. Even though the
value of a volatile variable can be changed by you, the programmer,
there may be another mechanism by which the value could be changed,
such as by an interrupt timer causing the value to be incremented.
The compiler needs to know that this value may be changed by some
external force when it optimizes the code. A study of code
optimization methods is very interesting, but beyond the scope of
this tutorial. Note that a constant can also be volatile, which
means that you cannot change it, but the system can through some
hardware function.
Ignore the output statement given in line 23 for a few minutes.
We will study it in some detail later in this chapter. If you are
experienced in K&R style programming, you may find line 5 and 21
a little strange. This illustrates prototyping and the modern
method of function definition as defined by the ANSI-C standard.
We will discuss this in great detail in chapter 4 of this tutorial.
Prototyping is optional in C but absolutely required in C++. For
that reason, chapter 4 of this tutorial is devoted entirely to
prototyping.
It would be advantageous for you to compile and execute this
program with your C++ compiler to see if you get the same result
as given in the comments at the end of the listing. One of the
primary purposes of compiling it is to prove that your compiler is
loaded and executing properly.
Page 1-2
Chapter 1 - Simple Things
THE SCOPE OPERATOR
// Chapter 1 - Program 2
#include <iostream.h>
int index = 13;
main()
{
float index = 3.1415;
cout << "The local index value is " << index << "\n";
cout << "The global index value is " << ::index << "\n";
::index = index + 7; // 3 + 7 should result in 10
cout << "The local index value is " << index << "\n";
cout << "The global index value is " << ::index << "\n";
}
// Result of execution
//
// The local index value is 3.1415
// The global index value is 13
// The local index value is 3.1415
// The global index value is 10
The example program named SCOPEOP.CPP ===============
illustrates another construct that is new to SCOPEOP.CPP
C++. There is no corresponding construct in ===============
either K&R or ANSI-C. This allows access to the
global variable named index even though there is
a local variable of the same name within the main function. The
use of the double colon in front of the variable name, in lines 11,
13, and 16, instructs the system that we are interested in using
the global variable named index, defined in line 4, rather than the
local variable defined in line 8.
The use of this technique allows access to the global variable for
any use. It could be used in calculations, as a function
parameter, or for any other purpose. It is not really good
programming practice to abuse this construct, because it could make
the code difficult to read. It would be best to use a different
variable name instead of reusing this name, but the construct is
available to you if you find that you need it sometime.
The scope operator allows access to global variables even though
hidden by a local variable. Be sure to compile and execute this
program before proceeding on to the next example program where we
will discuss the cout operator in lines 10, 11, 15, and 16.
THE iostream LIBRARY
// Chapter 1 - Program 3
#include <iostream.h>
#include <string.h>
main()
{
int index;
float distance;
char letter;
char name[25];
index = -23;
distance = 12.345;
letter = 'X';
strcpy(name,"John Doe");
cout << "The value of index is " << index << "\n";
cout << "The value of distance is " << distance << "\n";
cout << "The value of letter is " << letter << "\n";
cout << "The value of name is " << name << "\n";
index = 31;
cout << "The decimal value of index is " << dec << index << "\n";
cout << "The octal value of index is " << oct << index << "\n";
cout << "The hex value of index is " << hex << index << "\n";
cout << "The character letter is " << (char)letter << "\n";
cout << "Input a decimal value --> ";
cin >> index;
cout << "The hex value of the input is " << index << "\n";
}
// Result of execution
//
// The value of index is -23
// The value of distance is 12.345
// The value of letter is X
// The value of name is John Doe
// The decimal value of index is 31
// The octal value of index is 37
// The hex value of index is 1f
// The character letter is X
// Input a decimal value --> 999
// The hex value of the input is 3e7
Examine the example program named MESSAGE.CPP ===============
for our first hint of object oriented MESSAGE.CPP
programming, even though it is a very simple ===============
one. In this program, we define a few variables
and assign values to them for use in the output
statements illustrated in lines 17 through 20, and in lines 23
through 26. The new operator cout is the output function to the
standard device, the monitor, but works a little differently from
our old familiar printf() function, because we do not have to tell
the system what type we are outputting.
C++, like the C language itself, has no input or output operations
as part of the language itself, but defines the stream library to
add input and output functions in a very elegant manner.
The operator <<, sometimes called the "put to" operator but more
properly called the insertion operator, tells the system to output
the variable or constant following it, but lets the system decide
how to output the data. In line 17, we first tell the system to
output the string, which it does by copying characters to the
monitor, then we tell it to output the value of index. Notice
however, that we fail to tell it what the type is or how to output
the value. Since we don't tell the system what the type is, it is
Page 1-3
Chapter 1 - Simple Things
up to the system to determine what the type is and to output the
value accordingly. After the system finds the correct type, we
also leave it up to the system to use the built in default as to
how many characters should be used for this output. In this case,
we find that the system uses exactly as many as needed to output
the data, with no leading or trailing blanks, which is fine for
this output. Finally, the newline character is output, and the
line of code is terminated with a semicolon.
When we called the cout output function in line 17, we actually
called two different functions because we used it to output a
string and a variable of type int. This is the first hint at
object oriented programming because we simply broadcast a message
to the system to print out a value, and let the system find an
appropriate function to do so. We are not required to tell the
system exactly how to output the data, we only tell it to output
it. This is a very weak example of object oriented programming,
and we will get into it in much more depth later.
In line 18, we tell the system to output a different string,
followed by a floating point number, and another string of one
character, the newline character. In this case, we told it to
output a floating point number without telling it that it was a
floating point number, once again letting the system choose the
appropriate output means based on its type. We did lose a bit of
control in the transaction, however, because we had no control over
how many significant digits to print before or after the decimal
point. We chose to let the system decide how to format the output
data.
The variable named letter is of type char, and is assigned the
value of the uppercase X in line 14, then printed as a letter in
line 19.
Because C++ has several other operators and functions available
with streams, you have complete flexibility in the use of the
stream output functions. You should refer to your compiler
documentation for details of other available formatting commands.
The cout and the printf() statements can be mixed in any way you
desire. Both statements result in output to the monitor.
MORE ABOUT THE stream LIBRARY
_________________________________________________________________
The stream library was defined for use with C++ in order to add to
the execution efficiency of the language. The printf() function
was developed early in the life of the C language and is meant to
be all things to all programmers. As a result, it became a huge
function with lots of extra baggage that is only used by a few
programmers. By defining the small special purpose stream library,
the designer of C++ allows the programmer to use somewhat limited
formatting capabilities, which are still adequate for most
programming jobs. If more elaborate formatting capabilities are
Page 1-4
Chapter 1 - Simple Things
required, the complete printf() library is available within any C++
program, and the two types of outputs can be freely mixed.
Lines 23 through 26 illustrate some of the additional features of
the stream library which can be used to output data in a very
flexible yet controlled format. The value of index is printed out
in decimal, octal, and hexadecimal format in lines 23 through 25.
When one of the special stream operators, dec, oct, or hex, is
output, all successive output will be in that number base. Looking
ahead to line 32, we find the value of index printed in hex format
due to the selection of the hexadecimal base in line 25. If none
of these special stream operators are output, the system defaults
to decimal format.
THE cin OPERATOR
_________________________________________________________________
In addition to the cout operator, there is a cin operator which is
used to read data from the standard input device, usually the
keyboard. The cin operator uses the >> operator, usually called
the "get from" operator but properly called the extraction
operator. It has most of the flexibility of the cout operator.
A brief example of the use of the cin operator is given in lines
28 through 30. The special stream operators, dec, oct, and hex,
also select the number base for the cin stream separately from the
cout stream. If none is specified, the input stream also defaults
to decimal.
In addition to the cout operator and the cin operator there is one
more standard operator, the cerr, which is used to output to the
error handling device. This device cannot be redirected to a file
like the output to the cout can be. The three operators, cout,
cin, and cerr, correspond to the stdout, the stdin, and the stderr
stream pointers of the programming language C. Their use will be
illustrated throughout the remainder of this tutorial.
The stream library also has file I/O capability which will be
briefly illustrated in the next example program.
Be sure to compile and execute this program before going on to the
next one. Remember that the system will ask you to enter an
integer value which will be echoed back to the monitor, but changed
to the hexadecimal base.
Examine the example program named FSTREAM.CPP ===============
for examples of the use of streams with files. FSTREAM.CPP
===============
In this program a file is opened for reading,
another for writing, and a third stream is
Page 1-5
Chapter 1 - Simple Things
opened to the printer to illustrate the semantics of stream
operations on a file. The only difference between the streams in
the last program and the streams in this program is the fact that
in the last program, the streams were already opened for us by the
system. You will note that the stream named printer is used in the
same way we used the cout operator in the last program. Finally,
because we wish to exercise good programming practice, we close
all of the files we have opened prior to ending the program.
The standard file I/O library is available with ANSI-C and is as
easy to use as the stream library and very portable. For more
information on the stream file I/O library, see Bjarne Stroustrup's
book which is listed in the introduction to this tutorial, or refer
to your compiler documentation.
Be sure to compile and execute this program. When you execute it,
it will request a file to be copied. You can enter the name of any
ASCII file that resides in the current directory.
VARIABLE DEFINITIONS
// Chapter 1 - Program 5
#include <iostream.h>
int index;
main()
{
int stuff;
int &another_stuff = stuff; // A synonym for stuff
stuff = index + 14; //index was initialized to zero
cout << "stuff has the value " << stuff << "\n";
stuff = 17;
cout << "another_stuff has the value " << another_stuff << "\n";
int more_stuff = 13; //not automatically initialized
cout << "more_stuff has the value " << more_stuff << "\n";
for (int count = 3;count < 8;count++) {
cout << "count has the value " << count << "\n";
char count2 = count + 65;
cout << "count2 has the value " << count2 << "\n";
}
static unsigned goofy; //automatically initialized to zero
cout << "goofy has the value " << goofy << "\n";
}
// Result of execution
//
// stuff has the value 14
// another_stuff has the value 17
// more_stuff has the value 13
// count has the value 3
// count2 has the value D
// count has the value 4
// count2 has the value E
// count has the value 5
// count2 has the value F
// count has the value 6
// count2 has the value G
// count has the value 7
// count2 has the value H
// goofy has the value 0
Examine the file named VARDEF.CPP for a few more ==============
additions to the C++ language which aid in VARDEF.CPP
writing a clear and easy to understand program. ==============
In C++, as in ANSI-C, global and static
variables are automatically initialized to zero
when they are declared. The variables named index in line 4, and
goofy in line 26 are therefore automatically initialized to zero.
Of course, you can still initialize either to some other value if
you so desire. Global variables are sometimes called external
since they are external to any functions.
Automatic variables, those declared inside of any function, are not
automatically initialized but will contain the value that happens
to be in the location where they are defined, which must be
considered a garbage value. The variable named stuff in line 8,
therefore does not contain a valid value, but some garbage value
which should not be used for any meaningful purpose. In line 11,
it is assigned a value based on the initialized value of index and
it is then displayed on the monitor for your examination.
THE REFERENCE VARIABLE
_________________________________________________________________
Notice the ampersand in line 9. This defines another_stuff as a
reference variable which is a new addition to C++. The reference
variable should not be used very often, if at all, in this context.
In order to be complete however, we will discuss its operation.
The reference variable is not quite the same as any other variable
because it operates like a self dereferencing pointer. Following
its initialization, the reference variable becomes a synonym for
Page 1-6
Chapter 1 - Simple Things
the variable stuff, and changing the value of stuff will change the
value of another_stuff because they are both actually referring to
the same variable. The synonym can be used to access the value of
the variable for any legal purpose in the language. It should be
pointed out that a reference variable must be initialized to
reference some other variable when it is declared or the compiler
will respond with an error. Following initialization, the
reference variable cannot be changed to refer to a different
variable.
The use of the reference variable in this way can lead to very
confusing code, but it has another use where it can make the code
very clear and easy to understand. We will study this use in
chapter 4 of this tutorial.
DEFINITIONS ARE EXECUTABLE STATEMENTS
_________________________________________________________________
Coming from your background of C, you will find the statement in
line 16 very strange, but this is legal in C++. Anyplace it is
legal to put an executable statement, it is also legal to declare
a new variable because a data declaration is defined as an
executable statement in C++. In this case, we define the new
variable named more_stuff and initialize it to the value of 13.
It has a scope from the point where it was defined to the end of
the block in which it is defined, so it is valid throughout the
remainder of the main program. The variable named goofy is
declared even later in line 26.
It is very significant that the variable is declared near its point
of usage. This makes it easier to see just what the variable is
used for, since it has a much more restricted scope of validity.
When you are debugging a program, it is convenient if the variable
declaration is located in close proximity to where you are
debugging the code.
WHAT ABOUT definition AND declaration?
_________________________________________________________________
The words definition and declaration refer to two different things
in C++, and in ANSI-C also for that matter. They really are
different and have different meanings, so we should spend a little
time defining exactly what the words mean in C++. A declaration
provides information to the compiler about the characteristics of
something such as a type or a function but it doesn't actually
define any code to be used in the executable program, and you are
permitted to make as many declarations of the same entity as you
desire. A definition, on the other hand, actually defines
something that will exist in the executable program, either some
useful variables, or some executable code, and you are required to
Page 1-7
Chapter 1 - Simple Things
have one and only one definition of each entity in the program.
In short, a declaration introduces a name into the program and a
definition introduces some code.
If we declare a struct, we are only declaring a pattern to tell the
compiler how to store data later when we define one or more
variables of that type. But when we define some variables of that
type, we are actually declaring their names for use by the
compiler, and defining a storage location to store the values of
the variables. Therefore, when we define a variable, we are
actually declaring it and defining it at the same time.
We will refer to these definitions many times throughout the course
of this tutorial so if this is not clear now, it will clear up
later.
A BETTER for LOOP
_________________________________________________________________
Take careful notice of the for loop defined in line 20. This loop
is a little clearer than the for loop that is available in ANSI-
C, because the loop index is defined in the for loop itself. The
scope of this loop index is from its declaration to the end of the
enclosing block. In this case its scope extends to line 29 since
the closing brace in line 29 corresponds to the most recent opening
brace prior to the declaration of the variable. Since the variable
is still available, it can be used for another loop index or for
any other purpose which an integer type variable can legally be
used for. The variable named count2 is declared and initialized
during each pass through the loop because it is declared within the
block controlled by the for loop. Its scope is only the extent of
the loop so that it is automatically deallocated each time the loop
is completed. It is therefore declared, initialized, used and
deallocated five times, once for each pass through the loop.
You will notice that the variable count2 is assigned a numerical
value in line 22 but when it is printed out, a character value is
actually output. This is because C++ (version 2.0 and later) is
careful to use the correct type.
Finally, as mentioned earlier, the static variable named goofy is
declared and automatically initialized to zero in line 26. Its
scope is from the point of its declaration to the end of the block
in which it is declared, line 29.
Operator precedence is identical to that defined for ANSI-C so no
attempt will be made here to define it. There is a small
Page 1-8
Chapter 1 - Simple Things
difference when some operators are overloaded which we will learn
to do later in this tutorial. Some of the operators act slightly
different when overloaded than the way they operate with elements
of the predefined language.
Do not worry about the previous paragraph, it will make sense later
in this tutorial after we have studied a few more topics.
1. Write a program that displays your name and date of birth on
the monitor three times using the cout function. Define any
variables you use as near as possible to their point of usage.
2. Write a program with a few const values and volatile variables
and attempt to change the value of the constants to see what
kind of error message your compiler will give you.
3. Write a program that uses streams to interactively read in
your birthday with three different cin statements. Print your
birthday in octal, decimal, and hexadecimal notation just for
the practice.
Page 1-9作者: createch 時間: 2009-9-5 12:28
C++ tutorial (Chapter 2: Compound Types)
Chapter 2
COMPOUND TYPES
for (result = win;result <= cancel;result++) {
if (result == omit)
cout << "The game was cancelled\n";
else {
cout << "The game was played ";
if (result == win)
cout << "and we won!";
if (result == lose)
cout << "and we lost.";
cout << "\n";
}
}
}
// Result of execution
//
// The game was played and we won!
// The game was played and we lost.
// The game was played
// The game was cancelled
Examine the file named ENUM.CPP for an example ==============
that uses an enumerated type variable. The ENUM.CPP
enumerated type is used in C++ in exactly the ==============
same way it was used in ANSI-C with one small
exception, the keyword enum is not required to
be used again when defining a variable of that type, but it can be
used if desired. It may be clearer to you to use the keyword when
defining a variable in the same manner that it is required to be
used in C, and you may choose to do so.
The example program uses the keyword enum in line 9, but omits it
in line 8 to illustrate to you that it is indeed optional. The
remainder of this program should be no problem for you to
understand. After studying it, be sure to compile and execute it
and examine the output.
cout << "The weight of dog1 is " << dog1.weight << "\n";
cout << "The weight of dog2 is " << dog2.weight << "\n";
cout << "The weight of chicken is " << chicken.weight << "\n";
}
// Result of execution
//
// The weight of dog1 is 15
// The weight of dog2 is 37
// The weight of chicken is 3
Examine the example program named STRUCTUR.CPP ================
for an illustration using a very simple STRUCTUR.CPP
structure. This structure is no different from ================
that used in ANSI-C except for the fact that the
keyword struct is not required to be used again
when defining a variable of that type. Lines 11 and 12 illustrate
the declaration of variables without the keyword, and line 13
indicates that the keyword struct can be included if desired. It
is up to you to choose which style you prefer to use in your C++
programs.
You should take note of the fact that this is a valid ANSI-C
program except for the fact that it uses the stream library, the
C++ comments, and the lack of use of the keyword struct in two of
the lines.
Once again, be sure to compile and execute this program after
studying it carefully, because the next example program is very
similar but it introduces a brand new construct not available in
standard C, the class.
cout << "The weight of dog1 is " << dog1.weight << "\n";
cout << "The weight of dog2 is " << dog2.weight << "\n";
cout << "The weight of chicken is " << chicken.weight << "\n";
}
// Result of execution
//
// The weight of dog1 is 15
// The weight of dog2 is 37
// The weight of chicken is 3
Examine the example program named CLASS1.CPP for ==============
our first example of a class in C++. This is CLASS1.CPP
the first class example, but it will not be the ==============
last, since the class is the major reason for
using C++ over ANSI-C or some other programming
language. You will notice the keyword class used in line 4, in
exactly the same way that the keyword struct was used in the last
program, and they are in fact very similar constructs. There are
definite differences, as we will see, but for the present time we
will be concerned more with their similarities.
The word animal in line 4 is the name of the class, and when we
declare variables of this type in lines 12 through 14, we can
either omit the keyword class or include it if we desire as
illustrated in line 14. In the last program, we declared 5
variables of a structure type, but in this program we declare 5
objects. They are called objects because they are of a class type.
The differences are subtle, and in this case the differences are
negligible, but as we proceed through this tutorial, we will see
that the class construct is indeed very important and valuable.
The class was introduced here only to give you a glimpse of what
is to come later in this tutorial.
The class is a type which can be used to declare objects in much
the same way that a structure is a type that can be used to declare
variables. Your dog named King is a specific instance of the
general class of dogs, and in a similar manner, an object is a
specific instance of a class. It would be well to take note of the
fact that the class is such a generalized concept that there will
be libraries of prewritten classes available in the marketplace
soon. You will be able to purchase classes which will perform some
generalized operations such as managing stacks, queues, or lists,
sorting data, managing windows, etc. This is because of the
generality and flexibility of the class construct. In fact, a few
class libraries are available now.
The new keyword public in line 5, followed by a colon, is necessary
in this case because the variables in a class are defaulted to a
private type and we could not access them at all without making
them public. Don't worry about this program yet, we will cover all
of this in great detail later in this tutorial.
Be sure to compile and run it to see that it does what we say it
does with your compiler. Keep in mind that this is your first
example of a class and it illustrates essentially nothing
concerning the use of this powerful C++ construct.
Page 2-2
Chapter 2 - Compound Types
THE FREE UNION OF C++
// Chapter 2 - Program 4
#include <iostream.h>
struct aircraft {
int wingspan;
int passengers;
union {
float fuel_load; // for fighters
float bomb_load; // for bombers
int pallets; // for transports
};
} fighter, bomber, transport;
Examine the program named UNIONEX.CPP for an ===============
example of a free union. In ANSI-C, all unions UNIONEX.CPP
must be named in order to be used, but this is ===============
not true in C++. When using C++ we can use a
free union, a union without a name. The union
is embedded within a simple structure and you will notice that
there is not a variable name following the declaration of the union
in line 11. In ANSI-C, we would have to name the union and give
a triple name (three names dotted together) to access the members.
Since it is a free union, there is no union name, and the variables
are accessed with only a doubly dotted name as illustrated in lines
18, 22, 26, 28, and 29.
You will recall that a union causes all the data contained within
the union to be stored in the same physical memory locations, such
that only one variable is actually available at a time. This is
exactly what is happening here. The variable named fuel_load,
bomb_load, and pallets are stored in the same physical memory
locations and it is up to the programmer to keep track of which
variable is stored there at any given time. You will notice that
the transport is assigned a value for pallets in line 26, then a
value for fuel_load in line 28. When the value for fuel_load is
assigned, the value for pallets is corrupted and is no longer
available since it was stored where fuel_load is currently stored.
The observant student will notice that this is exactly the way the
union is used in ANSI-C except for the way components are named.
The remainder of the program should be easy for you to understand,
so after you study it and understand it, compile and execute it.
C++ TYPE CONVERSIONS
// Chapter 2 - Program 5
#include <iostream.h>
main()
{
int a = 2;
float x = 17.1, y = 8.95, z;
char c;
c = (char)a + (char)x;
c = (char)(a + (int)x);
c = (char)(a + x);
c = a + x;
z = (float)((int)x * (int)y);
z = (float)((int)x * (int)y);
z = (float)((int)(x * y));
z = x * y;
c = char(a) + char(x);
c = char(a + int(x));
c = char(a + x);
c = a + x;
z = float(int(x) * int(y));
z = float(int(x) * int(y));
z = float(int(x * y));
z = x * y;
}
// Result of execution
//
// (There is no output from this program)
Examine the program named TYPECONV.CPP for a few ================
examples of type conversions in C++. The type TYPECONV.CPP
conversions are done in C++ in exactly the same ================
manner as they are done in ANSI-C, but C++ gives
you another form for doing the conversions.
Lines 10 through 18 of this program use the familiar "cast" form
of type conversions used in ANSI-C, and there is nothing new to the
experienced C programmer. You will notice that lines 10 through
13 are actually all identical to each other. The only difference
is that we are coercing the compiler to do the indicated type
conversions prior to doing the addition and the assignment in some
of the statements. In line 13, the int type variable will be
converted to type float prior to the addition, then the resulting
float will be converted to type char prior to being assigned to the
variable c.
Page 2-3
Chapter 2 - Compound Types
Additional examples of type coercion are given in lines 15 through
18 and all four of these lines are essentially the same.
he examples given in lines 20 through 28 are unique to C++ and are
not valid in ANSI-C. In these lines the type coercions are written
as though they are function calls instead of the "cast" method as
illustrated earlier. Lines 20 through 28 are identical to lines
10 through 18.
You may find this method of type coercion to be clearer and easier
to understand than the "cast" method and in C++ you are free to use
either, or to mix them if you so desire, but your code could be
very difficult to read if you indescriminantly mix them.
Be sure to compile and execute this example program.
1. Starting with the program ENUM.CPP, add the enumerated value
of forfeit to the enumerated type game_result, and add a
suitable message and logic to get the message printed in some
way.
2. Add the variable height of type float to the class of
CLASS1.CPP and store some values in the new variable. Print
some of the values out. Move the new variable ahead of the
keyword public: and see what kind of error message results.
We will cover this error in chapter 5 of this tutorial.
Page 2-4作者: createch 時間: 2009-9-5 12:28
C++ tutorial (Chapter 3: Pointers)
Chapter 3
POINTERS
Because pointers are so important in C and C++, this chapter will
review some of the more important topics concerning pointers. Even
if you are extremely conversant in the use of pointers, you should
not completely ignore this chapter because some new material unique
to C++ is presented here.
POINTER REVIEW
// Chapter 3 - Program 1
#include <iostream.h>
main()
{
int *pt_int;
float *pt_float;
int pig = 7, dog = 27;
float x = 1.2345, y = 32.14;
void *general;
pt_int = &pig;
*pt_int += dog;
cout << "Pig now has the value of " << *pt_int << "\n";
general = pt_int;
pt_float = &x;
y += 5 * (*pt_float);
cout << "y now has the value of " << y << "\n";
general = pt_float;
const char *name1 = "John"; // Value cannot be changed
char *const name2 = "John"; // Pointer cannot be changed
}
// Result of execution
//
// Pig now has the value of 34
// y now has the value of 38.3125
Examine the program named POINTERS.CPP for a ================
simple example of the use of pointers. This is POINTERS.CPP
a pointer review and if you are comfortable with ================
the use of pointers, you can skip this example
program completely.
A pointer in either ANSI-C or C++ is declared with an asterisk
preceding the variable name. The pointer is then a pointer to a
variable of that one specific type and should not be used with
variables of other types. Thus pt_int is a pointer to an integer
type variable and should not be used with any other type. Of
course, an experienced C programmer knows that it is simple to
coerce the pointer to be used with some other type by using a cast,
but he must assume the responsibility for its correct usage.
In line 12 the pointer named pt_int is assigned the address of the
variable named pig and line 13 uses the pointer named pt_int to add
the value of dog to the value of pig because the asterisk
dereferences the pointer in exactly the same manner as standard C.
The address is used to print out the value of the variable pig in
line 14 illustrating the use of a pointer with the stream output
operator cout. Likewise, the pointer to float named pt_float is
assigned the address of x, then used in a trivial calculation in
line 18.
If you are not completely comfortable with this trivial program
using pointers, you should review the use of pointers in any good
C programming book or Coronado Enterprises C tutorial before
proceeding on because we will assume that you have a thorough
knowledge of pointers throughout the remainder of this tutorial.
It is not possible to write a C program of any significant size or
complexity without the use of pointers.
CONSTANT POINTERS AND POINTERS TO CONSTANTS
_________________________________________________________________
The definition of C++ allows a pointer to a constant to be defined
such that the value to which the pointer points cannot be changed
Page 3-1
Chapter 3 - Pointers
but the pointer itself can be moved to another variable or
constant. The method of defining a pointer to a constant is
illustrated in line 22. In addition to a pointer to a constant,
you can also declare a constant pointer, one that cannot be
changed. Line 23 illustrates this. Note that neither of these
pointers are used in illustrative code.
Either of these constructs can be used to provide additional
compile time checking and improve the quality of your code. If you
know a pointer will never be moved due to its nature, you should
define it as a constant pointer. If you know that a value will not
be changed, it can be defined as a constant and the compiler will
tell you if you ever inadvertently attempt to change it.
A POINTER TO VOID
_________________________________________________________________
The pointer to void is actually a part of the ANSI-C standard but
is relatively new so it is commented upon here. A pointer to void
can be assigned the value of any other pointer type. You will
notice that the pointer to void named general is assigned an
address of an int type in line 15 and the address of a float type
in line 20 with no cast and no complaints from the compiler. This
is a relatively new concept in C and C++. It allows a programmer
to define a pointer that can be used to point to many different
kinds of things to transfer information around within a program.
A good example is the malloc() function which returns a pointer to
void. This pointer can be assigned to point to any entity, thus
transferring the returned pointer to the correct type.
A pointer to void is aligned in memory in such a way that it can
be used with any of the simple predefined types available in C++,
or in ANSI-C for that matter. They will also align with any
compound types the user can define since compound types are
composed of the simpler types.
Be sure to compile and execute this program.
DYNAMIC ALLOCATION AND DEALLOCATION
// Chapter 3 - Program 2
#include <iostream.h>
struct date {
int month;
int day;
int year;
};
main()
{
int index, *point1, *point2;
point1 = &index;
*point1 = 77;
point2 = new int;
*point2 = 173;
cout << "The values are " << index << " " <<
*point1 << " " << *point2 << "\n";
point1 = new int;
point2 = point1;
*point1 = 999;
cout << "The values are " << index << " " <<
*point1 << " " << *point2 << "\n";
delete point1;
Examine the program named NEWDEL.CPP for our ================
first example of the new and delete operators. NEWDEL.CPP
The new and delete operators do dynamic ================
allocation and deallocation in much the same
manner that malloc() and free() do in your old
favorite C implementation.
During the design of C++, it was felt that since dynamic allocation
and deallocation are such a heavily used part of the C programming
language and would also be heavily used in C++, it should be a part
of the language, rather than a library add-on. The new and delete
Page 3-2
Chapter 3 - Pointers
operators are actually a part of the C++ language and are
operators, much like the addition operator or the assignment
operator. They are therefore very efficient, and are very easy to
use as we will see in this example program.
Lines 14 and 15 illustrate the use of pointers in the tradition of
C and line 16 illustrates the use of the new operator. This
operator requires one modifier which must be a type as illustrated
here. The pointer named point2 is now pointing at the dynamically
allocated integer variable which exists on the heap, and can be
used in the same way that any dynamically allocated variable is
used in ANSI-C. Line 18 illustrates displaying the value on the
monitor which was assigned in line 17.
Line 20 allocates another new variable and line 21 causes point2
to refer to the same dynamically allocated variable as point1 is
pointing to. In this case, the reference to the variable that
point2 was previously pointing to has been lost and it can never
be used or deallocated. It is lost on the heap until we return to
the operating system when it will be reclaimed for further use, so
this is obviously not good practice. Note that point1 is
deallocated with the delete operator in line 25, and point2 can not
actually be deleted. Since the pointer point1 itself is not
changed, it is actually still pointing to the original data on the
heap. This data could probably be referred to again using point1,
but it would be terrible programming practice since you have no
guarantee what the system will do with the pointer or the data.
The data storage is returned to the free list to be allocated in
a subsequent call, and will soon be reused in any practical
program.
Since the delete operator is defined to do nothing if it is passed
a NULL value, it is legal to ask the system to delete the data
pointed to by a pointer with the value of NULL, but nothing will
actually happen. It is actually wasted code. The delete operator
can only be used to delete data allocated by a new operator. If
the delete is used with any other kind of data, the operation is
undefined and anything can happen. According to the ANSI standard,
even a system crash is a legal result of this illegal operation,
and can be defined as such by the compiler writer.
In line 27, we declare some floating point variables. You will
remember that in C++ the variables do not have to be declared at
the beginning of a block. A declaration is an executable statement
and can therefore appear anywhere in a list of executable
statements. One of the float variables is allocated within the
declaration to illustrate that this can be done. Some of the same
operations are performed on these float type variables as were done
on the int types earlier.
Some examples of the use of a structure are given in lines 35
through 41 and should be self explanatory.
Page 3-3
Chapter 3 - Pointers
Finally, since the new operator requires a type to determine the
size of the dynamically allocated block, you may wonder how you can
allocate a block of arbitrary size. This is possible by using the
construct illustrated in line 47 where a block of 37 char sized
entities, which will be 37 bytes, is allocated. A block of 133
bytes greater than the size of the date structure is allocated in
line 49. It is therefore clear that the new operator can be used
with all of the flexibility of the malloc() function which you are
familiar with.
The standard functions which you have been using in C for dynamic
memory management, malloc(), calloc(), and free(), are also
available for use in C++ and can be used in the same manner they
were used in C. The new and delete operators should not be
intermixed with the older function calls since the results may be
unpredictable. If you are updating code with the older function
calls, continue to use them for any additions to the code. If you
are designing and coding a new program you should use the newer
constructs because they are a built in part of the language rather
than an add on and are therefore more efficient.
void print_stuff(float data_to_ignore)
{
printf("This is the print stuff function.\n");
}
void print_message(float list_this_data)
{
printf("The data to be listed is %f\n", list_this_data);
}
void print_float(float data_to_print)
{
printf("The data to be printed is %f\n", data_to_print);
}
// Result of execution
//
// This is the print stuff function.
// This is the print stuff function.
// The data to be listed is 6.283180
// The data to be listed is 13.000000
// The data to be printed is 3.141590
// The data to be printed is 3.141590
Examine the program named FUNCPNT.CPP for an ===============
example of using a pointer to a function. It FUNCPNT.CPP
must be pointed out that there is nothing new ===============
here, the pointer to a function is available in
ANSI-C as well as in C++ and works in the manner
described here for both languages. It is not regularly used by
most C programmers, so it is defined here as a refresher. If you
are comfortable with the use of pointers to functions, you can skip
this discussion entirely.
There is nothing unusual about this program except for the pointer
to a function declared in line 7. This declares a pointer to a
function which returns nothing (void) and requires a single formal
parameter, a float type variable. You will notice that all three
of the functions declared in lines 4 through 6 fit this profile and
are therefore candidates to be called with this pointer. If you
have not used prototyping in C, these lines will look strange to
you. Don't worry about them at this point since we will study
prototyping in the next chapter of this tutorial.
Observe that in line 14 we call the function print_stuff() with the
parameter pi and in line 15 we assign the function pointer named
function_pointer the value of print_stuff() and use the function
pointer to call the same function again in line 16. Lines 14 and
16 are therefore identical in what is accomplished because of the
pointer assignment in line 15. In lines 17 through 22, a few more
Page 3-4
Chapter 3 - Pointers
illustrations of the use of the function pointer are given. You
will be left to study these on your own.
Since we assigned the name of a function to a function pointer, and
did not get an assignment error, the name of a function must be a
pointer to that function. This is exactly the case. A function
name is a pointer to that function, but it is a pointer constant
and cannot be changed. This is exactly the case we found when we
studied arrays in ANSI-C at some point in our C programming
background. An array name is a pointer constant to the first
element of the array.
Since the name of the function is a constant pointer to that
function, we can assign the name of the function to a function
pointer and use the function pointer to call the function. The
only caveat is that the return value and the number and types of
parameters must be identical. Most C and C++ compilers will not,
and in fact, can not warn you of type mismatches between the
parameter lists when the assignments are made. This is because the
assignments are done at runtime when no type information is
available to the system, rather than at compile time when all type
information is available.
The use and operation of pointers must be thoroughly understood
when we get to the material on dynamic binding and polymorphism
later in this tutorial. It will be discussed in detail at that
time.
1. When dynamically allocated data is deleted, it is still
actually in memory, stored on the heap. Repeat the output
statement from line 23 of NEWDEL.CPP immediately following the
delete in line 25 to see if the values are really still there.
Repeat it once again just prior to the end of the program when
the data spaces should have been written over to see if you
get garbage out. Even if your compiler reports the correct
data, it is terrible practice to count on this data still
being there because in a large dynamic program, the heap space
will be used repeatedly.
2. Add a function to FUNCPNT.CPP which uses a single integer for
a parameter and attempt to call it by using the function
pointer to see if you get the correct data into the function.
Page 3-5作者: createch 時間: 2009-9-5 12:29
C++ tutorial Chapter 4 -Functions
Chapter 4
FUNCTIONS
This chapter discusses enhancements in the capabilities of
functions that have been made to C++. These changes make
programming more convenient and permit the compiler to do further
checking for errors. A fair amount of time is also spent in this
chapter teaching the modern form of function definition and
prototyping.
Prototyping allows the compiler to do additional type checking for
your function calls which can detect some programming errors. The
first two example programs in this chapter are designed to teach
prototyping and what it will do for you. Prototyping is a
relatively new addition to C, so even some experienced C
programmers are not familiar with it. If you have experience with
prototyping you can skip directly to the section named PASS BY
REFERENCE on page 4-4 of this chapter.
PROTOTYPES
// Chapter 4 - Program 1
#include <iostream.h>
void do_stuff(int wings, float feet, char eyes);
main()
{
int arm = 2;
float foot = 1000.0;
char lookers = 2;
// Result of execution
//
// There are 3 wings.
// There are 12 feet.
// There are 4 eyes.
//
// There are 2 wings.
// There are 1000 feet.
// There are 2 eyes.
Examine the file named PROTYPE1.CPP for our ================
first look at a prototype and an illustration of PROTYPE1.CPP
how it is used. The prototyping used in C++ is ================
no different than that used in ANSI-C.
Actually, many C programmers take a rather dim
view of prototyping and seem reluctant to use it, but with C++ it
is considerably more important and is in much heavier use. In
fact, prototyping is required to be used in some situations in C++.
A prototype is a limited model of a more complete entity to come
later. In this case, the full function is the complete entity to
come later and the prototype is illustrated in line 4. The
prototype gives a model of the interface to the function that can
be used to check the calls to the function for the proper number
of parameters and the correct types of parameters. Each call to
the function named do_stuff() must have exactly three parameters
or the compiler will give an error message. In addition to the
correct number of parameters, the types must be compatible or the
compiler will issue an error message. Notice that when the
compiler is working on lines 12 and 13, the type checking can be
done based on the prototype in line 4 even though the function
itself is not yet defined. If the prototype is not given, the
number of parameters will not be checked, nor will the types of the
parameters be checked. Even if you have the wrong number of
parameters, you will get an apparently good compile and link, but
the program may do some very strange things when it is executed.
Page 4-1
Chapter 4 - Functions
To write the prototype, simply copy the header from the function
to the beginning of the program and append a semicolon to the end
as a signal to the compiler that this is not a function but a
prototype. The variable names given in the prototype are optional
and act merely as comments to the program reader since they are
completely ignored by the compiler. You could replace the variable
name wings in line 4 with your first name and there would be no
difference in compilation. Of course, the next person that had to
read your program would be somewhat baffled with your choice of
variable names.
In this case, the two function calls to this function, given in
lines 12 and 13, are correct so no error will be listed during
compilation.
Even though we wish to use the char type for eyes in the function,
we wish to use it as a number rather than as a character. The cast
to int in line 20 is required to force the printout of the
numerical value rather than an ASCII character. The next example
program is similar but without the cast to int.
We mentioned compatible types earlier so we should review them just
a bit in order to make our discussion of prototyping complete.
Compatible types are any simple types that can be converted from
one to another in a meaningful way. For example, if you used an
integer as the actual parameter and the function was expecting a
float type as the formal parameter, the system would do the
conversion automatically, without mentioning it to you. This is
also true of a float changing to a char, or a char changing to an
int. There are definite conversion rules which would be followed.
These rules are given in great detail in section 3.2 of the draft
of the ANSI-C standard and are also given on page 198 of the second
edition of the K&R reference.
If we supplied a pointer to an integer as the actual parameter and
expected an integer as the formal parameter in the function, the
conversion would not be made because they are two entirely
different kinds of values. Likewise, a structure would not be
converted automatically to a long float, an array, or even to a
different kind of structure, they are all incompatible and cannot
be converted in any meaningful manner. The entire issue of type
compatibility as discussed in chapter 2 of this tutorial applies
equally well to the compatibility of types when calling a function.
Likewise, the type specified as the return type, in this case void,
must be compatible with the expected return type in the calling
statement, or the compiler will issue a warning.
Page 4-2
Chapter 2 - Functions
HOW DOES PROTOTYPING WORK?
_________________________________________________________________
This is your chance to try prototyping for yourself and see how
well it works and what kinds of error messages you get when you do
certain wrong things. Change the actual parameters in line 12 to
read (12.2, 13, 12345) and see what the compiler says about that
change. It will probably say nothing because they are all type
compatible. If you change it to read (12.0, 13), it will issue a
warning or error because there are not enough arguments given.
Likewise you should receive an error message if you change one of
the parameters in line 13 to an address by putting an ampersand in
front of one of the variable names. Finally, change the first word
in line 4 from void to int and see what kind of error message is
given. You will first be required to make the function header in
line 16 agree with the prototype, then you will find that there is
not a variable returned from the function. You should have a good
feeling that prototyping is doing something good for you after
making these changes.
Be sure to compile and execute this program then make the changes
recommended above, attempting to compile it after each change.
// Chapter 4 - Program 2
#include <iostream.h>
void do_stuff(int, float, char);
main()
{
int arm = 2;
float foot = 1000.0;
char lookers = 65;
void do_stuff(int wings, // Number of wings
float feet, // Number of feet
char eyes) // Number of eyes
{
cout << "There are " << wings << " wings." << "\n";
cout << "There are " << feet << " feet." << "\n";
cout << "There are " << eyes << " eyes." << "\n\n";
}
// Result of execution
//
// There are 3 wings.
// There are 12 feet.
// There are C eyes.
//
// There are 2 wings.
// There are 1000 feet.
// There are A eyes.
A LITTLE MORE PROTOTYPING
_________________________________________________________________
Examine the next example program named ================
PROTYPE2.CPP for a little more information on PROTYPE2.CPP
prototyping. This program is identical to the ================
last one except for a few small changes. The
variable names have been omitted from the
prototype in line 4 merely as an illustration that they are
interpreted as comments by the C++ compiler. The function header
is formatted differently to allow for a comment alongside each of
the actual parameters. This should make the function header a
little more self explanatory. However, you should remember that
comments should not be used to replace careful selection of
variable names. In this particular case, the comments add
essentially nothing to the clarity of the program.
WHAT DOES PROTOTYPING COST?
_________________________________________________________________
Prototyping is essentially free because it costs absolutely nothing
concerning the run time size or speed of execution. Prototyping
is a compile time check and slows down the compile time a
negligible amount because of the extra checking that the compiler
must do. If prototyping finds one error for you that you would
have had to find with a debugger, it has more than paid for itself
Page 4-3
Chapter 4 - Functions
for use in an entire project. I once spent 12 hours of debugging
time to find that I forgot to pass the address of a variable to a
function. Prototyping would have found the error on the first
compilation of this 2000 line program.
The only price you pay to use prototyping is the extra size of the
source files because of the prototypes, and the extra time for the
compiler to read the prototypes during the compilation process, but
both costs are negligible.
Be sure to compile and execute this example program. You will find
that it is identical to the last example program.
// Chapter 4 - Program 3
#include <iostream.h>
#include <stdio.h>
void fiddle(int in1, int &in2);
main()
{
int count = 7, index = 12;
cout << "The values are ";
printf("%3d %3d\n", count, index);
fiddle(count, index);
cout << "The values are ";
printf("%3d %3d\n", count, index);
}
void fiddle(int in1, int &in2)
{
in1 = in1 + 100;
in2 = in2 + 100;
cout << "The values are ";
printf("%3d %3d\n", in1, in2);
}
// Result of execution
//
// The values are 7 12
// The values are 107 112
// The values are 7 112
PASS BY REFERENCE
_________________________________________________________________
Examine The file named PASSREF.CPP for an ===============
example of a pass by reference, a construct PASSREF.CPP
which is not available in ANSI-C. The reference ===============
variable was mentioned in chapter 1 and it was
recommended there that you don't use it in the
manner illustrated there. This example program illustrates a
situation where it can be used to your advantage. The pass by
reference allows the passing of a variable to a function and
returning the changes made in the function to the main program.
In ANSI-C the same effect can be seen when a pointer to a variable
is passed to a function, but use of a reference variable is a
little cleaner.
Observe the prototype in line 4 where the second variable has an
ampersand in front of the variable name. The ampersand instructs
the compiler to treat this variable just like it were passed a
pointer to the variable since the actual variable from the main
program will be used in the function. In the function itself, in
lines 21 through 24, the variable in2 is used just like any other
variable but we are using the variable passed to this function from
the main program not a copy of it. The other variable named in1
is treated just like any other normal variable in ANSI-C. In
effect, the name in2 is a synonym for the variable named index in
the main program.
If you prefer to omit the variable names in the prototypes, you
would write the prototype as follows;
void fiddle(int, int&);
If you are a Pascal programmer, you will recognize that the
variable named in1 is treated just like a normal parameter in a
Pascal call, a call by value. The variable named in2 however, is
treated like a variable with the reserved word VAR used in front
of it usually referred to as a call by reference. The reference
variable is actually a self dereferencing pointer which refers to,
or points to, the original value.
Page 4-4
Chapter 4 - Functions
When you compile and execute this program, you will find that the
first variable got changed in the function but was returned to its
original value when we returned to the main program. The second
variable however, was changed in the function and the new value was
reflected back into the variable in the main program which we can
see when the values are listed on the monitor.
// Chapter 4 - Program 4
#include <iostream.h>
#include <stdio.h>
int get_volume(int length, int width = 2, int height = 3);
main()
{
int x = 10, y = 12, z = 15;
cout << "Some box data is " << get_volume(x, y, z) << "\n";
cout << "Some box data is " << get_volume(x, y) << "\n";
cout << "Some box data is " << get_volume(x) << "\n";
cout << "Some box data is ";
cout << get_volume(x, 7) << "\n";
cout << "Some box data is ";
cout << get_volume(5, 5, 5) << "\n";
}
int get_volume(int length, int width, int height)
{
printf("%4d %4d %4d ", length, width, height);
return length * width * height;
}
// Result of execution
//
// 10 12 15Some box data is 1800
// 10 12 3Some box data is 360
// 10 2 3Some box data is 60
// Some box data is 10 7 3 210
// Some box data is 5 5 5 125
Examine the file named DEFAULT.CPP for an ===============
example of the use of default parameters in C++. DEFAULT.CPP
This program really looks strange since it ===============
contains default values for some of the
parameters in the prototype, but these default
values are very useful as we will see shortly.
This prototype says that the first parameter named length must be
given for each call of this function because a default value is not
supplied. The second parameter named width, however, is not
required to be specified for each call, and if it is not specified,
the value 2 will be used for the variable width within the
function. Likewise, the third parameter is optional, and if it is
not specified, the value of 3 will be used for height within the
function.
In line 11 of this program, all three parameters are specified so
there is nothing unusual about this call from any other function
call we have made. Only two values are specified in line 12
however, so we will use the default value for the third parameter
and the system acts as if we called it with get_value(x, y, 3)
since the default value for the third value is 3. In line 13, we
only specified one parameter which will be used for the first
formal parameter, and the other two will be defaulted. The system
will act as if we had called the function with get_volume(x, 2, 3).
Note that the output from these three lines is reversed. This will
be explained shortly.
There are a few rules which should be obvious but will be stated
anyway. Once a parameter is given a default value in the list of
formal parameters, all of the remaining must have default values
also. It is not possible to leave a hole in the middle of the
list, only the trailing values can be defaulted. Of course, the
defaulted values must be of the correct types or a compiler error
will be issued. The default values can be given in either the
prototype or the function header, but not in both. If they are
given in both places, the compiler must not only use the default
value, but it must carefully check to see that both values are
identical. This could further complicate an already very
complicated problem, that of writing a C++ compiler.
As a matter of style, it is highly recommended that the default
values be given in the prototype rather than in the function. The
Page 4-5
Chapter 4 - Functions
reason will be obvious when we begin using object oriented
programming techniques.
WHY IS THE OUTPUT SCRAMBLED?
_________________________________________________________________
When the compiler finds a cout statement, the complete line of code
is initially scanned from right to left to evaluate any functions,
then the data is output field by field from left to right.
Therefore in line 11, get_value() is evaluated with its internal
output displayed first. Then the fields of the cout are displayed
from left to right with "Some box data is" displayed next.
Finally, the result of the return from get_value() is output in int
format, the type of the returned value. The end result is that the
output is not in the expected order when lines 11 through 13 are
executed. (The output is not what you would intuitively expect to
happen so appears to be a deficiency in the language. A call to
Borland International, the writers of Turbo C++ and Borland C++,
verified that this is operating correctly.)
Lines 15 through 18 are similar to any two of the lines of code in
lines 11 through 13, but are each separated into two lines so the
output is in the expected order.
Be sure to compile and execute DEFAULT.CPP after you understand it.
Note that the funny output order will appear again later in this
tutorial.
// Chapter 4 - Program 5
#include <iostream.h>
#include <stdarg.h>
// Declare a function with one required parameter
void display_var(int number, ...);
main()
{
int index = 5;
int one = 1, two = 2;
display_var(one, index);
display_var(3, index, index + two, index + one);
display_var(two, 7, 3);
}
va_start(param_pt,number); // Call the setup macro
cout << "The parameters are ";
for (int index = 0 ; index < number ; index++)
cout << va_arg(param_pt,int) << " "; // Extract a parameter
cout << "\n";
va_end(param_pt); // Closing macro
}
// Result of Execution
//
// The parameters are 5
// The parameters are 5 7 6
// The parameters are 7 3
VARIABLE NUMBER OF ARGUMENTS
_________________________________________________________________
Examine the program named VARARGS.CPP for an ===============
illustration of the use of a variable number of VARARGS.CPP
arguments in a function call. ===============
We have gone to a lot of trouble to get the
compiler to help us by carefully checking how many parameters we
use in the function calls and checking the types of the parameters.
On rare occasion, we may wish to write a function that uses a
variable number of parameters. The printf() function is a good
example of this. ANSI-C has a series of three macros available in
the "stdarg.h" header file to allow the use of a variable number
of arguments. These are available for use with C++ also, but we
need a way to eliminate the strong type checking that is done with
all C++ functions. The three dots illustrated in line 6 will do
this for us. This prototype says that a single argument of type
int is required as the first parameter, then no further type
checking will be done by the compiler.
You will note that the main program consists of three calls to the
function, each with a different number of parameters, and the
system does not balk at the differences in the function calls. In
Page 4-6
Chapter 4 - Functions
fact, you could put as many different types as you desire in the
calls. As long as the first one is an int type variable, the
system will do its best to compile and run it for you. Of course
the compiler is ignoring all type checking beyond the first
parameter so it is up to you to make sure you use the correct
parameter types in this call.
In this case the first parameter gives the system the number of
additional parameters to look for and handle. In this simple
program, we simply display the numbers on the monitor to illustrate
that they really did get handled properly.
Of course, you realize that using a variable number of arguments
in a function call can lead to very obscure code and should be used
very little in a production program, but the capability exists if
you need it. Be sure to compile and execute this program.
// Chapter 4 - Program 6
#include <iostream.h>
overload do_stuff; // This is optional
int do_stuff(const int); // This squares an integer
int do_stuff(float); // This triples a float & returns int
float do_stuff(const float, float); // This averages two floats
main()
{
int index = 12;
float length = 14.33;
float height = 34.33;
cout << "12 squared is " << do_stuff(index) << "\n";
cout << "24 squared is " << do_stuff(2 * index) << "\n";
cout << "Three lengths is " << do_stuff(length) << "\n";
cout << "Three heights is " << do_stuff(height) << "\n";
cout << "The average is " << do_stuff(length,height) << "\n";
}
int do_stuff(const int in_value) // This squares an integer
{
return in_value * in_value;
}
int do_stuff(float in_value) // Triples a float & return int
{
return (int)(3.0 * in_value);
}
// This averages two floats
float do_stuff(const float in1, float in2)
{
return (in1 + in2)/2.0;
}
// Result of execution
//
// 12 squared is 144
// 24 squared is 576
// Three lengths is 42
// Three heights is 102
// The average is 24.330002
FUNCTION NAME OVERLOADING
_________________________________________________________________
Examine the file named OVERLOAD.CPP for an ================
example of a program with the function names OVERLOAD.CPP
overloaded. This is not possible in ANSI-C, but ================
is perfectly legal and in fact used quite
regularly in C++. At first this will seem a bit
strange, but it is one of the keystones of object oriented
programming. You will see its utility and purpose very clearly in
later chapters of this tutorial.
You will notice in this example program that there are three
functions, in addition to the main function, and all three have the
same name. Your first question is likely to be, "Which function
do you call when you call do_stuff()?" That is a valid question
and the answer is, the function that has the correct number of
formal parameters of the correct types. If do_stuff() is called
with an integer value or variable as its actual parameter, the
function beginning in line 23 will be called and executed. If the
single actual parameter is of type float, the function beginning
in line 28 will be called, and if two floats are specified, the
function beginning in line 34 will be called.
It should be noted that the return type is not used to determine
which function will be called. Only the formal parameters are used
to determine which overloaded function will be called.
The keyword overload used in line 4 tells the system that you
really do intend to overload the name do_stuff, and the overloading
is not merely an oversight. This is only required in C++ version
1.2. C++ version 2.0 and greater do not require the keyword
overload but allows it to be used optionally in order to allow the
existing body of C++ code to be compatible with newer compilers.
It is not necessary to use this keyword because, when overloading
Page 4-7
Chapter 4 - Functions
is used in C++, it is generally used in a context in which it is
obvious that the function name is overloaded.
The actual selection of which function to actually call is done at
compile time, not at execution time so the program is not slowed
down. If each of the overloaded function names were changed to
different names, each being unique, there would be no difference
in execution size or speed of the resulting program.
Overloading of function names may seem very strange to you, and it
is strange if you are used to the rules of K&R or ANSI-C
programming. As you gain experience with C++, you will feel very
comfortable with this and you will use it a lot in your C++
programming.
Note the use of the keyword const used in some of the function
prototypes and headers. Once again, this prevents the programmer
from accidentally changing the formal parameter within the
function. In a function as short as these, there is no real
problem with an accidental assignment. In a real function that you
occasionally modify, you could easily forget the original intention
of the use of a value and attempt to change it during an extended
debugging session.
1. Change the type of wings in the prototype of PROTYPE1.CPP to
float so that it disagrees with the function definition to see
if you get a compilation error.
2. Change the function definition in PROTYPE1.CPP to agree with
the changed prototype. Compile and execute the program
without changing the calls in lines 12 and 13. Explain the
results.
3. In DEFAULT.CPP, remove the default value from the prototype
for height only to see what kind of compiler error you get.
Only the last values of the list can be defaulted.
4. In OVERLOAD.CPP, change the names of the three functions so
that each is a unique name and compare the size of the
resulting executable file with that given for the present
program.
Page 4-8作者: createch 時間: 2009-9-5 12:29
C++ Tutorial: (Chapter 5 - ENCAPSULATION)
Chapter 5
ENCAPSULATION
As mentioned in Chapter 1, object oriented programming will seem
very unnatural to a programmer with a lot of procedural programming
experience. This chapter is the beginning of the definition of
object oriented programming, and we will study the topic of
encapsulation which is a "divide and conquer" technique. As we
stated earlier, there are a lot of new terms used with object
oriented programming. Don't be intimidated by the new terminology,
we will study the terms one at a time in a meaningful order.
Encapsulation is the process of forming objects which we will
discuss throughout this chapter. An encapsulated object is often
called an abstract data type and it is what object oriented
programming is all about. Without encapsulation, which involves
the use of one or more classes, there is no object oriented
programming. Of course there are other topics concerning object
oriented programming, but this is the cornerstone.
WHY BOTHER WITH ENCAPSULATION?
_________________________________________________________________
We need encapsulation because we are human, and humans make errors.
When we properly encapsulate some code, we actually build an
impenetrable wall to protect the contained code from accidental
corruption due to the silly little errors that we are all prone to
make. We also tend to isolate errors to small sections of code to
make them easier to find and fix. We will have a lot more to say
about the benefits of encapsulation as we progress through the
tutorial.
cout << "The value of dog1 is " << dog1.data_store << "\n";
cout << "The value of dog2 is " << dog2.data_store << "\n";
cout << "The value of dog3 is " << dog3.data_store << "\n";
cout << "The value of piggy is " << piggy << "\n";
}
// Result of execution
//
// The value of dog1 is 12
// The value of dog2 is 17
// The value of dog3 is -13
// The value of piggy is 123
NO INFORMATION HIDING
_________________________________________________________________
The program named OPEN.CPP is a really stupid ==============
program because it does next to nothing, but it OPEN.CPP
will be the beginning point for our discussion ==============
of encapsulation, otherwise known as information
hiding. Information hiding is an important part
of object oriented programming and you should have a good grasp of
what it is by the time we finish this chapter.
A very simple structure is defined in lines 4 through 6 which
contains a single int type variable within the structure. This is
sort of a silly thing to do but it will illustrate the problem we
wish to overcome in this chapter. Three variables are declared in
line 10, each of which contains a single int type variable and each
Page 5-1
Chapter 5 - Encapsulation
of the three variables are available anywhere within the main
function. Each variable can be assigned, incremented, read,
modified, or have any number of operations performed on it. A few
of the operations are illustrated in lines 13 through 21 and should
be self explanatory to anyone with a little experience with the C
programming language.
An isolated local variable named piggy is declared and used in the
same section of code to illustrate that there is nothing magic
about this code.
Study this simple program carefully because it is the basis for
beginning our study of encapsulation. Be sure to compile and
execute this program, then we will go on to the next example
program.
// Chapter 5 - Program 2
#include <iostream.h>
class one_datum {
int data_store;
public:
void set(int in_value);
int get_value(void);
};
// dog1.data_store = 115; This is illegal in C++
// dog2.data_store = 211; This is illegal in C++
cout << "The value of dog1 is " << dog1.get_value() << "\n";
cout << "The value of dog2 is " << dog2.get_value() << "\n";
cout << "The value of dog3 is " << dog3.get_value() << "\n";
cout << "The value of piggy is " << piggy << "\n";
}
// Result of execution
//
// The value of dog1 is 12
// The value of dog2 is 17
// The value of dog3 is -13
// The value of piggy is 123
INFORMATION HIDING
_________________________________________________________________
Examine the program named CLAS.CPP for our first ==============
example of a program with a little information CLAS.CPP
hiding contained in it. This program is ==============
identical to the last one except for the way it
does a few of its operations. We will take the
differences one at a time and explain what is happening here. Keep
in mind that this is a trivial program and the safeguards built
into it are not needed for such a simple program but are used here
to illustrate how to use these techniques in a larger much more
complicated program.
The first difference is that we have a class instead of a structure
beginning in line 4 of this program. The only difference between
a class and a structure is that a class begins with a private
section whereas a structure has no private section automatically
defined. The keyword class is used to declare a class as
illustrated here.
The class named one_datum is composed of the single variable named
data_store and two functions, one named set() and the other named
get_value(). A more complete definition of a class is a group of
variables and one or more functions that can operate on that data.
Stay with us, we will tie this all together in a meaningful and
useful way very soon.
WHAT IS A PRIVATE SECTION?
_________________________________________________________________
A private section of a class is a section of data which cannot be
accessed outside of the class, it is hidden from any outside
access. Thus, the variable named data_store which is a part of the
object (an object will be defined completely later) named dog1
declared in line 23 is not available for use anywhere in the main
Page 5-2
Chapter 5 - Encapsulation
program. It is as if we have built a "brick wall" around the
variables to protect them from accidental corruption by outside
programming influences. It seems a little dumb to declare a
variable in the main program that we cannot use, but that is
exactly what we did.
Figure 5-1 is a graphical representation of the class with its
"brick wall" built around the data to protect it. You will notice
the small peep holes we have opened up to allow the user to gain
access to the functions. The peep holes were opened by declaring
the functions in the public section of the class.
WHAT IS A PUBLIC SECTION?
_________________________________________________________________
A new keyword, public, is introduced in line 6 which states that
anything following this keyword can be accessed from outside of
this class. Because the two functions are defined following the
keyword public, they are both public and available for use in the
calling function or any other function that is within the scope of
the calling function. This opens two small peepholes in the solid
wall of protection. You should keep in mind that the private
variable is not available to the calling program. Thus, we can
only use the variable by calling one of the two functions defined
as a part of the class. These are called member functions because
they are members of the class.
Since we have declared two functions, we need to define them by
saying what each function will actually do. This is done in lines
11 through 19 where they are each defined in the normal way, except
that the class name is prepended onto the function name and
separated from it by a double colon. These two function
definitions are called the implementation of the functions. The
class name is required because we can use the same function name
in other classes and the compiler must know with which class to
associate each function implementation.
One of the key points to be made here is that the private data
contained within the class is available within the implementation
of the member functions of the class for modification or reading
in the normal manner. You can do anything with the private data
within the function implementations which are a part of that class,
but the private data of other classes is hidden and not available
within the member functions of this class. This is the reason we
must prepend the class name to the function names of this class
when defining them.
It would be well to mention at this point that it is legal to
include variables and functions in the private part and additional
variables and functions in the public part. In most practical
situations, variables are included in only the private part and
functions are included in only the public part of a class
definition. Occasionally, variables or functions are used in the
Page 5-3
Chapter 5 - Encapsulation
other part. This sometimes leads to a very practical solution to
a particular problem, but in general, the entities are used only
in the places mentioned.
In C++ we have three scopes of variables, local, file and class.
Local variables are localized to a single function and file
variables are available anywhere in a file following their
definition. A variable with class scope is available anywhere
within the scope of a class and nowhere else.
You must be very confused by this point since we have given a lot
of rules but few reasons for doing all of this. Stay with us and
you will soon see that there are very practical reasons for doing
all of this.
MORE NEW TERMINOLOGY
_________________________________________________________________
As with most new technologies, developers seem to delight in making
up new names for all aspects of their new pet. Object oriented
programming is no different, so we must learn new names for some
of our old familiar friends if we are going to learn how to
effectively use it. To help you learn this new programming
terminology, we will list a few of them here and begin using them
in the text to get you used to seeing and using them.
A class is a grouping of data and methods (functions).
A class is very much like a type as used in ANSI-C, it
is only a pattern to be used to create a variable which
can be manipulated in a program.
An object is an instance of a class, which is similar to
a variable defined as an instance of a type. An object
is what you actually use in a program since it has values
and can be changed.
A method is a function contained within the class. You
will find the functions used within a class referred to
as methods.
A message is the same thing as a function call. In
object oriented programming, we send messages instead of
calling functions. For the time being, you can think of
them as identical. Later in this tutorial we will see
that they are in fact slightly different.
With all the new terminology, we will continue our study of the
program named CLAS.CPP and show you how to use the class. We can
now say that we have a class composed of one variable and two
methods. The methods operate on the variable contained in the
class when they receive messages to do so. In this tutorial we
will use the terms object and variable interchangeably because both
names are very descriptive of what the object really is.
Page 5-4
Chapter 5 - Encapsulation
This is a small point but it could be easily overlooked. Lines 7
and 8 of this program are actually the prototypes for the two
methods, and is our first example of the use of a prototype within
a class. This is the reason we spent so much time on prototypes
in the last chapter. You will notice line 7 which says that the
method named set requires one parameter of type int and returns
nothing, hence the return type is void. The method named
get_value() however, according to line 8, has no input parameters
but returns an int type value to the caller.
SENDING A MESSAGE
_________________________________________________________________
Following all of the definitions in lines 1 through 19, we finally
come to the program where we actually use the class. In line 23
we declare three objects of the class one_datum and name the
objects dog1, dog2, and dog3. Each object contains a single data
point which we can set through use of one method or read its value
through use of the other method, but we cannot directly set or read
the value of the data point because it is hidden within the "block
wall" around the class. In line 26, we send a message to the
object named dog1 instructing it to set its internal value to 12,
and even though this looks like a function call, it is properly
called sending a message to a method. Remember that the object
named dog1 has a method associated with it called set() that sets
its internal value to the actual parameter included within the
message. You will notice that the form is very much like the means
of accessing the elements of a structure. You mention the name of
the object with a dot connecting it to the name of the method. In
a similar manner, we send a message to each of the other two
objects dog2 and dog3 to set their values to those indicated.
Lines 31 and 32 have been commented out because the operations are
illegal since the variable named data_store is private and not
available to the code outside of the object itself. It should be
obvious, but it will be pointed out that the data contained within
the object named dog1 is not available within the methods of dog2
or dog3 because they are different objects. These rules are all
devised to help you develop better code more quickly and you will
soon see how they help.
The other method defined for each object is used in lines 34
through 36 to illustrate how it can be used. In each case, another
message is sent to each object and the returned result is output
to the monitor via the stream library.
USING A NORMAL VARIABLE
_________________________________________________________________
There is another variable named piggy declared and used throughout
this example program that illustrates that a normal variable can
Page 5-5
Chapter 5 - Encapsulation
be intermixed with the objects and used in the normal manner. The
use of this variable should pose no problem to you, so after you
understand the program, be sure to compile and execute it. It
would be a good exercise for you to remove the comments from lines
31 and 32 to see what kind of error message your compiler issues.
This program illustrates information hiding but it will not be
clear to you that it really does anything worthwhile until we study
the next two programs. Be sure to compile and execute this program
before continuing on to the next example program.
// Chapter 5 - Program 3
#include <iostream.h>
int area(int rec_height, int rec_width);
struct rectangle {
int height;
int width;
};
struct pole {
int length;
int depth;
};
int area(int rec_height, int rec_width) //Area of a rectangle
{
return rec_height * rec_width;
}
cout << "The area of the box is " <<
area(box.height, box.width) << "\n";
cout << "The area of the square is " <<
area(square.height, square.width) << "\n";
cout << "The funny area is " <<
area(square.height, box.width) << "\n";
cout << "The bad area is " <<
area(square.height, flag_pole.depth) << "\n";
}
// Result of execution
//
// The area of the box is 120
// The area of the square is 64
// The funny area is 80
// The bad area is 48
A PROGRAM WITH PROBLEMS
_________________________________________________________________
Examine the program named OPENPOLE.CPP for an ================
example of a program with a few serious problems OPENPOLE.CPP
that will be overcome in the next example ================
program by using the principles of
encapsulation.
We have two structures declared, one being a rectangle and the
other being a pole. The data fields should be self explanatory
with the exception of the depth of the flagpole which is the depth
it is buried in the ground, the overall length of the pole is
therefore the sum of the length and the depth.
Based on your experience with ANSI-C, you should have no problem
understanding exactly what this program is doing, but you may be
a bit confused at the meaning of the result found in line 38 where
we multiply the height of the square with the width of the box.
This is perfectly legal to do in ANSI-C or C++, but the result has
no earthly meaning because the data are for two different entities.
Likewise, the result calculated in line 40 is even sillier because
the product of the height of the square and the depth of the
flagpole has absolutely no meaning in any real world physical
system we can think up.
Wouldn't it be neat if we had a way to prevent such stupid things
from happening in a large production program. If we had a good
program that defined all of the things we can do with a square and
another program that defined everything we could do with a pole,
and if the data could be kept mutually exclusive, we could prevent
these silly things from happening.
It should come as no real surprise to you that the next program
will do just those things for us and do it in a very elegant way.
Before proceeding on to the next example program, you should
compile and execute this one even though it displays some silly
results.
// Chapter 5 - Program 4
#include <iostream.h>
class rectangle { // A simple class
int height;
int width;
public:
int area(void); // with two methods
void initialize(int, int);
};
int rectangle::area(void) //Area of a rectangle
{
return height * width;
}
cout << "The area of the box is " <<
box.area() << "\n";
cout << "The area of the square is " <<
square.area() << "\n";
// cout << "The funny area is " <<
// area(square.height, box.width) << "\n";
// cout << "The bad area is " <<
// area(square.height, flag_pole.depth) << "\n";
}
// Result of execution
//
// The area of the box is 120
// The area of the square is 64
Page 5-6
Chapter 5 - Encapsulation
OBJECTS PROTECT DATA
_________________________________________________________________
Examine the program named CLASPOLE.CPP as an ================
example of data protection in a very simple CLASPOLE.CPP
program. ================
In this program the rectangle is changed to a
class with the same two variables which are now private, and two
methods to handle the private data. One method is used to
initialize the values of the objects created and the other method
to return the area of the object. The two methods are defined in
lines 12 through 21 in the manner described earlier in this
chapter. The pole is left as a structure to illustrate that the
two can be used together and that C++ is truly an extension of
ANSI-C.
In line 33 we declare two objects, once again named box and square,
but this time we cannot assign values directly to their individual
components because they are private elements of the class. Lines
36 through 38 are commented out for that reason and the messages
are sent to the objects in lines 40 and 41 to tell them to
initialize themselves to the values input as parameters. The
flag_pole is initialized in the same manner as in the previous
program. Using the class in this way prevents us from making the
silly calculations we did in the last program. The compiler is now
being used to prevent the erroneous calculations. The end result
is that the stupid calculations we did in the last program are not
possible in this program so lines 50 through 53 have been commented
out. Once again, it is difficult to see the utility of this in
such a simple program. In a large program, using the compiler to
enforce the rules can pay off in a big way.
Figure 5-2 is a graphical illustration of the two objects available
for use within the calling program. Even though the square and the
box are both objects of class rectangle, their private data is
hidden from each other such that neither can purposefully or
accidentally change the others data.
This is the abstract data type mentioned earlier in this chapter,
a model with an allowable set of variables for data storage and a
set of allowable operations that can be performed on that stored
data. The only operations that can be performed on the data are
those defined by the methods which prevents many kinds of erroneous
or silly operations. Encapsulation and data hiding bind the data
and procedures, or methods, tightly together and limit the scope
and visibility of each. Once again, we have the divide and conquer
technique in which an object is separated from the rest of the code
and carefully developed in complete isolation from it. Only then
is it integrated into the rest of the code with a few very simple
interfaces.
Page 5-7
Chapter 5 - Encapsulation
HAVE YOU EVER USED THIS TECHNIQUE BEFORE?
_________________________________________________________________
A good example of the use of this technique is in the file commands
you have been using with ANSI-C. The data in the file is only
available through the predefined functions provided by your
compiler writer. You have no direct access to the actual data
because it is impossible for you to address the actual data stored
on the disk. The data is therefore private data, as far as you are
concerned, but the available functions are very much like methods
in C++. There are two aspects of this technique that really count
when you are developing software. First, you can get all of the
data you really need from the file system because the interface is
complete, but secondly, you cannot get any data that you do not
need. You are prevented from getting into the file handling system
and accidentally corrupting some data stored within it. You are
also prevented from using the wrong data because the functions
available demand a serial access to the data.
Another example is in the monitor and keyboard handling routines.
You are prevented from getting into the workings of them and
corrupting them accidentally, or on purpose if you have such a
bent, but once again, you are provided with all of the data
interfaces that you really need.
Suppose you are developing a program to analyze some
characteristics of flagpoles. You would not wish to accidentally
use some data referring to where the flagpole program was stored
on your hard disk as the height of the flagpole, nor would you wish
to use the cursor position as the flagpole thickness or color. All
code for the flagpole is developed alone, and only when it is
finished, is it available for external use. When using it, you
have a very limited number of operations which you can do with the
class. The fact that the data is hidden from you protects you from
accidentally doing such a thing when you are working at midnight
to try to meet a schedule. Once again, this is referred to as
information hiding and is one of the primary advantages of object
oriented programming over procedural techniques.
Based on the discussion given above you can see that object
oriented programming is not really new, since it has been used in
a small measure for as long as computers have been popular. The
newest development, however, is in allowing the programmer to
partition his programs in such a way that he too can practice
information hiding and reduce the debugging time.
WHAT DOES THIS COST?
_________________________________________________________________
It should be clear that this technique will cost you something in
efficiency because every access to the elements of the object will
require the time and inefficiency of a call to a function, or
perhaps I should be more proper and refer to it as a method. The
Page 5-8
Chapter 5 - Encapsulation
time saved in building a large program, however, could easily be
saved in debug time when it comes time to iron out the last few
bugs. This is because a program made up of objects that closely
match the application are much easier to understand than a program
that does not.
This is obviously such a small program that it is silly to try to
see any gain with this technique. In a real project however, it
could be a great savings if one person developed all of the details
of the rectangle, programmed it, and made it available to you to
simply use. This is exactly what has been done for you if you
consider the video monitor an object. There is a complete set of
preprogrammed and debugged routines you can use to make the monitor
do anything you wish it to do, all you have to do is study the
interface to the routines and use them, expecting them to work.
As we mentioned earlier, it is impossible for you to multiply the
size of your monitor screen by the depth of the flag pole because
that information is not available to you to use in a corruptible
way.
After you understand some of the advantages of this style of
programming, be sure to compile and execute this program.
// Chapter 5 - Program 5
#include <iostream.h>
class rectangle { // A simple class
int height;
int width;
public:
rectangle(void); // with a constuctor,
int area(void); // two methods,
void initialize(int, int);
~rectangle(void); // and a destructor
};
cout << "The area of the box is " <<
box.area() << "\n";
cout << "The area of the square is " <<
square.area() << "\n";
// cout << "The funny area is " <<
// area(square.height, box.width) << "\n";
// cout << "The bad area is " <<
// area(square.height, flag_pole.depth) << "\n";
}
// Result of execution
//
// The area of the box is 36
// The area of the square is 36
// The area of the box is 120
// The area of the square is 64
CONSTRUCTORS AND DESTRUCTORS
_________________________________________________________________
The file named CONSPOLE.CPP introduces ==================
constructors and destructors and should be CONSPOLE.CPP
examined at this time. ==================
This example program is identical to the last
example except that a constructor has been added as well as a
destructor. The constructor always has the same name as the class
itself and is declared in line 8, then defined in lines 14 through
18. The constructor is called automatically by the C++ system when
the object is declared and can therefore be of great help in
preventing the use of an uninitialized variable. When the object
named box is declared in line 46, the constructor is called
automatically by the system. The constructor sets the values of
height and width each to 6 in the object named box. This is
printed out for reference in lines 49 and 50. Likewise, when the
square is declared in line 46, the values of the height and the
width of the square are each initialized to 6 when the constructor
is called automatically.
A constructor is defined as having the same name as the class
itself. In this case both are named rectangle. The constructor
cannot have a return type associated with it since it is not
permitted to have a user defined return type. It actually has a
predefined return type, a pointer to the object itself, but we will
not be concerned about this until much later in this tutorial.
Even though both objects are assigned values by the constructor,
they are initialized in lines 58 and 59 to new values and
Page 5-9
Chapter 5 - Encapsulation
processing continues. Since we have a constructor that does the
initialization, we should probably rename the method named
initialize() something else but it illustrates the concept involved
here.
The destructor is very similar to the constructor except that it
is called automatically when each of the objects goes out of scope.
You will recall that automatic variables have a limited lifetime
since they cease to exist when the enclosing block in which they
were declared is exited. When an object is about to be
automatically deallocated, its destructor, if one exists, is called
automatically. A destructor is characterized as having the same
name as the class but with a tilde prepended to the class name.
A destructor has no return type.
A destructor is declared in line 11 and defined in lines 31 through
35. In this case the destructor only assigns zeros to the
variables prior to their being deallocated, so nothing is really
accomplished. The destructor is only included for illustration of
how it is used. If some blocks of memory were dynamically
allocated within an object, a destructor should be used to
deallocate them prior to losing the pointers to them. This would
return their memory to the free store for further use later in the
program.
It is interesting to note that if a constructor is used for an
object that is declared prior to the main program, otherwise known
as globally, the constructor will actually be executed prior to the
execution of the main program. In like manner, if a destructor is
defined for such a variable, it will execute following the
completion of execution of the main program. This will not
adversely affect your programs, but it is interesting to make note
of.
// Chapter 5 - Program 6
#include <iostream.h>
class box {
int length;
int width;
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void) {return (length * width);}
~box(void); //Destructor
};
// This method will set a box size to the two input parameters
void box::set(int new_length, int new_width)
{
length = new_length;
width = new_width;
}
main()
{
box small, medium, large; //Three boxes to work with
small.set(5, 7);
// Note that the medium box uses the values
// supplied by the constructor
large.set(15, 20);
cout << "The small box area is " << small.get_area() << "\n";
cout << "The medium box area is " << medium.get_area() << "\n";
cout << "The large box area is " << large.get_area() << "\n";
}
// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300
Examine the file named BOXES1.CPP for an example ==============
of how not to package an object for universal BOXES1.CPP
use. This packaging is actually fine for a very ==============
small program but is meant to illustrate to you
how to split your program up into smaller more
manageable files when you are developing a large program or when
you are part of a team developing a large system. The next three
example programs in this chapter will illustrate the proper method
of packaging a class.
This program is very similar to the last one with the pole
structure dropped and the class named box. The class is defined
in lines 4 through 12, the implementation of the class is given in
lines 15 through 34, and the use of the class is given in lines 37
through 50. With the explanation we gave about the last program,
Page 5-10
Chapter 5 - Encapsulation
the diligent student should have no problem understanding this
program in detail.
The method in line 10 contains the implementation for the method
as a part of the declaration because it is very simple, and because
it introduces another new topic which you will use often in C++
programming. When the implementation is included in the
declaration, it will be assembled inline wherever this function is
called leading to much faster code. This is because there is no
overhead to accomplish the call to the method. In some cases this
will lead to code that is both smaller and faster. This is yet
another illustration of the efficiency built into the C++
programming language.
Compile and execute this program in preparation for our study of
the next three examples which are a repeat of this program in a
slightly different form.
// Chapter 5 - Program 7
class box {
int length;
int width;
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void) {return (length * width);}
~box(void); //Destructor
};
// Result of execution
//
// This header file cannot be compiled or executed
THE CLASS HEADER FILE
_________________________________________________________________
If you examine BOX.H carefully, you will see ===============
that it is only the class definition. No BOX.H
details are given of how the various methods are ===============
implemented except of course for the inline
method named get_area(). This gives the
complete definition of how to use the class with no implementation
details. You would be advised to keep a hardcopy of this file
available as we study the next two files. You will notice that it
contains lines 4 through 12 of the previous example program named
BOXES1.CPP.
This is called the class header file and cannot be compiled or
executed.
// This method will set a box size to the two input parameters
void box::set(int new_length, int new_width)
{
length = new_length;
width = new_width;
}
// Result of execution
//
// This implementation file cannot be executed
THE CLASS IMPLEMENTATION FILE
_________________________________________________________________
Examine the file named BOX.CPP for the ===============
implementation of the methods declared in the BOX.CPP
class header file. Notice that the class header ===============
file is included into this file in line 2 which
contains all of the prototypes for its methods.
The code from lines 15 through 34 of BOXES1.CPP is contained in
this file which is the implementation of the methods declared in
the class named box.
Page 5-11
Chapter 5 - Encapsulation
This file can be compiled but it cannot be executed because there
is no main entry point which is required for all ANSI-C or C++
programs. When it is compiled, the object code will be stored in
the current directory and available for use by other programs. It
should be noted here that the result of compilation is usually
referred to as an object file because it contains object code.
This use of the word object has nothing to do with the word object
as used in object oriented programming. It is simply a matter of
overloading the use of the word. The practice of referring to the
compiled result as an object file began long before the method of
object oriented programming was ever considered.
The separation of the definition and the implementation is a major
step forward in software engineering. The definition file is all
the user needs in order to use this class effectively in a program.
He needs no knowledge of the actual implementation of the methods.
If he had the implementation available, he may study the code and
find a trick he could use to make the overall program slightly more
efficient, but this would lead to nonportable software and possible
bugs later if the implementor changed the implementation without
changing the interface. The purpose of object oriented programming
is to hide the implementation in such a way that the implementation
can not affect anything outside of its own small and well defined
boundary or interface.
You should compile this implementation file now and we will use the
result with the next example program.
// Chapter 5 - Program 9
#include <iostream.h>
#include "box.h"
main()
{
box small, medium, large; //Three boxes to work with
small.set(5, 7);
// Note that the medium box uses the values
// supplied by the constructor
large.set(15, 20);
cout << "The small box area is " << small.get_area() << "\n";
cout << "The medium box area is " << medium.get_area() << "\n";
cout << "The large box area is " << large.get_area() << "\n";
}
// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300
USING THE BOX OBJECT
_________________________________________________________________
Examine the file named BOXES2.CPP and you will ================
find that the class we defined previously is BOXES2.CPP
used within this file. In fact, these last ================
three programs taken together are identical to
the program named BOXES1.CPP studied earlier.
The BOX.H file is included here, in line 3, since the definition
of the box class is needed to declare three objects and use their
methods. You should have no trouble seeing that this is a repeat
of the previous program and will execute in exactly the same way.
There is a big difference in BOXES1.CPP and BOXES2.CPP as we will
see shortly.
A very important distinction must be made at this point. We are
not merely calling functions and changing the terminology a little
to say we are sending messages. There is an inherent difference
in the two operations. Since the data for each object is tightly
bound up in the object, there is no way to get to the data except
through the methods and we send a message to the object telling it
to perform some operation based on its internally stored data.
However, whenever we call a function, we take along the data for
Page 5-12
Chapter 5 - Encapsulation
it to work with as parameters since it doesn't contain its own
data.
Be sure to compile and execute this program, but when you come to
the link step, you will be required to link this program along with
the result of the compilation when you compiled the class named
box. The file is probably named BOX.OBJ that must be linked with
this file. You may need to consult the documentation for your C++
compiler to learn how to do this. Even if it seems to be a lot of
trouble to learn how to link several files together, it will be
worth your time to do so now because we will be linking several
more multifile C++ programs in the remainder of this tutorial.
If you are using Turbo C++, this is your first opportunity to use
a project file. If you are using Zortech C++ or one of the other
implementations, you can use the "make" facility included with your
compiler. Regardless of which C++ compiler you are using, it would
pay you to stop and learn how to use the multifile technique
provided with your compiler because you will need to use it several
times before the end of this tutorial. The nature of C++ tends to
drive the programmer to use many files for a given programming
project and you should develop the habit early.
INFORMATION HIDING
_________________________________________________________________
The last three example programs illustrate a method of information
hiding that can have a significant impact on the quality of
software developed for a large project. Since the only information
the user of the class really needs is the class header, that is all
he needs to be given. The details of implementation can be kept
hidden from him to prevent him from studying the details and
possibly using a quirk of programming to write some rather obtuse
code. Since he doesn't know exactly what the implementor did, he
must follow only the definition given in the header file. This can
have a significant impact on a large project. As mentioned
earlier, accidental corruption of data is prevented also.
Another reason for hiding the implementation is economic. The
company that supplied you with your C++ compiler gave you many
library functions but did not supply the source code to the library
functions, only the interface to each function. You know how to
use the file access functions but you do not have the details of
implementation, nor do you need them. Likewise a class library
industry can develop which supplies users with libraries of high
quality, completely developed and tested classes, for a licensing
fee of course. Since the user only needs the interface defined,
he can be supplied with the interface and the object (compiled)
code for the class and can use it in any way he desires. The
suppliers source code is protected from accidental or intentional
compromise and he can maintain complete control over it.
Page 5-13
Chapter 5 - Encapsulation
It is very important that you understand the principles covered in
this chapter before proceeding on to the next chapter. If you feel
you are a little weak in any of the areas covered here, you should
go over them again before proceeding on. A point that should be
made here that may be obvious to you, is that it requires some
amount of forethought to effectively use classes.
ABSTRACT DATA TYPES
_________________________________________________________________
We mentioned the abstract data type at the beginning of this
chapter and again briefly midway through, and it is time to
describe it a little more completely. An abstract data type is a
group of data, each of which can store a range of values, and a set
of methods or functions that can operate on that data. Since the
data are protected from any outside influence, it is protected and
said to be encapsulated. Also, since the data is somehow related,
it is a very coherent group of data that may be highly interactive
with each other, but with little interaction of its class outside
the scope.
The methods, on the other hand, are coupled to the outside world
through the interface, but there are a limited number of contacts
with the outside world and therefore a weak coupling with the
outside. The object is therefore said to be loosely coupled to the
outside world. Because of the tight coherency and the loose
coupling, ease of maintenance of the software is greatly enhanced.
The ease of maintenance may be the greatest benefit of object
oriented programming.
It may bother you that even though the programmer may not use the
private variables directly outside of the class, they are in plain
sight and he can see what they are and can probably make a good
guess at exactly how the class is implemented. The variables could
have been hidden completely out of sight in another file, but
because the designers of C++ wished to make the execution of the
completed application as efficient as possible, the variables were
left in the class definition where they can be seen but not used.
A function outside of a class can be defined to be a friend
function by the class which gives the friend free access to the
private members of the class. This in effect, opens a small hole
in the protective shield of the class, so it should be used very
carefully and sparingly. There are cases where it helps to make
a program much more understandable and allows controlled access to
the data. Friend functions will be illustrated in some of the
example programs later in this tutorial. It is mentioned here for
completeness of this section. A single isolated function can be
declared as a friend, as well as members of other classes, and even
Page 5-14
Chapter 5 - Encapsulation
entire classes can be given friend status if needed in a program.
Neither a constructor nor a destructor can be a friend function.
THE struct IN C++
_________________________________________________________________
The struct is still useable in C++ and operates just like it does
in ANSI-C with one addition. You can include methods in a
structure that operate on data in the same manner as in a class,
but all methods and data are automatically defaulted to be public
in a structure. Of course you can make any of the data or methods
private by defining a private section within the structure. The
structure should be used only for constructs that are truly
structures. If you are building even the simplest objects, you are
advised to use classes to define them.
A VERY PRACTICAL CLASS
_________________________________________________________________
The examples of encapsulation used in this chapter have all been
extremely simple in order to illustrate the mechanics of
encapsulation. Since it would be expedient to study a larger
example the date class is given below for your instruction. The
date class is a complete nontrivial class which can be used in any
program to get the current date and print it as an ASCII string in
any of four predefined formats. It can also be used to store any
desired date and format it for display.
// Chapter 5 - Program 10
// This date class is intended to illustrate how to write a non-
// trivial class in C++. Even though this class is non-trivial,
// it is still simple enough for a new C++ programmer to follow
// all of the details.
#ifndef DATE_H
#define DATE_H
class date {
protected:
int month; // 1 through 12
int day; // 1 through max_days
int year; // 1500 through 2200
static char out_string[25]; // Format output area
static char format; // Format to use for output
// Calculate how many days are in any given month
// Note - This is a private method which can be called only
// from within the class itself
int days_this_month(void);
public:
// Constructor - Set the date to the current date and set
// the format to 1
date(void);
// Set the date to these input parameters
// if return = 0 ---> All data is valid
// if return = 1 ---> Something out of range
int set_date(int in_month, int in_day, int in_year);
// Get the month, day, or year of the stored date
int get_month(void) { return month; };
int get_day(void) { return day; };
int get_year(void) { return year; };
// Select the desired string output format for use when the
// get_date_string is called
void set_date_format(int format_in) { format = format_in; };
// Return an ASCII-Z string depending on the stored format
// format = 1 Aug 29, 1991
// format = 2 8/29/91
// format = 3 8/29/1991
// format = 4 29 Aug 1991 Military time
// format = ? Anything else defaults to format 1
char *get_date_string(void);
// Return Jan Feb Mar Apr etc.
char *get_month_string(void);
};
#endif
Examine the file named DATE.H which is the ==============
header file for the date class. This file is so DATE.H
well commented that we don't have much else to ==============
say about it. If you understand the principles
covered in this chapter you should have no
problem understanding this class. The first thing that is new to
you is the reserved word protected which is used in line 12. We
will define this word in a couple of chapters. Until then, pretend
that it means the same thing as private and you will be close
enough for this present example. The code in lines 8 and 9 along
with line 55 will be explained shortly. For the present time,
simply pretend those lines of code are not there. Also the keyword
static as used in lines 16 and 17 will be explained later.
You should spend the time necessary to completely understand this
class header, with the exception of the new things added, before
going on to the implementation for this class.
// Chapter 5 - Program 11
// This file contains the implementation for the date class.
#include <stdio.h> // Prototype for sprintf
#include <time.h> // Prototypes for the current date
#include "date.h"
char date::format; // This defines the static data member
char date:ut_string[25]; // This defines the static string
// Constructor - Set date to current date, and
// set format to the default of 1
date::date(void)
{
time_t time_date;
struct tm *current_date;
time_date = time(NULL); // DOS system call
current_date = localtime(&time_date); // DOS system call
month = current_date->tm_mon + 1;
day = current_date->tm_mday;
year = current_date->tm_year + 1900;
format = 1;
}
// Set the date to these input parameters
// if return = 0 ---> All data is valid
// if return = 1 ---> Something out of range
int date::set_date(int in_month, int in_day, int in_year)
{
int temp = 0;
int max_days;
// The limits on the year are purely arbitrary
if (in_year < 1500) { // Check that the year is between
year = 1500; // 1500 and 2200
temp = 1;
} else {
if (in_year > 2200) {
year = 2200;
temp = 1;
} else
year = in_year;
}
if(in_month < 1) { // Check that the month is between
month = temp = 1; // 1 and 12
} else {
if (in_month > 12) {
month = 12;
temp = 1;
} else
month = in_month;
}
max_days = days_this_month();
if (in_day < 1) { // Check that the day is between
day = temp = 1; // 1 and max_days
} else {
if (in_day > max_days) {
day = max_days;
temp = 1;
} else
day = in_day;
}
// Return Jan Feb Mar Apr etc.
char *date::get_month_string(void)
{
return month_string[month];
}
// Return an ASCII-Z string depending on the stored format
// format = 1 Aug 29, 1991
// format = 2 8/29/91
// format = 3 8/29/1991
// format = 4 29 Aug 1991 Military time
// format = ? Anything else defaults to format 1
char *date::get_date_string(void)
{
switch (format) {
// This printout assumes that the year will be
// between 1900 and 1999
case 2 : sprintf(out_string, "%02d/%02d/%02d",
month, day, year - 1900);
break;
case 3 : sprintf(out_string, "%02d/%02d/%04d",
month, day, year);
break;
case 1 : // Fall through to the default case
default : sprintf(out_string, "%s %d, %04d",
month_string[month], day, year);
break;
}
return out_string;
}
// Since this is declared in the private part of the class
// header is is only available for use within the class.
// It is hidden from use outside of the class.
int date::days_this_month(void)
{
if (month != 2)
return days[month];
if (year % 4) // Not leap year
return 28;
if (year % 100) // It is leap year
return 29;
if (year % 400) // Not leap year
return 28;
return 29; // It is leap year
}
The file named DATE.CPP is the implementation ==============
for the date class and once again, there is DATE.CPP
nothing unusual or difficult about this code. ==============
It uses very simple logic to store and format
the date in a usable manner. You should study
Page 5-15
Chapter 5 - Encapsulation
this code until you understand it completely before going on to the
next example which will use the date class in a main program.
// Chapter 5 - Program 12
// This is a very limited test of the date class
#include <iostream.h>
#include "date.h"
void main(void)
{
date today, birthday;
birthday.set_date(7, 21, 1960);
cout << "Limited test of the date class\n";
cout << "Today is " << today.get_date_string() << "\n";
cout << "Birthday is " << birthday.get_date_string() << "\n";
today.set_date_format(4);
cout << "Today is " << today.get_date_string() << "\n";
cout << "Birthday is " << birthday.get_date_string() << "\n";
}
// Result of execution
// Limited test of the date class
// Today is Jan 20, 1992
// Birthday is Jul 21, 1960
// Today is 20 Jan 1992
// Birthday is 21 Jul 1960
The very simple program named USEDATE.CPP is a ===============
main program that uses the date class to list USEDATE.CPP
the current date and another date on the ===============
monitor. Once again, you should have no problem
understanding this program so nothing more will
be said about it.
You should spend the time necessary to understand these three files
because they are the starting point for a practical track in the
next few chapters. This class will be used in conjunction with
others to illustrate single and multiple inheritance. Even though
you do not understand all of the details of these files, spend
enough time that you are comfortable with the structure and the
major points of them.
We will continue our discussion of encapsulation in the next
chapter.
1. Add a method to CLAS.CPP which will supply the square of the
stored value. Include some code in the main program to read
and display the squared values.
2. Continuing with CLAS.CPP, add a constructor to initialize the
stored value to 10 and add a few lines of code to the main
program to display the values immediately following the object
definition.
3. Add an output statement to the rectangle constructor of the
program named CONSPOLE.CPP and another to the destructor to
prove to yourself that they really are called by the system
when we said they are.
4. Write a more comprehensive program to use the date class
presented at the end of this chapter.
5. Write a name class which is somewhat similar to the date class
which can store any name in three parts and return the full
name in any of several different formats such as the
following;
John Paul Doe
J. P. Doe
Doe, John Paul
and any other formats you desire.
If this is carefully planned, it could be useful to you
someday.
Page 5-16作者: createch 時間: 2009-9-5 12:30
C++ Tutorial - Chapter 6 MORE ENCAPSULATION
Chapter 6
MORE ENCAPSULATION
The purpose of this chapter is to illustrate how to use some of the
traditional aspects of C or C++ with classes and objects. Pointers
to an object as well as pointers within an object will be
illustrated. Arrays embedded within an object, and an array of
objects will be illustrated. Since objects are simply another C++
data construct, all of these things are possible and can be used
if needed.
In order to have a systematic study, we will use the program named
BOXES1.CPP from the last chapter as a starting point and we will
add a few new constructs to it for each example program. You will
recall that it was a very simple program with the class definition,
the class implementation, and the main calling program all in one
file. This was selected as a starting point because we will
eventually make changes to all parts of the program and it will be
convenient to have it all in a single file for illustrative
purposes. It must be kept in mind however that the proper way to
use these constructs is to separate them into the three files as
was illustrated in BOX.H, BOX.CPP, and BOXES2.CPP in the last
chapter. This allows the implementor of box to supply the user
with only the interface, namely BOX.H. Not giving him the
implementation file named BOX.CPP, is practicing the technique of
information hiding.
As we have said many times, it seems silly to break up such a small
program into three separate files, and it is sort of silly. The
last chapter of this tutorial will illustrate a program large
enough to require dividing the program up into many separate files.
// Chapter 6 - Program 1
#include <iostream.h>
class box {
int length;
int width;
static int extra_data; // Declaration of extra_data
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void);
int get_extra(void) {return extra_data++;}
};
// This method will set a box size to the two input parameters
void box::set(int new_length, int new_width)
{
length = new_length;
width = new_width;
}
// This method will calculate and return the area of a box instance
int box::get_area(void)
{
return (length * width);
}
main()
{
box small, medium, large, group[4]; //Seven boxes to work with
small.set(5, 7);
large.set(15, 20);
for (int index = 1 ; index < 4 ; index++) //group[0] uses default
group[index].set(index + 10, 10);
cout << "The small box area is " << small.get_area() << "\n";
cout << "The medium box area is " << medium.get_area() << "\n";
cout << "The large box area is " << large.get_area() << "\n";
for (index = 0 ; index < 4 ; index++)
cout << "The array box area is " <<
group[index].get_area() << "\n";
cout << "The extra data value is " << small.get_extra() << "\n";
cout << "The extra data value is " << medium.get_extra() << "\n";
cout << "The extra data value is " << large.get_extra() << "\n";
cout << "The extra data value is " << group[0].get_extra() << "\n";
cout << "The extra data value is " << group[3].get_extra() << "\n";
}
// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300
// The array box area is 64
// The array box area is 110
// The array box area is 120
// The array box area is 130
// The extra data value is 1
// The extra data value is 2
// The extra data value is 3
// The extra data value is 4
// The extra data value is 5
AN ARRAY OF OBJECTS
_________________________________________________________________
Examine the file named OBJARRAY.CPP for our ================
first example of an array of objects. This file OBJARRAY.CPP
is nearly identical to the file named BOX1.CPP ================
until we come to line 44 where an array of 4
boxes are declared.
Recalling the operation of the constructor you will remember that
each of the four box objects will be initialized to the values
defined within the constructor since each box will go through the
constructor as they are declared. In order to declare an array of
objects, a constructor for that object must not require any
parameters. (We have not yet illustrated a constructor with
initializing parameters, but we will in the next program.) This
Page 6-1
Chapter 6 - More Encapsulation
is an efficiency consideration since it would probably be an error
to initialize all elements of an array of objects to the same
value. We will see the results of executing the constructor when
we compile and execute the file later.
Line 49 defines a for loop that begins with 1 instead of the normal
starting index for an array leaving the first object, named
group[0], to use the default values stored when the constructor was
called. You will observe that sending a message to one of the
objects uses the same construct as is used for any object. The
name of the array followed by its index in square brackets is used
to send a message to one of the objects in the array. This is
illustrated in line 50 and the operation of that code should be
clear to you. The other method is called in the output statement
in lines 57 and 58 where the area of the four boxes in the group
array are listed on the monitor.
Another fine point should be pointed out. The integer variable
named index is declared in line 49 and is still available for use
in line 56 since we have not yet left the enclosing block which
begins in line 43 and extends to line 65.
DECLARATION AND DEFINITION OF A VARIABLE
_________________________________________________________________
An extra variable was included for illustration, the one named
extra_data in line seven. Since the keyword static is used to
modify this variable in line 7, it is an external variable and only
one copy of this variable will ever exist. All seven objects of
this class share a single copy of this variable which is global to
the objects defined in line 44.
The variable is actually only declared here which says it will
exist somewhere, but it is not defined. A declaration says the
variable will exist and gives it a name, but the definition
actually defines a place to store it somewhere in the computers
memory space. By definition, a static variable can be declared in
a class header but it cannot be defined there, so it is defined
somewhere outside of the header, usually in the implementation
file. In this case it is defined in line 16 and can then be used
throughout the class.
Line 23 of the constructor sets the single global variable to 1
each time an object is declared. Only one assignment is necessary
so the other six are actually wasted code. To illustrate that
there is only one variable shared by all objects of this class,
the method to read its value also increments it. Each time it is
read in lines 60 through 64, it is incremented and the result of
the execution proves that there is only a single variable shared
by all objects of this class. You will also note that the method
named get_extra() is defined within the class declaration so it
will be assembled into the final program as inline code.
Page 6-2
Chapter 6 - More Encapsulation
Be sure you understand this program and especially the static
variable, then compile and execute it to see if you get the same
result as listed at the end of the program.
// Chapter 6 - Program 2
#include <iostream.h>
class box {
int length;
int width;
char *line_of_text;
public:
box(char *input_line); //Constructor
void set(int new_length, int new_width);
int get_area(void);
};
// This method will set a box size to the two input parameters
void box::set(int new_length, int new_width)
{
length = new_length;
width = new_width;
}
// This method will calculate and return the area of a box instance
int box::get_area(void)
{
cout << line_of_text << "= ";
return (length * width);
}
main()
{
box small("small box "), //Three boxes to work with
medium("medium box "),
large("large box ");
small.set(5, 7);
large.set(15, 20);
cout << "The area of the ";
cout << small.get_area() << "\n";
cout << "The area of the ";
cout << medium.get_area() << "\n";
cout << "The area of the ";
cout << large.get_area() << "\n";
}
// Result of execution
//
// The area of the small box = 35
// The area is the medium box = 64
// The area is the large box = 300
A STRING WITHIN AN OBJECT
_________________________________________________________________
Examine the program named OBJSTRNG.CPP for our ================
first example of an object with an embedded OBJSTRNG.CPP
string. Actually, the object does not have an ================
embedded string, it has an embedded pointer, but
the two work so closely together that we can
study one and understand both.
You will notice that line 7 contains a pointer to a string named
line_of_text. The constructor contains an input parameter which
is a pointer to a string which will be copied to the string named
line_of_text within the constructor. We could have defined the
variable line_of_text as an actual array in the class, then used
strcpy() to copy the string into the object and everything would
have worked the same, but we will leave that as an exercise for you
at the end of this chapter. It should be pointed out that we are
not limited to passing a single parameter to a constructor. Any
number of parameters can be passed, as will be illustrated later.
You will notice that when the three boxes are declared this time,
we supply a string constant as an actual parameter with each
declaration which is used by the constructor to assign the string
pointer some data to point to. When we call get_area() in lines
48 through 53, we get the message displayed and the area returned.
It would be prudent to put these operations in separate methods
since there is no apparent connection between printing the message
and calculating the area, but it was written this way to illustrate
that it can be done. What this really says is that it is possible
to have a method that has a side effect, the message output to the
monitor, and a return value, the area of the box. However, as we
discussed in chapter 4 when we studied DEFAULT.CPP, the order of
evaluation is sort of funny, so we broke each line into two lines.
After you understand this program, compile and execute it.
// Chapter 6 - Program 3
#include <iostream.h>
class box {
int length;
int width;
int *point;
public:
box(void); //Constructor
void set(int new_length, int new_width, int stored_value);
int get_area(void) {return length * width;} // Inline
int get_value(void) {return *point;} // Inline
~box(); //Destructor
};
box::box(void) //Constructor implementation
{
length = 8;
width = 8;
point = new int;
*point = 112;
}
// This method will set a box size to the input parameters
void box::set(int new_length, int new_width, int stored_value)
{
length = new_length;
width = new_width;
*point = stored_value;
}
main()
{
box small, medium, large; //Three boxes to work with
small.set(5, 7, 177);
large.set(15, 20, 999);
cout << "The small box area is " << small.get_area() << "\n";
cout << "The medium box area is " << medium.get_area() << "\n";
cout << "The large box area is " << large.get_area() << "\n";
cout << "The small box stored value is " <<
small.get_value() << "\n";
cout << "The medium box stored value is " <<
medium.get_value() << "\n";
cout << "The large box stored value is " <<
large.get_value() << "\n";
}
// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300
// The small box stored value is 177
// The medium box stored value is 112
// The large box stored value is 999
AN OBJECT WITH AN INTERNAL POINTER
_________________________________________________________________
The program named OBJINTPT.CPP is our first ================
example program with an embedded pointer which OBJINTPT.CPP
will be used for dynamic allocation of data. ================
In line 7 we declare a pointer to an integer
variable, but it is only a pointer, there is no storage associated
with it. The constructor therefore allocates an integer type
Page 6-3
Chapter 6 - More Encapsulation
variable on the heap for use with this pointer in line 21. It
should be clear to you that the three objects created in line 45
each contain a pointer which points into the heap to three
different locations. Each object has its own dynamically allocated
variable for its own private use. Moreover each has a value of 112
stored in its dynamically allocated data because line 22 stores
that value in each of the three locations, once for each call to
the constructor.
In such a small program, there is no chance that we will exhaust
the heap, so no test is made for unavailable memory. In a real
production program, it would be expedient to test that the value
of the returned pointer is not NULL to assure that the data
actually did get allocated.
The method named set() has three parameters associated with it and
the third parameter is used to set the value of the new dynamically
allocated variable. There are two messages passed, one to the
small box and one to the large box. As before, the medium box is
left with its default values.
The three areas are displayed followed by the three stored values
in the dynamically allocated variables, and we finally have a
program that requires a destructor in order to be completely
proper. If we simply leave the scope of the objects as we do when
we leave the main program, we will leave the three dynamically
allocated variables on the heap with nothing pointing to them.
They will be inaccessible and will therefore represent wasted
storage on the heap. For that reason, the destructor is used to
delete the variable which the pointer named point is referencing
as each object goes out of existence. In this case, lines 37 and
38 assign values to variables that will be automatically deleted.
Even though these lines of code really do no good, they are legal
statements.
Actually, in this particular case, the variables will be
automatically reclaimed when we return to the operating system
because all program cleanup is done for us at that time. If this
were a function that was called by another function however, the
heap space would be wasted. This is an illustration of good
programming practice, that of cleaning up after yourself when you
no longer need some dynamically allocated variables.
One other construct should be mentioned once again, that of the
inline method implementations in line 11 and 12. As we mentioned
in chapter 5 and repeated earlier in this chapter, inline functions
can be used where speed is of the utmost in importance since the
code is assembled inline rather than by actually making a method
call. Since the code is defined as part of the declaration, the
system will assemble it inline, and a separate implementation for
these methods is not needed. If the inline code is too involved,
the compiler is allowed to ignore the inline request and will
actually assemble it as a separate method, but it will do it
invisibly to you and will probably not even tell you about it.
Page 6-4
Chapter 6 - More Encapsulation
Remember that we are interested in using information hiding and
inline code prevents hiding of the implementation, putting it out
in full view. Many times you will be more interested in speeding
up a program than you are in hiding a trivial implementation.
Since most inline methods are trivial, feel free to use the inline
code construct.
Be sure to compile and execute this program.
// Chapter 6 - Program 4
#include <iostream.h>
class box {
int length;
int width;
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void);
};
// This method will set a box size to the two input parameters
void box::set(int new_length, int new_width)
{
length = new_length;
width = new_width;
}
// This method will calculate and return the area of a box instance
int box::get_area(void)
{
return (length * width);
}
main()
{
box small, medium, large; //Three boxes to work with
box *point; //A pointer to a box
small.set(5, 7);
large.set(15, 20);
point = new box; // Use the defaults supplied by the constructor
cout << "The small box area is " << small.get_area() << "\n";
cout << "The medium box area is " << medium.get_area() << "\n";
cout << "The large box area is " << large.get_area() << "\n";
cout << "The new box area is " << point->get_area() << "\n";
point->set(12, 12);
cout << "The new box area is " << point->get_area() << "\n";
delete point;
}
// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300
// The new box area is 64
// The new box area is 144
A DYNAMICALLY ALLOCATED OBJECT
_________________________________________________________________
Examine the file named OBJDYNAM.CPP for our ================
first look at a dynamically allocated object. OBJDYNAM.CPP
This is not any different than any other ================
dynamically allocated object, but an example is
always helpful.
In line 39 we declare a pointer to an object of type box and since
it is only a pointer with nothing to point to, we dynamically
allocate an object for it in line 44, with the object being created
on the heap just like any other dynamically allocated variable.
When the object is created in line 44, the constructor is called
automatically to assign values to the two internal storage
variables. Note that the constructor is not called when the
pointer is declared since there is nothing to initialize. It is
called when the object is allocated.
Reference to the components of the object are handled in much the
same way that structure references are made, through use of the
pointer operator as illustrated in lines 50 through 52. Of course
you can use the pointer dereferencing method without the arrow such
as (*point).set(12, 12); as a replacement for line 51 but the arrow
notation is much more universal and should be used. Finally, the
object is deleted in line 54 and the program terminates. If there
were a destructor for this class, it would be called as part of the
delete statement to clean up the object prior to deletion.
You have probably noticed by this time that the use of objects is
not much different from the use of structures. Be sure to compile
and execute this program after you have studied it thoroughly.
// Chapter 6 - Program 5
#include <iostream.h>
class box {
int length;
int width;
box *another_box;
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void);
void point_at_next(box *where_to_point);
box *get_next(void);
};
// This method will set a box size to the two input parameters
void box::set(int new_length, int new_width)
{
length = new_length;
width = new_width;
}
// This method will calculate and return the area of a box instance
int box::get_area(void)
{
return (length * width);
}
// This method causes the pointer to point to the input parameter
void box::point_at_next(box *where_to_point)
{
another_box = where_to_point;
}
// This method returns the box the current one points to
box *box::get_next(void)
{
return another_box;
}
main()
{
box small, medium, large; //Three boxes to work with
box *box_pointer; //A pointer to a box
small.set(5, 7);
large.set(15, 20);
cout << "The small box area is " << small.get_area() << "\n";
cout << "The medium box area is " << medium.get_area() << "\n";
cout << "The large box area is " << large.get_area() << "\n";
box_pointer = &small;
box_pointer = box_pointer->get_next();
cout << "The box pointed to has area " <<
box_pointer->get_area() << "\n";
}
// Result of execution
//
// The small box area is 35
// The medium box area is 64
// The large box area is 300
// The box pointed to has area 64
AN OBJECT WITH A POINTER TO ANOTHER OBJECT
_________________________________________________________________
The program named OBJLIST.CPP contains an object ===============
with an internal reference to another object of OBJLIST.CPP
its own class. This is the standard structure ===============
used for a singly linked list and we will keep
the use of it very simple in this program.
Page 6-5
Chapter 6 - More Encapsulation
The constructor contains the statement in line 21 which assigns the
pointer the value of NULL to initialize the pointer. This is a
good idea for all of your programming, don't allow any pointer to
point off into space, but initialize all pointers to something.
By assigning the pointer within the constructor, you guarantee that
every object of this class will automatically have its pointer
initialized. It will be impossible to overlook the assignment of
one of these pointers.
Two additional methods are declared in lines 12 and 13 with the one
in line 13 having a construct we have not yet mentioned in this
tutorial. This method returns a pointer to an object of the box
class. As you are aware, you can return a pointer to a struct in
standard C, and this is a parallel construct in C++. The
implementation in lines 48 through 51 returns the pointer stored
within the object. We will see how this is used when we get to the
actual program.
An extra pointer named box_pointer is declared in the main program
for use later and in line 66 we make the embedded pointer within
the small box point to the medium box, and in line 67 we make the
embedded pointer within the medium box point to the large box. We
have effectively generated a linked list with three elements. In
line 69 we make the extra pointer point to the small box.
Continuing in line 70 we use it to refer to the small box and
update it to the value contained in the small box which is the
address of the medium box. We have therefore traversed from one
element of the list to another by sending a message to one of the
objects. If line 70 were repeated exactly as shown, it would cause
the extra pointer to refer to the large box, and we would have
traversed the entire linked list which is only composed of three
elements.
ANOTHER NEW KEYWORD this
_________________________________________________________________
Another new keyword is available in C++, the keyword this. The
word this is defined within any object as being a pointer to the
object in which it is contained. It is implicitly declared as;
class_name *this;
and is initialized to point to the object for which the member
function is invoked. This pointer is most useful when working with
pointers and especially with a linked list when you need to
reference a pointer to the object you are inserting into the list.
The keyword this is available for this purpose and can be used in
any object. Actually the proper way to refer to any variable
within a list is through use of the predefined pointer this, by
writing this->variable_name, but the compiler assumes the pointer
is used, and we can simplify every reference by omitting the
Page 6-6
Chapter 6 - More Encapsulation
pointer. Use of the keyword this is not illustrated in a program
at this point, but will be used in one of the larger example
programs later in this tutorial.
You should study this program until you understand it completely
then compile and execute it in preparation for our next example
program.
// Chapter 6 - Program 6
#include <iostream.h>
class box {
int length;
int width;
box *another_box;
public:
box(void); //Constructor
void set(int new_length, int new_width);
int get_area(void);
void point_at_next(box *where_to_point);
box *get_next(void);
};
// This method will set a box size to the two input parameters
void box::set(int new_length, int new_width)
{
length = new_length;
width = new_width;
}
// This method will calculate and return the area of a box instance
int box::get_area(void)
{
return (length * width);
}
// This method causes the pointer to point to the input parameter
void box::point_at_next(box *where_to_point)
{
another_box = where_to_point;
}
// This method returns the box that this one points to
box *box::get_next(void)
{
return another_box;
}
main()
{
box *start = NULL; // Always points to the start of the list
box *temp; // Working pointer
box *box_pointer; // Used for box creation
// Generate the list
for (int index = 0 ; index < 10 ; index++ ) {
box_pointer = new box;
box_pointer->set(index + 1, index + 3);
if (start == NULL)
start = box_pointer; // First element in list
else
temp->point_at_next(box_pointer); // Additional element
temp = box_pointer;
}
// Print the list out
temp = start;
do {
cout << "The area is " << temp->get_area() << "\n";
temp = temp->get_next();
} while (temp != NULL);
// Delete the list
temp = start;
do {
temp = temp->get_next();
delete start;
start = temp;
} while (temp != NULL);
}
// Result of execution
//
// The area is 3
// The area is 8
// The area is 15
// The area is 24
// The area is 35
// The area is 48
// The area is 63
// The area is 80
// The area is 99
// The area is 120
A LINKED LIST OF OBJECTS
_________________________________________________________________
The next example program in this chapter is ===============
named OBJLINK.CPP and is a complete example of OBJLINK.CPP
a linked list written in object oriented ===============
notation.
This program is very similar to the last one. In fact it is
identical until we get to the main program. You will recall that
in the last program the only way we had to set or use the embedded
pointer was through use of the two methods named point_at_next()
and get_next() which are listed in lines 40 through 51 of the
present program. We will use these to build up our linked list
then traverse and print the list. Finally, we will delete the
entire list to free the space on the heap.
In lines 56 to 58 we declare three pointers for use in the program.
The pointer named start will always point to the beginning of the
list, but temp will move down through the list as we create it.
The pointer named box_pointer will be used for the creation of each
object. We execute the loop in lines 61 through 69 to generate
the list where line 62 dynamically allocates a new object of the
box class and line 63 fills it with nonsense data for illustration.
If this is the first element in the list, the start pointer is set
to point to this element, but if elements already exist, the last
element in the list is assigned to point to the new element. In
either case, the temp pointer is assigned to point to the last
element of the list, in preparation for adding another element if
there is another element to be added.
In line 72, the pointer named temp is pointed to the first element
and it is used to increment its way through the list by updating
itself in line 75 during each pass through the loop. When temp has
the value of NULL, which it gets from the last element of the list,
we are finished traversing the list.
Finally, we delete the entire list by starting at the beginning and
deleting one element each time we pass through the loop in lines
79 through 84.
A careful study of the program will reveal that it does indeed
generate a linked list of ten elements, each element being an
object of class box. The length of this list is limited by the
Page 6-7
Chapter 6 - More Encapsulation
practicality of how large a list we desire to print out, but it
could be lengthened to many thousands of these simple elements
provided you have enough memory available to store them all.
Once again, the success of the dynamic allocation is not checked
as it should be in a correctly written program. Be sure to compile
and execute this example program.
// Chapter 6 - Program 7
#include <iostream.h>
class mail_info {
int shipper;
int postage;
public:
void set(int in_class, int in_postage)
{shipper = in_class; postage = in_postage; }
int get_postage(void) {return postage;}
};
class box {
int length;
int width;
mail_info label;
public:
void set(int l, int w, int s, int p) {
length = l;
width = w;
label.set(s, p); }
int get_area(void) {return length * width;}
};
cout << "The area is " << small.get_area() << "\n";
cout << "The area is " << medium.get_area() << "\n";
cout << "The area is " << large.get_area() << "\n";
}
// Result of Execution
//
// The area is 8
// The area is 30
// The area is 80
Examine the program named NESTING.CPP for an =================
example of nesting classes which results in NESTING.CPP
nested objects. A nested object could be =================
illustrated with your computer in a rather
simple manner. The computer itself is composed
of many items which work together but work entirely differently,
such as a keyboard, a disk drive, and a power supply. The computer
is composed of these very dissimilar items and it is desireable to
discuss the keyboard separately from the disk drive because they
are so different. A computer class could be composed of several
objects that are dissimilar by nesting the dissimilar classes
within the computer class.
If however, we wished to discuss disk drives, we may wish to
examine the characteristics of disk drives in general, then examine
the details of a hard disk, and the differences of floppy disks.
This would involve inheritance because much of the data about both
drives could be characterized and applied to the generic disk drive
then used to aid in the discussion of the other three. We will
study inheritance in the next three chapters, but for now we will
look at the embedded or nested class.
This example program contains a class named box which contains an
object of another class embedded within it in line 16, the
mail_info class. This object is available for use only within the
class implementation of box because that is where it is defined.
The main program has objects of class box defined but no objects
of class mail_info, so the mail_info class cannot be referred to
in the main program. In this case, the mail_info class object is
meant to be used internally to the box class and one example is
given in line 21 where a message is sent to the label.set() method
to initialize the variables. Additional methods could be used as
needed, but these are given as an illustration of how they can be
called.
Of prime importance is the fact that there are never any objects
of the mail_info class declared directly in the main program, they
are inherently declared when the enclosing objects of class box are
declared. Of course objects of the mail_info class could be
declared and used in the main program if needed, but they are not
in this example program. In order to be complete, the box class
Page 6-8
Chapter 6 - More Encapsulation
should have one or more methods to use the information stored in
the object of the mail_info class. Study this program until you
understand the new construct, then compile and execute it.
If the class and the nested classes require parameter lists for
their respective constructors an initialization list can be given.
This will be discussed and illustrated later in this tutorial.
// Chapter 6 - Program 8
#include <iostream.h>
class box {
int length;
int width;
public:
void set(int l, int w) {length = l; width = w;}
int get_area(void) {return length * width;}
friend box operator+(box a, box b); // Add two boxes
friend box operator+(int a, box b); // Add a constant to a box
friend box operator*(int a, box b); // Multiply a box by a constant
};
box operator+(box a, box b) // Add two boxes' widths together
{
box temp;
temp.length = a.length;
temp.width = a.width + b.width;
return temp;
}
box operator+(int a, box b) // Add a constant to a box
{
box temp;
temp.length = b.length;
temp.width = a + b.width;
return temp;
}
box operator*(int a, box b) // Multiply a box by a constant
{
box temp;
temp.length = a * b.length;
temp.width = a * b.width;
return temp;
}
cout << "The area is " << small.get_area() << "\n";
cout << "The area is " << medium.get_area() << "\n";
cout << "The area is " << large.get_area() << "\n";
temp = small + medium;
cout << "The new area is " << temp.get_area() << "\n";
temp = 10 + small;
cout << "The new area is " << temp.get_area() << "\n";
temp = 4 * large;
cout << "The new area is " << temp.get_area() << "\n";
}
// Result of Execution
//
// The area is 8
// The area is 30
// The area is 80
// The new area is 20
// The new area is 28
// The new area is 1280
The example file named OPOVERLD.CPP contains ================
examples of overloading operators. This allows OPOVERLD.CPP
you to define a class of objects and redefine ================
the use of the normal operators. The end result
is that objects of the new class can be used in
as natural a manner as the predefined types. In fact, they seem
to be a part of the language rather than your own add-on.
In this case we overload the + operator and the * operator, with
the declarations in lines 10 through 12, and the definitions in
lines 16 through 40. The methods are declared as friend functions
so we can use the double parameter functions as listed. If we did
not use the friend construct, the function would be a part of one
of the objects and that object would be the object to which the
message was sent. Including the friend construct allows us to
separate this method from the object and call the method with infix
notation. Using this technique, it can be written as object1 +
object2 rather than object1.operator+(object2). Also, without the
friend construct we could not use an overloading with an int type
variable for the first parameter because we can not send a message
to an integer type variable such as int.operator+(object). Two of
the three operator overloadings use an int for the first parameter
so it is necessary to declare them as friend functions.
There is no upper limit to the number of overloadings for any given
operator. Any number of overloadings can be used provided the
parameters are different for each particular overloading.
The header in line 16 illustrates the first overloading where the
+ operator is overloaded by giving the return type followed by the
keyword operator and the operator we wish to overload. The two
formal parameters and their types are then listed in the
parentheses and the normal function operations are given in the
implementation of the function in lines 18 through 21. The
observant student will notice that the implementation of the friend
functions are not actually a part of the class because the class
name is not prepended onto the method name in line 16. There is
nothing unusual about this implementation, it should be easily
understood by you at this point. For purposes of illustration,
some silly mathematics are performed in the method implementation,
but any desired operations can be done.
Page 6-9
Chapter 6 - More Encapsulation
The biggest difference occurs in line 56 where this method is
called by using the infix notation instead of the usual message
sending format. Since the variables small and medium are objects
of the box class, the system will search for a way to use the +
operator on two objects of class box and will find it in the
overloaded operator+ method we have just discussed. The operations
within the method implementation can be anything we need them to
be, and they are usually much more meaningful than the silly math
included here.
In line 58 we ask the system to add an int type constant to an
object of class box, so the system finds the other overloading of
the + operator beginning in line 25 to perform this operation.
Also in line 60 we ask the system to use the * operator to do
something to an int constant and an object of class box, which it
satisfies by finding the method in lines 34 through 40. Note that
it would be illegal to attempt to use the * operator the other way
around, namely large * 4 since we did not define a method to use
the two types in that order. Another overloading could be given
with reversed types, and we could use the reverse order in a
program.
You will notice that when using operator overloading, we are also
using function name overloading since some of the function names
are the same.
When we use operator overloading in this manner, we actually make
our programs look like the class is a natural part of the language
since it is integrated into the language so well. C++ is therefore
an extendible language and can be molded to fit the mechanics of
the problem at hand.
Each new topic we study has its pitfalls which must be warned
against and the topic of operator overloading seems to have the
record for pitfalls since it is so prone to misuse and has several
problems. The overloading of operators is only available for
classes, you cannot redefine the operators for the predefined
simple types. This would probably be very silly anyway since the
code could be very difficult to read if you changed some of them
around.
The logical and (&&) and the logical or (||) operators can be
overloaded for the classes you define, but they will not operate
as short circuit operators. All members of the logical
construction will be evaluated with no regard concerning the
outcome. Of course the normal predefined logical operators will
continue to operate as short circuit operators as expected, but not
the overloaded ones.
Page 6-10
Chapter 6 - More Encapsulation
If the increment (++) or decrement (--) operators are overloaded,
the system has no way of telling whether the operators are used as
preincrement or postincrement (or predecrement or postdecrement)
operators. Which method is used is implementation dependent, so
you should use them in such a way that it doesn't matter which is
used.
Be sure to compile and execute OPOVERLD.CPP before continuing on
to the next example program.
// Chapter 6 - Program 9
#include <iostream.h>
class many_names {
int length;
int width;
public:
many_names(void); // Constructors
many_names(int len);
many_names(int len, int wid);
void display(void); // Display functions
void display(int one);
void display(int one, int two);
void display(float number);
};
// Result of execution
//
// From void display function, area = 64
// From int display function, area = 64
// From two int display function, area = 64
// From float display function, area = 64
// From void display function, area = 80
// From float display function, area = 180
FUNCTION OVERLOADING IN A CLASS
_________________________________________________________________
Examine the program named FUNCOVER.CPP for an ================
example of function name overloading within a FUNCOVER.CPP
class. In this program the constructor is ================
overloaded as well as one of the methods to
illustrate what can be done.
This file illustrates some of the uses of overloaded names and a
few of the rules for their use. You will recall that the function
selected is based on the number and types of the formal parameters
only. The type of the return value is not significant in overload
resolution.
In this case there are three constructors. The constructor which
is actually called is selected by the number and types of the
parameters in the definition. In line 77 of the main program the
three objects are declared, each with a different number of
parameters and inspection of the results will indicate that the
correct constructor was called based on the number of parameters.
In the case of the other overloaded methods, the number and type
of parameters is clearly used to select the proper method. You
will notice that one method uses a single integer and another uses
a single float type variable, but the system is able to select the
correct one. As many overloadings as desired can be used provided
that all of the parameter patterns are unique.
You may be thinking that this is a silly thing to do but it is, in
fact, a very important topic. Throughout this tutorial we have
been using an overloaded operator and you haven't been the least
confused over it. It is the cout operator which operates as an
overloaded function since the way it outputs data is a function of
the type of its input variable or the field we ask it to display.
Many programming languages have overloaded output functions so you
can output any data with the same function name.
Be sure to compile and execute this program.
Page 6-11
Chapter 6 - More Encapsulation
SEPARATE COMPILATION
_________________________________________________________________
Separate compilation is available with C++ and it follows the
identical rules as given for ANSI-C separate compilation. As
expected, separately compiled files can be linked together.
However, since classes are used to define objects, the nature of
C++ separate compilation is considerably different from that used
for ANSI-C. This is because the classes used to create the objects
are not considered as external variables, but as included classes.
This makes the overall program look different from a pure ANSI-C
program. Your programs will take on a different appearance as you
gain experience in C++.
ANOTHER PRACTICAL EXAMPLE
_________________________________________________________________
Once again we come to the practical part of this lesson where we
study a practical class that can actually be used in a program but
is still simple enough for the student to completely understand.
// Chapter 6 - Program 10
// This is probably the minimum usable time class, but is intended as
// an illustration of a class rather than to build an all inclusive
// class for future use. Each student can develop his own to suit
// his own taste.
#ifndef TIME_H
#define TIME_H
class time_of_day {
protected:
int hour; // 0 through 23
int minute; // 0 through 59
int second; // 0 through 59
static char format; // Format to use for output
static char out_string[25]; // Format output area
public:
// Constructor - Set time to current time and format to 1
time_of_day(void);
time_of_day(int H) {hour = H; minute = 0; second = 0; };
time_of_day(int H, int M) {hour = H; minute = M; second = 0; };
time_of_day(int H, int M, int S) {hour = H;
minute = M; second = S; };
// Set the time to these input values
// return = 0 ---> data is valid
// return = 1 ---> something is out of range
int set_time(void);
int set_time(int hour_in);
int set_time(int hour_in, int minute_in);
int set_time(int hour_in, int minute_in, int second_in);
// Select string output format
void set_time_format(int format_in) { format = format_in; };
// Return an ASCII-Z string depending on the stored format
// format = 1 13:23:12
// format = 2 13:23
// format = 3 1:23 PM
char *get_time_string(void);
};
#endif
In the last chapter we studied the date class ================
and in this chapter we will study a simple time TIME.H
class. You should begin by studying the file ================
named TIME.H which will look very similar to the
date class header. The only major difference in
this class from the date class is the overloaded constructors and
methods. The program is a very practical example that illustrates
very graphically that many constructor overloadings are possible.
// Chapter 6 - Program 11
#include <stdio.h> // For the sprintf function
#include <time.h> // For the time & localtime functions
#include "time.h" // For the class header
char time_of_day::format; // This defines the static data member
char time_of_day:ut_string[25]; // This defines the string
// Constructor - Set time to current time
// and format to 1
time_of_day::time_of_day(void)
{
time_t time_date;
struct tm *current_time;
// Set the time to these input values
// return = 0 ---> data is valid
// return = 1 ---> something out of range
int time_of_day::set_time(void) {return set_time(0, 0, 0); };
int time_of_day::set_time(int H) {return set_time(H, 0, 0); };
int time_of_day::set_time(int H, int M) {return set_time(H, M, 0); };
int time_of_day::set_time(int hour_in, int minute_in, int second_in)
{
int error = 0;
if (second_in < 0) {
second_in = 0;
error = 1;
} else if (second_in > 59) {
second_in = 59;
error = 1;
}
second = second_in;
return error;
}
// Return an ASCII-Z string depending on the stored format
// format = 1 13:23:12
// format = 2 13:23
// format = 3 1:23 PM
char *time_of_day::get_time_string(void)
{
switch (format) {
case 2 : sprintf(out_string, "%2d:%02d", hour, minute);
break;
case 3 : if (hour == 0)
sprintf(out_string, "12:%02d AM", minute);
else if (hour < 12)
sprintf(out_string, "%2d:%02d AM", hour, minute);
else if (hour == 12)
sprintf(out_string, "12:%02d PM", minute);
else
sprintf(out_string, "%2d:%02d PM",
hour - 12, minute);
break;
case 1 : // Fall through to default so the default is also 1
default : sprintf(out_string, "%2d:%02d:%02d",
hour, minute, second);
break;
}
return out_string;
}
The implementation for the time class is given ================
in the file named TIME.CPP. Once again, the TIME.CPP
code is very simple and you should have no ================
problem understanding this example in its
entirety. It should be pointed out that three
of the four overloadings actually call the fourth so that the code
did not have to be repeated four times. This is a perfectly good
coding practice and illustrates that other member functions can be
called from within the implementation.
void main(void)
{
date today;
time_of_day now, lunch(12, 15);
cout << "This program executed on " << today.get_date_string() <<
" at " << now.get_time_string() << "\n";
cout << "We are planning lunch at " << lunch.get_time_string() <<
" tomorrow.\n";
lunch.set_time(13);
cout << "We decided to move lunch to "<< lunch.get_time_string()
<< " due to a late meeting.\n";
}
// Result of execution
// This program executed on Jan 20, 1991 at 10:34:16
// We are planning lunch at 12:15:00 tomorrow.
// We decided to move lunch to 13:00:00 due to a late meeting.
The example program named USETIME.CPP is a very ===============
simple program that uses the time class in a USETIME.CPP
very rudimentary way as an illustration for you. ===============
You should be able to understand this program in
a very short time. It will be to your advantage
to completely understand the practical example programs given at
the end of the last chapter and the end of this chapter. As
mentioned above, we will use the time class and the date class as
the basis for both single and multiple inheritance in the next
three chapters.
Page 6-12
Chapter 6 - More Encapsulation
WHAT SHOULD BE THE NEXT STEP?
_________________________________________________________________
At this point you have learned enough C++ to write meaningful
programs and it would be to your advantage to stop studying and
begin using the knowledge you have gained. Because C++ is an
extension to ANSI-C, it can be learned in smaller pieces than would
be required if you are learning a completely new language. You
have learned enough to study and completely understand the example
program given in chapter 12, the Flyaway adventure game. You
should begin studying this program now.
One of your biggest problems is learning to think in terms of
object oriented programming. It is not a trivial problem if you
have been programming in procedural languages for any significant
length of time. However, it can be learned by experience, so you
should begin trying to think in terms of classes and objects
immediately. Your first project should use only a small number of
objects and the remainder of code can be completed in standard
procedural programming techniques. As you gain experience, you
will write more of the code for any given project using classes and
objects but every project will eventually be completed in
procedural code.
After you have programmed for a while using the techniques covered
up to this point in the tutorial, you can continue on to the next
few chapters which will discuss inheritance and virtual functions.
1. Modify OBJDYNAM.CPP to make the objects named small and medium
pointers, then dynamically allocate them prior to using them.
2. Modify the loop in line 61 of OBJLINK.CPP so that the loop
will store 1000 elements in the list before stopping. You
will probably wish to remove the printout from line 74 so the
program will stop in a reasonable time. You may also get an
integer overflow indicated by wrong answers if you send a
message to get_area() with such large numbers. That will
depend upon your compiler. You should also add a test to
assure that the memory did not become exhausted after each
dynamic allocation.
3. Write a program that uses both the date and time classes in
a meaningful manner. No answer will be given in the ANSWERS
directory for this exercise since it is so straight forward.
These classes can be used in all of your future C++ programs
to time stamp the time and date of execution.
Page 6-13作者: createch 時間: 2009-9-5 12:30
C++ Tutorial Chapter 7: INHERITANCE
Chapter 7
INHERITANCE
One reason to use inheritance is that it allows you to reuse code
from a previous project but gives you the flexibility to slightly
modify it if the old code doesn't do exactly what you need for the
new project. It doesn't make sense to start every new project from
scratch since some code will certainly be repeated in several
programs and you should strive to build on what you did previously.
Moreover, it is easy to make an error if we try to modify the
original class, but we are less likely to make an error if we leave
the original alone and only add to it. Another reason for using
inheritance is if the project requires the use of several classes
which are very similar but slightly different.
In this chapter we will concentrate on the mechanism of inheritance
and how to build it into a program. A better illustration of why
you would use inheritance will be given in later chapters where we
will discuss some practical applications of object oriented
programming.
The principle of inheritance is available with several modern
programming languages and is handled slightly differently with
each. C++ allows you to inherit all or part of the members and
methods of a class, modify some, and add new ones not available in
the parent class. You have complete flexibility, and as usual,
the method used with C++ has been selected to result in the most
efficient code execution.
A SIMPLE CLASS TO START WITH
// Chapter 7 - Program 1
// vehicle header file
#ifndef VEHICLE_H
#define VEHICLE_H
class vehicle {
protected:
int wheels;
float weight;
public:
void initialize(int in_wheels, float in_weight);
int get_wheels(void);
float get_weight(void);
float wheel_loading(void);
};
Examine the file named VEHICLE.H for a simple =================
class which we will use to begin our study of VEHICLE.H
inheritance in this chapter. There is nothing =================
unusual about this class header, it has been
kept very simple. It consists of four simple
methods which can be used to manipulate data pertaining to our
vehicle. What each method does is not especially important at this
time. We will eventually refer to this as a base class or parent
class, but for the time being, we will simply use it like any other
class to show that it is indeed identical to the classes already
studied. Note that we will explain the added keyword protected
shortly.
Ignore lines 4, 5, and 17 until the end of this chapter where they
will be explained in detail. This file cannot be compiled or
executed because it is only a header file.
Page 7-1
Chapter 7 - Inheritance
// Chapter 7 - Program 2
#include "vehicle.h"
// initialize to any data desired
void vehicle::initialize(int in_wheels, float in_weight)
{
wheels = in_wheels;
weight = in_weight;
}
// get the number of wheels of this vehicle
int vehicle::get_wheels()
{
return wheels;
}
// return the weight of this vehicle
float vehicle::get_weight()
{
return weight;
}
// return the weight on each wheel
float vehicle::wheel_loading()
{
return weight/wheels;
}
THE IMPLEMENTATION FOR VEHICLE
_________________________________________________________________
Examine the file named VEHICLE.CPP and you will ===============
find that it is the implementation of the VEHICLE.CPP
vehicle class. The initialize() method assigns ===============
the values input as parameters to the wheels and
weight variables. We have methods to return the
number of wheels and the weight, and finally, we have one that does
a trivial calculation to return the loading on each wheel. We will
have a few examples of methods that do some significant processing
later, but at this point, we are more interested in learning how
to set up the interface to the classes, so the implementations will
be kept trivial.
As stated above, this is a very simple class which will be used in
the next program. Later in this tutorial we will use it as a base
class. You should compile this class at this time in preparation
for the next example program, but you cannot execute it because
there is no entry point.
// Chapter 7 - Program 3
#include <iostream.h>
#include "vehicle.h"
cout << "The car has " << car.get_wheels() << " wheels.\n";
cout << "The truck has a loading of " << truck.wheel_loading()
<< " pounds per wheel.\n";
cout << "The motorcycle weighs " << motorcycle.get_weight()
<< " pounds.\n";
cout << "The sedan weighs " << sedan.get_weight()
<< " pounds, and has " << sedan.get_wheels()
<< " wheels.\n";
}
// Result of execution
//
// The car has 4 wheels.
// The truck has a loading of 2500 pounds per wheel.
// The motorcycle weighs 900 pounds.
// The sedan weighs 3000 pounds, and has 4 wheels.
USING THE VEHICLE CLASS
_________________________________________________________________
The file named TRANSPRT.CPP uses the vehicle ================
class in exactly the same manner as we TRANSPRT.CPP
illustrated in the last chapter. This should be ================
an indication to you that the vehicle class is
truly nothing more than a normal class as
defined in C++. We will make it a little special, however, by
using it unmodified as a base class in the next few example files
to illustrate inheritance. Inheritance uses an existing class and
adds functionality to it to accomplish another, possibly more
complex job.
You should have no problem understanding the operation of this
program. It declares four objects of the vehicle class,
initializes them, and prints out a few of the data points to
illustrate that the vehicle class can be used as a simple class
because it is a simple class. We are referring to it as a simple
class as opposed to calling it a base class or derived class as we
will do shortly.
If you thoroughly understand this program, you should compile and
execute it, remembering to link the vehicle object file with this
object file.
// Chapter 7 - Program 4
#ifndef CAR_H
#define CAR_H
#include "vehicle.h"
class car : public vehicle {
int passenger_load;
public:
void initialize(int in_wheels, float in_weight, int people = 4);
int passengers(void);
};
#endif
// Result of execution
//
// (this file cannot be executed)
OUR FIRST DERIVED CLASS
_________________________________________________________________
Examine the file named CAR.H for our first example of the use of
a derived class or child class. The vehicle class is inherited due
Page 7-2
Chapter 7 - Inheritance
to the ": public vehicle" added to line 4. This ==============
derived class named car is composed of all of CAR.H
the information included in the base class ==============
vehicle, and all of its own additional
information. Even though we did nothing to the
class named vehicle, we made it into a base class because of the
way we are using it here. To go a step further, even though it
will be used as a base class in an example program later in this
chapter, there is no reason it cannot continue to be used as a
simple class in the previous example program. In fact, it can be
used as a single class and a base class in the same program. The
question of whether it is a simple class or a base class is
answered by the way it is used.
A discussion of terminology is needed here. When discussing object
oriented programming in general, a class that inherits another is
often called a derived class or a child class, but the most proper
term as defined for C++ is a derived class. Since these terms are
very descriptive, and most writers tend to use the terms
interchangeably, we will also use these terms in this tutorial.
Likewise the proper C++ terminology for the inherited class is to
call it a base class, but parent class and super class are
sometimes used.
A base class is a rather general class which can cover a wide range
of objects, whereas a derived class is somewhat more restricted but
at the same time more useful. For example if we had a base class
named programming language and a derived class named C++, then we
could use the base class to define Pascal, Ada, C++, or any other
programming language, but it would not tell us about the use of
classes in C++ because it can only give a general view of each
language. On the other hand, the derived class named C++ could
define the use of classes, but it could not be used to describe the
other languages because it is too narrow. A base class tends to
be more general, and a derived class is more specific.
In this case, the vehicle base class can be used to declare objects
that represent trucks, cars, bicycles, or any number of other
vehicles you can think up. The class named car however can only
be used to declare an object that is of type car because we have
limited the kinds of data that can be intelligently used with it.
The car class is therefore more restrictive and specific than the
vehicle class. The vehicle class is more general than the car
class.
If we wished to get even more specific, we could define a derived
class using car as the base class and name it sports_car and
include such information as red_line_limit for the tachometer which
would be silly for the family station wagon. The car class would
therefore be used as a derived class and a base class at the same
time, so it should be clear that these names refer to how a class
is used.
Page 7-3
Chapter 7 - Inheritance
HOW DO WE DECLARE A DERIVED CLASS?
_________________________________________________________________
Enough generalities about classes, let's get down to the specifics.
A derived class is defined by including the header file for the
base class as is done in line 2, then the name of the base class
is given following the name of the derived class separated by a
colon as is illustrated in line 4. Ignore the keyword public
immediately following the colon in this line. It is optional and
we will study it in detail in the next chapter. All objects
declared as being of class car therefore are composed of the two
variables from the class vehicle because they inherit those
variables, and the single variable declared in the class car named
passenger_load.
An object of this class will have three of the four methods of
vehicle and the two new ones declared here. The method named
initialize() which is part of the vehicle class will not be
available here because it is hidden by the local version of
initialize() which is a part of the car class. The local method
will be used if the name is repeated allowing you to customize your
new class. Figure 7-1 is a graphical representation of an object
of this class.
Note once again that the implementation for the base class only
needs to be supplied in its compiled form. The source code for the
implementation can be hidden for economic reasons to aid software
developers. Hiding the source code also allows the practice of
information hiding. The header for the base class must be
available as a text file since the class definitions are required
in order to use the class.
int car::passengers(void)
{
return passenger_load;
}
// Result of execution
//
// (this file cannot be executed)
THE CAR CLASS IMPLEMENTATION
_________________________________________________________________
Examine the file named CAR.CPP which is the ===============
implementation file for the car class. The CAR.CPP
first thing you should notice is that this file ===============
has no indication of the fact that it is a
derived class of any other file, that can only
be determined by inspecting the header file for the class. Since
we can't tell if it is a derived class or not, it is written in
exactly the same way as any other class implementation file.
The implementations for the two new methods are written in exactly
the same way as methods are written for any other class. If you
think you understand this file, you should compile it for later
use.
// Chapter 7 - Program 6
#ifndef TRUCK_H
#define TRUCK_H
#include "vehicle.h"
class truck : public vehicle {
int passenger_load;
float payload;
public:
void init_truck(int how_many = 2, float max_load = 24000.0);
float efficiency(void);
int passengers(void);
};
#endif
// Result of execution
//
// (this file cannot be executed)
Page 7-4
Chapter 7 - Inheritance
ANOTHER DERIVED CLASS
_________________________________________________________________
Examine the file named TRUCK.H for an example of ===============
another class that uses the vehicle class and TRUCK.H
adds to it. Of course, it adds different things ===============
to it because it will specialize in those things
that pertain to trucks. In fact it adds two
more variables and three methods. Once again, ignore the keyword
public following the colon in line 7 for a few minutes and we will
cover it in detail in the next chapter of this tutorial. See
figure 7-2.
A very important point that must be made is that the car class and
the truck class have absolutely nothing to do with each other, they
only happen to be derived classes of the same base class or parent
class as it is sometimes called.
Note that both the car and the truck classes have methods named
passengers() but this causes no problems and is perfectly
acceptable. If classes are related in some way, and they certainly
are if they are both derived classes of a common base class, you
would expect them to be doing somewhat similar things. In this
situation there is a good possibility that a method name would be
repeated in both child classes.
int truck::passengers(void)
{
return passenger_load;
}
// Result of execution
//
// (this file cannot be executed)
THE TRUCK IMPLEMENTATION
_________________________________________________________________
Examine the file named TRUCK.CPP for the =================
implementation of the truck class. It has TRUCK.CPP
nothing unusual included in it. =================
You should have no problem understanding this
implementation. Your assignment at this point is to compile it in
preparation for our example program that uses all three of the
classes defined in this chapter.
unicycle.initialize(1, 12.5);
cout << "The unicycle has " <<
unicycle.get_wheels() << " wheel.\n";
cout << "The unicycle's wheel loading is " <<
unicycle.wheel_loading() << " pounds on the single tire.\n";
cout << "The unicycle weighs " <<
unicycle.get_weight() << " pounds.\n\n";
car sedan;
sedan.initialize(4, 3500.0, 5);
cout << "The sedan carries " << sedan.passengers() <<
" passengers.\n";
cout << "The sedan weighs " << sedan.get_weight() << " pounds.\n";
cout << "The sedan's wheel loading is " <<
sedan.wheel_loading() << " pounds per tire.\n\n";
truck semi;
semi.initialize(18, 12500.0);
semi.init_truck(1, 33675.0);
cout << "The semi weighs " << semi.get_weight() << " pounds.\n";
cout << "The semi's efficiency is " <<
100.0 * semi.efficiency() << " percent.\n";
}
// Result of execution
//
// The unicycle has 1 wheel.
// The unicycle's wheel loading is 12.5 pounds on the single tire.
// The unicycle weighs 12.5 pounds.
//
// The sedan carries 5 passengers.
// The sedan weighs 3500 pounds.
// The sedan's wheel loading is 875 pounds per tire.
//
// The semi weighs 12500 pounds.
// The semi's efficiency is 72.929072 percent.
USING ALL THREE CLASSES
_________________________________________________________________
Examine the example program named ALLVEHIC.CPP ================
for an example that uses all three of the ALLVEHIC.CPP
classes we have been discussing in this chapter. ================
It uses the parent class vehicle to declare
objects and also uses the two child classes to
declare objects. This was done to illustrate that all three
classes can be used in a single program.
All three of the header files for the classes are included in lines
3 through 5 so the program can use the components of the classes.
Notice that the implementations of the three classes are not in
Page 7-5
Chapter 7 - Inheritance
view here and do not need to be in view. This allows the code to
be used without access to the source code for the actual
implementation of the class. However, it should be clear that the
header file definition must be available.
In this example program, only one object of each class is declared
and used but as many as desired could be declared and used in order
to accomplish the programming task at hand. You will notice how
clean and uncluttered the source code is for this program since the
classes were developed, debugged, and stored away previously, and
the interfaces were kept very simple. There is nothing new here
so you should have no trouble understanding the operation of this
program.
Compiling and executing this program will take a bit of effort but
the process is not complicated. The three classes and the main
program can be compiled in any order desired. All four must be
compiled prior to linking the four resulting object (or binary)
files together. Finally, you can execute the complete program.
Be sure you do the required steps to compile and execute this
program because the effective use of C++ will require you to
compile many separate files and link them together. This is
because of the nature of the C++ language, but it should not be a
burden if a good "make" capability exists with your compiler. If
you are using the Borland implementation of C++, the "project"
capability will make this a snap.
WHY THE #ifndef VEHICLE_H ?
_________________________________________________________________
We promised to return to the strange looking preprocessor directive
in lines 4, 5 and 17 in the VEHICLE.H file, and this is the time
for it. When we define the derived class car, we are required to
supply it with the full definition of the interface to the vehicle
class since car is a derived class of vehicle and must know all
about its parent. We do that by including the vehicle class into
the car class, and the car class can be compiled. The vehicle
class must also be included in the header file of the truck class
for the same reason.
When we get to the main program, we must inform it of the details
of all three classes, so all three header files must be included
as is done in lines 3 through 5 of ALLVEHIC.CPP, but this leads to
a problem. When the preprocessor gets to the car class, it
includes the vehicle class because it is listed in the car class
header file, but since the vehicle class was already included in
line 3 of ALLVEHIC.CPP, it is included twice and we attempt to
redefine the class vehicle. Of course it is the same definition,
but the system doesn't care, it simply doesn't allow redefinition
of a class. We allow the double inclusion of the file and at the
same time prevent the double inclusion of the class by building a
bridge around it using the word VEHICLE_H. If the word is already
defined, the definition is skipped, but if the word is not defined,
Page 7-6
Chapter 7 - Inheritance
the definition is included and the word is defined at that time.
The end result is the actual inclusion of the class only once, even
though the file is included more than once. You should have no
trouble understanding the logic of the includes if you spend a
little time studying this program sequence.
Even though ANSI-C allows multiple definitions of entities,
provided the definitions are identical, C++ does not permit this.
The primary reason is because the compiler would have great
difficulty in knowing if it has already made a constructor call for
the redefined entity, if there is one. A multiple constructor call
for a single object could cause great havoc, so C++ was defined to
prevent any multiple constructor calls by making it illegal to
redefine any entity. This is not a problem in any practical
program.
The name VEHICLE_H was chosen as the word because it is the name
of the file, with the period replaced by the underline. If the
name of the file is used systematically in all of your class
definitions, you cannot have a name clash because the filename of
every class must be unique. It would be good for you to get into
the practice of building the optional skip around all of your
classes. All class definition files in the remainder of this
tutorial will include this skip around to prevent multiple
inclusions and to be an example for you. You should get into the
practice of adding the skip around to all of your class headers no
matter how trivial they may seem to be.
// Chapter 7 - Program 9
// This class inherits the date class and adds one variable and one
// method to it.
#ifndef NEWDATE_H
#define NEWDATE_H
#include "date.h"
class new_date : public date {
protected:
int day_of_year; // New member variable
public:
int get_day_of_year(void); // New method
};
#endif
OUR FIRST PRACTICAL INHERITANCE
_________________________________________________________________
Continuing where we started in chapter 5, we ===============
will inherit the date class into the file named NEWDATE.H
NEWDATE.H and add a member variable and a new ===============
method to the class. Actually, this is not a
good way to add the day_of_year to the date
class since it is available in the structure returned from the
system call in the date class. However, we are more interested in
illustrating inheritance in a practical example than we are in
developing a perfect class, so we will live with this inefficiency.
You will note that we add one variable and one method to create our
new class.
// Chapter 7 - Program 10
#include "newdate.h"
extern int days[];
// This routine ignores leap year for simplicity, and adds
// the days in each month for all months less than the
// current month, then adds the days in the current month
// up to today.
int new_date::get_day_of_year(void)
{
int index = 0;
day_of_year = 0;
while (index < month)
day_of_year += days[index++];
return (day_of_year += day);
}
The program named NEWDATE.CPP contains the ===============
implementation for the added method and should NEWDATE.CPP
be simple for the student to understand. This ===============
class implementation uses the array days[] from
the date class implementation since it was
defined as a global variable there. The method named
get_time_of_day() involves very simple logic but still adjusts for
leap years.
// Chapter 7 - Program 11
#include <iostream.h>
#include "newdate.h"
cout << "Today is day " << now.get_day_of_year() << "\n";
cout << "Dec 31 is day " << later.get_day_of_year() << "\n";
cout << "Feb 19 is day " << birthday.get_day_of_year() << "\n";
}
// Result of execution
// Today is day 20
// Dec 31 is day 365
// Feb 19 is day 50
Page 7-7
Chapter 7 - Inheritance
Finally, the example program named TRYNDATE.CPP ================
will use the new class in a very simple way to TRYNDATE.CPP
illustrate that the derived class is as easy to ================
use as the base class and in fact the main
program has no way of knowing that it is using
a derived class.
You should compile and link this program to gain the experience of
doing so. Remember that it will be necessary to link in the object
code for the original date class as well as the object code from
the newdate class and the main program.
1. Add another object of the vehicle class to ALLVEHIC.CPP named
bicycle, and do some of the same operations as were done to
the unicycle. You will only need to recompile the main
program and link all four files together to get an executable
file, the three classes will not require recompilation.
2. Add the optional skip around the header files of the classes
named car and truck. Then recompile all four files and relink
them to get an executable file.
3. Add a new method to the truck class to return the total weight
of the truck plus its payload and add code to ALLVEHIC.CPP to
read the value out and display it on the monitor. This will
require an addition to TRUCK.H, another addition to TRUCK.CPP,
and of course the changes to the main program named
ALLVEHIC.CPP. The answer is given as three files named
CH07_3A.H (TRUCK.H), CH07_3B.CPP (TRUCK.CPP) and the changed
main program is found in CH07_3C.CPP in the answer directory
on the distribution disk for this tutorial.
4. Add a variable named sex of type char to the name class you
developed in chapter 5 as well as methods to set and retrieve
the value of this variable. The only legal inputs are 'M' or
'F'. These additions should be done by inheriting the name
class into the new class.
Page 7-8作者: createch 時間: 2009-9-5 12:31
C++ tutorial: Chapter 8 - MORE INHERITANCE
Chapter 8
MORE INHERITANCE
In the last chapter we developed a model using modes of
transportation to illustrate the concept of inheritance. In this
chapter we will use that model to illustrate some of the finer
points of inheritance and what it can be used for. If it has been
a while since you read and studied chapter 7, it would be good for
you to return to that material and review it in preparation for a
more detailed study of the topic of inheritance.
// Chapter 8 - Program 1
#include <iostream.h>
class vehicle {
protected:
int wheels;
float weight;
public:
void initialize(int in_wheels, float in_weight);
int get_wheels(void) {return wheels;}
float get_weight(void) {return weight;}
float wheel_loading(void) {return weight/wheels;}
};
class car : public vehicle {
int passenger_load;
public:
void initialize(int in_wheels, float in_weight, int people = 4);
int passengers(void) {return passenger_load;}
};
class truck : public vehicle {
int passenger_load;
float payload;
public:
void init_truck(int how_many = 2, float max_load = 24000.0);
float efficiency(void);
int passengers(void) {return passenger_load;}
};
main()
{
vehicle unicycle;
unicycle.initialize(1, 12.5);
cout << "The unicycle has " <<
unicycle.get_wheels() << " wheel.\n";
cout << "The unicycle's wheel loading is " <<
unicycle.wheel_loading() << " pounds on the single tire.\n";
cout << "The unicycle weighs " <<
unicycle.get_weight() << " pounds.\n\n";
car sedan;
sedan.initialize(4, 3500.0, 5);
cout << "The sedan carries " << sedan.passengers() <<
" passengers.\n";
cout << "The sedan weighs " << sedan.get_weight() << " pounds.\n";
cout << "The sedan's wheel loading is " <<
sedan.wheel_loading() << " pounds per tire.\n\n";
truck semi;
semi.initialize(18, 12500.0);
semi.init_truck(1, 33675.0);
cout << "The semi weighs " << semi.get_weight() << " pounds.\n";
cout << "The semi's efficiency is " <<
100.0 * semi.efficiency() << " percent.\n";
}
// initialize to any data desired
void
vehicle::initialize(int in_wheels, float in_weight)
{
wheels = in_wheels;
weight = in_weight;
}
// Result of execution
//
// The unicycle has 1 wheel.
// The unicycle's wheel loading is 12.5 pounds on the single tire.
// The unicycle weighs 12.5 pounds.
//
// The sedan carries 5 passengers.
// The sedan weighs 3500 pounds.
// The sedan's wheel loading is 875 pounds per tire.
//
// The semi weighs 12500 pounds.
// The semi's efficiency is 72.929072 percent.
A close examination of the file named ================
INHERIT1.CPP will reveal that it is identical to INHERIT1.CPP
the program developed in chapter 7 named ================
ALLVEHIC.CPP except that the program text is
rearranged. The biggest difference is that some
of the simpler methods in the classes have been changed to inline
code to shorten the file considerably. In a practical programming
situation, methods that are this short should be programmed inline
since the actual code to return a simple value is shorter than the
code required to send a message to a non-inline method.
The only other change is the reordering of the classes and
associated methods with the classes all defined first, followed by
the main program. This puts all class interface definitions on a
single page to make the code easier to study. The implementations
for the methods are deferred until the end of the file where they
are available for quick reference but are not cluttering up the
class definitions which we wish to study carefully in this chapter.
This should be an indication to you that there is considerable
flexibility in the way the classes and methods can be arranged in
C++. Of course you realize that this violates the spirit of C++
and its use of separate compilation, but is only done here for
convenience. The best way to package all of the example programs
in this chapter is like the packaging illustrated in chapter 7.
As mentioned before, the two derived classes, car and truck, each
have a variable named passenger_load which is perfectly legal, and
the car class has a method of the same name, initialize(), as one
defined in the super-class named vehicle. The rearrangement of the
files in no way voids this allowable repeating of names.
After you have convinced yourself that this program is truly
identical to the program named ALLVEHIC.CPP from chapter 7, compile
and execute it with your compiler to assure yourself that this
Page 8-1
Chapter 8 - More Inheritance
arrangement is legal. Due to this means of code packaging, you
will not need a "make" file or a "project" capability to compile
and execute this code. This is to make it easy to compile and
execute the example programs in this chapter.
THE SCOPE OPERATOR
_________________________________________________________________
Because the method initialize() is defined in the derived car
class, it hides the method of the same name which is part of the
base class, and there may be times you wish to send a message to
the method in the base class for use in the derived class object.
This can be done by using the scope operator in the following
manner in the main program;
sedan.vehicle::initialize(4, 3500.0);
As you might guess, the number and types of parameters must agree
with those of the method in the base class because it will respond
to the message.
// Chapter 8 - Program 2
#include <iostream.h>
class vehicle {
protected:
int wheels;
float weight;
public:
void initialize(int in_wheels, float in_weight);
int get_wheels(void) {return wheels;}
float get_weight(void) {return weight;}
float wheel_loading(void) {return weight/wheels;}
};
class car : vehicle {
int passenger_load;
public:
void initialize(int in_wheels, float in_weight, int people = 4);
int passengers(void) {return passenger_load;}
};
class truck : vehicle {
int passenger_load;
float payload;
public:
void init_truck(int how_many = 2, float max_load = 24000.0);
float efficiency(void);
int passengers(void) {return passenger_load;}
};
main()
{
vehicle unicycle;
unicycle.initialize(1, 12.5);
cout << "The unicycle has " <<
unicycle.get_wheels() << " wheel.\n";
cout << "The unicycle's wheel loading is " <<
unicycle.wheel_loading() << " pounds on the single tire.\n";
cout << "The unicycle weighs " <<
unicycle.get_weight() << " pounds.\n\n";
car sedan;
sedan.initialize(4, 3500.0, 5);
cout << "The sedan carries " << sedan.passengers() <<
" passengers.\n";
// cout << "The sedan weighs " << sedan.get_weight() << " pounds.\n";
// cout << "The sedan's wheel loading is " <<
// sedan.wheel_loading() << " pounds per tire.\n\n";
// Result of execution
//
// The unicycle has 1 wheel.
// The unicycle's wheel loading is 12.5 pounds on the single tire.
// The unicycle weighs 12.5 pounds.
//
// The sedan carries 5 passengers.
Examine the file named INHERIT2.CPP carefully ================
and you will notice that it is a repeat of the INHERIT2.CPP
last example program with a few minor changes. ================
You will notice that the derived classes named
car and truck do not have the keyword public prior to the name of
the base class in the first line of each. The keyword public, when
included prior to the base class name, makes all of the methods
defined in the base class available for use in the derived class
just as if they were defined as part of the derived class.
Therefore, in the previous program, we were permitted to call the
methods defined as part of the base class from the main program
even though we were working with an object of one of the derived
classes. One example of when we did this, was when we sent a
message to the sedan to get its weight in an output statement of
the main program.
In the present program, without the keyword public prior to the
base class name, the only methods available for objects of the car
class, are those that are defined as part of the class itself, and
therefore we only have the methods named initialize() and
passengers() available for use with objects of class car. In this
program, the only inheritance is that of variables since the two
variables are inherited into objects of class car.
When we declare an object of type car, according to the definition
of the C++ language, it contains three variables. It contains the
Page 8-2
Chapter 8 - More Inheritance
one defined as part of its class named passenger_load and the two
that are part of its parent class, wheels and weight. All are
available for direct use within its methods because of the use of
the keyword protected in the base class. The variables are a part
of an object of class car when it is declared and are stored as
part of the object. We will show you the details of access to the
parent class variables within derived classes shortly in this
chapter. For now, we will return to the use of the subclasses in
this example program.
The observant student will notice that several of the output
statements have been commented out of the main program since they
are no longer legal or meaningful operations. Lines 57 through 59
have been commented out because the methods named get_weight() and
wheel_loading() are not inherited into the car class without the
keyword public in the car class definition. You will notice that
initialize() is still available but this is the one in the car
class, not the method of the same name in the vehicle class.
Moving on to the use of the truck class in the main program, we
find that lines 63 and 65 are commented out for the same reason as
given above, but lines 66 and 67 are commented out for an entirely
different reason. Even though the method named efficiency() is
available and can be called as a part of the truck class, it cannot
be used because we have no way to initialize the wheels or weight
of the truck objects. We can get the weight of the truck objects,
as we have done in line 106, but since the weight has no way to be
initialized, the result is meaningless and lines 66 and 67 are
commented out.
As you have surely guessed by now, there is a way around all of
these problems and we will cover them shortly. In the meantime,
be sure to compile and execute this example program to see that
your compiler gives the same result. It would be a good exercise
for you to reintroduce some of the commented out lines to see what
sort of an error message your compiler issues for these errors.
// Chapter 8 - Program 3
#include <iostream.h>
class vehicle {
protected:
int wheels;
float weight;
public:
void initialize(int in_wheels, float in_weight);
int get_wheels(void) {return wheels;}
float get_weight(void) {return weight;}
float wheel_loading(void) {return weight/wheels;}
};
class car : vehicle {
int passenger_load;
public:
void initialize(int in_wheels, float in_weight, int people = 4);
int passengers(void) {return passenger_load;}
};
class truck : vehicle {
int passenger_load;
float payload;
public:
void init_truck(int in_wheels, float in_weight,
int how_many = 2, float max_load = 24000.0);
float efficiency(void);
int passengers(void) {return passenger_load;}
};
main()
{
vehicle unicycle;
unicycle.initialize(1, 12.5);
cout << "The unicycle has " <<
unicycle.get_wheels() << " wheel.\n";
cout << "The unicycle's wheel loading is " <<
unicycle.wheel_loading() << " pounds on the single tire.\n";
cout << "The unicycle weighs " <<
unicycle.get_weight() << " pounds.\n\n";
car sedan;
sedan.initialize(4, 3500.0, 5);
cout << "The sedan carries " << sedan.passengers() <<
" passengers.\n";
// cout << "The sedan weighs " << sedan.get_weight() << " pounds.\n";
// cout << "The sedan's wheel loading is " <<
// sedan.wheel_loading() << " pounds per tire.\n\n";
// Result of execution
//
// The unicycle has 1 wheel.
// The unicycle's wheel loading is 12.5 pounds on the single tire.
// The unicycle weighs 12.5 pounds.
//
// The sedan carries 5 passengers.
// The semi's efficiency is 41.612484 percent.
INITIALIZING ALL DATA
_________________________________________________________________
If you will examine the example program named ================
INHERIT3.CPP, you will find that we have fixed INHERIT3.CPP
the initialization problem that we left dangling ================
in the last example program.
The method named init_truck() now contains all four of the
parameters as input data which get transferred to the four
variables. Following the initialization, it is permissible to call
the semi.efficiency() method in line 67 and 68 of the main program.
Be sure to compile and execute this program following your detailed
study of it.
// Chapter 8 - Program 4
#include <iostream.h>
class vehicle {
protected:
int wheels;
float weight;
public:
void initialize(int in_wheels, float in_weight);
int get_wheels(void) {return wheels;}
float get_weight(void) {return weight;}
float wheel_loading(void) {return weight/wheels;}
};
class car : public vehicle {
private:
int passenger_load;
public:
void initialize(int in_wheels, float in_weight, int people = 4);
int passengers(void) {return passenger_load;}
};
class truck : public vehicle {
private:
int passenger_load;
float payload;
public:
void init_truck(int how_many = 2, float max_load = 24000.0);
float efficiency(void);
int passengers(void) {return passenger_load;}
};
main()
{
vehicle unicycle;
unicycle.initialize(1, 12.5);
cout << "The unicycle has " <<
unicycle.get_wheels() << " wheel.\n";
cout << "The unicycle's wheel loading is " <<
unicycle.wheel_loading() << " pounds on the single tire.\n";
cout << "The unicycle weighs " <<
unicycle.get_weight() << " pounds.\n\n";
car sedan;
sedan.initialize(4, 3500.0, 5);
cout << "The sedan carries " << sedan.passengers() <<
" passengers.\n";
cout << "The sedan weighs " << sedan.get_weight() << " pounds.\n";
cout << "The sedan's wheel loading is " <<
sedan.wheel_loading() << " pounds per tire.\n\n";
truck semi;
semi.initialize(18, 12500.0);
semi.init_truck(1, 33675.0);
cout << "The semi weighs " << semi.get_weight() << " pounds.\n";
cout << "The semi's efficiency is " <<
100.0 * semi.efficiency() << " percent.\n";
}
// initialize to any data desired
void
vehicle::initialize(int in_wheels, float in_weight)
{
wheels = in_wheels;
weight = in_weight;
}
// Result of execution
//
// The unicycle has 1 wheel.
// The unicycle's wheel loading is 12.5 pounds on the single tire.
// The unicycle weighs 12.5 pounds.
//
// The sedan carries 5 passengers.
// The sedan weighs 3500 pounds.
// The sedan's wheel loading is 875 pounds per tire.
//
// The semi weighs 12500 pounds.
// The semi's efficiency is 72.929072 percent.
Page 8-3
Chapter 8 - More Inheritance
WHAT IS PROTECTED DATA?
_________________________________________________________________
Examine the program named INHERIT4.CPP for an ================
example we will use to define protected data. INHERIT4.CPP
Just to make the program more versatile, we have ================
returned to the use of the keyword public prior
to the name of the parent classes in lines 18
and 29 of the class definitions.
If the data within a base class were totally available in all
classes inheriting that base class, it would be a simple matter for
a programmer to inherit the superclass into a derived class and
have free access to all data in the parent class. This would
completely override the protection afforded by the use of
information hiding. For this reason, the data in a class are not
automatically available to the methods of an inheriting class.
There are times when you may wish to automatically inherit all
variables directly into the subclasses and have them act just as
though they were defined as a part of those classes also. For this
reason, the designer of C++ has provided the keyword protected.
In the present example program, the keyword protected is given in
line 5 so that all of the data of the vehicle class can be directly
imported into any derived classes but are not available outside of
the class or derived classes. All data are automatically defaulted
to private type if no specifier is given. The keyword private can
be used as illustrated in lines 19 and 30 but adds nothing due to
the fact that class members default to private by definition.
You will notice that the variables named wheels and weight are
available to use in the method named initialize() in lines 85
through 91 just as if they were declared as a part of the car class
itself. We can now state the rules for the three means of defining
variables and methods.
private - The variables and methods are not available to any
outside calling routines, and they are not available to
any derived classes inheriting this class.
protected - The variables and methods are not available to any
outside calling routines, but they are directly available
to any derived class inheriting this class.
public - All variables and methods are freely available to all
outside calling routines and to all derived classes.
You will note that these three means of definition can also be used
in a struct type. The only difference with a struct is that
everything defaults to public until one of the other keywords is
used.
Be sure to compile and execute this program before continuing on
to the next example program.
// Chapter 8 - Program 5
#include <iostream.h>
class vehicle {
int wheels;
float weight;
public:
void initialize(int in_wheels, float in_weight);
int get_wheels(void) {return wheels;}
float get_weight(void) {return weight;}
float wheel_loading(void) {return weight/wheels;}
};
class car : public vehicle {
private:
int passenger_load;
public:
void initialize(int in_wheels, float in_weight, int people = 4);
int passengers(void) {return passenger_load;}
};
class truck : public vehicle {
private:
int passenger_load;
float payload;
public:
void init_truck(int how_many = 2, float max_load = 24000.0);
float efficiency(void);
int passengers(void) {return passenger_load;}
};
main()
{
vehicle unicycle;
unicycle.initialize(1, 12.5);
cout << "The unicycle has " <<
unicycle.get_wheels() << " wheel.\n";
cout << "The unicycle's wheel loading is " <<
unicycle.wheel_loading() << " pounds on the single tire.\n";
cout << "The unicycle weighs " <<
unicycle.get_weight() << " pounds.\n\n";
car sedan;
sedan.initialize(4, 3500.0, 5);
cout << "The sedan carries " << sedan.passengers() <<
" passengers.\n";
cout << "The sedan weighs " << sedan.get_weight() << " pounds.\n";
cout << "The sedan's wheel loading is " <<
sedan.wheel_loading() << " pounds per tire.\n\n";
truck semi;
semi.initialize(18, 12500.0);
semi.init_truck(1, 33675.0);
cout << "The semi weighs " << semi.get_weight() << " pounds.\n";
cout << "The semi's efficiency is " <<
100.0 * semi.efficiency() << " percent.\n";
}
// initialize to any data desired
void
vehicle::initialize(int in_wheels, float in_weight)
{
wheels = in_wheels;
weight = in_weight;
}
// Result of execution
//
// The unicycle has 1 wheel.
// The unicycle's wheel loading is 12.5 pounds on the single tire.
// The unicycle weighs 12.5 pounds.
//
// The sedan carries 5 passengers.
// The sedan weighs 3500 pounds.
// The sedan's wheel loading is 875 pounds per tire.
//
// The semi weighs 12500 pounds.
// The semi's efficiency is 72.929072 percent.
Page 8-4
Chapter 8 - More Inheritance
WHAT IS PRIVATE DATA?
_________________________________________________________________
Examine the file named INHERIT5.CPP where the ================
data is allowed to use the private default. In INHERIT5.CPP
this program, the data is not available for use ================
in the derived classes, so the only way the data
in the base class can be used is through use of
messages to methods in the base class.
It seems a little silly to have to call methods in the base class
to get to the data which is actually a part of the derived class,
but that is the way C++ is defined to work. This would indicate
to you that you should spend some time thinking about how any class
you define will be used. If you think somebody may wish to inherit
your class into a new class and expand it, you should make the data
members protected so they can be easily used in the new class. Be
sure to compile and execute this program.
class car : public vehicle {
int passenger_load;
public:
car(void) {passenger_load = 4;}
void initialize(int in_wheels, float in_weight, int people = 4);
int passengers(void) {return passenger_load;}
};
class truck : public vehicle {
int passenger_load;
float payload;
public:
truck(void) {passenger_load = 3; payload = 22222.0;}
void init_truck(int how_many = 2, float max_load = 24000.0);
float efficiency(void);
int passengers(void) {return passenger_load;}
};
main()
{
vehicle unicycle;
// unicycle.initialize(1, 12.5);
cout << "The unicycle has " <<
unicycle.get_wheels() << " wheel.\n";
cout << "The unicycle's wheel loading is " <<
unicycle.wheel_loading() << " pounds on the single tire.\n";
cout << "The unicycle weighs " <<
unicycle.get_weight() << " pounds.\n\n";
car sedan;
// sedan.initialize(4, 3500.0, 5);
cout << "The sedan carries " << sedan.passengers() <<
" passengers.\n";
cout << "The sedan weighs " << sedan.get_weight() << " pounds.\n";
cout << "The sedan's wheel loading is " <<
sedan.wheel_loading() << " pounds per tire.\n\n";
// Result of execution
//
// The unicycle has 7 wheel.
// The unicycle's wheel loading is 1587.285767 pounds on the single tire.
// The unicycle weighs 11111 pounds.
//
// The sedan carries 4 passengers.
// The sedan weighs 11111 pounds.
// The sedan's wheel loading is 1587.285767 pounds per tire.
//
// The semi weighs 11111 pounds.
// The semi's efficiency is 66.666667 percent.
Examine the example program named INHERIT6.CPP ================
for yet another variation to our basic program, INHERIT6.CPP
this time adding constructors. ================
The vehicle class has a constructor to
initialize the number of wheels and the weight to the indicated
values and has no surprising constructs. The car and truck classes
each have a constructor also to initialize their unique variables
to some unique values. If you jump ahead to the main program, you
will find that the initializing statements are commented out for
each of the objects so we must depend on the constructors to
initialize the variables. The most important thing to glean from
this example program is the fact that when one of the constructors
is called for a derived class, the constructor is also called for
the parent class. In fact, the constructor for the parent class
will be called before the constructor for the derived class is
called. All of the data will be initialized, including the data
inherited from the parent class.
We will say much more about constructors used with inheritance in
the next chapter of this tutorial. Be sure to compile and execute
this example program.
POINTERS TO AN OBJECT AND AN ARRAY OF OBJECTS
_________________________________________________________________
Examine the example program named INHERIT7.CPP for examples of the
use of an array of objects and a pointer to an object. In this
// Chapter 8 - Program 7
#include <iostream.h>
class vehicle {
protected:
int wheels;
float weight;
public:
void initialize(int in_wheels, float in_weight);
int get_wheels(void) {return wheels;}
float get_weight(void) {return weight;}
float wheel_loading(void) {return weight/wheels;}
};
class car : public vehicle {
int passenger_load;
public:
void initialize(int in_wheels, float in_weight, int people = 4);
int passengers(void) {return passenger_load;}
};
class truck : public vehicle {
int passenger_load;
float payload;
public:
void init_truck(int how_many = 2, float max_load = 24000.0);
float efficiency(void);
int passengers(void) {return passenger_load;}
};
main()
{
vehicle unicycle;
unicycle.initialize(1, 12.5);
cout << "The unicycle has " <<
unicycle.get_wheels() << " wheel.\n";
cout << "The unicycle's wheel loading is " <<
unicycle.wheel_loading() << " pounds on the single tire.\n";
cout << "The unicycle weighs " <<
unicycle.get_weight() << " pounds.\n\n";
car sedan[3];
int index;
for (index = 0 ; index < 3 ; index++) {
sedan[index].initialize(4, 3500.0, 5);
cout << "The sedan carries " << sedan[index].passengers() <<
" passengers.\n";
cout << "The sedan weighs " << sedan[index].get_weight() <<
" pounds.\n";
cout << "The sedan's wheel loading is " <<
sedan[index].wheel_loading() << " pounds per tire.\n\n";
}
truck *semi;
semi = new truck;
semi->initialize(18, 12500.0);
semi->init_truck(1, 33675.0);
cout << "The semi weighs " << semi->get_weight() << " pounds.\n";
cout << "The semi's efficiency is " <<
100.0 * semi->efficiency() << " percent.\n";
delete semi;
}
// initialize to any data desired
void
vehicle::initialize(int in_wheels, float in_weight)
{
wheels = in_wheels;
weight = in_weight;
}
// Result of execution
//
// The unicycle has 1 wheel.
// The unicycle's wheel loading is 12.5 pounds on the single tire.
// The unicycle weighs 12.5 pounds.
//
// The sedan carries 5 passengers.
// The sedan weighs 3500 pounds.
// The sedan's wheel loading is 875 pounds per tire.
//
// The sedan carries 5 passengers.
// The sedan weighs 3500 pounds.
// The sedan's wheel loading is 875 pounds per tire.
//
// The sedan carries 5 passengers.
// The sedan weighs 3500 pounds.
// The sedan's wheel loading is 875 pounds per tire.
//
// The semi weighs 12500 pounds.
// The semi's efficiency is 72.929072 percent.
Page 8-5
Chapter 8 - More Inheritance
program, the objects are instantiated from an ================
inherited class and the intent of this program INHERIT7.CPP
is to illustrate that there is nothing magic ================
about a derived class.
The program is identical to the first program in this chapter until
we get to the main program where we find an array of 3 objects of
class car declared in line 52. It should be obvious that any
operation that is legal for a simple object is legal for an object
that is part of an array, but we must be sure to tell the system
which object of the array we are interested in by adding the array
subscript as we do in lines 56 through 62. The operation of this
portion of the program should be very easy for you to follow, so
we will go on to the next construct of interest.
You will notice, in line 65, that we do not declare an object of
type truck but a pointer to an object of type truck. In order to
use the pointer, we must give it something to point at which we do
in line 67 by dynamically allocating an object. Once the pointer
has an object to point to, we can use the object in the same way
we would use any object, but we must use the pointer notation to
access any of the methods of the object. This is illustrated for
you in lines 68 through 72, and will be further illustrated in the
example program of chapter 12 in this tutorial.
Finally, we deallocate the object in line 73. You should spend
enough time with this program to thoroughly understand the new
material presented here, then compile and execute it.
THE NEW TIME CLASS
_________________________________________________________________
We began a series of nontrivial classes in chapter 5 where we
developed a date class, then a time class, and finally a newdate
class in the last chapter. Now it is your turn to add to this
series. Your assignment is to develop the newtime class which
inherits the time class and adds a new member variable named
seconds_today and a method to calculate the value of seconds since
midnight to fill the variable.
A complete solution to this problem will be found in the ANSWERS
directory on the distribution disk. The files named NEWTIME.H,
NEWTIME.CPP, and TRYNTIME.CPP are the solution files. It would be
a good exercise for you to attempt to write this new class before
you look at the example solution.
1. Remove the comment delimiters from lines 65 through 67 of
INHERIT2.CPP to see what kind of results are returned. Remove
them from line 57 to see what kind of an error is reported by
the compiler for this error.
2. Add cout statements to each of the constructors of
INHERIT5.CPP to output messages to the monitor so you can see
the order of sending messages to the constructors.作者: createch 時間: 2009-9-5 12:31
C++ tutorial : Chapter 9 -MULTIPLE INHERITANCE AND FUTURE DIRECTIONS
Chapter 9
MULTIPLE INHERITANCE AND FUTURE DIRECTIONS
C++ version 2.0 was released by AT&T during the summer of 1989, and
the major addition to the language is multiple inheritance, the
ability to inherit data and methods from more than one class into
a subclass. Multiple inheritance and a few of the other additions
to the language will be discussed in this chapter along with some
of the expected future directions of the language.
Several companies have C++ compilers available in the marketplace,
and many others are sure to follow. Because the example programs
in this tutorial are designed to be as generic as possible, most
should be compilable with any good quality C++ compiler provided
it follows the AT&T definition of version 2.1 or newer. Many of
these examples will not work with earlier definitions because the
language was significantly changed with the version 2.1 update.
After completing this tutorial, you should have enough experience
with the language to study additional new constructs on your own
as they are implemented by the various compiler writers. We will
update the entire tutorial as soon as practical following
procurement of any new compiler, but hopefully the language will
not change rapidly enough now to warrant an update oftener than
annually. Please feel free to contact us for information on
updates to the Coronado Enterprises C++ tutorial.
A major recent addition to the C++ language is the ability to
inherit methods and variables from two or more parent classes when
building a new class. This is called multiple inheritance, and is
purported by many people to be a major requirement for an object
oriented programming language. Some writers, however, have
expressed doubts as to the utility of multiple inheritance. To
illustrate the validity of this, it was not easy to think up a good
example of the use of multiple inheritance as an illustration for
this chapter. In fact, the resulting example is sort of a forced
example that really does nothing useful. It does however,
illustrate the mechanics of the use of multiple inheritance with
C++, and that is our primary concern at this time.
The biggest problem with multiple inheritance involves the
inheritance of variables or methods from two or more parent classes
with the same name. Which variable or method should be chosen as
the inherited variable or method if two or more have the same name?
This will be illustrated in the next few example programs.
Page 9-1
Chapter 9 - Multiple Inheritance and Future Directions
cout << "The efficiency of the Ford is " <<
chuck_ford.efficiency() << "\n";
cout << "The cost per mile for Chuck to drive is " <<
chuck_ford.cost_per_mile() << "\n";
cout << "The cost of Chuck driving the Ford for a day is " <<
chuck_ford.cost_per_full_day(1.129) << "\n";
}
// Result of execution
//
// The efficiency of the Ford is .625
// The cost per mile for Chuck to drive is 0.227273
// The cost of Chuck driving the Ford for a day is 195.530762
An examination of the file named MULTINH1.CPP ================
will reveal the definition of two very simple MULTINH1.CPP
classes in lines 4 through 27 named moving_van ================
and driver.
In order to keep the program as simple as possible, all of the
member methods are defined as inline functions. This puts the code
for the methods where it is easy to find and study. You will also
notice that all variables in both classes are declared to be
protected so they will be readily available for use in any class
which inherits them. The code for each class is kept very simple
so that we can concentrate on studying the interface to the methods
rather than spending time trying to understand complex methods.
As mentioned previously, chapter 12 will illustrate the use of non-
trivial methods.
In line 30, we define another class named driven_truck which
inherits all of the data and all of the methods from both of the
previously defined classes. In the last two chapters, we studied
how to inherit a single class into another class, and to inherit
two or more classes, the same technique is used except that we use
a list of inherited classes separated by commas as illustrated in
line 30. The observant student will notice that we use the keyword
public prior to the name of each inherited class in order to be
able to freely use the methods within the subclass. In this case,
we didn't define any new variables, but we did introduce two new
methods into the subclass in lines 32 through 39.
We declared an object named chuck_ford which presumably refers to
someone named Chuck who is driving a Ford moving van. The object
named chuck_ford is composed of four variables, three from the
moving_van class, and one from the driver class. Any of these four
variables can be manipulated in any of the methods defined within
the driven_truck class in the same way as in a singly inherited
situation. A few examples are given in lines 47 through 56 of the
main program and the diligent student should be able to add
additional output messages to this program if he understands the
principles involved.
All of the rules for private or protected variables and public or
private method inheritance as used with single inheritance extends
to multiple inheritance.
You will notice that both of the parent classes have a method named
initialize(), and both of these are inherited into the subclass
with no difficulty. However, if we attempt to send a message to
Page 9-2
Chapter 9 - Multiple Inheritance and Future Directions
one of these methods, we will have a problem, because the system
does not know which we are referring to. This problem will be
solved and illustrated in the next example program.
Before going on to the next example program, it should be noted
that we have not declared any objects of the two parent classes in
the main program. Since the two parent classes are simply normal
classes themselves, it should be apparent that there is nothing
magic about them and they can be used to define and manipulate
objects in the usual fashion. You may wish to do this to review
your knowledge of simple classes and objects of those classes.
Be sure to compile and execute this program after you understand
its operation completely.
cout << "The efficiency of the Ford is " <<
chuck_ford.efficiency() << "\n";
cout << "The cost per mile for Chuck to drive is " <<
chuck_ford.cost_per_mile() << "\n";
cout << "The cost per day for the Ford is " <<
chuck_ford.moving_van::cost_per_full_day(1.129) <<
"\n";
cout << "The cost of Chuck for a full day is " <<
chuck_ford.driver::cost_per_full_day(15.75) <<
"\n";
cout << "The cost of Chuck driving the Ford for a day is " <<
chuck_ford.driven_truck::cost_per_full_day(1.129) <<
"\n";
}
// Result of execution
//
// The efficiency of the Ford is .625
// The cost per mile for Chuck to drive is 0.227273
// The cost per day for the Ford is 95.530769
// The cost of Chuck for a full day is 100.0
// The cost of Chuck driving the Ford for a day is 195.530762
MORE DUPLICATE METHOD NAMES
_________________________________________________________________
The second example program in this chapter named ================
MULTINH2.CPP, illustrates the use of classes MULTINH2.CPP
with duplicate method names being inherited into ================
a derived class.
If you study the code, you will find that a new method has been
added to all three of the classes named cost_per_full_day(). This
was done intentionally to illustrate how the same method name can
be used in all three classes. The class definitions are no problem
at all, the methods are simply named and defined as shown. The
problem comes when we wish to use one of the methods since they are
all the same name and they have the same numbers and types of
parameters and identical return types. This prevents some sort of
an overloading rule to disambiguate the message sent to one or more
of the methods.
The method used to disambiguate the method calls are illustrated
in lines 60, 64, and 68 of the main program. The solution is to
prepend the class name to the method name with the double colon as
used in the method implementation definition. This is referred to
as qualifying the method name. Qualification is not necessary in
line 68 since it is the method in the derived class and it will
take precedence over the other method names. Actually, you could
qualify all method calls, but if the names are unique, the compiler
can do it for you and make your code easier to write and read.
Be sure to compile and execute this program and study the results.
The observant student will notice that there is a slight
discrepancy in the results given in lines 79 through 81, since the
first two values do not add up to the third value exactly. This
is due to the limited precision of the float variable but should
cause no real problem.
Page 9-3
Chapter 9 - Multiple Inheritance and Future Directions
cout << "The efficiency of the Ford is " <<
chuck_ford.efficiency() << "\n";
cout << "The cost per mile for Chuck to drive is " <<
chuck_ford.cost_per_mile() << "\n";
cout << "The cost of Chuck driving the Ford for a day is " <<
chuck_ford.cost_per_full_day(1.129) << "\n";
cout << "The total weight is " << chuck_ford.total_weight() <<
"\n";
}
// Result of execution
//
// The efficiency of the Ford is .625
// The cost per mile for Chuck to drive is 0.227273
// The cost of Chuck driving the Ford for a day is 195.530762
// The total weight is 12250
If you will examine the example program named ================
MULTINH3.CPP, you will notice that each base MULTINH3.CPP
class has a variable with the same name. ================
According to the rules of inheritance, an object
of the driven_truck class will have two variables with the same
name, weight. This would be a problem if it weren't for the fact
that C++ has defined a method of accessing each one in a well
defined way. You have probably guessed that we will use
qualification to access each variable. Lines 38 and 45 illustrate
the use of the variables. It may be obvious, but it should be
explicitly stated, that there is no reason that the subclass itself
cannot have a variable of the same name as those inherited from the
parent classes. In order to access it, no qualification would be
required.
It should be apparent to you that once you understand single
inheritance, multiple inheritance is nothing more than an extension
of the same rules. Of course, if you inherit two methods or
variables of the same name, you must use qualification to allow the
compiler to select the correct one.
// Chapter 9 - Program 4
#ifndef DATETIME_H
#define DATETIME_H
#include "newdate.h"
#include "time.h"
class datetime : public new_date, public time_of_day {
public:
datetime(void) { ; }; // Default to todays date and time
datetime(int M, int D, int Y, int H, int Mn, int S) :
new_date(), // Member initializer
time_of_day(H, Mn, S) // Member initializer
{ set_date(M, D, Y); }; // Constructor body
Examine the example program named DATETIME.H ================
for a practical example using multiple DATETIME.H
inheritance. You will notice that we are ================
returning to our familiar date and time classes
from earlier chapters.
There is a good deal to be learned from this very short header file
since it is our first example of member initialization. There are
two constructors for this class, the first being a very simple
constructor that does nothing in itself as is evident from an
examination of line 12. This constuctor allows the constructors
to be executed for the classes new_date and time_of_day. In both
cases a constructor will be executed that requires no parameters,
and such a constructor is available for each of these two classes.
The second constuctor is more interesting since it does not simply
use the default constructor, but instead passes some of the input
parameters to the inherited class constructors. Following the
colon in line 13 are two member initializers which are used to
initialize members of this class. Since the two parent classes are
inherited, they are also members of this class and can be
initialized as shown. Each of the member initializers is actually
a call to a constructor of the parent classes and it should be
evident that there must be a constructor with the proper number of
Page 9-4
Chapter 9 - Multiple Inheritance and Future Directions
input parameters to respond to the messages given. You will note
that in line 14, we are actually calling the constructor with no
parameters given explicitly. If we chose, we could simply let the
system call that constructor automatically, but this gives us an
explicit comment on what is happening.
MORE ABOUT MEMBER INITIALIZERS
_________________________________________________________________
Actually, we can use the member initializer to initialize class
members also. If we had a class member of type int named
member_var, we could initialize it also by mentioning the name of
the member followed by the value we desired to initialize it to in
parentheses. If we wished to initialize it to the value 13, we
could use the following line of code in the member initializer
list;
member_var(13);
Following all member initialization, the normal constructor code
is executed which in this case is given in line 16.
ORDER OF MEMBER INITIALIZATION
_________________________________________________________________
The order of member initialization may seem a bit strange, but it
does follow a few simple rules. The order of member initialization
does not follow the order given by the initialization list, but
another very strict order over which you have complete control.
All inherited classes are initialized first in the order they are
listed in the class header. If lines 14 and 15 were reversed,
class new_date would still be initialized first because it is
mentioned first in line 8. It has been mentioned that C++ respects
its elders and initializes its parents prior to itself. That
should be a useful memory aid in the use of member initializers.
Next, all local class members are initialized in the order in which
they are declared in the class, not the order in which they are
declared in the initialization list. Actually, it would probably
be good practice to not use the member initializer to initialize
class members but instead to initialize them in the normal
constructor code.
Finally, after the member initializers are all executed in the
proper order, the main body of the constructor is executed in the
normal manner.
// Chapter 9 - Program 5
#include <iostream.h>
#include "datetime.h"
cout << "Now = " << now.get_date_string() << " "
<< now.get_time_string() << " and is day "
<< now.get_day_of_year() << "\n";
cout << "Birthday = " << birthday.get_date_string() << " "
<< birthday.get_time_string() << " and is day "
<< birthday.get_day_of_year() << "\n";
cout << "Special = " << special.get_date_string() << " "
<< special.get_time_string() << " and is day "
<< special.get_day_of_year() << "\n";
}
// Result of execution
// Now = Jan 20, 1992 21:12:56 and is day 20
// Birthday = Oct 18, 1938 1:30:17 and is day 291
// Special = Feb 19, 1950 13:30:00 and is day 50
USING THE NEW CLASS
_________________________________________________________________
The example program named USEDTTM.CPP uses the datetime class we
just built, and like our previous examples, the main program is
Page 9-5
Chapter 9 - Multiple Inheritance and Future Directions
kept very simple and straight forward. You will ===============
note that the default constructor is used for USEDTTM.CPP
the object named now, and the constructor with ===============
the member initializers is used with the objects
named birthday and special.
The diligent student should have no trouble understanding the
remaining code in this example.
FUTURE DIRECTIONS OF C++
_________________________________________________________________
An ANSI committee has been formed to write an ANSI standard for
C++. They first met in the Spring of 1990 and are expected to
complete the standard in about three years. Until the new standard
is released, the C++ language is expected to stay fairly stable.
However, due to the nature of compiler writers and their desire to
slightly improve their offerings over their competitors, you can
bet that the language will not remain static during this three year
period.
Many small changes have been added during the past year that barely
affect the casual programmer, or even the heavy user of the
language. You can be sure that the language will evolve slowly and
surely into a very usable and reliable language. There are two
areas, however, that should be discussed in a little detail because
they will add so much to the language in future years. Those two
topics are parameterized types and exception handling.
Many times, when developing a program, you wish to perform some
operation on more than one data type. For example you may wish to
sort a list of integers, another list of floating point numbers,
and a list of alphabetic strings. It seems silly to have to write
a separate sort function for each of the three types when all three
are sorted in the same logical way. With parameterized types, you
will be able to write a single sort routine that is capable of
sorting all three of the lists.
This is already available in the Ada language as the generic
package or procedure. Because it is available in Ada, there is a
software components industry that provides programmers with
prewritten and thoroughly debugged software routines that work with
many different types. When this is generally available in C++,
there will be a components industry for C++ and precoded, debugged
and efficient source code will be available off the shelf to
perform many of the standard operations. These operations will
include such things as sorts, queues, stacks, lists, etc.
Page 9-6
Chapter 9 - Multiple Inheritance and Future Directions
Bjarne Stroustrup has announced that parameterized types, otherwise
known as templates or generics, will be available in a future
version of C++. He has presented a paper with details of one way
to implement them, but this is only a suggestion, not a
specification.
Borland International has included templates in version 3.0 of
Borland C++, and hopefully their implementation will be very close
to the final definition of templates.
The next three example programs will illustrate the use of
templates with Borland's compiler, but may not work with other
compilers.
// Chapter 9 - Program 6
#include <stdio.h>
template<class ANY_TYPE>
ANY_TYPE maximum(ANY_TYPE a, ANY_TYPE b)
{
return (a > b) ? a : b;
}
void main(void)
{
int x = 12, y = -7;
float real = 3.1415;
char ch = 'A';
THE FIRST TEMPLATE
_________________________________________________________________
The example program named TEMPLAT1.CPP is the ================
first example of the use of a template. This TEMPLAT1.CPP
program is so simple it seems silly to even ================
bother with it but it will illustrate the use of
the parameterized type.
The template is given in lines 4 through 8 with the first line
indicating that it is a template with a single type to be replaced,
the type ANY_TYPE. This type can be replaced by any type which can
be used in the comparison operation in line 7. If you have defined
a class, and you have overloaded the operator ">", then this
template can be used with objects of your class. Thus, you do not
have to write a maximum function for each type or class in your
program.
This function is included automatically for each type it is called
with in the program, and the code itself should be very easy to
understand.
The diligent student should realize that nearly the same effect can
be achieved through use of a macro, except that when a macro is
used, the strict type checking is not done. Because of this and
because of the availability of the inline method capability in C++,
the use of macros is essentially non-existent by experienced C++
programmers.
int_stack.push(x);
int_stack.push(y);
int_stack.push(77);
float_stack.push(real);
float_stack.push(-12.345);
float_stack.push(100.01);
string_stack.push("This is line 1");
string_stack.push("This is the second line");
string_stack.push("This is the third line");
string_stack.push(name);
printf("\n Strings\n");
do {
printf("%s\n", string_stack.pop());
} while (!string_stack.empty());
}
// Result of execution
// Integer stack ---> 12 -7 77
// Float stack ---> 3.141 -12.345 100.010
//
// Strings
// John Herkimer Doe
// This is the third line
// This is the second line
// This is line 1
A CLASS TEMPLATE
_________________________________________________________________
The example program named TEMPLAT2.CPP is a ================
little more involved since it provides a TEMPLAT2.CPP
template for an entire class rather than a ================
single function. The template code is given in
lines 6 through 16 and a little study will show
Page 9-7
Chapter 9 - Multiple Inheritance and Future Directions
that this is an entire class definition. The diligent student will
recognize that this is a very weak stack class since there is
nothing to prevent popping data from an empty stack, and there is
no indication of a full stack. Our intent, however, is to
illustrate the use of the parameterized type and to do so using the
simplest class possible.
In the main program we create an object named int_stack in line 25
which will be a stack designed to store integers, and another
object named float_stack in line 26 which is designed to store
float type values. In both cases, we enclose the type we desire
this object to work with in "<>" brackets, and the system creates
the object by first replacing all instances of ANY_TYPE with the
desired type, then creating the object of that type. You will note
that any type can be used that has an assignment capability since
lines 13 and 14 use the assignment operator on the parameterized
type.
Even though the strings are all of differing lengths, we can even
use the stack to store a stack of strings if we only store a
pointer to the strings and not the entire string. This is
illustrated in the object named string_stack declared in line 27
and used later in the program.
This program should be fairly easy for you to follow if you spend
a bit of time studying it. You should compile and run it if you
have a compiler that will handle this new construct.
// Chapter 9 - Program 8
#include <stdio.h>
#include "date.h"
string_stack.push("This is line 1");
string_stack.push("This is the second line");
string_stack.push("This is the third line");
string_stack.push(name);
for (int index = 0 ; index < 5 ; index++) {
extra = *class_stack.pop();
printf("Date = %d %d %d\n", extra.get_month(),
extra.get_day(), extra.get_year());
};
printf("\n Strings\n");
do {
printf("%s\n", string_stack.pop());
} while (!string_stack.empty());
}
// Result of execution
// Date = 1 7 1992
// Date = 1 7 1992
// Date = 1 7 1992
// Date = 1 7 1992
//
// Strings
// John Herkimer Doe
// This is the third line
// This is the second line
// This is line 1
REUSING THE STACK CLASS
_________________________________________________________________
The program named TEMPLAT3.CPP uses the same ================
class with the template as defined in the last TEMPLAT3.CPP
program but in this case, it uses the date class ================
developed earlier as the stack members. More
specifically, it uses a pointer to the date
class as the stack member.
Because class assignment is legal, you could also store the actual
class in the stack rather than just the pointer to it. To do so
however, would be very inefficient since the entire class would be
copied into the stack each time it is pushed and the entire class
would be copied out again when it was popped. Use of the pointer
is a little more general, so it was illustrated here for your
benefit.
All three of the previous programs can be compiled and executed if
you have a copy of Borland C++ version 3.0. Other compilers may
not work with these programs since parameterized types are not yet
a part of the C++ specification.
Page 9-8
Chapter 9 - Multiple Inheritance and Future Directions
A future version of C++ will have some form of exception handling
to allow the programmer to trap errors and prevent the system from
completely shutting down when a fatal error occurs. The Ada
language allows the programmer to trap any error that occurs, even
system errors, execute some recovery code, and continue on with the
program execution in a very well defined way. Bjarne Stroustrup,
working in conjunction with the ANSI-C++ committee, has announced
that some form of exception handling will be implemented but he has
not stated what form it would take as of this writing.
WHAT SHOULD BE YOUR NEXT STEP?
_________________________________________________________________
Once again, we have reached a major milestone in C++ programming.
With the ability to use inheritance, you have nearly all of the
tools you need to effectively use the object oriented programming
techniques of C++ and you would do well to stop studying again and
begin programming. The only topic left with C++ is virtual methods
which are used for dynamic binding or polymorphism. This will be
covered in the next two chapters. The vast majority of all
programming can be done without dynamic binding, and in attempting
to force it into every program, you could wind up with an
unreadable mess, so you should approach it slowly.
Page 9-9作者: createch 時間: 2009-9-5 12:31
C++ tutorial: Chapter 10 VIRTUAL FUNCTIONS
Chapter 10
VIRTUAL FUNCTIONS
Once again we are into a completely new topic with terminology
which will be new to you. If you are new to object oriented
programming, you should follow along in this chapter very carefully
because every attempt has been made to define every detail of this
new and somewhat intimidating topic. However, if you are well
versed in object oriented programming, simply learning to use C++,
you may wish to skip the first four programs in this chapter and
go directly to the example program named VIRTUAL5.CPP and continue
from there to the end of the chapter.
One term which must be defined is polymorphism, a rather large word
that simply means similar when used in the context of object
oriented programming. Objects are polymorphic if they have some
similarities but are still somewhat different. We will see how it
is used in the context of object oriented programming as we proceed
through this chapter.
We have already studied operator overloading and function
overloading in this tutorial, and they are a subtle form of
polymorphism since in both cases, a single entity is used to refer
to two or more things. The use of virtual functions can be a great
aid in programming some kinds of projects as you will see in these
two chapters.
// Chapter 10 - Program 1
#include <iostream.h>
class vehicle {
int wheels;
float weight;
public:
void message(void) { cout << "Vehicle message\n";}
};
class car : public vehicle {
int passenger_load;
public:
void message(void) { cout << "Car message\n";}
};
class truck : public vehicle {
int passenger_load;
float payload;
public:
int passengers(void) {return passenger_load;}
};
class boat : public vehicle {
int passenger_load;
public:
int passengers(void) {return passenger_load;}
void message(void) { cout << "Boat message\n";}
};
main()
{
vehicle unicycle;
car sedan;
truck semi;
boat sailboat;
// Result of execution
//
// Vehicle message
// Car message
// Vehicle message
// Boat message
A SIMPLE PROGRAM WITH INHERITANCE
_________________________________________________________________
Examine the example program named VIRTUAL1.CPP ================
for the basic program outline we will use for VIRTUAL1.CPP
all discussion in this chapter. Since this ================
program has nothing to do with virtual
functions, the name may be somewhat misleading.
It is named VIRTUAL1.CPP because it is part of a series of programs
intended to illustrate the use of virtual functions. The last
program in this chapter will illustrate the proper use of virtual
functions.
The first program is very simple and you will recognize it as being
somewhat similar to the programs studied in the last chapter except
that this program is greatly simplified in order to effectively
instruct you in the use of a virtual function. You will notice
that many of the methods from the last chapter have been completely
dropped from this example for simplicity, and a new method has been
added to the parent class, the method named message() in line 8.
Throughout this chapter we will be studying the operation of the
method named message() in the base class and the derived classes.
Page 10-1
Chapter 10 - Virtual Functions
For that reason, there is a method named message() in the car class
as well as in the new class named boat in lines 27 through 32.
You will also notice that there is a lack of a method named
message() in the truck class. This has been done on purpose to
illustrate the use of the virtual method, or if you prefer, you can
refer to it as a virtual function. You will recall that the method
named message() from the base class is available in the truck class
because the method from the base class is inherited with the
keyword public included in line 19. You will also notice that the
use of the keyword public in lines 12 and 27 actually do nothing
because the only method available in the base class is also
available in the derived classes. There are no methods actually
inherited. Leaving the keyword in the header poses no problem
however, so it will be left there for your study.
The method named message() in the base class and in the derived
classes has been kept very simple on purpose. Once again, we are
interested in the technique of the virtual method rather than a
long complicated example.
The main program is as simple as the classes, one object of each
of the classes is declared in lines 37 through 40 and the method
named message() is called once for each object. The result of
executing the program indicates that the method for each is called
except for the object named semi, which has no method named
message(). As discussed in the last chapter, the method named
message() from the parent class is called and the data output to
the monitor indicates that this did happen since it displays
"Vehicle message" for the object named semi.
The data for the objects is of no concern in this chapter so all
data is allowed to default to private type and none is inherited
into the derived classes. Some of the data is left in the example
program simply to make the classes look like classes. Based on
your experience with C++ by now, you realize that the data could
be removed since it is not used.
After you understand this program, compile and execute it to see
if your compiler gives the same result of execution.
// Chapter 10 - Program 2
#include <iostream.h>
class vehicle {
int wheels;
float weight;
public:
virtual void message(void) { cout << "Vehicle message\n";}
};
class car : public vehicle {
int passenger_load;
public:
void message(void) { cout << "Car message\n";}
};
class truck : public vehicle {
int passenger_load;
float payload;
public:
int passengers(void) {return passenger_load;}
};
class boat : public vehicle {
int passenger_load;
public:
int passengers(void) {return passenger_load;}
void message(void) { cout << "Boat message\n";}
};
main()
{
vehicle unicycle;
car sedan;
truck semi;
boat sailboat;
// Result of execution
//
// Vehicle message
// Car message
// Vehicle message
// Boat message
ADDING THE KEYWORD VIRTUAL
_________________________________________________________________
As you examine the next example program named ================
VIRTUAL2.CPP, you will notice that there is one VIRTUAL2.CPP
small change in line 8. The keyword virtual has ================
been added to the declaration of the method
named message() in the parent class.
It may be a bit of a disappointment to you to learn that this
program operates no differently than the last example program.
This is because we are using objects directly and virtual methods
Page 10-2
Chapter 10 - Virtual Functions
have nothing to do with objects, only with pointers to objects as
we will see soon. There is an additional comment in line 46
illustrating that since all four objects are of different classes,
it is impossible to assign any object to any other object in this
program. We will soon see that some pointer assignments are
permitted between objects.
After you are sure that the fact that they are virtual functions,
or methods, has nothing to do with the objects as they are
instantiated, compile and execute this example program to see if
your compiler results in the same output as that listed.
// Chapter 10 - Program 3
#include <iostream.h>
class vehicle {
int wheels;
float weight;
public:
void message(void) { cout << "Vehicle message\n";}
};
class car : public vehicle {
int passenger_load;
public:
void message(void) { cout << "Car message\n";}
};
class truck : public vehicle {
int passenger_load;
float payload;
public:
int passengers(void) {return passenger_load;}
};
class boat : public vehicle {
int passenger_load;
public:
int passengers(void) {return passenger_load;}
void message(void) { cout << "Boat message\n";}
};
main()
{
vehicle *unicycle;
car *sedan;
truck *semi;
boat *sailboat;
unicycle = new vehicle;
unicycle->message();
sedan = new car;
sedan->message();
semi = new truck;
semi->message();
sailboat = new boat;
sailboat->message();
}
// Result of execution
//
// Vehicle message
// Car message
// Vehicle message
// Boat message
USING OBJECT POINTERS
_________________________________________________________________
Examine the example program named VIRTUAL3.CPP ================
and you will find a repeat of the first program VIRTUAL3.CPP
but with a different main program. ================
In this program the keyword virtual has been
removed from the method declaration in the parent class in line 8,
and the main program declares pointers to the objects rather than
declaring the objects themselves in lines 37 through 40. Since we
only declared pointers to the objects we find it necessary to
allocate the objects before using them by using the new operator
in lines 42 through 49. Upon running the program, we find that
even though we are using pointers to the objects we have done
nothing different than what we did in the first program. Upon
execution, we find that the program operates in exactly the same
manner as the first example program in this chapter. This should
not be surprising because a pointer to a method can be used to
operate on an object in the same manner as an object can be
manipulated.
Be sure to compile and execute this program before continuing on
to the next example program. The observant student will notice
that we failed to deallocate the objects prior to terminating the
program. As always, in such a simple program, it doesn't matter
because the heap will be cleaned up automatically when we return
to the operating system.
// Chapter 10 - Program 4
#include <iostream.h>
class vehicle {
int wheels;
float weight;
public:
virtual void message(void) { cout << "Vehicle message\n";}
};
class car : public vehicle {
int passenger_load;
public:
void message(void) { cout << "Car message\n";}
};
class truck : public vehicle {
int passenger_load;
float payload;
public:
int passengers(void) {return passenger_load;}
};
class boat : public vehicle {
int passenger_load;
public:
int passengers(void) {return passenger_load;}
void message(void) { cout << "Boat message\n";}
};
main()
{
vehicle *unicycle;
car *sedan;
truck *semi;
boat *sailboat;
unicycle = new vehicle;
unicycle->message();
sedan = new car;
sedan->message();
semi = new truck;
semi->message();
sailboat = new boat;
sailboat->message();
}
// Result of execution
//
// Vehicle message
// Car message
// Vehicle message
// Boat message
A POINTER AND A VIRTUAL FUNCTION
_________________________________________________________________
The example program named VIRTUAL4.CPP is ================
identical to the last program except for the VIRTUAL4.CPP
addition of the keyword virtual to line 8 once ================
again.
I hope you are not terribly disappointed to find that this program,
including the keyword virtual, is still identical to the last
program. Once again we are simply using pointers to each of the
Page 10-3
Chapter 10 - Virtual Functions
objects, and in every case the pointer is of the same type as the
object to which it points. You will begin to see some changes in
the next example program, so be patient, we are almost there.
Once again, it would be best for you to compile and execute this
program.
The four previous programs were meant to instruct you in what
virtual functions do not do. The next two will show you what
virtual functions do.
// Chapter 10 - Program 5
#include <iostream.h>
class vehicle {
int wheels;
float weight;
public:
void message(void) { cout << "Vehicle message\n";}
};
class car : public vehicle {
int passenger_load;
public:
void message(void) { cout << "Car message\n";}
};
class truck : public vehicle {
int passenger_load;
float payload;
public:
int passengers(void) {return passenger_load;}
};
class boat : public vehicle {
int passenger_load;
public:
int passengers(void) {return passenger_load;}
void message(void) { cout << "Boat message\n";}
};
main()
{
vehicle *unicycle;
unicycle = new vehicle;
unicycle->message();
delete unicycle;
unicycle = new car;
unicycle->message();
delete unicycle;
unicycle = new truck;
unicycle->message();
delete unicycle;
unicycle = new boat;
unicycle->message();
delete unicycle;
}
// Result of execution
//
// Vehicle message
// Vehicle message
// Vehicle message
// Vehicle message
A SINGLE POINTER TO THE PARENT CLASS
_________________________________________________________________
Examine the example program named VIRTUAL5.CPP ================
where we almost use a virtual method. Be just VIRTUAL5.CPP
a little patient because we are almost ready to ================
actually use a virtual method.
You will notice that this is another copy of our program with the
keyword virtual omitted from line 8 and with a totally different
main program. In this program, we only declare a single pointer
to a class and the pointer is pointing to the base class of the
class hierarchy. We will use the single pointer to refer to each
of the four classes and observe what the output of the method named
message() is.
A little digression is in order to understand how we can use a
pointer which has been declared to point to one class, to actually
refer to another class. If we referred to a vehicle (in the real
world, not necessarily in this program), we could be referring to
a car, a truck, a motorcycle, or any other kinds of transportation,
because we are referring to a very general form of an object. If
however, we were to refer to a car, we are excluding trucks,
motorcycles, and all other kinds of transportation, because we are
referring to a car specifically. The more general term of vehicle
can therefore refer to many kinds of vehicles, but the more
specific term of car can only refer to a single kind of vehicle,
namely a car.
We can apply the same thought process in C++ and say that if we
have a pointer to a vehicle (remembering that a pointer is actually
a reference), we can use that pointer to refer to any of the more
specific objects, and that is indeed legal in C++ according to the
definition of the language. In a like manner, if we have a pointer
to a car, we cannot use that pointer to reference any of the other
classes including the vehicle class because the pointer to the car
class is too specific and restricted to be used on any of the other
classes.
Page 10-4
Chapter 10 - Virtual Functions
THE C++ POINTER RULE
_________________________________________________________________
The rule as given in C++ terms is as follows. A pointer declared
as pointing to a base class can be used to point to an object of
a derived class of that base class, but a pointer to a derived
class cannot be used to point to an object of the base class or to
any of the other derived classes of the base class. In our program
therefore, we are allowed to declare a pointer to the vehicle class
which is the base class, and use that pointer to refer to objects
of either the base class or any of the derived classes.
This is exactly what we do in the main program. We declare a
single pointer which points to the vehicle class and use it to
point to objects of each of the classes in the same order as in the
last four programs. In each case, we allocate the object, send a
message to the method named message() and deallocate the object
before going on to the next class. You will notice that when we
send the four messages, we are sending the message to the same
method, namely the method named message() which is a part of the
vehicle base class. This is because the pointer has a class
associated with it. Even though the pointer is actually pointing
to four different classes in this program, the program acts as if
the pointer is always pointing to an object of the parent class
because the pointer is of the type of the parent class.
The next program will finally do something you have not seen in any
C program or in any C++ program in this tutorial up to this point.
After you compile and execute the current program, we will go on
to study our first virtual function.
// Chapter 10 - Program 6
#include <iostream.h>
class vehicle {
int wheels;
float weight;
public:
virtual void message(void) { cout << "Vehicle message\n";}
};
class car : public vehicle {
int passenger_load;
public:
void message(void) { cout << "Car message\n";}
};
class truck : public vehicle {
int passenger_load;
float payload;
public:
int passengers(void) {return passenger_load;}
};
class boat : public vehicle {
int passenger_load;
public:
int passengers(void) {return passenger_load;}
void message(void) { cout << "Boat message\n";}
};
main()
{
vehicle *unicycle;
unicycle = new vehicle;
unicycle->message();
delete unicycle;
unicycle = new car;
unicycle->message();
delete unicycle;
unicycle = new truck;
unicycle->message();
delete unicycle;
unicycle = new boat;
unicycle->message();
delete unicycle;
}
// Result of execution
//
// Vehicle message
// Car message
// Vehicle message
// Boat message
AN ACTUAL VIRTUAL FUNCTION
_________________________________________________________________
We finally come to an example program with a ================
virtual function that operates as a virtual VIRTUAL6.CPP
function and exhibits dynamic binding or ================
polymorphism as it is called. This is in the
program named VIRTUAL6.CPP.
This program is identical to the last example program except that
the keyword virtual is added to line 8 to make the method named
message() a virtual function. You will notice that the keyword
virtual only appears in the base class, all classes that derive
this class will have the corresponding method automatically
declared virtual by the system. In this program, we will once
again use the single pointer to the base class and allocate, use,
then delete an object of each of the four available classes using
the identical code we used in the last program. However, because
of the addition of the keyword virtual in line 8, this program acts
entirely different from the last example program.
Page 10-5
Chapter 10 - Virtual Functions
Since the method named message() is declared to be a virtual method
in its declaration in the base class, anytime we refer to this
method with a pointer to the base class, we actually execute the
method associated with one of the derived classes if there is a
method available in the derived class and if the pointer is
actually pointing to that derived class. When the program is
executed, the output reflects the same output we saw in the other
cases when we were actually calling the methods in the derived
classes, but now we are using a pointer of the base class type to
make the calls.
You will notice that in lines 40, 44, 48, and 52, even though the
code is identical in each line, the system is making the decision
of which method to actually call based on the type of the pointer
when each message is sent. The decision of which method to call
is not made during the time when the code is compiled but when the
code is executed. This is dynamic binding and can be very useful
in some programming situations. In fact, there are only three
different calls made because the class named truck does not have
a method named message(), so the system simply uses the method from
the base class to satisfy the message passed. For this reason, a
virtual function must have an implementation available in the base
class which will be used if there is not one available in one or
more of the derived classes. Note that the message is actually
sent to a pointer to the object, but this is splitting hairs and
should not be overly emphasized at this time.
It is probably not obvious but the observant student will note that
the structure of the virtual function in the base class and each
of the derived classes is identical. The return type and the
number and types of the parameters must be identical for all since
a single statement can be used to call any of them.
IS THIS REALLY SIGNIFICANT?
_________________________________________________________________
This program probably does not seem to do much when you first
approach it, but the dynamic binding is a very useful construct and
will be illustrated in the next chapter with a rather simple
program that uses the technique of dynamic binding to implement a
personnel list for a small company.
If the keyword virtual is used, the system will use late binding
which is done at run time, but if the keyword is not included,
early binding will be used. What these words actually mean is that
with late binding, the compiler does not know which method will
actually respond to the message because the type of the pointer is
not known at compile time. With early binding, however, the
compiler decides at compile time what method will respond to the
message sent to the pointer.
Page 10-6
Chapter 10 - Virtual Functions
Be sure to compile and execute this example program before
continuing on to the next chapter where we will see a practical
example of the use of this technique.
1. Modify VIRTUAL3.CPP to deallocate the objects prior to
terminating the program.
2. Add a message() method to the truck class of VIRTUAL6.CPP to
observe the use of the new method instead of defaulting to the
parent class method.
Page 10-7作者: createch 時間: 2009-9-5 12:32
C++ Tutorial: Chapter 11 MORE VIRTUAL FUNCTIONS
Chapter 11
MORE VIRTUAL FUNCTIONS
This chapter will actually be a continuation of the topics covered
in the last chapter but this will be a fuller explanation of what
virtual functions are and how they can be used in a program. We
will present a simple database program with a virtual function to
show how it can be used, then we will go on to illustrate a more
complex use of the virtual function in a manner that finally
illustrates its utility and reason for existence.
HOW TO START AN OOP PROJECT
_________________________________________________________________
The observant student will notice that we begin our use of object
oriented programming by identifying an object, or in this case a
class of objects and even some subordinate objects, which we
completely define. When we get to the main program we then have
a simple job with the remaining needs and they are completed using
standard procedural programming techniques which we are familiar
with. This is the way to begin any object oriented programming
project, by first identifying a few objects that can be separated
conveniently from the rest of the code, programming them, then
writing the main program. It should be added that, for your first
project using objects, do not try to make everything an object.
Select a few objects and after gaining experience with object
oriented programming techniques, use more objects on future
projects. Most programmers use too many objects for their first
project and write very obtuse, unreadable code.
// Chapter 11 - Program 1
#ifndef PERSON_H
#define PERSON_H
class person {
protected: // Make these variables available to the subclasses
char name[25];
int salary;
public:
virtual void display(void);
};
#endif
THE PERSON HEADER FILE
_________________________________________________________________
Examine the file named PERSON.H for the ================
definition file for the person class. This PERSON.H
class definition should cause you no problem to ================
understand since there is nothing new here. The
only thing that should be mentioned about this
class is that the protected mode is used for the variables so that
they are readily available in the derived classes which will
inherit this class.
THE PERSON IMPLEMENTATION
_________________________________________________________________
The implementation for the person class is given here and it is a
little strange in the way it is written and used. The intent of
this program is that the virtual method named display() in this
Page 11-1
Chapter 11 - More Virtual Functions
// Chapter 11 - Program 2
#include <iostream.h>
#include "person.h"
// This method should never be called. If it is ever
// called, it is considered an error.
void
person::display(void)
{
cout << "person::display - missing subclass method\n";
}
file will never be used, but it is required by ================
the C++ compiler to be used for a default in PERSON.CPP
case some of the subclasses do not have this ================
function available. In the main program we will
be careful to never call this function due to
the nature of the program we are writing. Keep in mind that C++
requires an implementation of all virtual functions even if they
are never used. In this case the message is obviously intended to
be output as an error message.
Be sure to compile this program prior to going on to the next class
definitions.
// Chapter 11 - Program 3
#ifndef SUPERVSR_H
#define SUPERVSR_H
// This defines three subclasses of the parent type person. Different
// data is stored for the different job classifications to illustrate
// that it can be done.
#include "person.h"
class supervisor : public person {
char title[25];
public:
void init_data(char in_name[], int in_salary, char in_title[]);
void display(void);
};
class programmer : public person {
char title[25];
char language[25];
public:
void init_data(char in_name[], int in_salary, char in_title[],
char in_language[]);
void display(void);
};
class secretary : public person {
char shorthand;
int typing_speed;
public:
void init_data(char in_name[], int in_salary,
char in_shorthand, char in_typing_speed);
void display(void);
};
#endif
THE SUPERVISOR HEADER
_________________________________________________________________
The file named SUPERVSR.H contains the class ================
definitions for the three derived classes, SUPERVSR.H
supervisor, programmer, and secretary. These ================
were all placed in a single file for two
reasons. The first reason is to simply
illustrate to you that this can be done, and secondly, to allow
some of the files to be combined on the disk and to require fewer
compilations by you prior to executing the resulting program. This
is actually a good way to combine these files since they are all
derived classes of a common class. It is a matter of style or
personal taste.
You will notice that all three of these classes contain a method
named display() and all have the same return value of void, and all
have the same number of parameters as the parent class's method of
the same name. All of this equality is required because they will
all be called by the same call statement. You will also notice
that the other method in each class has the same name, but
different numbers and types of formal parameters which prevents
this method from being used as a virtual method.
The remainder of this file is simple and you should be able to read
the code and understand it completely. Once again, this file
cannot be compiled or executed.
void
secretary::display(void)
{
cout << "Secretary ---> " << name << "'s salary is " << salary <<
".\n";
cout << " " << name << " types " << typing_speed <<
" per minute and can ";
if (!shorthand) cout << "not ";
cout << "take shorthand.\n\n";
}
THE SUPERVISOR IMPLEMENTATION
_________________________________________________________________
The file named SUPERVSR.CPP contains the ================
implementation for the three classes. If you SUPERVSR.CPP
spend a little time studying the code, you will ================
find that each of the methods named init_data()
simply initializes all fields to those passed in
as the actual arguments in a very simple manner.
Page 11-2
Chapter 11 - More Virtual Functions
The method named display(), however, outputs the stored data in
different ways for each class since the data is so different in
each of the classes. Even though the interface to these three
methods is identical, the actual code is significantly different.
There is no reason code besides output could not have been used,
but the output is so visible when the program is executed that it
was chosen for this illustration.
This file should be compiled at this time in preparation for the
next example program which will use all four classes as defined in
these four files.
for (int index = 0 ; index < 6 ; index++ )
staff[index]->display();
cout << "End of employee list.\n";
}
// Result of execution
// XYZ Staff -- note salary is monthly.
//
// Supervisor --> Big John's salary is 5100 and is the President.
//
// Programmer --> Joe Hacker's salary is 3500 and is debugger.
// Joe Hacker's specialty is Pascal.
//
// Programmer --> OOP Wizard's salary is 7700 and is senior analyst.
// OOP Wizard's specialty is C++.
//
// Secretary ---> Tillie Typer's salary is 2200.
// Tillie typer types 85 per minute and can take shorthand.
//
// Supervisor --> Tom Talker's salary is 5430 and is the sales manager.
//
// Programmer --> Dave Debugger's salary is 5725 and is code maintainer.
// Dave Debugger's specialty is assembly language.
//
// End of employee list.
THE FIRST CALLING PROGRAM
_________________________________________________________________
The file named EMPLOYEE.CPP is the first program ================
that uses the classes developed in this chapter, EMPLOYEE.CPP
and you will find that it is a very simple ================
program.
We begin with an array of ten pointers, each pointing to the base
class. As you recall from the last chapter, this is very important
when using virtual functions, the pointer must point to the base
class. The pointers that will be stored in this array will all
point to objects of the derived classes however. When we use the
resulting pointers to refer to the methods, the system will choose
the method at run time, not at compile time as nearly all of our
other programs have been doing.
We allocate six objects in lines 16 through 39, initialize them to
some values using the methods named init_data(), then assign the
pointers to the members of the array of pointers to person.
Finally, in lines 41 and 42, we call the methods named display()
to display the stored data on the monitor. You will notice that
even though we only use one method call in line 42, we actually
send messages to each of the three methods named display() in the
subclasses. This is true dynamic binding because if we were to
change the values of some of the pointers in the array, we would
then call different methods with the same pointers.
Be sure to compile and execute this program before continuing on
in this chapter. You will recall that the linking step requires
you to combine several files in order to satisfy all system calls.
After you have done that, we will use the same objects in another
way to show how they can be reused.
A PURE VIRTUAL FUNCTION
_________________________________________________________________
The pure virtual function is also available in the C++ toolbox of
possible constructs. You can use a pure virtual function in the
Page 11-3
Chapter 11 - More Virtual Functions
present example program by changing line 10 of PERSON.H to read as
follows;
virtual void display(void) = 0;
You must then eliminate PERSON.CPP from the project or make
sequence. An implementation for a pure virtual function cannot
exist in the base class.
Every derived class must include a function for each pure virtual
function which is inherited into the derived class. This assures
that there will be a function available for each call and none will
ever need to be answered by the base class. You are not permitted
to create an object of any class which contains one or more pure
virtual functions because there is nothing to answer a message if
one is sent to the pure virtual method. The compiler will enforce
the two rules mentioned in this paragraph.
// Chapter 11 - Program 6
#ifndef ELEMLIST_H
#define ELEMLIST_H
#define NULL 0
#include "person.h"
class employee_list; // Forward declaration
class employee_element { // One element of the linked list
person *employee_data;
employee_element *next_employee;
public:
employee_element(person *new_employee)
{next_employee = NULL;
employee_data = new_employee;};
friend class employee_list;
};
class employee_list { // The linked list
employee_element *start;
employee_element *end_of_list;
public:
employee_list() {start = NULL;}
void add_person(person *new_employee);
void display_list(void);
};
#endif
THE LINKED LIST CLASS
_________________________________________________________________
Examination of the file named ELEMLIST.H will ================
reveal the definition of two more classes which ELEMLIST.H
will be used to build a linked list of employees ================
to illustrate a more practical way to use the
dynamic binding we have been studying in this
chapter.
The two classes were put in the same file because they work
together so closely and neither is of much value without the other.
You will notice that the elements of the linked list do not contain
any data, only a pointer to the person class that we developed for
the last program, so that the linked list will be composed of
elements of the person class without modifying the class itself.
There are two interesting constructs used here that must be pointed
out before going on to the next program. The first is the partial
declaration given in line 8 which allows us to refer to the class
named employee_list before we actually define it. The complete
declaration for the class is given in lines 22 through 29. The
second construct of interest is the friend class listed in line 17
where we give the entire class named employee_list free access to
the variables which are a part of the employee_element class. This
is necessary because the method named add_person() must access the
pointers contained in employee_element. We could have defined an
additional method as a part of employee_element and used this
method to refer to the pointers but it was felt that these two
classes work so well together that it is not a problem to open a
window between the classes. We still have complete privacy from
all other programs and classes declared as parts of this program.
Page 11-4
Chapter 11 - More Virtual Functions
Note that the single method included in the employee_element class
is implemented in inline code. Two of the methods of employee_list
are still open so we need an implementation for this class.
temp = start;
do {
temp->employee_data->display();
temp = temp->next_employee;
} while (temp != NULL);
}
THE LINKED LIST IMPLEMENTATION
_________________________________________________________________
The file named ELEMLIST.CPP is the ================
implementation for the linked list classes and ELEMLIST.CPP
should be self explanatory if you understand how ================
a singly linked list operates. All new elements
are added to the end of the current list. This
was done to keep it simple but a sorting mechanism could be added
to sort the employees by name if desired.
The method to display the list simply traverses the list and calls
the method named display() in line 30 once for each element of the
list.
It is important for you to take notice that in this entire class,
there is no mention made of the existence of the three derived
classes, only the base class named person is mentioned. The linked
list therefore has no hint that the three subclasses even exist,
but in spite of that, we will see this class send messages to the
three subclasses as they are passed through this logic. That is
exactly what dynamic binding is, and we will have a little more to
say about it after we examine the calling program.
// Now display the entire list
list.display_list();
cout << "End of employee list.\n";
}
// Result of execution
// XYZ Staff -- note salary is monthly.
//
// Supervisor --> Big John's salary is 5100 and is the President.
//
// Programmer --> Joe Hacker's salary is 3500 and is debugger.
// Joe Hacker's specialty is Pascal.
//
// Programmer --> OOP Wizard's salary is 7700 and is senior analyst.
// OOP Wizard's specialty is C++.
//
// Secretary ---> Tillie Typer's salary is 2200.
// Tillie typer types 85 per minute and can take shorthand.
//
// Supervisor --> Tom Talker's salary is 5430 and is the sales manager.
//
// Programmer --> Dave Debugger's salary is 5725 and is code maintainer.
// Dave Debugger's specialty is assembly language.
//
// End of employee list.
THE LINKED LIST IMPLEMENTATION
_________________________________________________________________
At this time you should examine the final ================
example program in this chapter named EMPLOYE2.CPP
EMPLOYE2.CPP for our best example of dynamic ================
binding in this tutorial, yet the program is
kept very simple.
This program is very similar to the example program named
EMPLOYEE.CPP with a few changes to better illustrate dynamic
binding. In line 7 we declare an object of the class employee_list
to begin our linked list. This is the only copy of the list we
will need for this program. For each of the elements, we allocate
the data, fill it, and send it to the linked list to be added to
the list where we allocate another linked list element to point to
the new data, and add it to the list. The code is very similar to
the last program down through line 40.
In line 43 we send a message to the display_list() method which
outputs the entire list of personnel. You will notice that the
linked list class defined in the files named ELEMLIST.H and
Page 11-5
Chapter 11 - More Virtual Functions
ELEMLIST.CPP are never informed in any way that the subclasses even
exist but they dutifully pass the pointers to these subclasses to
the correct methods and the program runs as expected.
If you changed PERSON.H to use a pure virtual function, it will
still work with this program just as we discussed earlier.
WHAT GOOD IS ALL OF THIS
_________________________________________________________________
Now that we have the program completely debugged and working,
suppose that we wished to add another class to the program, for
example a class named consultant because we wished to include some
consultants in our business. We would have to write the class of
course and the methods within the classes, but the linked list
doesn't need to know that the new class is added, so it does not
require any changes in order to update the program to handle
consultant class objects. In this particular case, the linked list
is very small and easy to understand, but suppose the code was very
long and complex as with a large database. It would be very
difficult to update every reference to the subclasses and add
another subclass to every list where they were referred to, and
this operation would be very error prone. In the present example
program, the linked list would not even have to be recompiled in
order to add the new functionality.
It should be clear to you that it would be possible to actually
define new types, dynamically allocate them, and begin using them
even while the program was executing if we properly partitioned the
code into executable units operating in parallel. This would not
be easy, but it could be done for a large database that was
tracking the inventory for a large retail store, or even for an
airlines reservation system. You probably have little difficulty
understanding the use of dynamically allocated memory for data, but
dynamically allocating classes or types is new and difficult to
grasp, but the possibility is there with dynamic binding.
1. Add a new class named consultant to the files named SUPERVSR.H
and SUPERVSR.CPP, then add code to EMPLOYE2.CPP to exercise
the new class. Note that you do not need to recompile the
linked list class in order to execute the new code and use the
new class. Even without recompiling the linked list class it
is capable of storing and passing the new class of data
provided of course that the new class is referred to using a
pointer to the parent class.
Page 11-6作者: createch 時間: 2009-9-5 12:32
C++ Tutorial: Chapter 12 FLYAWAY [end of the book]
Chapter 12
FLYAWAY
Now that you have learned lots of things about C++, and know how
to write and use a single isolated class, you have the problem of
how to build a program with several classes that work together to
accomplish some task. After some amount of thought, it seems that
an adventure game would be a good candidate for a relatively large
example program. It has lots of input and output and requires a
good deal of flexibility while running since there are so many
things that can be included in the game as obstacles, mazes, items
to find, and puzzles to solve.
The adventure game presented in this chapter is unique as far as
I know, since I have never heard of another adventure game
featuring an airport. The location is not nearly as important as
the code used to get through the airport. You are advised to play
the game to get familiar with what the code does, then study the
code to see how it works. Finally, you are given an assignment to
extend the code which will be the real test of whether you
understand its operation.
PLAYING THE GAME : FLYAWAY.EXE
_________________________________________________________________
Please compile and link the FLYAWAY program so that you will have
the FLYAWAY.EXE
The entire program is composed of 15 files and will take a little
effort on your part to properly compile and link it, but you should be able
to do so using your compiler.
Prior to studying the source code for this game,
it would be to your advantage to spend some time
playing the game to get familiar with what the
game does. Load the file FLYAWAY.EXE and begin
the adventure through the airport.
If you have played adventure games before, sometimes called
interactive fiction, you should begin trying various commands to
find your way through the airport to your proper plane. If you
have not played before, a few hints are in order concerning how to
play the game.
The object of the game is to get to your proper plane on time so
you can fly away to your vacation. Of course there a few obstacles
and problems along the way and they will be brought up at the
appropriate time. It will be up to you to solve the puzzles
associated with each problem. To add a little excitement, you only
have about twenty-five minutes to get to your plane, with each move
Page 12-1
Chapter 12 - Flyaway Adventure Game
taking a minute, so you must hurry. Of course, just getting to the
plane on time is not enough, there are a few additional
requirements. You will find what they are as you progress through
the game. You will probably find it necessary to restart the game
many times before you arrive at your destination unscathed and on
time.
THE METHOD OF PLAY
_________________________________________________________________
The method of play is extremely simple. You simply wander around
the airport looking for things to do and places to go. You move
around the airport by giving the system commands to go in a
direction with four choices available, north, south, east, or west.
You can abbreviate any of these four direction commands to the
first letter only, and you can use either upper or lower case. The
system may move you to another area of the airport, or it may tell
you that you can't go that way. Try loading the game now and
typing the four directions once each to see what happens. If this
is not clear, enter the word help to get you started.
In addition to moving around, you can pick up items or ask for more
information in any of the rooms. Try telling the system to look
around the room and see what additional information it gives you
for each room, some of the clues for solving the puzzle are given
in the clues issued in response to a look command. Another
important command is inventory which will give you a list of the
items you possess at any given point in time. Type the word
inventory at this time to see what items you possess.
The remainder of the commands consist of two words, a verb and a
noun. These can be given in either order, since the system is
smart enough to know the difference, and additional words may be
given following the legal words. If you give the system a command
that is not in its limited vocabulary, it will tell you it doesn't
understand that word. Try telling the system to drop an item you
possess, or get an item that is located in the room you are
currently in.
Several friends have played this game with no more knowledge than
you have been given. One solved it in 40 minutes, but most took
about an hour to complete the game. After you play the game for
awhile, return to the text and we will study the source code for
the game. The entire source code for the game is on your
distribution disk. The game was purposely kept small so the code
could be easily grasped by a programming student. There is no
reason the game could not have been made much larger by the
addition of more rooms, items, and traps. You may choose to do
just that to gain experience in working with C++.
Page 12-2
Chapter 12 - Flyaway Adventure Game
// This file contains a few general purpose definitions to be used
// with several of the FLYAWAY adventure game classes.
A FEW SPECIAL CONSTANTS
_________________________________________________________________
The file named FLYAWAY.H contains the ===============
definitions for TRUE and FALSE as well as the FLYAWAY.H
enumerated type defining the legal dictionary of ===============
words for use in playing the game. The list was
started at a value of 1 so the value of zero can
be used to indicate that the word in question was not in the
library and hence not a legal word for use with the game.
The #ifndef in line 5 is required because this header file is
included in many of the other files and if it is included more than
once, there will be a multiple definition, and hence an error. A
class only needs to be defined once, so after it is defined by one
of the includes, the name ITEMS_H will be defined and any other
defines will be ignored. This is necessary because of the separate
compilation capability of C++. This was described in more detail
near the end of chapter 7.
// This is the game clock. It increments once for every
// move, the increment being accomplished in the method
// named inc_and_print_time.
#ifndef CLOCK_H
#define CLOCK_H
class clock {
int hour;
int minute;
public:
clock(void);
int present_hour(void) {return hour;}
int present_minute(void) {return minute;}
void inc_and_print_time(void);
};
#endif
THE FIRST CLASS - clock
_________________________________________________________________
Examine the file named CLOCK.H for the ===============
definition of the clock class. This is the CLOCK.H
class for the game clock, and only one instance ===============
of this class will be used. It will be used for
the object time_of_day defined in line 23 of
FLYAWAY.CPP.
The class is very simple, consisting of only two variables, the
hour and the minute, and four methods. The first method is the
constructor used to initialize the clock to 8:51 as you can see if
you refer to the implementation of this class in the file named
CLOCK.CPP. The next two methods are used to get the current values
of the two variables. The final method is much more interesting
since it does more. It updates the time of day clock and outputs
the user prompt to ask for the next command. This may not be the
best place to output the user prompt since this class is devoted
to the time of day and associated operations, but this was chosen
as the place to do it since the time of day is part of the user
prompt. You will notice that the clock was initialized to 8:51,
but the first time output was 8:52 when you played the game. In
order to simplify the coding later, when we need to decide if we
made it to the plane on time, the time was incremented at the
beginning of each game move. The time is therefore the same when
the command is entered and when it is executed, hence the time is
incremented prior to even the first output.
The clock class is by far the simplest class in the adventure game
and should be simple for you to understand. After you are sure you
understand it, we will go on to the next class.
Page 12-3
Chapter 12 - Flyaway Adventure Game
// This class reads and parses the user input, checks that a valid
// verb has been input, and checks for a valid noun.
#ifndef WORDS_H
#define WORDS_H
#include "flyaway.h"
class words {
enum word verb;
enum word noun;
void read_a_line(word &wd1, word &wd2);
int get_an_ASCII_word(char in_string[]);
int find_in_dictionary(char in_string[]);
int is_a_verb(enum word input_word);
int is_a_noun(enum word input_word);
int is_a_direction(enum word input_word);
int is_an_operation(enum word input_word);
public:
void get_command(void);
enum word get_verb(void) { return verb; };
enum word get_noun(void) { return noun; };
int is_a_verb(void) { return is_a_verb(verb); };
int is_a_noun(void) { return is_a_noun(noun); };
int is_a_direction(void) { return is_a_direction(verb); };
int is_an_operation(void) { return is_an_operation(verb); };
void stop_game(void) { verb = quit; };
The input command parsing routines are defined ===============
within the words class and the code for the WORDS.H
class is in WORDS.CPP. The code is ===============
straightforward and simple to understand if you
study it, so only a few comments will be made
about this class.
The method get_command() reads two words from the keyboard by
calling the function read_a_line() and stores the words in the
class members verb and noun. It stores zero for either or both of
the words if it does not find a valid noun and a valid verb.
Two methods are included to provide the verb or noun which was
input as the last user input. This allows any code that has
visibility of the object based on this class to find out what the
player would like to do.
There are four methods beginning with is_ in this class that are
used to determine if a word is a verb, a noun, a direction, or an
operation. These are called upon from various places within the
program. What they do should be easy for you to understand, but
it will take a little thought on your part to see why these are
needed in other parts of the code.
Finally the simple method named stop_game() is used to set the verb
to the value of quit so the game will be ended by the control logic
in the main program FLYAWAY.CPP.
All of the source code for the implementation is given in the file
named WORDS.CPP. Since this code is fairly simple and well
commented, you will be left on your own to study it to whatever
depth you desire.
// This stores the items located in each room and also in
// the players posession. It consists of four boolean
// variables and methods to use them.
#ifndef ITEMS_H
#define ITEMS_H
#include "words.h"
class items {
int keys_on_hand; // TRUE if keys are here, otherwise FALSE
int candy_on_hand;
int ticket_on_hand;
int money_on_hand;
public:
items(void); // Constructor, set all to FALSE
void add_item(word item_to_add); // Add one item to list
void drop_item(word item_to_drop); // Drop one item from list
int item_here(word item_to_check); // Returns TRUE or FALSE
void list_items(void); // List personal items
void list_items_in_room(void); // List location items
};
#endif
THE SECOND CLASS - items
_________________________________________________________________
If you examine the files named ITEMS.H and ===============
ITEMS.CPP, you will find the complete ITEMS.H
definitions of the handling of the items that ===============
you carried around the airport in the game.
There were exactly four transportable items that
could be located in each room or carried by yourself, the keys, the
candy, the ticket, and the money. The keys and the money keep you
from getting through security and the ticket and candy are required
to get you safely on the plane and enroute to your destination.
The four items are stored in the class named items in the form of
TRUE or FALSE since that is the only required indication. A TRUE
means the item is located here, and a FALSE means the item is not
Page 12-4
Chapter 12 - Flyaway Adventure Game
here. The values of TRUE and FALSE are defined in FLYAWAY.H.
Finally, there are six methods to operate on these items.
The first method is a constructor to set all items to FALSE, and
the next two are used to either get a specific item, or drop one.
The fourth method is used to tell us if the item is located here
and the last two are used to tell us what items are on hand in this
location. You will notice that the final two are different because
different text was desired depending on whether you are carrying
the items, or they are located in a room somewhere.
This file, like all other header files, is protected from multiple
inclusion by the #ifndef construct discussed earlier.
This class is used in line 24 of FLYAWAY.CPP to define an object
for the player named personal_items which stores the list of items
the player is carrying around. It is also used in the class
location as an embedded or nested object to store the items that
are located in each of the 19 locations in the game.
Once again, the implementation for this class is so simple that you
will have no difficulty in understanding it.
// This takes care of all gate assignments and flight scheduling.
// The players flight is shuffled (changed) each move until he reads
// his ticket. If he gets to the proper gate prior to reading the
// monitor in the waiting area, (reading the monitor at the ticket
// counter doesn't matter), the gates are rescheduled.
//
// The method named check_flight does all of the required checking
// to see that everything was done properly prior to getting on
// the plane. It only does checking if the player is on one of the
// planes.
#ifndef SCHEDULE_H
#define SCHEDULE_H
#include "location.h"
class schedule {
location *gate[4]; // Gate names
int flight_number[4]; // There are four flights [0] to [3]
char *destination[4];
int depart_hour[4];
int depart_minute[4];
int flights_frozen; // Frozen after monitor is read in the
// waiting area
int gates_frozen; // Frozen after ticket is read
int my_gate;
THE FLIGHT AND GATE CLASS - schedule
_________________________________________________________________
Examine the files named SCHEDULE.H and ================
SCHEDULE.CPP for our first example of a rather SCHEDULE.H
large class, the one that does the flight and ================
gate scheduling. You will find a large number
of variables in this class, and eight methods to
handle the variables. Instead of a detailed description of each
variable and method, we will only give a brief overview of the
class.
Only one object of this class is declared named flight_info in line
22 of the main program named FLYAWAY.CPP. The constructor
initializes the flight possibilities, and the method named
shuffle_gates() shuffles all gates around if the player arrives at
his correct gate without reading the monitor in the waiting area.
Once the monitor in the waiting area is read, the flights_frozen
variable is made TRUE. Likewise, the players destination is
changed during every move by the method named shuffle_flights()
until the player reads his ticket invoking the method named
list_actual_destination().
This class contains the methods to list the data seen on the
monitor, as well as the data seen when invoking the command look
at one of the gates. Finally, this class contains the method named
check_flight() which searches through the list of requirements to
see if the player has completed all requirements to successfully
reach the final destination for his vacation.
Page 12-5
Chapter 12 - Flyaway Adventure Game
You will notice that several of the location objects were required
to be available within this code and are listed as extern in lines
12 through 21 of the implementation of the class. The only other
thing to point out is the rest room requirement prior to boarding
the flight. Line 28 is where the global variable is defined and
initialized, then in line 77 it is set TRUE if the current location
is the rest room, since this is called once during each player
move. Finally, the state of this variable is checked in line 230
of this file and the appropriate action taken. You will note that
the main program is not aware that the rest room variable exists
or that anything happens as a result of this variable. In addition
to information hiding, we may coin a new term, something like
"Information Ignorance", since the main program did not even need
to be aware that there was a requirement to visit the rest room.
Even though this is a relatively large and complex class, it is
well commented so no further information will be given concerning
the implementation.
// This is the definition of the 19 different locations it is
// possible to enter. This class contains an embedded object of
// class "items" to store the elements in each location. The
// message is output automatically when the location is entered,
// and the look_message is output when the player gives the look
// command.
#ifndef LOCATION_H
#define LOCATION_H
#include "items.h" // This gets the definition of the item list
class location {
location *north_move; // Where we go to, north of here
location *east_move; // Where we go to, east of here
location *south_move; // Where we go to, south of here
location *west_move; // Where we go to, west of here
char *message; // Message output when we enter here
char *look_message; // The message output for a "look"
items list_of_items; // The list of items in this location
public:
void init(location *valid_north, // These four directions are
location *valid_east, // initialized when init
location *valid_south, // is called.
location *valid_west,
char *local_message,
char *local_look_message);
location *move(word direction); // Move to another location
void add_item(word item_to_add); // This puts an item here
void drop_item(word item_to_drop);// Item picked up by player
char item_here(word item_to_check);// Is this item here?
void display_message(void); // This displays the message
void display_list_of_items(void); // Display items found here
// and a few room details.
};
#endif
THE MOST USED CLASS - location
_________________________________________________________________
The file named LOCATION.H is the header file for ================
the class named location. It is the class that LOCATION.H
controls all of the moves from location to ================
location.
This class is a bit unusual in that most of the stored data is in
the form of pointers to the various entities. The first four are
the locations to which we will go if we move in one of the four
directions from the current location. You will note that they are
pointers to those four locations. Next we have pointers to two
different character strings associated with this room. Finally in
line 22, we declare the object named list_of_items which is an
object of class items defined earlier. Note that this is an
embedded class, a class embedded within the location class. It is
not a parent class which we are inheriting something from. In fact
we are instantiating an object of class items for use within the
room since the room is allowed to store any combination of the four
items contained in the class named items.
There is no constructor used with this class since we choose to
initialize the locations one by one. The method named init() has
6 variable parameters, all of which are pointers, associated with
it which it uses to initialize the first six variables of this
object. The last variable, an object of class items, is
initialized through use of the constructor associated with its
class. Referring to lines 40 through 171 of the implementation for
the map class, you will find all of the initialization code for the
19 objects of class location. If you drew a map when you played
the game, you will see the interconnections between the various
Page 12-6
Chapter 12 - Flyaway Adventure Game
locations embedded in the initialization statements. Notice there
is no way to get back to the car from the passenger drop off area,
because presumably the car leaves when you get out of it.
The next method, named move(), returns a pointer to the new
location if a move was legal, otherwise it returns a NULL value.
The observant student will also notice that there are special cases
involved with getting out of the snack bar and getting through
security. These are located here because they are part of the move
logic. If you played the game to the complete conclusion, you
surely had trouble with at least one of these situations.
The rest of the methods in this class should be self explanatory
and will not be discussed any further.
// The first three messages are relatively long, so they have
// been removed from the text in order to make the text more
// readable.
char startup_message[] =
" Welcome to Flyaway, version 2.20\n\n"
" Your best friend offered to drop you off at the airport\n"
" so you can begin your dream vacation and you have just\n"
" arrived at the passenger drop off area. You have about\n"
" 25 minutes to get to your plane, you haven't had any \n"
" lunch, and you have a full bladder. Be very careful, \n"
" there is a lot of construction going on all around the \n"
" airport. Good luck!\n\n"
" Type help if you want a few clues and a word list.\n\n";
char paper_message[] =
"\n C++ TUTORIAL RELEASED\n"
"Coronado Enterprises has a full line of computer language\n"
"programming tutorials available. Write and ask for the\n"
"latest information.\n"
" Coronado Enterprises\n"
" 12501 Coronado Ave NE\n"
" Albuquerque, NM 87122\n\n"
"There is another story about danger at the airport due to\n"
"construction. Be very careful!\n";
char help_message[] = // Help
"Each action requires a verb, or a verb and a noun, and only\n"
"the first two words of the command are significant, any \n"
"other words on a line are ignored. The four directions can\n"
"be abbreviated to the first letter to make it easier to get\n"
"to your flight. The entire vocabulary is given as;\n\n"
" ------- verbs ------- ---- nouns ----\n"
" north drop read keys money\n"
" east get buy candy monitor\n"
" south look help ticket paper\n"
" west quit inventory\n\n"
" look = give more information on current location\n"
" inventory = list items I am carrying\n\n"
"You better hurry, you just wasted a minute reading this.\n\n";
// These messages are output when entering a location
char your_car_message[] = ""; // Never needed - can't enter here
char pass_drop_off_message[] =
"You are in the passenger drop off area.\n";
char lobby_message[] =
"You are in the airport lobby.\n";
char baggage_claim_message[] =
"You are in the baggage claim area. There are a few bags left\n"
"over from the last flight slowly going around the carrosel.\n";
char dark_room_message[] =
"You are in a poorly lit room and as you move about, you step\n"
"on a board that gives way under your weight. You fall into \n"
"a subbasement and are killed. No vacation - game over.\n";
char ticket_counter_message[] =
"You are at the ticket counter area.\n";
char tunnel_message[] =
"You are in the tunnel to the gates.\n";
char rest_room_message[] =
"You are in the rest room, and it really feels good to have\n"
"taken care of that problem.\n";
char snack_bar_message[] =
"You are in the snack bar and gift shop.\n";
char security_message[] =
"You are in the security and inspection area leading toward\n"
"all gates.\n";
char waiting_area_message[] =
"You are in the waiting area between the gates.\n";
char gate1_message[] =
"You are in the gate 1 waiting area.\n";
char gate2_message[] =
"You are in the gate 2 waiting area.\n";
char gate3_message[] =
"You are in the gate 3 waiting area.\n";
char gate4_message[] =
"You are in the gate 4 waiting area.\n";
char plane_message[] =
"You are inside of a large Jumbo-jet. The doors close, and\n"
"the plane taxis away from the gate to prepare for takeoff.\n";
// These are output in response to a "look" command
char y_c_look_message[] =
"You are in your car with your friend, but that should have\n"
"been obvious. You better hurry, you may miss your plane.\n";
char p_d_o_look_message[] =
"The airport entrance is to the north.\n";
char l_look_message[] =
"A small newsstand is here, and the latest edition of the news-\n"
"paper is on the newsstand. A group of three ragged looking\n"
"characters have signs that proclaim \"BAN THE BOMB\", and are\n"
"trying to sell you some books.\n";
char b_c_look_message[] =
"There is a dark room to the west with a sign near the door\n"
"that cautions you to keep out - danger. You should not enter\n"
"this room under any circumstances.\n";
char d_r_look_message[] = "";
char t_c_look_message[] =
"There is a departure monitor on the west wall with a list of\n"
"all of the currently active flights. Wilbur Snuffle is stand-\n"
"ing behind the ticket counter. He asks if you have any baggage\n"
"to check through.\n";
char t_look_message[] =
"A sign on the north wall says \"TO ALL GATES\".\n";
char r_r_look_message[] =
"That's not at all polite to look around in here, and there is\n"
"nothing of interest here, you better hurry to your flight.\n";
char s_b_look_message[] =
"There are many expensive items for sale here, but have no need\n"
"of any of those expensive things. They are always overpriced\n"
"at the airport anyway. A sweet looking old lady is ready to\n"
"help you find what you need.\n";
char s_look_message[] =
"The security inspectors are to the north, and they look you\n"
"over very carefully as you enter. The tall inspector is Ralph\n"
"and the short one is either Homer or Bill.\n";
char w_a_look_message[] =
"There is a departure monitor on the north wall, and a lot of\n"
"construction material laying around everywhere.\n";
char g1_look_message[] = "";
char g2_look_message[] = "";
char g3_look_message[] = "";
char g4_look_message[] = "";
char plane_look_message[] = "";
THE LOCATION MESSAGES
_________________________________________________________________
Examine the file named MESSAGE.TXT for a ===============
complete listing of the messages output to the MESSAGE.TXT
monitor when each location was entered. You ===============
will also find the text for each of the messages
output in response to a look command in this
file. These were put into a separate file only for the purpose of
reducing the size of the map class implementation file. It does
not reduce the compile time since these messages are not separately
compiled. They are included into the file and compiled each time
the map file MAP.CPP is compiled. You will note that a few of the
messages have no text at all, only the empty quote marks, but are
included in order to have something for the initialization code to
work with.
Three other messages are stored here for convenience in lines 5
through 40. Their use and meaning should be self-evident.
// XXXXX X X X X X X X X X
// X X X X X X X X X X X X
// X X X X X X X X X X X X
// XXX X X X X X X X X X
// X X X XXXXX X X X XXXXX X
// X X X X X X X X X X X
// X XXXXX X X X X X X X X
// FLYAWAY - version 2.20
// Written by: Gordon Dodrill - Jan 20, 1992
// Copywrite 1989, 1990, 1992 - Coronado Enterprises
words input_words; // The player's command inputs
map airport; // The physical layout of the airport
schedule flight_info; // Schedule and gate information
clock time_of_day; // The system timekeeper
items personal_items; // Things the player carries with him
main()
{
airport.initialize();
do {
input_words.get_command(); // Get user inputs
flight_info.shuffle_flights(); // Until monitor read
flight_info.shuffle_gates(); // Until ticket read
airport.perform_action(); // Try to perform the request
flight_info.check_flight(); // Did you get there?
} while (input_words.get_verb() != quit);
}
THE MAIN PROGRAM
_________________________________________________________________
We finally reach the main program, the one that ===============
actually does the top level control. Examine FLYAWAY.CPP
the program named FLYAWAY.CPP and we will look ===============
at some of its interesting characteristics.
Beginning with the main() entry point itself, we see that following
a call to airport.initialize(), we enter a single do while loop
which terminates when the player enters the word quit or when the
verb quit comes up some other way. There are other ways to set the
verb to quit because it is generated internally in some cases such
as at end of game.
Page 12-7
Chapter 12 - Flyaway Adventure Game
The loop itself consists of 5 method calls. First we call the
function named input_words.get_command() to get the players input
command in line 30. Next we send two messages to the object named
flight_info to shuffle the flights and gates if the proper actions
have not been performed, then we call airport.perform_action()
which we will describe in a few paragraphs. Finally, we send a
messages to the object named flight_info to check if the player has
reached one of the gates. Remember that within most of the methods
we perform checks to see if we need to do the thing requested in
the message, then either perform the action or simply return to the
caller or message sender.
// This class handles the airport layout. It defines which direc-
// tion you can go from each location and performs the actions
// requested by the player.
THE WORKING METHOD
_________________________________________________________________
The only function we have not mentioned yet is ===============
the one that does most of the interesting work, MAP.H
the function named perform_action() which begins ===============
in line 183 of the MAP.CPP file. This function
looks at the verb and noun, if there is one, and
causes the correct action to be performed. Because of the way we
packaged all of the other routines, this function is a snap to
implement and to study. If you go through each of the else if
clauses in this function, you will have no trouble understanding
what action is taken for each of the input commands. You will
notice that many of the actions have conditional clauses before the
action is taken. For example, it is illegal to buy candy unless
the player has money, the location has candy, and the location must
be the snack_bar according to the rules of the game.
Finally, at the end of this method in line 277, we have the default
case if nothing else was accomplished. It is assumed that there
was something funny requested such as a request to get a monitor.
Both of these are legal words but they make no sense together.
FINAL COMMENTS ON FLYAWAY
_________________________________________________________________
Now that you have played the game for awhile and studied the game
in detail, you should have an appreciation for how this game can
be written. Of course, it could be written in any of several
thousand different ways of packaging and definition. This has been
only one of the ways.
Because the student may be left with the sinking feeling that this
method simply fell out of the sky or was arrived at in some other
esoteric way, it would only be fair to point out that several
earlier attempts at outlining this project were attempted and
rejected prior to this arrangement. Also, when this tutorial was
being updated from version 2.0 to 2.2, the entire program was
restructured. In version 2.0 and prior versions, about 50% of the
Page 12-8
Chapter 12 - Flyaway Adventure Game
code was in classes, but due to additional programing experience,
about 98% of the flyaway program is now encapsulated in classes.
Object oriented programming requires the same forethought as non-
object oriented programming, but the object oriented compiler will
help you in the coding and debugging phase since the compiler will
find and flag many of the oversight errors we are so good at
introducing into our code. It was observed during the coding and
debugging phase of this project that in nearly every case, when the
program finally got through the compiler, the program would
actually run without bombing out the system. This is not always
the case using any standard procedural programming language.
YOUR PROGRAMMING PROJECT
_________________________________________________________________
This programming assignment is intended to give you a little
experience in working with a relatively large project as opposed
to the very small programs we have been working with in this
tutorial.
Add a suitcase to the game, to be found in the car at arrival, and
which must be checked in at the ticket counter prior to attempting
to get through airport security. This will not be trivial since
several classes will be affected. Some of the operations you will
have to do are listed below.
1. Add the noun "suitcase" and the verb "check" to the word list.
Of course, they must be entered at the right place in the
list.
2. Add the suitcase to the items class, including additional code
to each of its methods.
3. Initialize the items at location your_car to include the
suitcase.
4. Add an additional check when passing through security to check
that the player is not carrying the suitcase. You can add any
sort of penalty desired, including death by firing squad for
attempting such an obviously crooked deed.
5. You will need to add a check when the player finally gets on
his correct airplane to see that he checked his suitcase. If
he did not, you could output any desired text indicating
stupidity or forgetfulness.
Since I have not actually added the suitcase to the game and tested
it, I am not sure that this is all that will be required, but it
should be the majority of effort required. The bottom line of this
effort is that if you understand this program enough to perform
Page 12-9
Chapter 12 - Flyaway Adventure Game
this modification, you have a good understanding of how the program
works and how objects work together to perform a task.
Once you understand this program, you should define a programming
project for yourself that will use object oriented programming
techniques and begin designing and programming it. The best way
to learn to use OOP is to actually use it.