I/O Classes in C++
IO Classes in C++
The C++ language does not directly handle input and output; instead, it uses a set of types defined in the standard library to manage IO. These types support IO operations for reading data from and writing data to devices, which can be files, console windows, etc. There are also types that allow memory IO, such as reading data from a string and writing data to a string.
Applications do not only perform IO operations from the console window; they often need to read and write named files, and using IO operations to handle string characters is very convenient. Therefore, besides istream and ostream, the standard library also defines other IO types, which are defined in three separate headers: iostream defines the basic types for reading and writing streams, fstream defines types for reading and writing named files, and sstream defines types for reading and writing memory string objects.
The types ifstream and istringstream both inherit from istream. Therefore, we can use ifstream and istringstream objects just like istream objects. It is important to note that IO objects cannot be copied or assigned, and reading or writing an IO object changes its state, so references passed and returned cannot be const.
Condition States
The IO classes define several functions and flags that help us access and manipulate the condition states of streams. Once a stream encounters an error, subsequent IO operations will fail. The simplest way to check a stream object is to use it as a condition:
while(cin >> word)
The IO library defines a machine-independent iostate type that provides complete functionality for expressing stream states: badbit indicates a system-level error, such as an unrecoverable read/write error; failbit indicates a recoverable error, such as expecting to read a number but reading a character instead; when the end of a file is reached, both eofbit and failbit are set. goodbit has a value of 0, indicating no errors. If any of badbit, failbit, or eofbit are set, the condition check for the stream will fail.
Output Buffer Management
Each output stream has a buffer to store data read and written by the program, such as in the following code:
os << "Hello, world";
It may print directly or be saved by the operating system to the buffer and printed later. There are several methods to explicitly flush the buffer:
cout << "Hello, world" << endl; // Output the string, then a newline, and flush the buffer
cout << "Hello, world" << flush; // Output the string and flush the buffer directly
cout << "Hello, world" << ends; // Output the string, then a null character, and flush the buffer
When an input stream is tied to an output stream, any attempt to read data from the input stream will first flush the associated output stream. The standard library ties cout and cin together, and you can manually tie istream and ostream together using the tie() function.
File Input and Output
open and close Functions
After defining an empty file stream object, you can call open to associate it with a file:
ifstream in(ifile); // Construct an ifstream and open the given file
ofstream out; // Output file stream not associated with any file
out.open(ifile + ".copy"); // Open the specified file
Once a file stream is opened, it remains associated with the corresponding file. If you attempt to associate this file stream with another file, you must first close the corresponding file stream:
in.close(); // Close the file
in.open(ifile + "2"); // Associate with a new file
File Modes
Each stream has an associated file mode, which indicates how the file is used. Each file stream type defines a default file mode, which is used if no file mode is specified. Files associated with ifstream are opened in in mode (read mode) by default; files associated with ofstream are opened in out mode (write mode) by default. The file modes are as follows:
in: Open for reading onlyout: Open for writingapp: Position at the end of the file before each write operationate: Define at the end of the file immediately after openingtrunc: Truncate the filebinary: Perform binary IO
By default, when opening an ofstream, the file’s contents will be discarded. To prevent an ofstream from clearing the contents of a given file, specify the app mode:
ofstream out("file1"); // The file will be cleared
ofstream out("file", ofstream::app | ofstream::out); // Open in app mode, contents will be preserved
The only way to preserve data in a file opened by ofstream is to specify the app mode or open in in mode.
String Streams
The sstream header defines three types to support memory IO. These types can write data to a string and read data from a string, treating the string as an IO stream.
When our task is to process entire lines of text, while other tasks involve processing individual words within a line, we can often use istringstream:
string line, word;
vector<PersonInfo> people;
while (getline(cin, line)) { // Continuously read a line from cin
PersonInfo info;
istringstream record(line); // Initialize the record object with the read line
record >> info.name; // Process each word in the line individually
while (record >> word)
info.phones.push_back(word);
people.push_back(info);
}
When we gradually construct output and want the final line to print, ostringstream is very useful:
for (const auto &entry : people) {
ostringstream formatted, badNums;
for (const auto &nums : entry.phones) {
if (!valid(nums)) { // If an invalid phone number is encountered, add it to badNums for output
badNums << " " << nums;
} else
formatted << " " << format(nums); // Add valid phone numbers to formatted
}
if (badNums.str().empty())
os << entry.name << " " << formatted.str() << endl;
else
cerr << "Input error: " << entry.name << " invalid number(s) " << badNums.str() << endl;
}