8.1 An Object-Oriented Library
To support or using different kinds of devices and different sized character streams, the library usesinheritance to define a set of object-oriented classes.
When one class inherits from another, we can use the same operations on both classes.
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:
1. iostream defines the types used to read and write to a console window
2. fstream defines the types used to read and write named files
3. sstream defines the types used to read and write in-memory strings.
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.
Header | Type | International Char |
iostream | istream, ostream, iostream | wistream, wostream, wiostream |
fstream | ifstream, ofstream, fstream | wifstream... |
sstream | istringstream, ostringstream, stringstream | ... |
International Character Support
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. So wostream, wistream, and wiostream read and write wchar_t data to or from a console window.
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 ouput 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 iniostream, the wide character file stream types infstream, and the wide character stringstreams in sstream.
No Copy or Assign for IO Objects
The library types do not allow copy or assignment:
ofstream out1, out2;
out1 = out2; //error: cannot assign stream objects
// print function: parameter is copied
ofstream print(ofstream);
out2 = print(out2); //error: cannot copy stream objects
This requirement has two implications:
1. we cannot have a vector(or other container) that holds stream objects, as only element types that support copy can be stored in vectors or other container types.
2. we cannot have a parameter or return type that is one of the stream types. If we need to pass or return IO object, it must be reference or pointer.
ofstream &print(ofstream &); //ok: takes a reference, no copy
while (print(out2)) {...} //ok: pass reference to out2
Typically, we pass a stream as a
nonconst reference because we need to read or write to the object. And reading or writing an IO object changes its state.
8.2 Condition States
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 easiest way to test whether a stream is okay is to test its truth value:
if (cin) //ok to use cin, it is in a valid state
while (cin >> word) //ok: read operation successful
IO Library Condition State P 288
Condition States
1. strm::iostate
Each stream object contains a condition state member that is managed through the setstate and clear operations.
2. strm::badbit, strm::failbit, strm::eofbit
The badbit indicates a system level failure, such as an unrecoverable read or write error. The failbit is set after a recoverable error, such as reading a char when numeric data was expected. The eofbit is set when an end-of-file is encountered. Hitting eof also sets the failbit.
3. s.eof(), s.fail(), s.bad(), s.good()
The state of the stream is revealed by the bad, fail, eof, and good operations. If any of the first three are true, then testing the stream itself will indicate that the stream is in an error state.
4. s.clear(), s.clear(flag), s.setstate(flag)
The clear and setstate operations change the state of the condition member. The clear put the condition back in its valid state.
5. s.rdstate()
rdstate member function returns an iostate value that corresponds to the entire current condition state of the stream:
//remember current state of cin
istream::iostate old_state = cin.rdstate();
cin.clear();
process_input(); //use cin
cin.setstate(old_state); //now reset cin to old state
Interrogating and Controlling the State of a Stream
int val;
// read cin and test only for EOF; loop is executed even if there are other IO failures
while (cin >> ival, !cin.eof())
{
if (cin.bad()) //input stream is corrupted; bail out
throw runtime_error("IO stream corrupted");
if (cin.fail()) //bad input
{
cerr << "bad data, try again"; //warn user
cin.clear(istream::failbit); //reset the stream
continue; //get next input
}
}
Dealing with Multiple States
When we need to set or clear multiple state bits. We can do so by making multiple calls to the setstateand clear functions. We can also use bitwise OR to do this(because different error type use different bit):
//sets both the badbit and the failbit
is.setstate (ifstream::badbit | ifstream::failbit);
It will turn on badbit and failbit, all other bits are zero.
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.
There are several conditions that cause the buffer to be flushed - that is, written - to the actual 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 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.
Flushing the Output Buffer:
There are three normally used manipulations that can flush buffer: flush, ends, endl
cout<< "hi" << flush; //flushes the buffer; adds no data
cout<< "hi" << ends; //insert a null, then flushes the buffer
cout<< "hi" << endl; //insert a newline, then flushes the buffer
The unitbuf Manipulator
If we want to flush every output, it is better to use the unitbuf manipulator:
cout<< unitbuf << "first" << "second"<< nounitbuf;
//equalsto
cout<< "first" << flush << "second" <<flush;
The nounitbuf manipulator restores the stream to use normal, system-managed buffer flushing.
Tying Input and OutputSteams 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.
Interface systems usually should be sure that their input and output streams are tied, so that any output, which might include prompts to the user, has been written before attempting to read.
The tie function takes a pointer to an ostream and ties the argument stream to the object on which tie was called:
//illustrationonly: the library ties cin and cout for us
cin.tie(&cout);
cin.tie(0); //break tie between cin and cout
cin.tie(&cerr);
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 I/O:
1. ifstream, derived fromistream, reads from a file
2. ofstream, derived fromostream, writes to a file
3. fstream, derived from iostream, reads and writes the same file
In addition to the behavior that fstream types inherit from iostream, they also define two new operations:open and close. These operations can be called on objects of fstream, ifstream or ofstream but not on other IO types.
8.4.1 Using File Stream Objects
When we want to read or write a file, we must define our own objects and bind them to the desired files. Supplying a file name as an initializer to an ifstream or ofstream object has the effect of opening the specified file:
//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());
PS: the IO library uses C-style character strings. Assuming the name of the file we wish to use is in a string, we can use c_str member to obtain C-style string.
P 139
We can also define an IO object and bind it to a file later. We bind an existing fstream object to the specified file by calling the open member.
ifstream infile; //unbound input file stream
ofstream outfile; //unbound output file stream
infile.open("in"); //open file named "in" in the current directory
outfile.open("out"); //open file named "out" in the current directory
Checking Whether an Open Succeeded
if (!infile)
{
cerr << "error: unable to open input file: "
<< ifile << endl;
return -1;
}
When we test a stream, the effect is to test whether the object is "okay: for input or output:
if (outfile) //ok to use outfile?
if (!outfile) //not ok to use outfile?
Rebinding a File Stream to a New File
Once a fstream has been opened, it remains associated with the specified file. To associate the fstream with a different file, we must first closethe existing file and then open a different file:
ifstream infile("in"); //open file named "in" for reading
infile.close(); //close "in"
infile.open("next"); //opens file named next for reading
The open function checks whether the stream is already open. If so, 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
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. (We should add clear after each close operation)
Two ways to read multiple files: 1. recreate the stream object for each file; 2. reuse the stream object and close/clear after each file.P 295
8.4.2 File Mode
Whenever we open a file, a file mode is specified. Different modes are represented by a set of values that can be controlled by bitwise operators:
in | open for input | ifstream/fstream |
out | open output | ofstream/fstream |
app | seek to the end before every write | ofstream/fstream |
ate | seek to the end immediately after the open | any file |
trunc | truncate an existing stream when opening it | ofstream/fstream |
binary | do IO operations in binary mode | any file |
binary: process the file as a sequence of bytes; it does no interpretation of the characters in the stream.
in: by default, files associated with anifstream are opened inin mode. It permits the file to be read.
out: by default, files associated with an ofstream are opened in out mode. It permits the file to be written.A file opened in out mode is truncated: All data stored in the file is discarded. (in effect, specifying out mode for an ofstream is equivalent to specifying both out and trunc)
app: 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("file2", ofstream::out | ofstream::trunc);
// append mode: adds new data at the end of existing file named "file2"
ofstream outfile3("file3", ofstream::app);
Using the Same File for Input and Output A.3.8(p.837)
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.Mode Is an Attribute of a File, Not a Stream
So we need to set mode each time we open a file.
8.4.3 A Program to Open and Check Input Files
// 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
}
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. We should include sstream header to use these classes:
1. istringstream, derived from istream, reads from a string
2. ostringstream, derived from ostream, writes to a string
3. stringstream, derived from iostream, reads and writes the same string
sstream types have a constructor that takes a string. The constructor will copy the string argument into the stringstream object. These classes also define a member named str to fetch or set the string value that the stringstream manipulates.
stringstream strm; | creates an unbound stringstream |
stringstream strm(s); | create a stringstream that holds a copy of the string s |
strm.str() | return a copy of the string that strm holds |
strm.str(s) | copies the string s into strm. return void |
Using a stringstream
Some programs need to do some processing on a per-line basis and other works on each word within each line:
string line, word; //will hold a line and word from input, respectively
while (getline(cin, line)) //read a line from the input into line
{
//do per-line processing
istringstream stream(line); //bind stream to the line we read
while (stream >> word)
{
//do per-word processing
}
}
stringstreams Provide Conversions and/or Formatting P 300One common use of stringstream is when we want to obtain automatic formatting across multiple data types.
The sstream input and output operations automatically convert an arithmetic type into its corresponding string representation or back again