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.
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;
std::cin >> x;
When std::cin >> x;
is executed, to the following steps are performed.
- 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.
- The reading operator
>>
scans the characters in the stream’s buffer and converts them automatically to the corresponding value of variablex
’s type (i.e. adouble
). This value is then stored in variablex
. 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. - 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.
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";
}
}
Alternatively, one can also write the following code.
#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 bad-state
std::cout << "File could not be accessed!!\n";
return 0; // end the main
}
// read the file
// do something with the data read from the file
}
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;
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
#include <iostream>
int main() {
std::cout << "Enter a list of numbers: ";
double x;
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
.