Contents

Part 2

Chapter 6

Input and Output Streams

We have finished looking at how we can organize and structure our programs. Now we are moving into the second part of the material and how we can structure the information in our program and within our program. We will first start looking at how C++ "sees" data and how we can use other input and output sources than cin and cout.

C++ views data as a stream of information flowing in and through your program. There can be input streams, i.e. data that your program uses to do it's job, and output streams, i.e. data that your program creates to communicate with the outside world. The nice thing about the streams is that once you know how an input or output stream works, they all use the same functions. So learning how cin and cout work, help you in learning how to use any input or output stream. The difference comes in how the streams are created and filled with data.

Filed based input and output is a staple for many programming applications. One of the easiest ways to get data from one program into another one is to use a file. A file can be used to check intermediate results and verify that data has been correctly "understood." A nice feature of C++ is that once you know how to use any input or output stream there is very little extra to learn to be able to use files for input and output.

File Stream

An extremely common way to get information into a program is via a file. The nice thing about a file is you can examine it and make sure is is correct. It also allows you to rerun a program using the same input or modify the input and run the program to get different results. To use an input file you need to #include . Like the iostream library which allows the use of cin and cout, fstream allows us to use input and output files. The f stands for file and the stream is how C++ thinks about data. Once we have the fstream library we can create input files and open them. There are two ways to open a file for reading. First, we can create the input file stream and open the file at the same time, or we can create the input file stream then later open the file. The nice this about this is it allows us a flexible way to create and open our files. Once the file is open, we can treat it like it is cin and use the same function we did before.

#include <fstream>  //this gives us access to ifstream and ofstream
#include <iostream> //this gives us access to endl, cin and cout
#include <string>   //this gives us access to string

using std::string;
using std::cout;
using std::ifstream; //this is our input file stream object
using std::endl;

int main()
{
    ifstream in;             // this creates our input file stream object
    in.open( "myfile.txt" ); //this will open the file myfile.txt for reading
    string word;
    in >> word;              //read a word from the file
    while( !in.fail() )
    {
        cout << word << endl;
        in >> word;          //read the next word from the file
    }

    in.close();              //close our file
    return 0;
}

Program 6.1

In Program 6.1, we see a very basic example of creating and opening a text file. Then we simply read each word from the file and print each word on a separate line. After we have finished reading the file, we close the file. It's always a good idea to close our files when we are done with them. This will free up system resources which can be a big deal on a system with limited resources.

Extraction

We have been using extraction (>>) exclusively to read our data, but we might need other ways to examine our streams or read data from our streams. C++ provides many different functions for doing this. There are a series of functions for examining an input stream. If you would like to read a single character from an input stream, you may use the get function. There are two different ways it can be called. If you would like to just look at the next character, but not actually read it, you can use the peek function.

Opening a file for input

When you want to use a file for input, you first must create the input file stream that will be used to read the file and then you need to open the file. There are two basic ways for doing this and these two steps can be combined into one step. Often the name of the input file isn't known until later or is specified at the time the program is executed and therefore we need to separate this process in to its two parts.

When we want to declare an input, or output, file stream, we need to #include a new library. We #include This is similar to the with the f on this library referring to file. You can see from the names of these two libraries that C++ refers to data, input and output, as streams. The data is flowing through the program.

Once we have the correct library included, we can create the input file stream, or ifstream, like this:

ifstream in;

This will create an input file stream called in. Notice that the variable's type is ifstream where the i stands for input and the f stands for file. So that is an input file stream or ifstream. You can probably guess how we make an output file stream.

Now that we have the ifstream, we need to open the file. We do this using the open command on the stream:

in.open( "myFile.txt" );

Assuming we have a file called "myFile.txt" in the same directory as our executable program the effect of the previous line would allow us to start reading from the file.

When we are done with the file, we close the stream by using the close command:

in.close();

In this case, we don't need to give the name of the file, we just need the name of the stream variable.

Opening a file for output

Building from our input file stream information, we use the same header when we want to open a file for writing so assuming we have the correct header #included we are ready. Opening an output file follows a very similar pattern:

ofstream out;
out.open( "output.txt");

First we create the ofstream, or output file stream, then we open the file using the open command.

Now we can close the file just like the input file: out.close();

Similarities and differences of file streams and other streams

The nice thing about streams in C++ is that once you learn about input streams, they all work in the same way. We can use extraction on an istream, e.g. cin, just like we can on an ifstream. The functions simply require an input stream to work. How the stream is created doesn't matter to the functions.

The same applies for ofstream and ostream. You can use insertion on an ostream, e.g. cout, or on an ofstream. All of the output manipulation we will discuss in the next section works on any of the output streams. We don't have to relearn a lot of complex interactions.

There are some input things to note about input and output file streams. If you are creating an ifstream and the file you are trying to open either isn't found or doesn't exist, then the ifstream is in the fail state. Any input operations on the stream will fail. So when you open the file either make sure the file you are opening is in the same directory as the executable or give the entire path to the file when you give the name of the file. If the output file doesn't exist, then the file will be created for you. If the file does exist, then by default the contents will be erased. If you need to reopen a file and you don't want the contents to be erased there is a way to override the default behavior.

When you are creating an input file stream, it's best to not hard code the name of the file in to the code. If you do hard code the name, then if you need to open a different file then you need to change the code and recompile before you can use the new file. There are a few different ways to handle this from asking for the filename to specifying the name of the file when you execute the program. Depending on your situation one of the two solutions might be more desirable. In either case, you'll need a string variable to represent the name of the file. You can then open the file this way:

string file;
//get the name of the file using 1 of the two suggestions
ifstream in;
in.open( file );

If you are using an older compiler you will need to convert the string to a character array before you can open it. We'll be discussing arrays in our next chapter. We can do that this way:

in.open( file.c_str() );

Output file formats (CSV, tabbed, other format)

When you are outputting data, there are many different ways you can do this. Depending on the purpose of the output file you may choose various different formats for each purpose. For example, if the output file is then to be used as input into another program a comma separate or tab separated file might be a good choice. If the data is to be shared on the web, maybe JSON or XML is the way to go. If the data is intended to be viewed by a person, then maybe an easier to read format might be the way to go. All of the formats would contain the same information but it would be represented in a different way each way suited to its intended purpose.

For the next set of examples, let's assume we are working with the following data:

Name: Dave 
School: VT
Course: ECE 1574
Grade: B+
Average: 88.65

CSV

A comma separated variable file is simply a file that has a comma between each of the fields. So we could represent the above data like this:

Dave,VT,ECE 1574,B+,88.65

You can see that this is a nice compact representation. The data itself can contain spaces. The one thing the data can't contain would be a comma. Some programs that use this format will wrap the string data in quotes, either single or double, to allow for the use of commas within string data. An alternative to CSV is tabbed delimited which would replace the commas with tabs. You can easily follow this logic and replace the commas or tabs with any number of delimiters, |, /, \, -, etc. Each of the delimited strategies would preclude the use of the delimiter in the data itself. Two delimiters next to each other would indicate that piece of data is missing.

Formatting output

A common activity when you are dealing with output is how to format the output so that it looks nice. There are many various ways to do this and depending on the use of the output you can spend almost no time or a great amount of time in the output format. The most basic way is to control how many characters are used in the output. To use this we need to introduce a new header. The header iomanip is the input output manipulation header. It contains the functions we need.

To control the width of the next piece of output, we use setw. setw will only control how many characters the next piece of output will receive and so we need to use it every time we want to control the width of the output. For example, say we are going to out the word cat and we would like two blank spaces to the left of the word. We could do this:

out << "  cat";

or we could use setw and do this:

out << setw(5) << "cat";

The advantage to this second approach is that we can use a variable in the place of the 5. So if we had a variable instead of the literal string "cat" we could use the length of the string to allow us to always have 2 blanks to the left of the word. By default the output is left justified, you can, of course, change the justification to right justified; there is no centered justification. I typically leave the justification as left and it eases how I can format my output.

setw will work on any type of output, double, long, short, char, string, etc. If you use a value that is too small then C++ will make the output width larger than request. For example:

out << setw(4)  << "David";

would produce "David" not "Davi" which would be another option.

Using setw and doubles can produce some interesting results. For example:

double y = 10.123456789
cout << setw(4) << y;

produces the following ouptut on my computer:

10.1235

This appears to violate the setw directive. What's it's done is use the setw as the number of digits to include after the decimal point. Another way to control the number of decimal places is to use setprecision. setprecision will control this in a various different ways. If you use setprecision by itself, then it will act like setw and include the whole number in it's counting of output characters. For example:

double y = 10.123456789
cout << setprecision(4) << y;

produces the following output on my computer:

10.12

This can get tricky, especially if the whole number could vary in number of digits. To help control that, I almost always include the manipulators fixed and showpoint when I do my output of doubles. This example

double y = 10.123456789
cout << fixed << showpoint << setprecision(4) << y;

produces the following output on my computer:

10.1235

Notice that the decimal places are rounded. This is last example we will always have 4 decimal places after the decimal point, regardless of how many decimal numbers are present. If there are fewer than 4 0's will be added to the end and if there are no decimals at all, e.g. the whole number 10, then we will have the decimal point and 4 0's.

Summary

This just begins to scratch the surface of file based input and output and control the format of the output. The nice thing is that we can begin with simple application of these ideas and build up to a more sophisticated use of these techniques as we learn more about programming. When I was learning C++, I wrote a lot of output that was not very pretty to look at and/or very difficult to modify if the output requirements changed. As I grew as a developer I was able to mature the techniques I used to allow for better use of these ideas and more robust output routines. Don't get discouraged if your output is not beautiful at first. If it is correct, it can always be made prettier. Pretty output that is wrong is useless, so focus on correctness and then work on the beautification of the output.


Written by: David McPherson © 2019

Creative Commons License
This work is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License.

hit counter of smallseotools