Google
 

Monday, January 10, 2011

C++ Primer, Fourth Edition Notes Chapter 8. The IO Library

Section 8.1 An Object-Oriented Library

  1. Conceptually, neither the kind of device nor the character size affect the IO operations we want to perform.
  2. To manage the complexity, the library uses inheritance to define a set of object-oriented classes.
  3. 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
  4. Each of the types in fstream and sstream is derived from a corresponding type defined in the iostream header

    Table 8.1. IO Library Types and Headers

    Header

    Type

    iostream

    istream reads from a stream

    ostream writes to a stream

    iostream reads and writes a stream; derived from istream and ostream,

    fstream

    ifstream, reads from a file; derived from istream

    ofstream writes to a file; derived from ostream

    fstream, reads and writes a file; derived from iostream

    sstream

    istringstream reads from a string; derived from istream

    ostringstream writes to a string; derived from ostream

    stringstream reads and writes a string; derived from iostream

    image
  5. 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
  6. International Character Support
    1. 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
    2. 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.
  7. No Copy or Assign for IO Objects
    1. the library types do not allow allow copy or assignment:
    2. 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
    3. 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
    4. 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.

Section 8.2 Condition States

  1. Keep in mind that the material we cover in this section and the next applies equally to plain streams, file streams, or string streams
  2. 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

    strm::iostate

    Name of the machine-dependent integral type, defined by each iostream class that is used to define the condition states.

    strm::badbit

    strm::iostate value used to indicate that a stream is corrupted.

    strm::failbit

    strm::iostate value used to indicate that an IO operation failed.

    strm::eofbit

    strm::iostate value used to indicate the a stream hit end-of-file.

    s.eof()

    true if eofbit in the stream s is set.

    s.fail()

    true if failbit in the stream s is set.

    s.bad()

    TRue if badbit in the stream s is set.

    s.good()

    true if the stream s is in a valid state.

    s.clear()

    Reset all condition values in the stream s to valid state.

    s.clear(flag)

    Set specified condition state(s) in s to valid. Type of flag is strm::iostate.

    s.setstate(flag)

    Add specified condition to s. Type of flag is strm::iostate.

    s.rdstate()

    Returns current condition of s as an strm::iostate value.

    a
  3. 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
    1.           if (cin)
      // ok to use cin, it is in a valid state

      while (cin >> word)
      // ok: read operation successful ...


  4. Condition States

    1. 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. 
    2. 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.
    3. 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.
    4. 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.
    5. The eofbit is set when an end-of-file is encountered. Hitting end-of-file also sets the failbit
    6. 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.
    7. 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).

  5. Interrogating and Controlling the State of a Stream

    1. Recall that the comma operator executes by evaluating each operand and returns its rightmost operand as its result.

  6. Accessing the Condition State

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

  7. Dealing with Multiple States

    1. 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
      // sets both the badbit and the failbit
      is.setstate(ifstream::badbit | ifstream::failbit);


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

Section 8.3 Managing the Output Buffer



  1. Each IO object manages a buffer, which is used to hold the data that the program reads and writes
  2. There are several conditions that cause the buffer to be flushedthat is, writtento 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

  3. Flushing the Output Buffer

    1. 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
          cout << "hi!" << flush;      // flushes the buffer; adds no data
      cout << "hi!" << ends; // inserts a null, then flushes the buffer
      cout << "hi!" << endl; // inserts a newline, then flushes the buffer

  4. The unitbuf Manipulator

    1. If we want to flush every output, it is better to use the unitbuf manipulator. This manipulator flushes the stream after every write
    2. The nounitbuf manipulator restores the stream to use normal, system-managed buffer flushing









      Caution: Buffers Are Not Flushed if the Program Crashes


      Output buffers are not flushed if the program terminates abnormally. When attempting to debug a program that has crashed, we often use the last output to help isolate the region of program in which the bug might occur. If the crash is after a particular print statement, then we know that the crash happened after that point in the program.


      When debugging a program, it is essential to make sure that any output you think should have been written was actually flushed. Because the system does not automatically flush the buffers when the program crashes, it is likely that there is output that the program wrote but that has not shown up on the standard output. It is still sitting in an output buffer waiting to be printed.


      If you use the last output to help locate the bug, you need to be certain that all the output really did get printed. Making sure that all output operations include an explicit flush or call to endl is the best way to ensure that you are seeing all the output that the program actually processed.


      Countless hours of programmer time have been wasted tracking through code that appeared not to have executed when in fact the buffer simply had not been flushed. For this reason, we tend to use endl rather than \n when writing output. Using endl means we do not have to wonder whether output is pending when a program crashes.


  5. Tying Input and Output Streams Together

    1. 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
    2. 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
    3. 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
    4. 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.
    // EXE-8.3.cpp : Defines the entry point for the console application.
    //

    #include "stdafx.h"

    #include <iostream>
    #include <string>
    using namespace std;

    int main(int argc, _TCHAR* argv[])
    {

    cout << "hi!" << ends; // inserts a null, then flushes the buffer
    cout << "hi!" << flush; // flushes the buffer; adds no data
    cout << "hi!" << endl; // inserts a newline, then flushes the buffer

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

    cout << endl;
    string tieTest;
    cin.tie(&cout); // illustration only: the library ties cin and cout for us
    ostream *old_tie = cin.tie();
    cin.tie(0); // break tie to cout, cout no longer flushed when cin is read
    cout << "test";
    cin >> tieTest ;
    cin.tie(&cerr); // ties cin and cerr, not necessarily a good idea!
    // ...
    cin.tie(0); // break tie between cin and cerr
    cin.tie(old_tie); // restablish normal tie between cin and cout


    int i;
    cin >> i;
    return 0;
    }

Section 8.4 File Input and Output



  1. 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.


  2. In addition to the behavior that fstream types inherit, they also define two new operations of their ownopen and closealong 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
  3. 8.4.1. Using File Stream Objects

    1. When we want to read or write a file, we must define our own objects, and bind them to the desired files
    2. Supplying a file name as an initializer to an ifstream or ofstream object has the effect of opening the specified file
    3. Before we use an fstream object, we must also bind it to a file to read or write
    4. 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









      Caution: File Names in C++


      For historical reasons, the IO library uses C-style character strings (Section 4.3, p. 130) rather than C++ strings to refer to file names. When we call open or use a file name as the initializer when creating an fstream object, the argument we pass is a C-style string, not a library string. Often our programs obtain file names by reading the standard input. As usual, it is a good idea to read into a string, not a C-style character array. Assuming that the name of the file we wish to use is in a string, we can use the c_str member (Section 4.3.2, p. 139) to obtain a C-style string.



    5. Checking Whether an Open Succeeded

      1. 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

    6. Rebinding a File Stream to a New File

      1. 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
      2. 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

    7. Clearing the State of a File Stream

      1. Closing a stream does not change the internal state of the stream object
      2. 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


  4. 8.4.2. File Modes

    1. 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

      in


      open for input


      out


      open output


      app


      seek to the end before every write


      ate


      seek to the end immediately after the open


      trunc


      truncate an existing stream when opening it


      binary


      do IO operations in binary mode

      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
    2. 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
    3. In effect, specifying out mode for an ofstream is equivalent to specifying both out and trunc
    4. The only way to preserve the existing data in a file opened by an ofstream is to specify app mode explicitly
    5. Using the Same File for Input and Output

      1. 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
      2. 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

    6. Mode Is an Attribute of a File, Not a Stream

      1. 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.

    7. Valid Combinations for Open Mode

      1. 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. File Mode Combinations

        out


        open for output; deletes existing data in the file


        out | app


        open for output; all writes at end of file


        out | trunc


        same as out


        in


        open for input


        in | out


        open for both input and output;
        positioned to read the beginning of the file


        in | out | trunc


        open for both input and output,
        deletes existing data in the file

        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

  5. 8.4.3. A Program to Open and Check Input Files
// EXE-8.4.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <string>
#include <vector>

using namespace std;

int main(int argc, _TCHAR* argv[])
{

// construct an ifstream and bind it to the file named ifile
string filePath = "filestreamtesting.txt";
ifstream infile1(filePath);
// check that the open succeeded
if (!infile1) {
cerr << "error: unable to open input file: "
<< infile1 << endl;
//return -1;
}

// ofstream output file object to write file named ofile
ofstream outfile(filePath);
//char *pO = "try to write the file!";

outfile.write(filePath.c_str(),filePath.length());
outfile.flush();
outfile.close();

ifstream infile(filePath);
char *p = new char(0);
infile.read(p,filePath.length());

for (int i = 0 ; i < filePath.length(); ++i)
{
cout <<*(p+ i) ;
}
while( *p != 0)
{
cout << *p++;
}
delete p; // error to delete the p
p = 0;

infile.close();

//ifstream infile("in"); // opens file named "in" for reading
//infile.close(); // closes "in"
infile.open("no existing file.txt"); // opens file named "next" for reading
if(!infile)
{
cout << "To open file failed!" << endl;
}
infile.close();


if("Clearing the State of a File Stream")
{
ifstream input;
vector<string> files;
files.push_back(filePath);
files.push_back("ReadMe.txt");
vector<string>::const_iterator it = files.begin();
string s;
// for each file in the vector
void process(string s);
while (it != files.end()) {
input.open(it->c_str()); // open the file
// if the file is ok, read and "process" the input
if (!input)
break; // error: bail out!
while(input >> s) // do the work on this file
process(s);
input.close(); // close file when we're done with it
input.clear(); // reset state to ok
++it; // increment iterator to get next file
}
}

if("File Modes")
{
// 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);
}
if("Using the Same File for Input and Output")
{
// open for input and output
fstream inOut("copyOut", fstream::in | fstream::out);
}
if("Mode Is an Attribute of a File, Not a Stream")
{
ofstream outfile;
// output mode set to out, "scratchpad" truncated
outfile.open("scratchpad", ofstream::out);
outfile.close(); // close outfile so we can rebind it
// appends to file named "precious"
outfile.open("precious", ofstream::app);
outfile.close();
// output mode set by default, "out" truncated
outfile.open("out");
}

if("A Program to Open and Check Input Files")
{
// opens in binding it to the given file
ifstream& open_file(ifstream &in, const string &file);


// failed to build with the following code
// can't call the code like the following
//ifstream infile = open_file(infile, filePath);
ifstream &infile = open_file(infile, filePath);
}

int i;
cin >> i;

return 0;
}

void process(string s)
{
cout << s << endl;
}

// opens in binding it to the given file
ifstream& open_file(ifstream &in, const string &file)
{
in.close(); // close in case it was already open
in.clear(); // clear any existing errors
// if the open fails, the stream will be in an invalid state
in.open(file.c_str()); // open the file we were given
return in; // condition state is good if open succeeded
}

Section 8.5 String Streams



  1. 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


  2. 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
  3. 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

    stringstream strm;


    Creates an unbound stringstream.


    stringstream strm(s);


    Creates a stringstream that holds a copy of the string s.


    strm.str()


    Returns a copy of the string that strm holds.


    strm.str(s)


    Copies the string s into strm. Returns void.


  4. Using a stringstream
  5. stringstreams Provide Conversions and/or Formatting

    1. One common use of stringstreams is when we want to obtain automatic formatting across multiple data types
    2. Reading an istringstream automatically converts from the character representation of a numeric value to its corresponding arithmetic value
    3. 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
    4. 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


// EXE-8.5.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
//here for using istringstream must include the sstream
#include <sstream>
#include <string>
#include <vector>

using namespace std;

int main(int argc, _TCHAR* argv[])
{
if("Using a stringstream")
{
string line, word; // will hold a line and word from input, respectively
cout << "Please input a string (\\0 for break):" << endl;
while (getline(cin, line)) { // read a line from the input into line
// do per-line processing
if(line == "\\0")
break;

istringstream strIn(line); // bind to stream to the line we read
while (strIn >> word){ // read a word from line
// do per-word processing
cout << "PrintOut:" << word << endl;
}
}

}

if("stringstreams Provide Conversions and/or Formatting")
{
int val1 = 512, val2 = 1024;
ostringstream format_message;
// ok: converts values to a string representation
format_message << "val1: " << val1 << "\n"
<< "val2: " << val2 << "\n";

// str member obtains the string associated with a stringstream
istringstream input_istring(format_message.str());
string dump; // place to dump the labels from the formatted message
// extracts the stored ascii values, converting back to arithmetic types
input_istring >> dump >> val1 >> dump >> val2;
cout << val1 << " " << val2 << endl; // prints 512 1024


}

int i;
cin >> i;

return 0;
}

No comments: