Chapter 8. The IO Library

CONTENTS

Section 8.1 An Object-Oriented Library 284

Section 8.2 Condition States 287

Section 8.3 Managing the Output Buffer 290

Section 8.4 File Input and Output 293

Section 8.5 String Streams 299

Chapter Summary 302

Defined Terms 302

In C++, input/output is provided through the library. The library defines a family of types that support IO to and from devices such as files and console windows. Additional types allow strings to act like files, which gives us a way to convert data to and from character forms without also doing IO. Each of these IO types defines how to read and write values of the built-in data types. In addition, class designers generally use the library IO facilities to read and write objects of the classes that they define. Class types are usually read and written using the same operators and conventions that the IO library defines for the built-in types.

This chapter introduces the fundamentals of the IO library. Later chapters will cover additional capabilities: Chapter 14 will look at how we can write our own input and output operators; Appendix A will cover ways to control formatting and random access to files.

Our programs have already used many IO library facilities:

istream (input stream) type, which supports input operations

ostream (output stream) type, which provides output operations

cin (pronounced see-in) an istream object that reads the standard input.

cout (pronounced see-out) an ostream object that writes to the standard output

cerr (pronounced see-err) an ostream object that writes to the standard error. cerr is usually used for program error messages.

• operator >>, which is used to read input from an istream object

• operator <<, which is used to write output to an ostream object

getline function, which takes a reference to an istream and a reference to a string and reads a word from the istream into the string

This chapter looks briefly at some additional IO operations, and discusses support for reading and writing files and strings. Appendix A covers how to control formatting of IO operations, support for random access to files, and support for unformatted IO. This primer does not describe the entire iostream library—in particular, we do not cover the system-specific implementation details, nor do we discuss the mechanisms by which the library manages input and output buffers or how we might write our own buffer classes. These topics are beyond the scope of this book. Instead, we’ll focus on those portions of the IO library that are most useful in ordinary programs.

8.1 An Object-Oriented Library

The IO types and objects we’ve used so far read and write streams of data and are used to interact with a user’s console window. Of course, real programs cannot be limited to doing IO solely to or from a console window. Programs often need to read or write named files. Moreover, it can be quite convenient to use the IO operations to format data in memory, thereby avoiding the complexity and run-time expense of reading or writing to a disk or other device. Applications also may have to read and write languages that require wide-character support.

Conceptually, neither the kind of device nor the character size affect the IO operations we want to perform. For example, we’d like to use >> to read data regardless of whether we’re reading a console window, a disk file, or an in-memory string. Similarly, we’d like to use that operator regardless of whether the characters we read fit in a char or require the wchar_t (Section 2.1.1, p. 34) type.

At first glance, the complexities involved in supporting or using these different kinds of devices and different sized character streams might seem a daunting problem. To manage the complexity, the library uses inheritance to define a set of object-oriented classes. We’ll have more to say about inheritance and object-oriented programming in Part IV, but generally speaking, types related by inheritance share a common interface. When one class inherits from another, we (usually) can use the same operations on both classes. More specifically, when two types are related by inheritance, we say that one class “inherits” the behavior—the interface—of its parent. In C++ we speak of the parent as the base class and the inheriting class as a derived class.

The IO types are defined in three separate headers: iostream defines the types used to read and write to a console window, fstream defines the types used to read and write named files, and sstream defines the types used to read and write in-memory strings. Each of the types in fstream and sstream is derived from a corresponding type defined in the iostream header. Table 8.1 lists the IO classes and Figure 8.1 on the next page illustrates the inheritance relationships among these types. Inheritance is usually illustrated similarly to how a family tree is displayed. The topmost circle represents a base (or parent) class. Lines connect a base class to its derived (or children) class(es). So, for example, this figure indicates that istream is the base class of ifstream and istringstream. It is also the base class for iostream, which in turn is the base class for sstream and fstream classes.

Table 8.1. IO Library Types and Headers

image

Figure 8.1. Simplified iostream Inheritance Hierarchy

image

Because the types ifstream and istringstream inherit from istream, we already know a great deal about how to use these types. Each program we’ve written that read an istream could be used to read a file (using the ifstream type) or a string (using the istringstream type). Similarly, programs that did output could use an ofstream or ostringstream instead of ostream. In addition to the istream and ostream types, the iostream header also defines the iostream type. Although our programs have not used this type, we actually know a good bit about how to use an iostream. The iostream type is derived from both istream and ostream. Being derived from both types means that an iostream object shares the interface of both its parent types. That is, we can use an iostream type to do both input and output to the same stream. The library also defines two types that inherit from iostream. These types can be used to read or write to a file or a string.

Using inheritance for the IO types has another important implication: As we’ll see in Chapter 15, when we have a function that takes a reference to a base-class type, we can pass an object of a derived type to that function. This fact means that a function written to operate on istream& can be called with an ifstream or istringstream object. Similarly, a function that takes an ostream& can be called with an ofstream or ostringstream object. Because the IO types are related by inheritance, we can write one function and apply it to all three kinds of streams: console, disk files, or string streams.

International Character Support

The stream classes described thus far read and write streams composed of type char. The library defines a corresponding set of types supporting the wchar_t type. Each class is distinguished from its char counterpart by a “w” prefix. Thus, the types wostream, wistream, and wiostream read and write wchar_t data to or from a console window. The file input and output classes are wifstream, wofstream, and wfstream. The wchar_t versions of string stream input and output are wistringstream, wostringstream, and wstringstream. The library also defines objects to read and write wide characters from the standard input and standard output. These objects are distinguished from the char counterparts by a “w” prefix: The wchar_t standard input object is named wcin; standard output is wcout; and standard error is wcerr.

Each of the IO headers defines both the char and wchar_t classes and standard input/output objects. The stream-based wchar_t classes and objects are defined in iostream, the wide character file stream types in fstream, and the wide character stringstreams in sstream.

No Copy or Assign for IO Objects

For reasons that will be more apparent when we study classes and inheritance in Parts III and IV, the library types do not allow allow copy or assignment:

image

This requirement has two particularly important implications. As we’ll see in Chapter 9, only element types that support copy can be stored in vectors or other container types. Because we cannot copy stream objects, we cannot have a vector (or other container) that holds stream objects.

The second implication is that we cannot have a parameter or return type that is one of the stream types. If we need to pass or return an IO object, it must be passed or returned as a pointer or reference:

image

Typically, we pass a stream as a nonconst reference because we pass an IO object intending to read from it or write to it. Reading or writing an IO object changes its state, so the reference must be nonconst.

Exercises Section 8.1

Exercise 8.1: Assuming os is an ofstream, what does the following program do?

    os << "Goodbye!" << endl;

What if os is an ostringstream? Whatif os is an ifstream?

Exercise 8.2: The following declaration is in error. Identify and correct the problem(s):

    ostream print(ostream os);

8.2 Condition States

Before we explore the types defined in fstream and sstream, we need to understand a bit more about how the IO library manages its buffers and the state of a stream. Keep in mind that the material we cover in this section and the next applies equally to plain streams, file streams, or string streams.

Inherent in doing IO is the fact that errors can occur. Some errors are recoverable; others occur deep within the system and are beyond the scope of a program to correct. The IO library manages a set of condition state members that indicate whether a given IO object is in a usable state or has encountered a particular kind of error. The library also defines a set of functions and flags, listed in Table 8.2, that give us access to and let us manipulate the state of each stream.

Table 8.2. IO Library Condition State

image

As an example of an IO error, consider the following code:

     int ival;
     cin >> ival;

If we enter Borges on the standard input, then cin will be put in an error state following the unsuccessful attempt to read a string of characters as an int. Similarly, cin will be in an error state if we enter an end-of-file. Had we entered 1024, then the read would be successful and cin would be in a good, non-error state.

To be used for input or output, a stream must be in a non-error state. The easiest way to test whether a stream is okay is to test its truth value:

image

The if directly tests the state of the stream. The while does so indirectly by testing the stream returned from the expression in the condition. If that input operation succeeds, then the condition tests true.

Condition States

Many programs need only know whether a stream is valid. Other programs need more fine-grained access to and control of the state of the stream. Rather than knowing that the stream is in an error state, we might want to know what kind of error was encountered. For example, we might want to distinguish between reaching end-of-file and encountering an error on the IO device.

Each stream object contains a condition state member that is managed through the setstate and clear operations. This state member has type iostate, which is a machine-dependent integral type defined by each iostream class. It is used as a collection of bits, much the way we used the int_quiz1 variable to represent test scores in the example in Section 5.3.1 (p. 156).

Each IO class also defines three const values of type iostate that represent particular bit patterns. These const values are used to indicate particular kinds of IO conditions. They can be used with the bitwise operators (Section 5.3, p. 154) to test or set multiple flags in one operation.

The badbit indicates a system level failure, such as an unrecoverable read or write error. It is usually not possible to continue using a stream after such an error. The failbit is set after a recoverable error, such as reading a character when numeric data was expected. It is often possible to correct the problem that caused the failbit to be set. The eofbit is set when an end-of-file is encountered. Hitting end-of-file also sets the failbit.

The state of the stream is revealed by the bad, fail, eof, and good operations. If any of bad, fail, or eof are true, then testing the stream itself will indicate that the stream is in an error state. Similarly, the good operation returns true if none of the other conditions is true.

The clear and setstate operations change the state of the condition member. The clear operations put the condition back in its valid state. They are called after we have remedied whatever problem occurred and we want to reset the stream to its valid state. The setstate operation turns on the specified condition to indicate that a problem occurred. setstate leaves the existing state variables unchanged except that it adds the additional indicated state(s).

Interrogating and Controlling the State of a Stream

We might manage an input operation as follows:

image

This loop reads cin until end-of-file or an unrecoverable read error occurs. The condition uses a comma operator (Section 5.9, p. 168). Recall that the comma operator executes by evaluating each operand and returns its rightmost operand as its result. The condition, therefore, reads cin and ignores its result. The result of the condition is the result of !cin.eof(). If cin hit end-of-file, the condition is false and we fall out of the loop. If cin did not hit end-of-file, we enter the loop, regardless of any other error the read might have encountered.

Inside the loop, we first check whether the stream is corrupted. If so, we exit by throwing an exception (Section 6.13, p. 215). If the input was invalid, we print a warning, and clear the failbit state. In this case, we execute a continue (Section 6.11, p. 214) to return to the start of the while to read another value into ival. If there were no errors, the rest of the loop can safely use ival.

Accessing the Condition State

The rdstate member function returns an iostate value that corresponds to the entire current condition state of the stream:

image

Dealing with Multiple States

Often we need to set or clear multiple state bits. We could do so by making multiple calls to the setstate or clear functions. Alternatively, we could use the bitwise OR (Section 5.3, p. 154) operator to generate a value to pass two or more state bits in a single call. The bitwise OR generates an integral value using the bit patterns of its operands. For each bit in the result, the bit is 1 if the corresponding bit is 1 in either of its operands. For example:

   // sets both the badbit and the failbit
   is.setstate(ifstream::badbit | ifstream::failbit);

tells the object is to turn on both the failbit and the badbit. The argument

     is.badbit | is.failbit

creates a value in which the bits corresponding to the badbit and to the failbit are both turned on—that is they are both set to 1. All other bits in the value are zero. The call to setstate uses this value to turn on the bits corresponding to badbit and failbit in the stream’s condition state member.

8.3 Managing the Output Buffer

Each IO object manages a buffer, which is used to hold the data that the program reads and writes. When we write

    os << "please enter a value: ";

the literal string is stored in the buffer associated with the stream os. There are several conditions that cause the buffer to be flushed—that is, written—to the actual output device or file:

  1. The program completes normally. All output buffers are emptied as part of the return from main.
  2. At some indeterminate time, the buffer can become full, in which case it will be flushed before writing the next value.
  3. We can flush the buffer explicitly using a manipulator (Section 1.2.2, p. 7) such as endl.
  4. We can use the unitbuf manipulator to set the stream’s internal state to empty the buffer after each output operation.
  5. We can tie the output stream to an input stream, in which case the output buffer is flushed whenever the associated input stream is read.

Exercises Section 8.2

Exercise 8.3: Write a function that takes and returns an istream&. The function should read the stream until it hits end-of-file. The function should print what it reads to the standard output. Reset the stream so that it is valid and return the stream.

Exercise 8.4: Test your function by calling it passing cin as an argument.

Exercise 8.5: What causes the following while to terminate?

    while (cin >> i) /* . . . */

Flushing the Output Buffer

Our programs have already used the endl manipulator, which writes a newline and flushes the buffer. There are two other similar manipulators. The first, flush, is used quite frequently. It flushes the stream but adds no characters to the output. The second, ends, is used much less often. It inserts a null character into the buffer and then flushes it:

image

The unitbuf Manipulator

If we want to flush every output, it is better to use the unitbuf manipulator. This manipulator flushes the stream after every write:

    cout << unitbuf << "first" << " second" << nounitbuf;

is equivalent to writing

    cout << "first" << flush << " second" << flush;

The nounitbuf manipulator restores the stream to use normal, system-managed buffer flushing.

Tying Input and Output Streams Together

When an input stream is tied to an output stream, any attempt to read the input stream will first flush the buffer associated output stream. The library ties cout to cin, so the statement

         cin >> ival;

causes the buffer associated with cout to be flushed.

image

Interactive systems usually should be sure that their input and output streams are tied. Doing so means that we are guaranteed that any output, which might include prompts to the user, has been written before attempting to read.

The tie function can be called on either istream or an ostream. It takes a pointer to an ostream and ties the argument stream to the object on which tie was called. When a stream ties itself to an ostream, then any IO operation on the stream that called tie flushes the buffer associated with the argument it passed to tie.

image

An ostream object can be tied to only one istream object at a time. To break an existing tie, we pass in an argument of 0.

8.4 File Input and Output

The fstream header defines three types to support file IO:

  1. ifstream, derived from istream, reads from a file.
  2. ofstream, derived from ostream, writes to a file.
  3. fstream, derived from iostream, reads and writes the same file.

The fact that these types are derived from the corresponding iostream types means that we already know most of what we need to know about how to use the fstream types. In particular, we can use the IO operators (<< and >>) to do formatted IO on a file, and the material covered in the previous sections on condition states apply identically to fstream objects.

In addition to the behavior that fstream types inherit, they also define two new operations of their own—open and close—along with a constructor that takes the name of a file to open. These operations can be called on objects of fstream, ifstream, or ofstream but not on the other IO types.

8.4.1 Using File Stream Objects

So far our programs have used the library-defined objects, cin, cout, and cerr. When we want to read or write a file, we must define our own objects, and bind them to the desired files. Assuming that ifile and ofile are strings with the names of the files we want to read and write, we might write code such as

    // construct an ifstream and bind it to the file named ifile
    ifstream infile(ifile.c_str());
    // ofstream output file object to write file named ofile
    ofstream outfile(ofile.c_str());

to define and open a pair of fstream objects. infile is a stream that we can read and outfile is a stream that we can write. Supplying a file name as an initializer to an ifstream or ofstream object has the effect of opening the specified file.

image

These definitions define infile as a stream object that will read from a file and outfile as an object that we can use to write to a file. Neither object is as yet bound to a file. Before we use an fstream object, we must also bind it to a file to read or write:

image

We bind an existing fstream object to the specified file by calling the open member. The open function does whatever system-specific operations are required to locate the given file and open it for reading or writing as appropriate.

Checking Whether an Open Succeeded

After opening a file, it is usually a good idea to verify that the open succeeded:

image

This condition is similar to those we’ve used to test whether cin had hit end-of-file or encountered some other error. When we test a stream, the effect is to test whether the object is “okay” for input or output. If the open fails, then the state of the fstream object is that it is not ready for doing IO. When we test the object

    if (outfile) // ok to use outfile?

a true return means that it is okay to use the file. Because we want to know if the file is not okay, we invert the return from checking the stream:

    if (!outfile) // not ok to use outfile?

Rebinding a File Stream to a New File

Once an fstream has been opened, it remains associated with the specified file. To associate the fstream with a different file, we must first close the existing file and then open a different file:

image

It is essential that we close a file stream before attempting to open a new file. The open function checks whether the stream is already open. If it is open, then it sets its internal state to indicate that a failure has happened. Subsequent attempts to use the file stream will fail.

Clearing the State of a File Stream

Consider a program that has a vector containing names of files it should open and read, doing some processing on the words stored in each file. Assuming the vector is named files, such a progam might have a loop like the following:

image

Each trip through the loop constructs the ifstream named input open to read the indicated file. The initializer in the constructor uses the arrow operator (Section 5.6, p. 164) which dereferences it and fetches the c_str member from the underlying string that it currently denotes. The file is opened by the constructor, and assuming the open succeeded, we read that file until we hit end-of-file or some other error condition. At that point, input is in an error state. Any further attempt to read from input will fail. Because input is local to the while loop, it is created on each iteration. That means that it starts out each iteration in a clean state—input.good() is true.

If we wanted to avoid creating a new stream object on each trip through the while, we might move the definition of input out of the while. This simple change means that we must manage the stream state more carefully. When we encounter end-of-file, or any other error, the internal state of the stream is set so that further reads or writes are not allowed. Closing a stream does not change the internal state of the stream object. If the last read or write operation failed, the state of the object remains in a failure mode until we execute clear to reset the condition of the stream. After the clear, it is as if we had created the object afresh.

If we wish to reuse an existing stream object, our while loop must remember to close and clear the stream on each trip through the loop:

image

Had we neglected the call to clear, this loop would read only the first file. To see why, consider what happens in this loop: First we open the indicated file. Assuming open succeeded, we read the file until we hit end-of-file or some other error condition. At that point, input is in an error state. If we close but do not clear the stream, then any subsequent input operation on input will fail. Once we have closed the file, we can open the next one. However, the read of input in the inner while will fail—after all, the last read from this stream hit end-of-file. The fact that the end-of-file was on a different file is irrelevant!

image

If we reuse a file stream to read or write more than one file, we must clear the stream before using it to read from another file.

Exercises Section 8.4.1

Exercise 8.6: Because ifstream inherits from istream, we can pass an ifstream object to a function that takes a reference to an istream. Use the function you wrote for the first exercise in Section 8.2 (p. 291) to read a named file.

Exercise 8.7: The two programs we wrote in this section used a break to exit the while loop if the open failed for any file in the vector. Rewrite these two loops to print a warning message if a file can’t be opened and continue processing by getting the next file name from the vector.

Exercise 8.8: The programs in the previous exercise can be written without using a continue statement. Write the program with and without using a continue.

Exercise 8.9: Write a function to open a file for input and read its contents into a vector of strings, storing each line as a separate element in the vector.

Exercise 8.10: Rewrite the previous program to store each word in a separate element.

8.4.2 File Modes

Whenever we open a file—either through a call to open or as part of initializing a stream from a file name—a file mode is specified. Each fstream class defines a set of values that represent different modes in which the stream could be opened. Like the condition state flags, the file modes are integral constants that we use with the bitwise operators (Section 5.3, p. 154) to set one or more modes when we open a given file. The file stream constructors and open have a default argument (Section 7.4.1, p. 253) to set the file mode. The value of the default varies based on the type of the stream. Alternatively, we can supply the mode in which to open the file. Table 8.3 on the next page lists the file modes and their meanings.

Table 8.3. File Modes

image

The modes out, trunc, and app may be specifed only for files associated with an ofstream or an fstream; in may be specified only for files associated with either ifstream or fstream. Any file may be opened in ate or binary mode. The ate mode has an effect only at the open: Opening a file in ate mode puts the file at the end-of-file immediately after the open. A stream opened in binary mode processes the file as a sequence of bytes; it does no interpretation of the characters in the stream.

By default, files associated with an ifstream are opened in in mode, which is the mode that permits the file to be read. Files opened by an ofstream are opened in out mode, which permits the file to be written. A file opened in out mode is truncated: All data stored in the file is discarded.

image

In effect, specifying out mode for an ofstream is equivalent to specifying both out and trunc.

The only way to preserve the existing data in a file opened by an ofstream is to specify app mode explicitly:

    //  output mode by default; truncates file named "file1"
    ofstream outfile("file1");
    // equivalent effect: "file1" is explicitly truncated
    ofstream outfile2("file1", ofstream::out | ofstream::trunc);
    //  append mode; adds new data at end of existing file named "file2"
    ofstream appfile("file2", ofstream::app);

The definition of outfile2 uses the bitwise OR operator (Section 5.3, p. 154) to open inOut in both out and trunc mode.

Using the Same File for Input and Output

An fstream object can both read and write its associated file. How an fstream uses its file depends on the mode specified when we open the file.

By default, an fstream is opened with both in and out set. A file opened with both in and out mode set is not truncated. If we open the file associated with an fstream with out mode, but not in mode specified, then the file is truncated. The file is also truncated if trunc is specified, regardless of whether in is specified. The following definition opens the file copyOut in both input and output mode:

    // open for input and output
    fstream inOut("copyOut", fstream::in | fstream::out);

Appendix A.3.8 (p. 837) discusses how to use a file that is opened for both input and output.

Mode Is an Attribute of a File, Not a Stream

The mode is set each time a file is opened:

image

The first call to open specifies ofstream::out. The file named “scratchpad” in the current directory is opened in output mode; the file will be truncated. When we open the file named “precious,” we ask for append mode. Any data in the file remains, and all writes are done at the end of the file. When we opened “out,” we did not specify an output mode explicitly. It is opened in out mode, meaning that any data currently in “out” is discarded.

image

Any time open is called, the file mode is set, either explicitly or implicitly. If a mode is not specified, the default value is used.

Valid Combinations for Open Mode

Not all open modes can be specified at once. Some are nonsensical, such as opening a file setting both in and trunc. That would yield a stream we intend to read but that we have truncated so that there is no data to read. Table 8.4 lists the valid mode combinations and their meanings.

Table 8.4. File Mode Combinations

image

Any open mode combination may also include ate. The effect of adding ate to any of these modes changes only the initial position of the file. Adding ate to any of these mode combinations positions the file to the end before the first input or output operation is performed.

8.4.3 A Program to Open and Check Input Files

Several programs in this book open a given file for input. Because we need to do this work in several programs, we’ll write a function, named open_file, to perform it. Our function takes references to an ifstream and a string. The string holds the name of a file to associate with the given ifstream:

image

Because we do not know what state the stream is in, we start by calling close and clear to put the stream into a valid state. We next attempt to open the given file. If the open fails, the stream’s condition state will indicate that the stream is unusable. We finish by returning the stream, which is either bound to the given file and ready to use or is in an error condition.

Exercises Section 8.4.3

Exercise 8.11: In the open_file function, explain why we call clear before the call to open. What would happen if we neglected to make this call? What would happen if we called clear after the open?

Exercise 8.12: In the open_file function, explain what the effect would be if the program failed to execute the close.

Exercise 8.13: Write a program similar to open_file that opens a file for output.

Exercise 8.14: Use open_file and the program you wrote for the first exercise in Section 8.2 (p. 291) to open a given file and read its contents.

8.5 String Streams

The iostream library supports in-memory input/output, in which a stream is attached to a string within the program’s memory. That string can be written to and read from using the iostream input and output operators. The library defines three kinds of string streams:

istringstream, derived from istream, reads from a string.

ostringstream, derived from ostream, writes to a string.

stringstream, derived from iostream, reads and writes a string.

To use any of these classes, we must include the sstream header.

Like the fstream types, these types are derived from the iostream types, meaning that all the operations on iostreams also apply to the types in sstream. In addition to the operations that the sstream types inherit, these types have a constructor that takes a string. The constructor copies the string argument into the stringstream object. The operations that read and write the stringstream read or write the string in the object. These classes also define a member named str to fetch or set the string value that the stringstream manipulates.

Note that although fstream and sstream share a common base class, they have no other interrelationship. In particular, we cannot use open and close on a stringstream, nor can we use str on an fstream.

Table 8.5. stringstream-Specific Operations

image

Using a stringstream

We’ve seen programs that need to deal with their input a word at a time or a line at a time. The first sort of programs use the string input operator and the second use the getline function. However, some programs need to do both: They have some processing to do on a per-line basis and other work that needs to be done on each word within each line. Using stringstreamslets us do so:

image

Here we use getline to get an entire line from the input. To get the words in each line, we bind an istringstream to the line that we read. We can then use the normal string input operator to read the words from each line.

stringstreams Provide Conversions and/or Formatting

One common use of stringstreams is when we want to obtain automatic formatting across multiple data types. For example, we might have a collection of numeric values but want their string representation or vice versa. The sstream input and output operations automatically convert an arithmetic type into its corresponding string representation or back again:

image

Here we create an empty ostringstream object named format_message and insert the indicated text into that object. What’s important is that the int values are automatically converted to their printable string equivalents. The contents of format_message are the characters

val1: 512 val2: 1024

We could retrieve the numeric value by using an istringstream to read from the string. Reading an istringstream automatically converts from the character representation of a numeric value to its corresponding arithmetic value:

image

Here we use the str member to obtain a copy of the string associated with the ostringstream we previously created. We bind input_istring to that string. When we read input_istring, the values are converted back to their original numeric representations.

image

To read input_string, we must parse the string into its component parts. We want the numeric values; to get them we must read (and ignore) the labels that are interspersed with the data we want.

Because the input operator reads typed values, it is essential that the types of the objects into which we read be compatible with the types of the values read from the stringstream. In this case, input_istring had four components: The string value val1: followed by 512 followed by the string val2: followed by 1024. As usual, whenweread strings using the input operator, whitespace is ignored. Thus, when we read the string associated with format_message, we can ignore the newlines that are part of that value.

Exercises Section 8.5

Exercise 8.15: Use the function you wrote for the first exercise in Section 8.2 (p. 291) to print the contents of an istringstream object.

Exercise 8.16: Write a program to store each line from a file in a vector<string>. Now use an istringstream to read each line from the vector a word at a time.

Chapter Summary

C++ uses library classes to handle input and output:

• The iostream classes handle stream-oriented input and output

• The fstream classes handle IO to named files

• The stringstream classes do IO to in-memory strings

All of these classes are related by inheritance. The input classes inherit from istream and the output classes from ostream. Thus, operations that can be performed on an istream object can also be performed on either an ifstream or an istringstream. Similarly for the output classes, which inherit from ostream.

Each IO object maintains a set of condition states that indicate whether IO can be done through this object. If an error is encountered—such as hitting end-of-file on an input stream—then the object’s state will be such that no further input can be done until the error is rectified. The library provides a set of functions to set and test these states.

Defined Terms

base class

A class that is the parent of another class. The base class defines the interface that a derived class inherits.

condition state

Flags and associated functions usable by any of the stream classes that indicate whether a given stream is usable. States and functions to get and set these states are listed in Table 8.2 (p. 288).

derived class

A derived class is one that shares an interface with its parent class.

file mode

Flags defined by the fstream classes that are specified when opening a file and control how a file can be used. Listed in Table 8.3 (p. 297).

fstream

Stream object that reads or writes a named file. In addition to the normal iostream operations, the fstream class also defines open and close members. The open member function takes a C-style character string that names the file to open and an optional open mode argument. By default ifstreams are opened with in mode, ofstreams with out mode, and fstreams with in and out mode set. The close member closes the file to which the stream is attached. It must be called before another file can be opened.

inheritance

Types that are related by inheritance share a common interface. A derived class inherits properties from its base class. Chapter 15 covers inheritance.

object-oriented library

A set of classes related by inheritance. Generally speaking, the base class of an object-oriented library defines an interface that is shared by the classes derived from that base class. In the IO library, the istream and ostream classes serve as base classes for the types defined in the fstream and sstream headers. We can use an object of a derived class as if it were an object of the base class. For example, we can use the operations defined for istream on an ifstream object.

stringstream

Stream object that reads or writes a string. In addition to the normal iostream operations, it also defines an overloaded member named str. Calling str with no arguments returns the string to which the stringstream is attached. Calling it with a string attaches the stringstream to a copy of that string.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset