Lesson 27. Using Streams for Input and Output

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

Image What streams are and how they are used

Image How to write to and read from files using streams

Image Useful C++ stream operations

Concept of Streams

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.

Important C++ Stream Classes and Objects

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.

Image

TABLE 27.1 Popularly Used C++ Stream Classes in the std Namespace


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.

Image

TABLE 27.2 Frequently Used Manipulators in the std Namespace for Working with Streams

Using std::cout for Writing Formatted Data to Console

std::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.

Changing Display Number Formats Using 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.

LISTING 27.1 Displaying an Integer in Decimal, Octal, and Hexadecimal Formats Using cout and <iomanip> Flags


  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 Image

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 Image

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.

LISTING 27.2 Using cout to Display Pi and a Circle’s Area Using Fixed-Point and Scientific Notations


  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: }


Output Image

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 Image

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.

Aligning Text and Setting Field Width Using 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.

LISTING 27.3 Set the Width of a Field via setw() and the Fill Characters Using setfill() Manipulators


  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 Image

Hey - default!
               Hey - right aligned!
***************Hey - right aligned!
Hey - back to default!

Analysis Image

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().

Using std::cin for Input

std::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().

Using std::cin for Input into a Plain Old Data Type

You 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.

LISTING 27.4 Using cin to Read Input into an int, a Floating-Point Number Using Scientific Notation into a double, and Three Letters into a char


  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 Image

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 Image

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.

Using std::cin::get for Input into char* Buffer

Just 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.

LISTING 27.5 Inserting into a char Buffer Without Exceeding Its Bounds


  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 Image

Enter a line:
Testing if I can cross the bounds of the buffer
charBuf: Testing i

Analysis Image

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.


Using 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.

LISTING 27.6 Inserting Text into a std::string Using cin


  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 Image

Enter your name: Siddhartha Rao
Hi Siddhartha

Analysis Image

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.

LISTING 27.7 Reading a Complete Line Input by User Using getline() and cin


  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 Image

Enter your name: Siddhartha Rao
Hi Siddhartha Rao

Analysis Image

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.

Using std::fstream for File Handling

std: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>


Opening and Closing a File Using 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:

Image ios_base::app—Appends to the end of existing files rather than truncating them

Image ios_base::ate—Places you at the end of the file, but you can write data anywhere in the file

Image ios_base::trunc—Causes existing files to be truncated; the default

Image ios_base::binary—Creates a binary file (default is text)

Image ios_base::in—Opens file for read operations only

Image ios_base::out—Opens file for write operations only

Creating and Writing a Text File Using open() and operator<<

After you have opened a file stream, you can write to it using operator <<, as Listing 27.8 demonstrates.

LISTING 27.8 Creating a New Text File and Writing Text into It Using ofstream


  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 Image

File open successful
Finished writing to file, will close now

Content of file HelloFile.txt:

My first text file!
Hello file!

Analysis Image

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.


Note

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.


Reading a Text File Using 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.

LISTING 27.9 Reading Text from 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: }


Output Image

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 Image

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.

Writing to and Reading from a Binary File

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.

LISTING 27.10 Writing a struct to a Binary File and Reconstructing It from the Same


  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 Image

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 Image

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.


Using std::stringstream for String Conversions

You 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.

LISTING 27.11 Converting an Integer Value into a String Representation and Vice Versa Using std::stringstream


  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 Image

Enter an integer: 45
Integer Input = 45
String gained from integer = 45
Integer gained from string, Copy = 45

Analysis Image

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.

Summary

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&A

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.

Workshop

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.

Quiz

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()?

Exercises

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();
}

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.218.67.91