Streams

Streams

Introduction

In C++, streams are the primary way to handle input and output.

  • A stream is simply a sequence of bytes.
    • Input/output streams are all sequences of bytes.
  • Stream data is read/written byte by byte
    • The data is stored in a “buffer” until program needs it.

Input stream

Buffering of input stream:

  • All input is stored in the buffer until std::cin extracts it.
  • Whitespace characters (like spaces, tabs, and newlines) are usually treated as separators between input values for certain data types.
  • The extraction >> operator skips leading whitespace automatically (like spaces and newlines) to find the next meaningful input.
  • <Enter> triggers the processing of input data, sending them to the buffer.

Input data is stored in a buffer until the program requires information from the buffer.

int main() {
    int a;
    int b;
    cout << "Enter a: ";
    cin >> a;
    cout << "Enter b: ";
    cin >> b;
    cout << "a + b = " << a + b << endl;
    return 0;
}
  • In scenario 2, both inputs are given at once.
    • After processing 5, 4 is remained in the buffer and is immediately available for the next input.
  • Scenario 1: <Enter> is pressed after each input.
Enter a: 10
Enter b: 20
a + b = 30
  • Scenario 2: <Enter> is pressed after the first input.
Enter a: 10 20
Enter b: a + b = 30

Stram states

A stream’s state indicates whether the stream is in a valid state or not.

  • Each stream type exposes the constant static members referred to collectively as its bits, which indicate a possible stream state.
Method State Meaning
cin.good() goodbit The last input operation was successful
cin.fail() failbit The last input operation failed
cin.eof() eofbit The end of the file is reached
cin.bad() badbit The stream has encountered a fatal error
  • To reset the stream state, use cin.clear().
  • Only goodbit is set to true.

Recall that every expression has a value and a type, streams implement an implicit bool coonversion, so you can check whether a stream is in a good working state simply.

int main() {
    std::string word;
    size_t count = 0;
    while (cin >> word) {
        ++count;
    }
    cout << "You entered " << count << " words." << endl;
    return 0;
}

After compiling your program, try to enter a large paragraph, use <Ctrl-D> to end the input(Unix) or <Ctrl-Z> followed by <Enter> to end the input(Windows).

Handle bad states

When a stream is in a bad state, it remains in that state until the program clears it.

while (!(cin >> number)) {
    cin.clear();
    cin.ignore(200, '\n');  // 200 is large enough to ignore any input
    
    cout << "Invalid input. Please try again." << endl;
}

cout << "The number is " << number << endl;
  • cin.clear() resets the stream state to goodbit.
  • cin.ignore(200, '\n') ignores up to 200 characters or until a newline character is encountered. 1

File streams

Introduction

The file stream classes are defined in <fstream> header, providing facilities to perform input and output operations on files.

  • ifstream for input operations, ofstream for output operations, and fstream for both input and output operations.
  • The file stream classes are RAII classes, which means they manage resources automatically. (Just as unique_ptr manages dynamic memory.)

Open files

There are two options to open a file:

  • Use constructor of the file stream class.
  • Use member function open() of the file stream class.
#include <fstream>

int main() {
    std::ifstream file1("infile.txt");
    std::ofstream file2("outfile.txt");
    std::fstream file3;
    file3.open("io.txt");

    // no need to close the file stream explicitly, 
    // as the destructor of the file stream class will close the file automatically.
}

Flags

The following table lists the flags used in the file stream classes.

Flag (in std::ios) File Meaning
in Must exist Read
out Created if doesn’t exist Erase the file; then write
app Created if doesn’t exist Append
in | out Must exist Read and write from beginning
in | app Created if doesn’t exist Update at end
out | app Created if doesn’t exist Append
out | trunc Created if doesn’t exist Erase the file; then read and write
in | out | app Created if doesn’t exist Update at end
in | out | trunc Created if doesn’t exist Erase the file; then read and write
  • By default, ifstream is in in mode, ofstream is in out mode, and fstream is in in | out mode.

Now suppose I have a file named story.txt in the current directory, and I want to read from it and write to another file named outfile.txt.

  • story.txt must exist. (in flag)
  • outfile.txt will be created if it doesn’t exist, if it exists, its content will be erased. (out flag)
int main() {
    // error handling omitted for brevity
    std::ifstream inputFile("novel.txt", std::ios::in);
    std::ofstream outputFile("outfile.txt", std::ios::out | std::ios::trunc);
    std::string word;
    while (inputFile >> word) {
        outputFile << word << ' ';
    }

    std::cout << "Content copied from novel.txt to outfile.txt successfully." << std::endl;
    return 0;
}

Error handling in file streams

Similar to input/output streams, file streams also have the four states: goodbit, failbit, eofbit, and badbit.

The error handling mechanism is similar to that of input/output streams. Here are two ways to check if a file was opened successfully:

  • When using a constructor to open a file, you can check using the is_open() member function which returns true if the file was opened successfully
  • When using either the constructor or open() method, you can use the implicit bool conversion of the file stream object (which returns false if any error bits are set)
  • Use clear() to reset the stream state to goodbit.

Example 1

Content of my input file:

This is for a:
51
and now
for b:
100

Task: read the value of a and b from the file and save their sum to a new file named result.txt.

  • fail() check: give a number to a, but read a character/word.
    • Clear the stream state and ignore the bad input.
  • eof() check: keep reading the file until the end.
    • Exit the loop when reaching the end of the file.

Assignment

You are given a file named hidden_numbers.txt, which contains characters and numbers, seperated by spaces.

Task: read the numbers from the file and save their sum to a new file named sum_numbers.txt.

Submission: You only need to write main.cpp and CMakeLists.txt, zip them along with hidden_numbers.txt and submit to Canvas.

std::getline()

std::getline(input_stream, str, delimiter);

  • Read a line from the input stream into str, stopping before delimiter.
  • If the delimiter is not found, the function reads the entire remaining content of the stream.
  • The default delimiter is '\n'.
  • getline() won’t ignore the whitespace characters.
// print the content of the file line by line
std::ifstream inputFile("story.txt");
std::string line;
while (std::getline(inputFile, line)) {
    std::cout << line << std::endl;
}

std::stringstream

The std::stringstream class is part of the <sstream> header.

  • It allows you to treat a string as a stream, enabling you to perform input and output operations on the string.
  • It is useful for parsing strings, extracting values, and performing various string manipulations.

Types of stringstream:

  • std::stringstream: for both input and output.
  • std::istringstream: for input only.
  • std::ostringstream: for output only.

Write and read from a stringstream:

  • << operator is used to write data to the stringstream.
  • >> operator is used to read data from the stringstream.
  • str() method is used to get the content of the stringstream as a string.
  • str(<string>) will set the stream’s content to be <string>.
  • clear() method is used to reset the state of the stringstream.

Extract numbers from a string and vice versa

std::stringstream ss;
ss << "Age: " << 25 << " Height: " << 5.9; // write to the stringstream

std::string result = ss.str(); // get the content of the stringstream as a string
std::cout << "Formatted string: " << result << std::endl;

std::string label;
int age;
float height;

// suppose we want to use another stringstream to extract the values
std::stringstream ss2;
ss2.str(result); // set the content to the string
ss2.clear(); // optional:reset the stream state to goodbit

ss2 >> label >> age >> label >> height;

std::cout << "Extracted age: " << age << std::endl;
std::cout << "Extracted height: " << height << std::endl;

Example 2

Content of the input file: A short story.

Task: read the story and count the number of words in it, then save the number to a new file named word_count.txt.

  • Use getline() to read the whole file. 2
  • Use stringstream to split the string into words.
  • Use ofstream to write the result to the file.

Example 3

Given a stock price csv file with the following format:

Date,Open,High,Low,Close,Volume
2024-01-01,100.0,105.0,99.5,102.5,5000
2024-01-02,102.5,108.0,101.0,106.0,6000
2024-01-03,106.0,110.0,105.0,109.5,7000

Task: add a new column named return to the file, which is the percentage change of the Close price from the previous day.

Footnotes

  1. cin will leave the newline character in the buffer.(Because of typing <Enter> after each input.)↩︎

  2. getline() will ends when encountering the delimiter or the end of the file, so no need to check eof()↩︎