I/O Classes in C++

Language:中文/EN

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 only
  • out: Open for writing
  • app: Position at the end of the file before each write operation
  • ate: Define at the end of the file immediately after opening
  • trunc: Truncate the file
  • binary: 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;
}