| |
Main Menu | Next Section |
The C++ Perspective on Input and Output
A. Introduction
In this chapter we explore in more detail the tools and functionality
provided with C++ to allow complex input and output. The contract
program had some rather complex output screens, and it would be
nice to make them as readable as possible. We have also observed
that a truly useful version of the contract program would be capable
of reading the contract 'database' from a file and writing that
database back to a file. In this way one would not need to re-enter
the data each time the program started. These are the kinds of
issues dealt with here.
C++ provides a great detail of built-in input-output functionality
and many commercial versions add their own features. Here, we will
only explore some of the most basic of these features. The goal
is not to provide you with a complete list of all the features
and how to use them but to give you an overview, so that you have
a sense of what is available and feel comfortable exploring additional
features on your own. For more information, you should check out Section XIV of the "Essentials of C++" and
the manual for your compiler. There are also a number of books
that go into more detail with regard to I/O. One useful book is
"Success with C++" by Kris Jamsa (chapters 1, 11, and
12). Another useful book is "Learning C++" by Neill
Graham (chapter 8). Whatever you do, be sure to play with and explore
the instructions and code you find. It is only by such play that
you will truly master C++ input and output.
II. Streams
You have already been introduced to the idea of a stream - a sequence
of symbols coming from some input device or going to some output
device. Further, you have used 'cin' as an input stream and 'cout'
as an output stream. As you know, these streams are declared and
defined for you in the header file "iostream.h".
By default, 'cin' is the stream that provides a connection from
the keyboard to your program, while 'cout' provides a connection
from your program to the screen. Some operating systems and user
interfaces allow you to redirect the default input and
output. When you redirect input or output, you are simply changing
the source or destination of a stream.
With Dos or Unix, for example, one can write:
to indicate that the file named "file.dat" will act as the
input to some program. (Substitute an actual program name for
"program name".) Likewise one can write:
to indicate that the output from "program name"
will be sent to the file "file.dat".
And, you can combine them together:
Note that the "<<" and ">>" Dos
and Unix character sequences have in one sense exactly the opposite
meanings that they have in C++. At the operating system level,
"<<" indicates that the "information depository"
that follows will be the input source of the program. At the same
level, ">>" indicates that what follows will be
the "information depository" for the output of the program. In C++, "<<" is used with output, while ">>" is used with input.
Still, while the meanings may be opposite, the symbolism makes
great sense at both levels. The only real difference is in the
placement of the source and destination identifiers. In Dos and Unix,
the source for an input is on the right and the destination (the program)
is on the left. Thus, we use "<<" to indicate
going from the source to the destination. In C++ the source (for
example, cin) is on the left and the destination (the variable
to hold the input) is on the right, so we use ">>".
The same (but reversed) analysis applies for output.
The redirection described here only has limited uses. One cannot
so easily redirect input and output in more complex user interfaces
such as windows-based environments. In addition, redirection is
an all or nothing proposition. Using "cin >> ....", for example,
the input can only come from one stream. One can choose to have
all the input come from a file or from the keyboard but, since
"cin" is one stream with one beginning and end, one
cannot have 'cin' represent two input sources. Likewise, with
'cout' one can choose to send the output to the screen or to a file
but not both. In other words, using 'cout' alone, one cannot write
the information about individual contracts to the screen and, in
the same program, send the complete database of contracts to a
file. We will learn later in this chapter how to have multiple input sources and multiple output destinations.
C. A More Complete Understanding of the Object-Oriented Paradigm
In turns out that the C++ input- output system is designed using
the full complexity of the object oriented paradigm. To understand
this system, we need to know more about this paradigm.
Back in Chapter 5 we introduced the object-oriented paradigm. In describing
it, we focused almost exclusively on the notion of encapsulation. This notion is extremely important in understanding what
object-oriented programming provides, but it is not, by itself,
the essence of the paradigm. Indeed, other approaches also emphasize
encapsulation. To be object-oriented, an approach needs two other
features, inheritance and polymorphism. We will
only briefly describe each of these here, but you should realize
that you don't really know the object-oriented paradigm or C++
unless you understand and can use these effectively.
1. Inheritance
We know what inheritance means with humans. You probably inherit
many of the features of your parents (and maybe you have already
or someday will inherit their money). In the object-oriented approach,
inheritance is a way of organizing the classes required in a program
and taking advantage of any redundancy found among the classes.
Often, different classes have almost the same structure and, if
we can avoid constant repetition of declaration and definition
code, we can avoid some work and errors.
Scientists also often use a similar approach in organizing those
aspects of the world they are studying. Biologists, for example,
create classification schemes for living beings, based on similarities.
Using this approach, one can then say that all dogs are mammals
and know, therefore, that all dogs have the common set of characteristics
held by mammals. It then becomes unnecessary to repeat these
characteristics in describing a specific species of dog. Later,
if someone tells you that "Cyndal" is a Belgian Sheep
Dog, you know that Cyndal is a mammal and that she, therefore,
has (inherits) certain characteristics from the type mammal.
Consider a program that deals with many different kinds of contracts
- office cleaning contracts, swimming pool contracts, athletic
contracts, business executive contracts, and all the variations
of each. There would be certain properties and responsibilities/behaviors
common to all contracts, and it would be best if these properties and behaviors could be
coded once for all contracts. Likewise, athletic and business
executive contracts have common properties and behaviors that
are not shared by office contracts. For example, there is no salary
field in an office contract. We might consider a common contract
class called 'employee' to represent the common features of athletic
and business executive contracts. Likewise, swimming pool and office
contracts might have common features, and we could group these
under the class 'cleaning' contracts. Below is a simplistic drawing
of how the inheritance structure might look:
DRAW Contract
Cleaning Employee
Office etc.
FIGURE 1
2. Polymorphism
The analysis and design of such inheritance structures is a course
in itself and won't be discussed here. In a moment, however, we
will see that such an inheritance structure is part of C++'s input -
output system.
First, we need to discuss polymorphism. Don't let the word overwhelm
you. Actually, you have already seen a hint of polymorphism. 'Poly'
means 'many' and a 'morph' has traditionally meant form. (More
recently it has taken on a somewhat new meaning as in graphics
where 'to morph' is to change from one form to another. ) Used
in conjunction with the object-oriented paradigm, polymorphism
refers to the ability of a variable to refer to instances from
different sub-classes in an inheritance structure and thereby
respond differently to the same message. In other words, the variable
can take on many forms. OK, OK, an example is needed.
Suppose we have a variable (call it 'theContract') that is capable
of referring to a specific office contract or to a specific swimming
pool, athletic or business executive contract. Suppose further,
that each of these classes has a method called "CalculateCost"
that calculates and returns the cost of the contract. Each of
these methods would have different definitions, depending on how
office vs swiming pool vs athletic vs business executive contract
costs are calculated. It would be nice if the system could call
the appropriate "CalculateCost" method depending on
the type of contract stored in 'theContract'. That is what polymorphism makes possible.
Using polymorphism, when the variable 'theContract' is sent the
'CalculateCost' message, the system responds by executing the
code corresponding to whatever type of contract 'theContract'
holds at this moment. At one point in the program, 'theContract'
may be holding an office contract, and the system will then execute
the office contract version of 'CalculateCost'. Later, if 'theContract'
holds an instance of the athletic contract class, the code corresponding
to athletic contracts will be executed. This can become a powerful
tool in programming. (It is often used, for example, in games
programming to simplify the process of getting all the game elements
to respond to a change in situation.)
3. Object-Based Input-Output
Input and Output can be greatly simplified if we implement their capabilities,
using these three essential properties of the object-oriented
paradigm - encapsulation, inheritance, and polymorphism. Consider
the different types of streams we might have - general input streams
(such as 'cin'); general output streams (such as 'cout'); streams
for handling file input; and streams for handling file output.
All these streams have some characteristics/behaviors in common
so, just as we did for contracts, we might consider a common class
(often called a base class) to represent all that is common.
All the classes would inherit from this class. We might also conclude
that file input streams have all the characteristics of general
input streams but add more. Therefore, they would inherit from
the general input class. Here is the inheritance structure for
part of the input-output system.
DRAW
Figure 2
Although simplified, this is essentially how C++ has organized the
input-output system. To begin with, the properties and behaviors
common to all input and output are stored in the 'ios' class.
For example, all streams have a property to record the 'error'
state of the stream. Rather than have this property re-declared
in all classes, it is declared once in 'ios' and inherited by
all the other classes and their instances.
Topics Covered in the "Essentials of C++"
| |
Main Menu | Next Section |