You have actually been using streams all through this book, starting with Lesson 1, “Getting Started,” in which you displayed “Hello World” on the screen using std::cout
. It’s time to give this part of C++ its due attention and learn streams from a practical point of view. In this lesson, you find out
What streams are and how they are used
How to write to and read from files using streams
Useful C++ stream operations
You are developing a program that reads from the disk, writes data to the display, reads user input from the keyboard, and saves data on the disk. Wouldn’t it be useful if you could treat all read activities and write activities using similar patterns irrespective of what device or location the data is coming from or going to? This is exactly what C++ streams offer you!
C++ streams are a generic implementation of read and write (in other words, input and output) logic that enables you to use certain consistent patterns toward reading or writing data. These patterns are consistent irrespective of whether you are reading data from the disk or the keyboard or whether you are writing to the display or back to the disk. You just need to use the right stream class, and the implementation within the class takes care of device- and OS-specific details.
Let’s refer to one relevant line taken from your first C++ program, Listing 1.1 in Lesson 1, again:
std::cout << "Hello World!" << std::endl;
That’s right: std::cout
is a stream object of class ostream
for console output. To use std::cout
, you included header <iostream
> that supplies this and other functionality such as std::cin
that allows you to read from a stream.
So, what do I mean when I say that streams allow consistent and device-specific access? If you were to write "Hello World"
to a text file, you would use this syntax on a file stream object fsHello
:
fsHello << "Hello World!" << endl; // "Hello World!" into a file stream
As you can see, after you’ve chosen the right stream class, writing “Hello World” to a file isn’t too different in C++ than writing it to the display.
Tip
operator<<
used when writing into a stream is called the stream insertion operator. You use it when writing to the display, file, and so on.
operator>>
used when writing a stream into a variable is called the stream extraction operator. You use it when reading input from the keyboard, file, and so on.
Going ahead, this lesson studies streams from a practical point of view.
C++ provides you with a set of standard classes and headers that help you perform some important and frequent I/O operations. Table 27.1 is a list of classes that you use frequently.
Note
cout, cin,
and cerr
are global objects of stream classes ostream, istream
, and ostream
, respectively. Being global objects, they’re initialized before main()
starts.
When using a stream class, you have the option of specifying manipulators that perform specific actions for you. std::endl
is one such manipulator that you have been using thus far to insert a newline character:
std::cout << "This lines ends here" << std::endl;
Table 27.2 demonstrates a few other such manipulator functions and flags.
std::cout
for Writing Formatted Data to Consolestd::cout
used for writing to the standard output stream is possibly the most used stream in this book thus far. Yet, it’s time to revisit cout
and use some of the manipulators in changing the way we are able to align and display data.
std::cout
It is possible to ask cout
to display an integer in hexadecimal or in octal notations. Listing 27.1 demonstrates using cout
to display an input number in various formats.
0: #include <iostream>
1: #include <iomanip>
2: using namespace std;
3:
4: int main()
5: {
6: cout << "Enter an integer: ";
7: int input = 0;
8: cin >> input;
9:
10: cout << "Integer in octal: " << oct << input << endl;
11: cout << "Integer in hexadecimal: " << hex << input << endl;
12:
13: cout << "Integer in hex using base notation: ";
14: cout<<setiosflags(ios_base::hex|ios_base::showbase|ios_base::uppercase);
15: cout << input << endl;
16:
17: cout << "Integer after resetting I/O flags: ";
18: cout<<resetiosflags(ios_base::hex|ios_base::showbase|ios_base::uppercase);
19: cout << input << endl;
20:
21: return 0;
22: }
Output
Enter an integer: 253
Integer in octal: 375
Integer in hexadecimal: fd
Integer in hex using base notation: 0XFD
Integer after resetting I/O flags: 253
Analysis
The code sample uses the manipulators presented in Table 27.2 to change the way cout
displays the same integer object input
, supplied by the user. Note how manipulators oct
and hex
are used in Lines 10 and 11. In Line 14 you use setiosflags()
telling it to display the numbers in hex
using uppercase letters, resulting in cout
displaying integer input 253
as 0XFD
. The effect of resetioflags()
used in Line 18 is demonstrated by the integer being displayed by cout
using decimal notation again. Another way to change the radix used in displaying integer to decimal would be the following:
cout << dec << input << endl; // displays in decimal
It is also possible to format the manner in which cout displays numbers such as Pi in that you can specify the precision, which in a fixed-point notation specifies the number of places after decimal to be shown, or you can have a number displayed using scientific notation. This and more is demonstrated by Listing 27.2.
0: #include <iostream>
1: #include <iomanip>
2: using namespace std;
3:
4: int main()
5: {
6: const double Pi = (double)22.0 / 7;
7: cout << "Pi = " << Pi << endl;
8:
9: cout << endl << "Setting precision to 7: " << endl;
10: cout << setprecision(7);
11: cout << "Pi = " << Pi << endl;
12: cout << fixed << "Fixed Pi = " << Pi << endl;
13: cout << scientific << "Scientific Pi = " << Pi << endl;
14:
15: cout << endl << "Setting precision to 10: " << endl;
16: cout << setprecision(10);
17: cout << "Pi = " << Pi << endl;
18: cout << fixed << "Fixed Pi = " << Pi << endl;
19: cout << scientific << "Scientific Pi = " << Pi << endl;
20:
21: cout << endl << "Enter a radius: ";
22: double radius = 0.0;
23: cin >> radius;
24: cout << "Area of circle: " << 2*Pi*radius*radius << endl;
25:
26: return 0;
27: }
Pi = 3.14286
Setting precision to 7:
Pi = 3.142857
Fixed Pi = 3.1428571
Scientific Pi = 3.1428571e+000
Setting precision to 10:
Pi = 3.1428571429e+000
Fixed Pi = 3.1428571429
Scientific Pi = 3.1428571429e+000
Enter a radius: 9.99
Area of circle: 6.2731491429e+002
Analysis
The output demonstrates how increasing the precision to 7 in Line 10 and to 10 in Line 16 changes the display of the value of Pi
. Also note how the manipulator scientific
results in the calculated area of the circle being displayed as 6.2731491429e+002
.
std::cout
One can use manipulators such as setw()
to set the width of the field in characters. Any insertion made to the stream is right aligned in this specified width. Similarly, setfill()
can be used to determine what character fills the empty area in such a situation, as demonstrated by Listing 27.3.
0: #include <iostream>
1: #include <iomanip>
2: using namespace std;
3:
4: int main()
5: {
6: cout << "Hey - default!" << endl;
7:
8: cout << setw(35); // set field width to 25 columns
9: cout << "Hey - right aligned!" << endl;
10:
11: cout << setw(35) << setfill('*');
12: cout << "Hey - right aligned!" << endl;
13:
14: cout << "Hey - back to default!" << endl;
15:
16: return 0;
17: }
Output
Hey - default!
Hey - right aligned!
***************Hey - right aligned!
Hey - back to default!
Analysis
The output demonstrates the effect of setw(35)
supplied to cout
in Line 8 and setfill('*')
supplied together with setw(35)
in Line 11. You see that the latter results in the free space preceding the text to be displayed to be filled with asterisks, as specified in setfill()
.
std::cin
for Inputstd::cin
is versatile and enables you to read input into the plain old data types, such as the int
, double
, and char*
, and you can also read lines or characters from the screen using methods such as getline()
.
std::cin
for Input into a Plain Old Data TypeYou can feed integers, doubles, and chars directly from the standard input via cin
. Listing 27.4 demonstrates the usage of cin
in reading simple data types from the user.
0: #include<iostream>
1: using namespace std;
2:
3: int main()
4: {
5: cout << "Enter an integer: ";
6: int inputNum = 0;
7: cin >> inputNum;
8:
9: cout << "Enter the value of Pi: ";
10: double Pi = 0.0;
11: cin >> Pi;
12:
13: cout << "Enter three characters separated by space: " << endl;
14: char char1 = ' ', char2 = ' ', char3 = ' ';
15: cin >> char1 >> char2 >> char3;
16:
17: cout << "The recorded variable values are: " << endl;
18: cout << "inputNum: " << inputNum << endl;
19: cout << "Pi: " << Pi << endl;
20: cout << "The three characters: " << char1 << char2 << char3 << endl;
21:
22: return 0;
23: }
Output
Enter an integer: 32
Enter the value of Pi: 0.314159265e1
Enter three characters separated by space:
c + +
The recorded variable values are:
inputNum: 32
Pi: 3.14159
The three characters: c++
Analysis
The most interesting part about Listing 27.4 is that you entered the value of Pi
using exponential notation, and cin
filled that data into double Pi
. Note how you are able to fill three-character variables within a single line as shown in Line 15.
std::cin::get
for Input into char*
BufferJust like cin
allows you to write directly into an int
, you can do the same with a C-style char
array, too:
cout << "Enter a line: " << endl;
char charBuf [10] = {0}; // can contain max 10 chars
cin >> charBuf; // Danger: user may enter more than 10 chars
When writing into a C-style string buffer, it is very important that you don’t exceed the bounds of the buffer to avoid a crash or a security vulnerability. So, a better way of reading into a C-style char buffer is this:
cout << "Enter a line: " << endl;
char charBuf[10] = {0};
cin.get(charBuf, 9); // stop inserting at the 9th character
This safer way of inserting text into a C-style buffer is demonstrated by Listing 27.5.
0: #include<iostream>
1: #include<string>
2: using namespace std;
3:
4: int main()
5: {
6: cout << "Enter a line: " << endl;
7: char charBuf[10] = {0};
8: cin.get(charBuf, 9);
9: cout << "charBuf: " << charBuf << endl;
10:
11: return 0;
12: }
Output
Enter a line:
Testing if I can cross the bounds of the buffer
charBuf: Testing i
Analysis
As the output indicates, you have only taken the first nine characters input by the user into the char
buffer due to the use of cin::get
as used in Line 8. This is the safest way to deal with buffers given a length.
Tip
As far as possible, don’t use char
arrays. Use std::string
instead of char*
wherever possible.
std::cin
for Input into a std::string
cin
is a versatile tool, and you can even use it to scan a string from the user directly into a std::string
:
std::string input;
cin >> input; // stops insertion at the first space
Listing 27.6 demonstrates input using cin
into a std::string
.
0: #include<iostream>
1: #include<string>
2: using namespace std;
3:
4: int main()
5: {
6: cout << "Enter your name: ";
7: string name;
8: cin >> name;
9: cout << "Hi " << name << endl;
10:
11: return 0;
12: }
Output
Enter your name: Siddhartha Rao
Hi Siddhartha
Analysis
The output perhaps surprises you as it displays only my first name and not the entire input string. So what happened? Apparently, cin
stops insertion when it encounters the first white space.
To allow the user to enter a complete line, including spaces, you need to use getline()
:
string name;
getline(cin, name);
This usage of getline()
with cin
is demonstrated in Listing 27.7.
0: #include<iostream>
1: #include<string>
2: using namespace std;
3:
4: int main()
5: {
6: cout << "Enter your name: ";
7: string name;
8: getline(cin, name);
9: cout << "Hi " << name << endl;
10:
11: return 0;
12: }
Output
Enter your name: Siddhartha Rao
Hi Siddhartha Rao
Analysis
getline()
as shown in Line 8 did the job of ensuring that white space characters are not skipped. The output now contains the complete line fed by the user.
std::fstream
for File Handlingstd:fstream
is a class that C++ provides for (relatively) platform-independent file access. std::fstream
inherits from std::ofstream
for writing a file and std::ifstream
for reading one.
In other words, std::fstream
provides you with both read and write functionality.
Tip
To use class std::fstream
or its base classes, include header:
#include <fstream>
open()
and close()
To use an fstream
, ofstream
, or ifstream
class, you need to open a file using method open()
:
fstream myFile;
myFile.open("HelloFile.txt",ios_base::in|ios_base::out|ios_base::trunc);
if (myFile.is_open()) // check if open() succeeded
{
// do reading or writing here
myFile.close();
}
open()
takes two arguments: The first is the path and name of the file being opened (if you don’t supply a path, it assumes the current directory settings for the application), whereas the second is the mode in which the file is being opened. The modes chosen allow the file to be created even if one exists (ios_base::trunc
) and allow you to read and write into the file (in | out
).
Note the usage of is_open()
to test whether open()
succeeded.
Caution
Closing the stream using close()
is essential to saving the file.
There is an alternative way of opening a file stream, which is via the constructor:
fstream myFile("HelloFile.txt",ios_base::in|ios_base::out|ios_base::trunc);
Alternatively, if you want to open a file for writing only, use the following:
ofstream myFile("HelloFile.txt", ios_base::out);
If you want to open a file for reading, use this:
ifstream myFile("HelloFile.txt", ios_base::in);
Tip
Irrespective of whether you use the constructor or the member method open()
, it is recommended that you check for the successful opening of the file via is_open()
before continuing to use the corresponding file stream object.
The various modes in which a file stream can be opened are the following:
ios_base::app—Appends to the end of existing files rather than truncating them
ios_base::ate—Places you at the end of the file, but you can write data anywhere in the file
ios_base::trunc—Causes existing files to be truncated; the default
ios_base::binary—Creates a binary file (default is text)
ios_base::in—Opens file for read operations only
ios_base::out—Opens file for write operations only
open()
and operator<<
After you have opened a file stream, you can write to it using operator <<
, as Listing 27.8 demonstrates.
0: #include<fstream>
1: #include<iostream>
2: using namespace std;
3:
4: int main()
5: {
6: ofstream myFile;
7: myFile.open("HelloFile.txt", ios_base::out);
8:
9: if (myFile.is_open())
10: {
11: cout << "File open successful" << endl;
12:
13: myFile << "My first text file!" << endl;
14: myFile << "Hello file!";
15:
16: cout << "Finished writing to file, will close now" << endl;
17: myFile.close();
18: }
19:
20: return 0;
21: }
Output
File open successful
Finished writing to file, will close now
Content of file HelloFile.txt:
My first text file!
Hello file!
Analysis
Line 7 opens the file in mode ios_base::out
—that is, exclusively for writing. In Line 9 you test if open()
succeeded and then proceed to write to the file stream using the insertion operator <<
as shown in Lines 13 and 14. Finally, you close at Line 17 and return.
Listing 27.8 demonstrates how you are able to write into a file stream the same way as you would write to the standard output (console) using cout
.
This indicates how streams in C++ allow for a similar way of handling different devices, writing text to the display via cout
in the same way one would write to a file via ofstream
.
open()
and operator>>
To read a file, one can use fstream
and open it using flag ios_base::in
or use ifstream
. Listing 27.9 demonstrates reading the file HelloFile.txt
created in Listing 27.8.
0: #include<fstream>
1: #include<iostream>
2: #include<string>
3: using namespace std;
4:
5: int main()
6: {
7: ifstream myFile;
8: myFile.open("HelloFile.txt", ios_base::in);
9:
10: if (myFile.is_open())
11: {
12: cout << "File open successful. It contains: " << endl;
13: string fileContents;
14:
15: while (myFile.good())
16: {
17: getline (myFile, fileContents);
18: cout << fileContents << endl;
19: }
20:
21: cout << "Finished reading file, will close now" << endl;
22: myFile.close();
23: }
24: else
25: cout << "open() failed: check if file is in right folder" << endl;
26:
27: return 0;
28: }
File open successful. It contains:
My first text file!
Hello file!
Finished reading file, will close now
Note
As Listing 27.9 reads the text file "HelloFile.txt"
created using Listing 27.8, you either need to move that file to this project’s working directory or merge this code into the previous one.
Analysis
As always, you perform check is_open()
to verify if the call to open()
in Line 8 succeeded. Note the usage of the extraction operator >>
in reading the contents of the file directly into a string
that is then displayed on using cout
in Line 18. We use getline()
in this sample for reading input from a file stream in an exactly identical way as you used it in Listing 27.7 to read input from the user, one complete line at a time.
The actual process of writing to a binary file is not too different from what you have learned thus far. It is important to use ios_base::binary flag
as a mask when opening the file. You typically use ofstream::write
or ifstream::read
as Listing 27.10 demonstrates.
0: #include<fstream>
1: #include<iomanip>
2: #include<string>
3: #include<iostream>
4: using namespace std;
5:
6: struct Human
7: {
8: Human() {};
9: Human(const char* inName, int inAge, const char* inDOB) : age(inAge)
10: {
11: strcpy(name, inName);
12: strcpy(DOB, inDOB);
13: }
14:
15: char name[30];
16: int age;
17: char DOB[20];
18: };
19:
20: int main()
21: {
22: Human Input("Siddhartha Rao", 101, "May 1916");
23:
24: ofstream fsOut ("MyBinary.bin", ios_base::out | ios_base::binary);
25:
26: if (fsOut.is_open())
27: {
28: cout << "Writing one object of Human to a binary file" << endl;
29: fsOut.write(reinterpret_cast<const char*>(&Input), sizeof(Input));
30: fsOut.close();
31: }
32:
33: ifstream fsIn ("MyBinary.bin", ios_base::in | ios_base::binary);
34:
35: if(fsIn.is_open())
36: {
37: Human somePerson;
38: fsIn.read((char*)&somePerson, sizeof(somePerson));
39:
40: cout << "Reading information from binary file: " << endl;
41: cout << "Name = " << somePerson.name << endl;
42: cout << "Age = " << somePerson.age << endl;
43: cout << "Date of Birth = " << somePerson.DOB << endl;
44: }
45:
46: return 0;
47: }
Output
Writing one object of Human to a binary file
Reading information from binary file:
Name = Siddhartha Rao
Age = 101
Date of Birth = May 1916
Analysis
In Lines 22–31, you create an instance of struct Human
that contains a name
, age
, and DOB
and persist it to the disk in a binary file MyBinary.bin
using ofstream
. This information is then read using another stream object of type ifstream
in Lines 33–44. The output of attributes such as name
and so on is via the information that has been read from the binary file. This sample also demonstrates the usage of ifstream
and ofstream
for reading and writing a file using ifstream::read
and ofstream::write
, respectively. Note the usage of reinterpret_cast
in Line 29 to essentially force the compiler to interpret the struct
as char*
. In Line 38, you use the C-style cast version of what is used in Line 29.
Note
If it were not for explanation purposes, I would’ve rather persisted struct Human
with all its attributes in an XML file. XML is a text- and markup-based storage format that allows flexibility and scalability in the manner in which information can be persisted.
If struct Human
were to be delivered in this version and after delivery if you were to add new attributes to it (like numChildren
, for instance), you would need to worry about ifstream::read
functionality being able to correctly read binary data created using the older versions.
std::stringstream
for String ConversionsYou have a string. It contains a string value 45
in it. How do you convert this string value into an integer with value 45
? And vice versa? One of the most useful utilities provided by C++ is class stringstream
that enables you to perform a host of conversion activities.
Tip
To use class std::stringstream
, include header:
#include <sstream>
Listing 27.11 demonstrates some simple stringstream
operations.
0: #include<fstream>
1: #include<sstream>
2: #include<iostream>
3: using namespace std;
4:
5: int main()
6: {
7: cout << "Enter an integer: ";
8: int input = 0;
9: cin >> input;
10:
11: stringstream converterStream;
12: converterStream << input;
13: string inputAsStr;
14: converterStream >> inputAsStr;
15:
16: cout << "Integer Input = " << input << endl;
17: cout << "String gained from integer = " << inputAsStr << endl;
18:
19: stringstream anotherStream;
20: anotherStream << inputAsStr;
21: int Copy = 0;
22: anotherStream >> Copy;
23:
24: cout << "Integer gained from string, Copy = " << Copy << endl;
25:
26: return 0;
27: }
Output
Enter an integer: 45
Integer Input = 45
String gained from integer = 45
Integer gained from string, Copy = 45
Analysis
You ask the user to enter an integer value. You first insert this integer into the stringstream
object, as shown in Line 12, using operator<<
. Then, you use the extraction operator>>
in Line 14 to convert this integer into a string
. After that, you use this string as a starting point and get an integer representation Copy
of the numeric value held in string inputAsStr
.
This lesson taught you C++ streams from a practical perspective. You learned that you have been using streams such as I/O streams cout
and cin
since the very beginning of the book. You now know how to create simple text files and how to read or write from them. You learned how stringstream
can help you convert simple types such as integers into strings, and vice versa.
Q I see that I can use fstream for both writing and reading to a file, so when should I use ofstream and ifstream?
A If your code or module needs to only be reading from a file, you should instead use ifstream
. Similarly, if it needs to only write to a file use ofstream
. In both cases fstream
would work fine, but for the sake of ensuring data and code integrity, it is better to have a restrictive policy similar to using const
, which is not compulsory either.
Q When should I use cin.get(), and when should I use cin.getline()?
A cin.getline()
ensures that you capture the entire line including white spaces entered by the user. cin.get()
helps you capture user input one character at a time.
Q When should I use stringstream?
A stringstream
supplies a convenient way of converting integers and other simple types into a string and vice versa, as also demonstrated by Listing 27.11.
The Workshop contains quiz questions to help solidify your understanding of the material covered and exercises to provide you with experience in using what you’ve learned. Try to answer the quiz and exercise questions before checking the answers in Appendix E, and be certain you understand the answers before going to the next lesson.
1. You need to only write to a file. What stream would you use?
2. How would you use cin
to get a complete line from the input stream?
3. You need to write std::string
objects to a file. Would you choose ios_base::binary
mode?
4. You opened a stream using open()
. Why bother using is_open()
?
1. BUG BUSTER: Find the error in the following code:
fstream myFile;
myFile.open("HelloFile.txt", ios_base::out);
myFile << "Hello file!";
myFile.close();
2. BUG BUSTER: Find the error in the following code:
ifstream myFile("SomeFile.txt");
if(myFile.is_open())
{
myFile << "This is some text" << endl;
myFile.close();
}
18.218.67.91