Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

lafore_robert_objectoriented_programming_in_c

.pdf
Скачиваний:
51
Добавлен:
27.03.2023
Размер:
8.94 Mб
Скачать

Virtual Functions

Base-class virtual functions need to be instantiated in all derived classes, or the classes themselves become abstract. Thus the Operand class needs a getNumber() function, even though it doesn’t store a number, and the Number class needs getOperand(), even though it doesn’t store an operand.

Expand this framework into a working program by adding a Stack class that holds Token objects, and a main() that pushes and pops various operators (such as + and *) and floating-point numbers (1.123) on and off the stack.

8.Let’s put a little twist into the HORSE example of Chapter 10 by making a class of extracompetitive horses. We’ll assume that any horse that’s ahead by the halfway point in the race starts to feel its oats and becomes almost unbeatable. From the horse class, derive a class called comhorse (for competitive horse). Overload the horse_tick() function in this class so that each horse can check if it’s the front-runner and if there’s another horse close behind it (say 0.1 furlong). If there is, it should speed up a bit. Perhaps not enough to win every time, but enough to give it a decided advantage.

How does each horse know where the other horses are? It must access the memory that holds them, which in the HORSE program is hArray. Be careful, however. You want to create comhorses, not horses. So the comhorse class will need to overload hArray. You may need to derive a new track class, comtrack, to create the comhorses.

You can continuously check if your horse is ahead of the (otherwise) leading horse, and if it’s by a small margin, accelerate your horse a bit.

9.Exercise 4 in Chapter 10 involved adding an overloaded destructor to the linklist class. Suppose we fill an object of such a destructor-enhanced class with data, and then assign the entire class with a statement such as

list2 = list1;

using the default assignment operator. Now, suppose we later delete the list1 object. Can we still use list2 to access the same data? No, because when list1 was deleted, its destructor deleted all its links. The only data actually contained in a linklist object is a pointer to the first link. Once the links are gone, the pointer in list2 becomes invalid, and attempts to access the list lead to meaningless values or a program crash.

One way to fix this is to overload the assignment operator so that it copies all the data links, as well as the linklist object itself. You’ll need to follow along the chain, copying each link in turn. As we noted earlier, you should overload the copy constructor as well. To make it possible to delete linklist objects in main(), you may want to create them using pointers and new. That makes it easier to test the new routines. Don’t worry if the copy process reverses the order of the data.

Notice that copying all the data is not very efficient in terms of memory usage. Contrast this approach with that used in the STRIMEM example in Chapter 10, which used only one set of data for all objects, and kept track of how many objects pointed to this data.

565

11

FUNCTIONS V

IRTUAL

Chapter 11

566

10.Carry out the modification, discussed in Exercise 7, to the PARSE program of Chapter 10. That is, make it possible to parse expressions containing floating-point numbers. Combine the classes from Exercise 7 with the algorithms from PARSE. You’ll need to operate on pointers to tokens instead of characters. This involves statements of the kind

Number* ptrN = new Number(ans); s.push(ptrN);

and

Operator* ptrO = new Operator(ch); s.push(ptrO);

Streams and Files

IN THIS CHAPTER

Stream Classes

568

 

Stream Errors

577

 

Disk File I/O with Streams

583

File Pointers

597

 

Error Handling in File I/O

601

File I/O with Member Functions 604

Overloading the Extraction and Insertion

 

Operators

616

 

 

• Memory as a Stream Object

620

Command-Line Arguments

622

Printer Output 624

 

CHAPT E R

12

Chapter 12

568

This chapter focuses on the C++ stream classes. We’ll start off with a look at the hierarchy in which these classes are arranged, and we’ll summarize their important features. The largest part of this chapter is devoted to showing how to perform file-related activities using C++ streams. We’ll show how to read and write data to files in a variety of ways, how to handle errors, and how files and OOP are related. Later in the chapter we’ll examine several other features of C++ that are related to files, including in-memory text formatting, command-line arguments, overloading the insertion and extraction operators, and sending data to the printer.

Stream Classes

A stream is a general name given to a flow of data. In C++ a stream is represented by an object of a particular class. So far we’ve used the cin and cout stream objects. Different streams are used to represent different kinds of data flow. For example, the ifstream class represents data flow from input disk files.

Advantages of Streams

C programmers may wonder what advantages there are to using the stream classes for I/O, instead of traditional C functions such as printf() and scanf(), and—for files—fprintf(), fscanf(), and so on.

One reason is simplicity. If you’ve ever used a %d formatting character when you should have used a %f in printf(), you’ll appreciate this. There are no such formatting characters in streams, since each object already knows how to display itself. This removes a major source of errors.

Another reason is that you can overload existing operators and functions, such as the insertion (<<) and extraction (>>) operators, to work with classes that you create. This makes your own classes work in the same way as the built-in types, which again makes programming easier and more error free (not to mention more aesthetically satisfying).

You may wonder whether stream I/O is important if you plan to program in an environment with a graphical user interface such as Windows, where direct text output to the screen is not used. Do you still need to know about C++ streams? Yes, because they are the best way to write data to files, and also to format data in memory for later use in text input/output windows and other GUI elements.

The Stream Class Hierarchy

The stream classes are arranged in a rather complex hierarchy. Figure 12.1 shows the arrangement of the most important of these classes.

Streams and Files

569

 

ios

Pointer

streambuf

 

 

istream

fstreambase

ostream

filebuf

iostream

ifstream

fstream

ofstream

istream…

iostream…

ostream…

withassign

withassign

withassign

IOSTREAM

FSTREAM

FIGURE 12.1

Stream class hierarchy.

We’ve already made extensive use of some stream classes. The extraction operator >> is a member of the istream class, and the insertion operator << is a member of the ostream class. Both of these classes are derived from the ios class. The cout object, representing the standard output stream, which is usually directed to the video display, is a predefined object of the ostream_withassign class, which is derived from the ostream class. Similarly, cin is an object of the istream_withassign class, which is derived from istream.

The classes used for input and output to the video display and keyboard are declared in the header file IOSTREAM, which we routinely included in our examples in previous chapters. The classes used specifically for disk file I/O are declared in the file FSTREAM. Figure 12.1 shows which classes are in which two header files. (Also, some manipulators are declared in IOMANIP, and in-memory classes are declared in STRSTREAM.) You may find it educational to print out these header files and trace the relationships among the various classes. They’re in your compiler’s INCLUDE subdirectory. Many questions about streams can be answered by studying their class and constant declarations.

12

AND S

F TREAMS

ILES

Chapter 12

570

As you can see from Figure 12.1, the ios class is the base class for the hierarchy. It contains many constants and member functions common to input and output operations of all kinds. Some of these, such as the showpoint and fixed formatting flags, we’ve seen already. The ios class also contains a pointer to the streambuf class, which contains the actual memory buffer into which data is read or written, and the low-level routines for handling this data. Ordinarily you don’t need to worry about the streambuf class, which is referenced automatically by other classes.

The istream and ostream classes are derived from ios and are dedicated to input and output, respectively. The istream class contains such functions as get(), getline(), read(), and the overloaded extraction (>>) operators, while ostream contains put() and write(), and the overloaded insertion (<<) operators.

The iostream class is derived from both istream and ostream by multiple inheritance. Classes derived from it can be used with devices, such as disk files, that may be opened for both input and output at the same time. Three classes—istream_withassign, ostream_withassign, and iostream_withassign—are inherited from istream, ostream, and iostream, respectively. They add assignment operators to these classes.

The following summary of stream classes may seem rather abstract. You may want to skim it now, and return to it later when you need to know how to perform a particular stream-related activity.

The ios Class

The ios class is the granddaddy of all the stream classes, and contains the majority of the features you need to operate C++ streams. The three most important features are the formatting flags, the error-status flags, and the file operation mode. We’ll look at formatting flags and error-status flags next. We’ll save the file operations mode for later, when we talk about disk files.

Formatting Flags

Formatting flags are a set of enum definitions in ios. They act as on/off switches that specify choices for various aspects of input and output format and operation. We won’t provide a detailed discussion of each flag, since we’ve already seen some of them in use, and others are more or less self-explanatory. Some we’ll discuss later in this chapter. Table 12.1 is a complete list of the formatting flags.

Streams and Files

TABLE 12.1 ios Formatting Flags

Flag

Meaning

 

 

skipws

Skip (ignore) whitespace on input

 

left

Left-adjust output [12.34

]

 

right

Right-adjust output [

12.34]

 

internal

Use padding between sign or base indicator and number [+

12.34]

dec

Convert to decimal

 

 

oct

Convert to octal

 

 

hex

Convert to hexadecimal

 

 

boolalpha

Convert bool to “true” or “false” strings

 

showbase

Use base indicator on output (0 for octal, 0x for hex)

 

showpoint

Show decimal point on output

 

uppercase

Use uppercase X, E, and hex output letters (ABCDEF)—the default is

 

lowercase

 

 

showpos

Display + before positive integers

 

scientific

Use exponential format on floating-point output [9.1234E2]

 

fixed

Use fixed format on floating-point output [912.34]

 

unitbuf

Flush all streams after insertion

 

stdio

Flush stdout, stderror after insertion

 

 

 

 

 

There are several ways to set the formatting flags, and different ones can be set in different ways. Since they are members of the ios class, you must usually precede them with the name ios and the scope-resolution operator (for example, ios::skipws). All the flags can be set using the setf() and unsetf() ios member functions. Look at the following example:

cout.setf(ios::left);

 

//

left justify output text

cout >> “This text is

left-justified”;

cout.unsetf(ios::left);

//

return to default (right justified)

Many formatting flags can be set using manipulators, so let’s look at them now.

Manipulators

Manipulators are formatting instructions inserted directly into a stream. We’ve seen examples before, such as the manipulator endl, which sends a newline to the stream and flushes it:

cout << “To each his own.” << endl;

571

12

AND S

F TREAMS

ILES

Chapter 12

572

We’ve also used the setiosflags() manipulator (see the SALEMON program in Chapter 7, “Arrays and Strings”):

cout << setiosflags(ios::fixed)

// use fixed decimal point

<<setiosflags(ios::showpoint) // always show decimal point

<<var;

As these examples demonstrate, manipulators come in two flavors: those that take an argument and those that don’t. Table 12.2 summarizes the important no-argument manipulators.

TABLE 12.2 No-Argument ios Manipulators

Manipulator

Purpose

ws

Turn on whitespace skipping on input

dec

Convert to decimal

oct

Convert to octal

hex

Convert to hexadecimal

endl

Insert newline and flush the output stream

ends

Insert null character to terminate an output string

flush

Flush the output stream

lock

Lock file handle

unlock

Unlock file handle

 

 

You insert these manipulators directly into the stream. For example, to output var in hexadecimal format, you can say

cout << hex << var;

Note that manipulators affect only the data that follows them in the stream, not the data that precedes them. Table 12.3 summarizes the important manipulators that take arguments. You need the IOMANIP header file for these functions.

TABLE 12.3 ios Manipulators with Arguments

Manipulator

Argument

Purpose

setw()

field width (int)

Set field width for output

setfill()

fill character (int)

Set fill character for output

 

 

(default is a space)

setprecision()

precision (int)

Set precision (number of digits

 

 

displayed)

setiosflags()

formatting flags (long)

Set specified flags

resetiosflags()

formatting flags (long)

Clear specified flags

 

 

 

Streams and Files

Functions

The ios class contains a number of functions that you can use to set the formatting flags and perform other tasks. Table 12.4 shows most of these functions, except those that deal with errors, which we’ll examine separately.

TABLE 12.4 ios Functions

Function

Purpose

ch = fill();

Return the fill character (fills unused part of field; default is space)

fill(ch);

Set the fill character

p = precision();

Get the precision (number of digits displayed for floating-point)

precision(p);

Set the precision

w = width();

Get the current field width (in characters)

width(w);

Set the current field width

setf(flags);

Set specified formatting flags (for example, ios::left)

unsetf(flags);

Unset specified formatting flags

setf(flags, field);

First clear field, then set flags

 

 

These functions are called for specific stream objects using the normal dot operator. For example, to set the field width to 12, you can say

cout.width(14);

The following statement sets the fill character to an asterisk (as for check printing):

cout.fill(‘*’);

You can use several functions to manipulate the ios formatting flags directly. For example, to set left justification, use

cout.setf(ios::left);

To restore right justification, use

cout.unsetf(ios::left);

A two-argument version of setf() uses the second argument to reset all the flags of a particular type or field. Then the flag specified in the first argument is set. This makes it easier to reset the relevant flags before setting a new one. Table 12.5 shows the arrangement.

For example

cout.setf(ios::left, ios::adjustfield);

clears all the flags dealing with text justification and then sets the left flag for left-justified output.

573

12

AND S

F TREAMS

ILES

Chapter 12

574

TABLE 12.5 Two-Argument Version of setf()

First Argument: Flags to Set

Second Argument: Field to Clear

dec, oct, hex

basefield

left, right, internal

adjustfield

scientific, fixed

floatfield

 

 

By using the techniques shown here with the formatting flags, you can usually figure out a way to format I/O not only for the keyboard and display, but, as we’ll see later in this chapter, for files as well.

The istream Class

The istream class, which is derived from ios, performs input-specific activities, or extraction. It’s easy to confuse extraction and the related output activity, insertion. Figure 12.2 emphasizes the difference.

FIGURE 12.2

File input and output.

Table 12.6 lists the functions you’ll most commonly use from the istream class.

TABLE 12.6 istream Functions

Function

Purpose

>>

Formatted extraction for all basic (and overloaded) types.

get(ch);

Extract one character into ch.

get(str)

Extract characters into array str, until ‘\n’.