Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Professional C++ [eng].pdf
Скачиваний:
284
Добавлен:
16.08.2013
Размер:
11.09 Mб
Скачать

Demystifying C++ I/O

A program’s fundamental job is to accept input and produce output. A program that produces no output of any sort would not be very useful. All languages provide some mechanism for I/O, either as a built-in part of the language or through OS-specific hooks. A good I/O system is both flexible and easy to use. Part of being flexible is polymorphism: flexible I/O systems support input and output through a variety of devices, such as files and the user console. They also support the reading and writing of different types of data. I/O is error-prone because data coming from a user can be incorrect or the underlying file system or other data source can be inaccessible. Thus, a good I/O system is also capable of handling error conditions.

If you are familiar with the C language, you have undoubtedly used printf() and scanf(). As I/O mechanisms, printf() and scanf() are certainly flexible. Through escape codes and variable placeholders, they can be customized to read in specially formatted data or output anything from an integer to a string. printf() and scanf(), however, falter on other measures of good I/O systems. They do not handle errors particularly well, they are not flexible enough to handle custom data types, and, worst of all in an object-oriented language like C++, they are not at all object oriented!

C++ provides a more refined method of input and output through a mechanism known as streams. Streams are a flexible and object-oriented approach to I/O. In this chapter, you will learn how to use streams for data output and input. You will also learn how to use the stream mechanism to read from various sources and write to various destinations, such as the user console, files, and even strings. This chapter covers the most commonly used I/O features. This chapter also covers the increasingly important topic of writing programs that can be localized to different regions around the world.

Using Streams

The stream metaphor takes a bit of getting used to. At first, streams may seem more complex than traditional C-style I/O, such as printf(). In reality, they seem complicated initially only because there is a deeper metaphor behind streams than there is behind printf(). Don’t worry though; after a few examples, you’ll never look back.

Chapter 14

What Is a Stream, Anyway?

As you read in Chapter 1, the cout stream is like a laundry chute for data. You throw some variables down the stream, and they are written to the user’s screen, or console. More generally, all streams can be viewed as data chutes. Streams vary in their direction and their associated source or destination. For example, the cout stream that you are already familiar with is an output stream so its direction is “out.” It writes data to the console so its associated destination is “console.” There is another standard stream called cin that accepts input from the user. Its direction is in, and its associated source is console. cout and cin are predefined instances of streams that are defined within the std namespace in C++.

Every input stream has an associated source. Every output stream has an associated destination.

Stream Sources and Destinations

Streams as a concept can be applied to any object that accepts data or emits data. You could write a stream-based network class or stream-based access to a MIDI-based instrument. In C++, there are three common sources and destinations for streams.

You have already read many examples of user, or console, streams. Console input streams make programs interactive by allowing input from the user during run time. Console output streams provide feedback to the user and output results.

File streams, as the name implies, read data from a file system and write data to a file system. File input streams are useful for reading in configuration data and saved files or for batch processing file-based data. File output streams are useful for saving state and providing output.

String streams are an application of the stream metaphor to the string type. With a string stream, you can treat character data just as you would treat any other stream. For the most part, this is merely a handy syntax for functionality that could be handled through methods on the string class. However, using stream syntax provides opportunities for optimization and can be far more convenient than direct use of the string class.

The rest of this section deals with console streams (cin and cout). Examples of file and string streams are provided later in this chapter. Other types of streams, such as printer output or network I/O are provided by the operating system and are not built into the language.

Output with Streams

Output using streams was introduced in Chapter 1 and has been used in almost every chapter in this book. This section will briefly revisit some of those basics then will introduce material that is more advanced.

Output Basics

Output streams are defined in the <ostream> header file. Most programmers include <iostream> in their programs, which in turn includes the headers for both input streams and output streams. The <iostream> header also declares the standard console output stream, cout.

380

Demystifying C++ I/O

The << operator is the simplest way to use output streams. C++ basic types, such as ints, pointers, doubles, and characters, can be output using <<. In addition, the C++ string class is compatible with <<, and C-style strings are properly output as well. Following are some examples of using << and their corresponding output.

int i = 7;

cout << i;

7

char ch = ‘a’;

cout << ch;

a

string myString = “Marni is adorable.”;

cout << myString;

Marni is adorable.

The cout stream is the built-in stream for writing to the console, or standard output. Recall that you can “chain” uses of << together to output multiple pieces of data. This is because the << operator returns the stream as its result so you can immediately use << again on the same stream. For example:

int i = 11;

cout << “On a scale of 1 to cute, Marni ranks “ << i << “!”;

On a scale of 1 to cute, Marni ranks 11!

C++ streams will correctly parse C-style escape codes, such as strings that contain \n, but it is much more hip to use the built-in endl mechanism for this purpose. The following example uses endl, which is defined in the std namespace to represent an end-of-line character and to flush the output buffer. Several lines of text are output using one line of code.

cout << “Line 1” << endl << “Line 2” << endl << “Line 3” << endl;

Line 1

Line 2

Line 3

Methods of Output Streams

The << operator is, without a doubt, the most useful part of output streams. However, there is additional functionality to be explored. If you take a peek at the <ostream> header file, you’ll see many lines of overloaded definitions of the << operator. You’ll also find some useful public methods.

put() and write()

put() and write() are raw output methods. Instead of taking an object or variable that has some defined behavior for output, these methods accept a character or character array, respectively. The data passed to these methods are output as is, without any special formatting or processing. Escape characters, such as \n are still output in their correct form (i.e., a carriage return), but no polymorphic output will occur. The following function takes a C-style string and outputs it to the console without using the << operator:

381

Chapter 14

void rawWrite(const char* data, int dataSize)

{

cout.write(data, dataSize);

}

The next function writes the prescribed index of a C-style string to the console using the put method.

void rawPutChar(const char* data, int charIndex)

{

cout.put(data[charIndex]);

}

flush()

When you write to an output stream, the stream does not necessarily write the data to its destination right away. Most output streams buffer, or accumulate data instead of writing it out as it comes in. The stream will flush, or write out the accumulated data, when one of the following conditions occurs:

A sentinel, such as the endl marker, is reached.

The stream goes out of scope and is destructed.

Input is requested from a corresponding input stream (i.e., when you make use of cin for input, cout will flush). In the section on file streams, you will learn how to establish this type of link.

The stream buffer is full.

You explicitly tell the stream to flush its buffer.

One way to explicitly tell a stream to flush is to call its flush() method, as in the code that follows.

cout << “abc”;

 

 

 

cout.flush();

//

abc is

written to the console.

cout <<

“def”;

 

 

 

cout <<

endl;

//

def is

written to the console.

 

 

 

 

 

Not all output streams are buffered. The cerr stream, for example, does not buffer its output.

Handling Output Errors

Output errors can arise in a variety of situations. Perhaps you are attempting to write to a file that does not exist or has been given read-only permissions. Maybe a disk error has prevented a write operation from succeeding or the console has somehow gotten into a locked-up state. None of the streams code you have read up until this point has considered these possibilities, mainly for brevity. However, in professional C++ programs, it is vital that you address any error conditions that occur.

When a stream is in its normal usable state, it is said to be “good.” The good() method can be called directly on a stream to determine whether or not the stream is currently good.

382

Demystifying C++ I/O

if (cout.good()) {

cout << “All good” << endl;

}

The good() method provides an easy way to obtain basic information about the validity of the steam, but it does not tell you why the steam is unusable. There is a method called bad() that provides a bit more information. If bad() returns true, it means that a fatal error has occurred (as opposed to any nonfatal condition like end-of-file). Another method, fail(), returns true if the most recent operation has failed, implying that the next operation will also fail. For example, after calling flush() on an output stream, you could call fail() to make sure the stream is still usable.

cout.flush();

if (cout.fail()) {

cerr << “Unable to flush to standard out” << endl;

}

To reset the error state of a stream, use the clear() method:

cout.clear();

Error checking is performed less frequently for console output streams than for file output streams or input streams. The methods discussed here apply for other types of streams as well and are revisited below as each type is discussed.

Output Manipulators

One of the unusual features of streams is that you can throw more than just data down the chute. C++ streams also recognize manipulators, objects that make a change to the behavior of the stream instead of, or in addition to, providing data for the stream to work with.

You have already seen one manipulator: endl. The endl manipulator encapsulates data and behavior. It tells the stream to output a carriage return and to flush its buffer. Following are some other useful manipulators, many of which are defined in the <ios> and <iomanip> standard header files.

boolalpha and noboolalpha. Tells the stream to output bool values as true and false

(boolalpha) or 1 and 0 (noboolalpha). The default is noboolalpha.

hex, oct, and dec. Outputs numbers in hexadecimal, octal, and base 10, respectively.

setprecision. Sets the number of decimal places that are output for fractional numbers. This is a parameterized manipulator (meaning that it takes an argument).

setw. Sets the field width for outputting numerical data. This is a parameterized manipulator.

setfill. Specifies the character that is used to pad numbers that are smaller than the specified width. This is a parameterized manipulator.

showpoint and noshowpoint. Forces the stream to always or never show the decimal point for floats and doubles with no fractional part.

The following program uses several of these manipulators to customize its output.

383