Streams

Programs often need to read data from devices such as a disk (where files are stored) and the keyboard. It’s also common that programs write data to files in a disk or to a computer display. Reading from and writing to these devices directly is not an easy task. There are many low level technical details that need to be considered and each device may have its own specificities. Therefore, the C++-standard library offers streams. A stream is an abstraction that represents a device, such as a file or keyboard, on which input (i.e. read) and output (i.e. write) operations are performed in a much simpler way. C++ programs do not read directly from, or write directly to, a device. C++-programs read from and write to streams. Streams shield the programmer from all low level technical intricacies of the different devices.

We have been using two specific streams in our programs, so far: std::cin and std::cout. A stream is connected to a device such as the keyboard. A stream variable consists of

  • a sequence of bytes, also known as buffer, where characters are placed and
  • three bits that indicate the state of the stream.

Streams

The stream variables std::cin and std::cout are defined in the C++-standard library iostream (which we always include in our programs). As depicted in the figure above, stream std::cin is connected to the keyboard, while stream std::cout is connected to the computer’s display.

Let us now see how stream input and output is performed. Note that “stream output” is just another way of saying “to write to a stream” and “stream input” means “to read from a stream”.

Stream output

Let’s illustrate the process involved in writing to a stream by using std::cout.

The stream std::cout is connected to the computer’s display, usually called console output. When a statement such as std::cout << x; is executed, the value of variable x is converted into a sequence of characters (corresponding to its text representation). For instance, if variable x is a double storing $12.347$ then this value is converted to the sequence of characters '1''2''.''3''4''7'. These characters are then inserted into the stream’s buffer for transmission to the output device, i.e. the computer’s display. The conversion of variable x’s value to text can be controlled by the programmer by using C++ manipulators, like in std::cout << std::fixed << std::setprecision(2) << x;.

To summarize. For output, the advantage of using streams is twofold: firstly, a variable’s value is converted to text, i.e. a sequence of characters; secondly, the transmission of the contents of a stream’s buffer to the output device (like a computer’s display) occurs automatically. Thus, the programmer just needs to use the output operation << for the transfer to take place.

Note that the stream output operation << (technically called stream insertion operator) is only available for

  • the basic types of the language, such as int, double, bool, etc, and
  • some types defined in the C++-standard library like std::string.

Thus, if you consider again the type Point representing a point in a $2$D-space then one cannot use operation << to display directly the value of variables of type Point.

struct Point {
  int x = 0;  // the x-coordinate
  int y = 0;  // the y-coordinate
};

Point p = {2, 6};

std::cout << p;  // compilation error!!

Instead, one can define a function to display a point.

void display_point(const Point& p) {
    std::cout << "(" << p.x << ", " << p.y << ")";
}

Point p = {2, 6};

display_point(p);

The programmer can also define the output operation << specifically for type Point, so that a statement like std::cout << p; can be executed. This programming technique goes under the name operator overloading and it is discussed in a more advanced C++ course.

Stream input

Let’s now illustrate the process involved in reading from a stream by using the stream std::cin.

The stream std::cin is connected to the computer’s keyboard, usually called console input. Any characters entered by a user through the keyboard are transfered automatically to the buffer of the stream. Consider the piece of code below.

double x = 0.0;

std::cin >> x;

When std::cin >> x; is executed, to the following steps are performed.

  1. If the stream’s buffer is empty then the program waits until characters are transfered from the device (e.g. computer’s keyboard) into the buffer. Otherwise, it proceeds with next step.
  2. The reading operator >> scans the characters in the stream’s buffer and converts them automatically to the corresponding value of variable x’s type (i.e. a double). This value is then stored in variable x. This scanning process starts at the beginning of the buffer and, it stops when it encounters a character that cannot be converted or when it reaches the end of the buffer.
  3. The sequence of characters scanned and converted in the previous step are removed from the stream’s buffer.

For example, if std::cin buffer would contain the characters '1''2''.''3''4''X''P''T''O' then std::cin >> x; would read $12.34$ into variable x. After the execution of the statement, std::cin buffer would contain the sequence of characters 'X''P''T''O'.

An interesting question is what would have happened if one would attempt to execute std::cin >> x; when the std::cin buffer only contains the characters 'X''P''T''O'. Obviously, it’s not possible to convert “XPTO” to a double. We discuss this problem in the section Test a stream state.

Note that the stream input operation >> (technically called stream extraction operator) is only available for

  • the basic types of the language, such as int, double, bool, etc, and
  • some types defined in the C++-standard library like std::string.

Thus, one cannot use operation >> to display directly the value of variables of a type defined by the programmer such as Point.

Point p;

std::cin >> p;  // compilation error!!

Instead, one can define a function to read a point.

Point read_point() {
    Point p;
    std::cin >> p.x >> p.y;

    return p;
}

Point p = read_point();

The programmer can also define the input operation >> specifically for type Point, though this concept is not covered in this course.

File streams

File streams follow the same principles introduced in the previous sections. The main difference is that a file stream is connected to a file in a disk.

File streams

The figure above illustrates two stream variables, utFil and inFil, both connected to a file in the disk. The stream utFil is used to write in the file. Writing to a file stream is similar to writing to std::cout. The stream inFil is used to read from the file. Reading from a file stream is similar to reading from std::cin. However, file stream variables must be defined in the program like any other variable. Next, section tells how this can be done.

Create file streams

To be able to read or write from a file, one needs to define a stream variable and connect it to a file. To this end, the C++-standard library fstream must be included in the program.

If one wants to write to a text file then one should create a stream of type std::ofstream. Type std::ofstream is defined in the library fstream. The following piece of code defines a stream variable named utFil of type std::ofstream and connects it to the text file employee_data.txt stored in the folder C:/My_data.

#include <fstream>

std::ofstream utFil("C:/My_data/employee_data.txt"); // create a stream and connect it to a file for writing

If the file “C:/My_data/employee_data.txt” does not exist then a new file is created. Otherwise, the existing file is erased first without any warning and a new empty file is created.

If one wants to read from a text file then one should create a stream of type std::ifstream. Type std::ifstream is defined in the library fstream. The following piece of code defines a stream variable named inFil of type std::ifstream. The stream is connected to a file whose name is given by the user. Once the stream is connected to the file, the characters in the user given file are automatically transfered to the stream’s buffer.

#include <fstream>

std::string file_name;

std::cout >> "Enter file name: ";
std::cin >> file_name;

std::ifstream inFil(file_name);  // create a stream and connect it to a file for reading

In a similar way, a stream of type std::ofstream can also be connected to a user given text file.

Test a stream state

Streams can be in either bad-state or good-state. If a stream is in bad-state then it’s not possible to read from or write to it. A stream in bad-state has some of its error bits turned to $1$, i.e. a stream is in good-state if all its error bits are set to zero.

The following situations set a stream in bad-state.

  • To attempt to connect a file stream of type std::ifstream to a non-existing file.
  • To attempt to create a new file (i.e. to connect a file stream of type std::ofstream to a new file) when the disk has no free space.
  • To attempt to connect a file stream to a file when the disk is damaged.
  • To attempt to read past the end of the stream’s buffer.
  • To attempt to read a non-numeric value, like some text, into a numeric variable (i.e. int ordouble).
  • To attempt to read “ctrl-Z” on Windows (“ctrl-D” on Mac OS or Linux).

The three first cases listed above point to the need for programs to test whether a file stream is in good-state, after attempting to connect it to a file. If an error has occurred at this point then reading from or writing to the file is meaningless.

Good programming practice: Test the state of a stream after attempting to connect it with a file.

So, how can one test whether a stream is in good state? In C++, a stream variable can be used as a test condition in an if-statement or in a loop. In this case, the stream is automatically converted to true, if the stream is in good-state. Otherwise, the stream is converted to false. An example is shown below with a stream to read from a text file.

#include <fstream>

int main() {
    std::string file_name;

    std::cout << "Enter file name: ";
    std::cin >> file_name;

    std::ifstream inFil(file_name);  // create a stream and connect it to a file for reading

    if (inFil) {  // test if stream is in good-state
        // read the file
        // do something with the data read from the file
    } else {
        std::cout << "File could not be accessed!!\n";
    }
}

When reading a list of numbers from std::cin (i.e. the user enters the input through the keyboard), it’s possible to mark the end of the input list with a non-number (like the string “STOP” or “ctrl-Z”).

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <iostream>

int main() {
    std::cout << "Enter a list of numbers: ";

    double x = 0;
    std::cin >> x;  // read first number

    while (std::cin) {  // test if stream is in good-state
        // do something with x

        std::cin >> x;  // read next number
    }
}

Consider the reading statement std::cin >> x;. When the user enters a non-number like “STOP” then it’s not possible to convert those characters to a number and the stream std::cin is set to bad-state. Thus, the test in the while-loop, line $9$, evaluates to false and the loop stops. Note that the test in line $9$ checks whether the last reading operation succeeded.

Another alternative way of writing the code above is given below.

1
2
3
4
5
6
7
8
9
10
11
#include <iostream>

int main() {
    std::cout << "Enter a list of numbers: ";

    double x = 0;

    while (std::cin >> x) {  // read + test if stream is in good-state
        // do something with x
    }
}

Let’s see why it’s possible to write a reading statement such as std::cin >> x; as a test condition of a loop. The stream input operation >> is a function defined in the language for the basic types (like double) that reads a value from a stream’s buffer, std::cin or a file stream, and stores the value read in a variable e.g. x. Then, the function >> returns the stream after the reading. Thus, if an error occurred during the reading, the stream returned is in bad-state and, consequently, the while-loop ends. The same idea applies to the stream input operation >> defined in the C++-standard library for types like std::string.