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

lafore_robert_objectoriented_programming_in_c

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

 

 

 

Streams and Files

 

TABLE 12.6

Continued

 

 

 

 

 

 

 

 

 

 

 

Function

 

Purpose

 

 

 

 

 

 

get(str,

MAX)

Extract up to MAX characters into array.

 

get(str,

DELIM)

Extract characters into array str until specified delimiter

 

 

 

(typically ‘\n’). Leave delimiting char in stream.

 

get(str,

MAX, DELIM)

Extract characters into array str until MAX characters or the

 

 

 

DELIM character. Leave delimiting char in stream.

 

getline(str, MAX, DELIM)

Extract characters into array str, until MAX characters or the

 

 

 

DELIM character. Extract delimiting character.

 

putback(ch)

Insert last character read back into input stream.

 

ignore(MAX, DELIM)

Extract and discard up to MAX characters until (and includ-

 

 

 

ing) the specified delimiter (typically ‘\n’).

 

peek(ch)

 

Read one character, leave it in stream.

 

count =

gcount()

Return number of characters read by a (immediately pre-

 

 

 

ceding) call to get(), getline(), or read().

 

read(str, MAX)

For files—extract up to MAX characters into str, until EOF.

 

seekg()

 

Set distance (in bytes) of file pointer from start of file.

 

seekg(pos, seek_dir)

Set distance (in bytes) of file pointer from specified place in

 

 

 

file. seek_dir can be ios::beg, ios::cur, ios::end.

 

pos = tellg(pos)

Return position (in bytes) of file pointer from start of file.

 

 

 

 

 

You’ve seen some of these functions, such as get(), before. Most of them operate on the cin object, which usually represents the data flow from the keyboard. However, the last four deal specifically with disk files.

The ostream Class

The ostream class handles output or insertion activities. Table 12.7 shows the most commonly used member functions of this class. The last four functions in this table deal specifically with disk files.

TABLE 12.7 ostream Functions

Function

Purpose

<<

Formatted insertion for all basic (and overloaded) types.

put(ch)

Insert character ch into stream.

flush()

Flush buffer contents and insert newline.

write(str, SIZE)

Insert SIZE characters from array str into file.

575

12

AND S

F TREAMS

ILES

576

Chapter 12

 

TABLE 12.7 Continued

 

Function

Purpose

 

seekp(position)

Set distance in bytes of file pointer from start of file.

 

seekp(position, seek_dir)

Set distance in bytes of file pointer, from specified place in

 

 

file. seek_dir can be ios::beg, ios::cur, or ios::end.

 

pos = tellp()

Return position of file pointer, in bytes.

 

 

 

The iostream and the _withassign Classes

The iostream class, which is derived from both istream and ostream, acts only as a base class from which other classes, specifically iostream_withassign, can be derived. It has no functions of its own (except constructors and destructors). Classes derived from iostream can perform both input and output.

There are three _withassign classes:

istream_withassign, derived from istream

ostream_withassign, derived from ostream

iostream_withassign, derived from iostream

These _withassign classes are much like those they’re derived from except that they include overloaded assignment operators so their objects can be copied.

Why do we need separate copyable and uncopyable stream classes? In general, it’s not a good idea to copy stream class objects. The reason is that each such object is associated with a particular streambuf object, which includes an area in memory to hold the object’s actual data. If you copy the stream object, it causes confusion if you also copy the streambuf object.

However, in a few cases it’s important to be able to copy a stream.

Accordingly, the istream, ostream, and iostream classes are made uncopyable (by making their overloaded copy constructors and assignment operators private), while the _withassign classes derived from them can be copied.

Predefined Stream Objects

We’ve already made extensive use of two predefined stream objects that are derived from the _withassign classes: cin and cout. These are normally connected to the keyboard and display, respectively. The two other predefined objects are cerr and clog.

cin, an object of istream_withassign, normally used for keyboard input

cout, an object of ostream_withassign, normally used for screen display

Streams and Files

577

cerr, an object of ostream_withassign, for error messages

clog, an object of ostream_withassign, for log messages

The cerr object is often used for error messages and program diagnostics. Output sent to cerr is displayed immediately, rather than being buffered, as cout is. Also, it cannot be redirected (more on this later). For these reasons you have a better chance of seeing a final output message from cerr if your program dies prematurely. Another object, clog, is similar to cerr in that it is not redirected, but its output is buffered, while cerr’s is not.

Stream Errors

So far in this book we’ve mostly used a rather straightforward approach to input and output, using statements of the form

cout << “Good morning”;

and

cin >> var;

However, as you may have discovered, this approach assumes that nothing will go wrong during the I/O process. This isn’t always the case, especially with input. What happens if a user enters the string “nine” instead of the integer 9, or pushes the Enter key without entering anything? Or what happens if there’s a hardware failure? In this section we’ll explore such problems. Many of the techniques we’ll see here are applicable to file I/O as well.

Error-Status Bits

The stream error-status flags constitute an ios enum member that reports errors that occurred in an input or output operation. They’re summarized in Table 12.8. Figure 12.3 shows how these flags look. Various ios functions can be used to read (and even set) these error flags, as shown in Table 12.9.

TABLE 12.8 Error-Status Flags

Name

Meaning

goodbit

No errors (no flags set, value = 0)

eofbit

Reached end of file

failbit

Operation failed (user error, premature EOF)

badbit

Invalid operation (no associated streambuf)

hardfail

Unrecoverable error

 

 

12

AND S

F TREAMS

ILES

578

Chapter 12

 

TABLE 12.9 Functions for Error Flags

Function

Purpose

int

=

eof();

Returns true if EOF flag set

int

=

fail();

Returns true if failbit or badbit or hardfail flag set

int

=

bad();

Returns true if badbit or hardfail flag set

int

=

good();

Returns true if everything OK; no flags set

clear(int=0); With no argument, clears all error bits; otherwise sets specified flags, as in

clear(ios::failbit)

FIGURE 12.3

Stream status flags.

Inputting Numbers

Let’s see how to handle errors when inputting numbers. This approach applies to numbers read either from the keyboard or from disk, as we’ll see later. The idea is to check the value of goodbit, signal an error if it’s not true, and give the user another chance to enter the correct input.

while(true)

// cycle until input OK

{

 

cout << “\nEnter an integer: “;

 

cin >> i;

 

if( cin.good() )

// if no errors

{

 

cin.ignore(10, ‘\n’);

// remove newline

break;

// exit loop

}

 

cin.clear();

// clear the error bits

cout << “Incorrect input”;

 

Streams and Files

579

cin.ignore(10, ‘\n’);

 

//

remove newline

}

 

 

 

 

cout

<< “integer is “ <<

i;

//

error-free integer

The most common error this scheme detects when reading keyboard input is the user typing nondigits (for instance, “nine” instead of “9”). This causes the failbit to be set. However, it also detects system-related failures that are more common with disk files.

Floating-point numbers (float, double, and long double) can be analyzed for errors in the same way as integers.

Too Many Characters

Too many characters sounds like a difficulty experienced by movie directors, but extra characters can also present a problem when reading from input streams. This is especially true when there are errors. Typically, extra characters are left in the input stream after the input is supposedly completed. They are then passed along to the next input operation, even though they are not intended for it. Often it’s a newline character that remains behind, but sometimes other characters are left over as well. To get rid of these extraneous characters the ignore(MAX, DELIM) member function of istream is used. It reads and throws away up to MAX characters, including the specified delimiter character. In our example, the line

cin.ignore(10, ‘\n’);

causes cin to read up to 10 characters, including the ‘\n’, and remove them from the input.

No-Input Input

Whitespace characters, such as tab space and ‘\n’, are normally ignored (skipped) when inputting numbers. This can have some undesirable side effects. For example, users, prompted to enter a number, may simply press the Enter key without typing any digits. (Perhaps they think that this will enter 0, or perhaps they are simply confused.) In the code shown above, as well as the simple statement

cin >> i;

pressing Enter causes the cursor to drop down to the next line, while the stream continues to wait for the number. What’s wrong with the cursor dropping to the next line? First, inexperienced users, seeing no acknowledgment when they press Enter, may assume the computer is broken. Second, pressing Enter repeatedly normally causes the cursor to drop lower and lower until the entire screen begins to scroll upward. This is all right in teletype-style interaction, where the program and the user simply type at each other. However, in text-based graphics programs (such as the ELEV program in Chapter 13, “Multifile Programs”), scrolling the screen disarranges and eventually obliterates the display.

12

AND S

F TREAMS

ILES

Chapter 12

580

Thus it’s important to be able to tell the input stream not to ignore whitespace. This is handled by clearing the skipws flag:

cout << “\nEnter an integer: “;

 

cin.unsetf(ios::skipws);

// don’t ignore whitespace

cin >> i;

 

if( cin.good() )

 

{

 

// no error

 

}

 

// error

 

Now if the user types Enter without any digits, the failbit will be set and an error generated. The program can then tell the user what to do, or reposition the cursor so the screen does not scroll.

Inputting Strings and Characters

The user can’t really make any serious errors inputting strings and characters, since all input, even numbers, can be interpreted as a string. However, if coming from a disk file, characters and strings should still be checked for errors, in case an EOF or something worse is encountered. Unlike the situation with numbers, you often do want to ignore whitespace when inputting strings and characters.

Error-Free Distances

Let’s look at a program in which user input to the English Distance class is checked for errors. This program simply accepts Distance values in feet and inches from the user and displays them. However, if the user commits an entry error, the program rejects the input with an appropriate explanation to the user, and prompts for new input.

The program is very simple except that the member function getdist() has been expanded to handle errors. Parts of this new code follow the approach of the fragment shown above. However, we’ve also added some statements to ensure that the user does not enter a floatingpoint number for feet. This is important because, while the feet value is an integer, the inches value is floating-point, and the user could easily become confused.

Ordinarily, if it’s expecting an integer, the extraction operator simply terminates when it sees a decimal point, without signaling an error. We want to know about such an error, so we read the feet value as a string instead of an int. We then examine the string with a homemade function isFeet(), which returns true if the string proves to be a correct value for feet. To pass the feet test, it must contain only digits, and they must evaluate to a number between –999 and 999.

(We assume that the Distance class will never be used for measuring larger feet values.) If the string passes the feet test, we convert it to an actual int with the library function atoi().

Streams and Files

The inches value is a floating-point number. We want to check its range, which should be 0 or greater but less than 12.0. We also check it for ios error flags. Most commonly, the failbit will be set because the user typed nondigits instead of a number. Here’s the listing for

ENGLERR:

//englerr.cpp

//input checking with English Distance class #include <iostream>

#include <string>

#include <cstdlib>

//for atoi(),

atof()

using namespace std;

 

 

int isFeet(string);

//declaration

 

////////////////////////////////////////////////////////////////

class

Distance

//English Distance class

{

 

 

 

private:

 

 

 

int feet;

 

 

 

float inches;

 

 

public:

 

 

 

Distance()

//constructor (no args)

 

{ feet = 0; inches = 0.0; }

 

 

Distance(int ft, float in)

//constructor (two args)

 

{ feet = ft; inches = in; }

 

 

void showdist()

//display distance

 

{ cout << feet << “\’-” << inches << ‘\”’; }

 

void getdist();

//get length from user

};

 

 

 

//--------------------------------------------------------------

 

 

 

void Distance::getdist()

//get length from user

{

 

 

 

string instr;

//for input string

while(true)

//cycle until feet are right

 

{

 

 

 

cout << “\n\nEnter feet: “;

 

 

 

cin.unsetf(ios::skipws);

//do not skip white space

 

cin >> instr;

//get feet as a string

 

if( isFeet(instr) )

//is it a correct feet value?

 

{

//yes

 

 

cin.ignore(10, ‘\n’);

//eat

chars, including newline

 

feet = atoi( instr.c_str() );

//convert to integer

 

break;

//break out of ‘while’

 

}

//no, not an integer

 

cin.ignore(10, ‘\n’);

//eat chars, including newline

cout << “Feet must be an integer less than 1000\n”;

}//end while feet

581

12

AND S

F TREAMS

ILES

582

Chapter 12

 

while(true)

//cycle until inches are right

{

 

cout << “Enter inches: “;

 

cin.unsetf(ios::skipws);

//do not skip white space

cin >> inches;

//get inches (type float)

if(inches>=12.0 || inches<0.0)

{

 

cout << “Inches must be between 0.0 and 11.99\n”;

cin.clear(ios::failbit);

//”artificially” set fail bit

}

 

if( cin.good() )

//check for cin failure

{

//(most commonly a non-digit)

cin.ignore(10, ‘\n’);

//eat the newline

break;

//input is OK, exit ‘while’

}

 

cin.clear();

//error; clear the error state

cin.ignore(10, ‘\n’);

//eat chars, including newline

cout << “Incorrect inches input\n”; //start again

} //end while inches

 

}

 

//--------------------------------------------------------------

 

int isFeet(string str)

//return true if the string

{

// is a correct feet value

int slen = str.size();

//get length

if(slen==0 || slen > 5)

//if no input, or too long

return 0;

//not an int

for(int j=0; j<slen; j++)

//check each character

 

//if not digit or minus

if( (str[j] < ‘0’ || str[j]

> ‘9’) && str[j] != ‘-’ )

return 0;

//string is not correct feet

double n = atof( str.c_str() );

//convert to double

if( n<-999.0 || n>999.0 )

//is it out of range?

return 0;

//if so, not correct feet

return 1;

//it is correct feet

}

////////////////////////////////////////////////////////////////

int main()

 

{

 

Distance d;

//make a Distance object

char ans;

 

do

 

{

 

d.getdist();

//get its value from user

cout << “\nDistance = “;

 

d.showdist();

//display it

cout << “\nDo another (y/n)? “; cin >> ans;

Streams and Files

583

cin.ignore(10, ‘\n’);

//eat chars, including newline

} while(ans != ‘n’);

//cycle until ‘n’

return 0;

 

}

 

We’ve used another dodge here: setting an error-state flag manually. We do this because we want to ensure that the inches value is greater than 0 but less than 12.0. If it isn’t, we turn on the failbit with the statement

cin.clear(ios::failbit); // set failbit

When the program checks for errors with cin.good(), it will find the failbit set and signal that the input is incorrect.

Disk File I/O with Streams

Most programs need to save data to disk files and read it back in. Working with disk files requires another set of classes: ifstream for input, fstream for both input and output, and ofstream for output. Objects of these classes can be associated with disk files, and we can use their member functions to read and write to the files.

Referring back to Figure 12.1, you can see that ifstream is derived from istream, fstream is derived from iostream, and ofstream is derived from ostream. These ancestor classes are in turn derived from ios. Thus the file-oriented classes derive many of their member functions from more general classes. The file-oriented classes are also derived, by multiple inheritance, from the fstreambase class. This class contains an object of class filebuf, which is a fileoriented buffer, and its associated member functions, derived from the more general streambuf class. You don’t usually need to worry about these buffer classes.

The ifstream, ofstream, and fstream classes are declared in the FSTREAM file.

C programmers will note that the approach to disk I/O used in C++ is quite different from that in C. The old C functions, such as fread() and fwrite(), will still work in C++, but they are not so well suited to the object-oriented environment. The new C++ approach is considerably cleaner and easier to implement. (Incidentally, be careful about mixing the old C functions with C++ streams. They don’t always work together gracefully, although there are ways to make them cooperate.)

Formatted File I/O

In formatted I/O, numbers are stored on disk as a series of characters. Thus 6.02, rather than being stored as a 4-byte type float or an 8-byte type double, is stored as the characters ‘6’, ‘.’, ‘0’, and ‘2’. This can be inefficient for numbers with many digits, but it’s appropriate in many situations and easy to implement. Characters and strings are stored more or less normally.

12

AND S

F TREAMS

ILES

Chapter 12

584

Writing Data

The following program writes a character, an integer, a type double, and two string objects to a disk file. There is no output to the screen. Here’s the listing for FORMATO:

//formato.cpp

//writes formatted output to a file, using <<

#include <fstream>

//for file I/O

#include <iostream>

 

#include <string>

 

using namespace std;

 

int main()

 

 

 

{

 

 

 

char ch

= ‘x’;

 

int j =

77;

 

 

double d =

6.02;

 

string str1 = “Kafka”;

//strings without

string str2 = “Proust”;

// embedded spaces

ofstream outfile(“fdata.txt”);

//create ofstream object

outfile

<<

ch

//insert (write) data

 

<<

j

 

 

<<

‘ ‘

//needs space between numbers

 

<<

d

 

 

<<

str1

 

 

<<

‘ ‘

//needs spaces between strings

 

<<

str2;

 

cout <<

“File written\n”;

 

return 0;

 

 

}

 

 

 

Here we define an object called outfile to be a member of the ofstream class. At the same time, we initialize it to the file FDATA.TXT. This initialization sets aside various resources for the file, and accesses or opens the file of that name on the disk. If the file doesn’t exist, it is created. If it does exist, it is truncated and the new data replaces the old. The outfile object acts much as cout did in previous programs, so we can use the insertion operator (<<) to output variables of any basic type to the file. This works because the insertion operator is appropriately overloaded in ostream, from which ofstream is derived.

When the program terminates, the outfile object goes out of scope. This calls its destructor, which closes the file, so we don’t need to close the file explicitly.

There are several potential formatting glitches. First, you must separate numbers (such as 77 and 6.02) with nonnumeric characters. Since numbers are stored as a sequence of characters,