6.1 Streams and Basic File I/O

Good heavens! for more than forty years i have been speaking prose without knowing it.

MOLIèRE, Le Bourgeois Gentilhomme

You are already using files to store your programs. You can also use files to store input for a program or to receive output from a program. The files used for program I/O are the same kind of files you use to store your programs. Streams, which we discuss next, allow you to write programs that handle file input and keyboard input in a unified way and that handle file output and screen output in a unified way.

A stream is a flow of characters (or other kind of data). If the flow is into your program, the stream is called an input stream. If the flow is out of your program, the stream is called an output stream. If the input stream flows from the keyboard, then your program will take input from the keyboard. If the input stream flows from a file, then your program will take its input from that file. Similarly, an output stream can go to the screen or to a file.

Although you may not realize it, you have already been using streams in your programs. The cin that you have already used is an input stream connected to the keyboard, and cout is an output stream connected to the screen. These two streams are automatically available to your program, as long as it has an include directive that names the header file iostream. You can define other streams that come from or go to files; once you have defined them, you can use them in your program in the same way you use the streams cin and cout.

For example, suppose your program defines a stream called inStream that comes from some file. (We’ll tell you how to define it shortly.) You can then fill an int variable named theNumber with a number from this file by using the following in your program:

int theNumber;
inStream >> theNumber;

Similarly, if your program defines an output stream named outStream that goes to another file, then you can output the value of this variable to this other file. The following will output the string "theNumber is" followed by the contents of the variable theNumber to the output file that is connected to the stream outStream:

outStream << "theNumber is" << theNumber << endl;

Once the streams are connected to the desired files, your program can do file I/O the same way it does I/O using the keyboard and screen.

Why Use Files for I/O?

The keyboard input and screen output we have used so far deal with temporary data. When the program ends, the data typed in at the keyboard and the data left on the screen go away. Files provide you with a way to store data permanently. The contents of a file remain until a person or program changes the file. If your program sends its output to a file, the output file will remain after the program has finished running. An input file can be used over and over again by many programs without the need to type in the data separately for each program.

The input and output files used by your program are the same kind of files that you read and write with an editor, such as the editor you use to write your programs. This means you can create an input file for your program or read an output file produced by your program whenever it’s convenient for you, as opposed to having to do all your reading and writing while the program is running.

Files also provide you with a convenient way to deal with large quantities of data. When your program takes its input from a large input file, the program receives a lot of data without making the user do a lot of typing.

File I/O

When your program takes input from a file, it is said to be reading from the file; when your program sends output to a file, it is said to be writing to the file. There are other ways of reading input from a file, but the method we will use reads the file from the beginning to the end (or as far as the program gets before ending). Using this method, your program is not allowed to back up and read anything in the file a second time. This is exactly what happens when the program takes input from the keyboard, so this should not seem new or strange. (As we will see, the program can reread a file starting from the beginning of the file, but this is “starting over,” not “backing up.”) Similarly, for the method we present here, your program writes output into a file starting at the beginning of the file and proceeding forward. It is not allowed to back up and change any output that it has previously written to the file. This is exactly what happens when your program sends output to the screen. You can send more output to the screen, but you cannot back up and change the screen output. The way that you get input from a file into your program or send output from your program into a file is to connect the program to the file by means of a stream.

In C++, a stream is a special kind of variable known as an object. We will discuss objects in the next section, but we will first describe how your program can use stream objects to do simple file I/O. If you want to use a stream to get input from a file (or give output to a file), you must declare the stream and you must connect the stream to the file.

You can think of the file that a stream is connected to as the value of the stream. You can disconnect a stream from one file and connect it to another file, so you can change the value of these stream variables. However, you must use special functions that apply only to streams in order to perform these changes. You cannot use a stream variable in an assignment statement the way that you can use a variable of type int or char. Although streams are variables, they are unusual sorts of variables.

The streams cin and cout are already declared for you, but if you want a stream to connect to a file, you must declare it just as you would declare any other variable. The type for input-file stream variables is named ifstream (for “input-file stream”). The type for output-file stream variables is named ofstream (for “output-file stream”). Thus, you can declare inStream to be an input stream for a file and outStream to be an output stream for another file as follows:

ifstream inStream;
ofstream outStream;

The types ifstream and ofstream are defined in the library with the header file fstream, and so any program that declares stream variables in this way must contain the following directive (normally near the beginning of the file):

#include <fstream>

When using the types ifstream and ofstream, your program must also contain the following, normally either at the start of the file or at the start of the function body that uses the types ifstream or ofstream:

using namespace std;

Stream variables, such as inStream and outStream declared earlier, must each be connected to a file. This is called opening the file and is done with a function named open. For example, suppose you want the input stream inStream connected to the file named infile.dat. Your program must then contain the following before it reads any input from this file:

inStream.open("infile.dat");

This may seem like rather strange syntax for a function call. We will have more to say about this peculiar syntax in the next section. For now, just notice a couple of details about how this call to open is written. First, the stream variable name and a dot (that is, a period) is placed before the function named open, and the file name is given as an argument to open. Also notice that the file name is given in quotes. The file name that is given as an argument is the same as the name you would use for the file if you wanted to write in it using the editor. If the input file is in the same directory as your program, you probably can simply give the name of the file in the manner just described. In some situations you might also need to specify the directory that contains the file. The details about specifying directories varies from one system to another. If you need to specify a directory, ask your instructor or some other local expert to explain the details.

You can also combine file opening with the declaration of the stream variable as follows:

ifstream inStream("infile.dat");

Once you have declared an input stream variable and connected it to a file using the open function, your program can take input from the file using the extraction operator >>. For example, the following reads two input numbers from the file connected to inStream and places them in the variables oneNumber and anotherNumber:

int oneNumber, anotherNumber;
inStream >> oneNumber >> anotherNumber;

An output stream is opened (that is, connected to a file) in the same way as just described for input streams. For example, the following declares the output stream outStream and connects it to the file named outfile.dat:

ofstream outStream;
outStream.open("outfile.dat");

When used with a stream of type ofstream, the member function open will create the output file if it does not already exist. If the output file does already exist, the member function open will discard the contents of the file so that the output file is empty after the call to open.

After a file is connected to the stream outStream with a call to open, the program can send output to that file using the insertion operator <<. For example, the following writes two strings and the contents of the variables oneNumber and anotherNumber to the file that is connected to the stream outStream (which in this example is the file named outfile.dat):

outStream << "oneNumber = " << oneNumber
<< " anotherNumber = " << anotherNumber;

Notice that when your program is dealing with a file, it is as if the file had two names. One is the usual name for the file that is used by the operating system. This name is called the external file name. In our sample code the external file names were infile.dat and outfile.dat. The external file name is in some sense the “real name” for the file. It is the name used by the operating system. The conventions for spelling these external file names vary from one system to another; you will need to learn these conventions from your instructor or from some other local expert. The names infile.dat and outfile.dat that we used in our examples might or might not look like file names on your system. You should name your files following whatever conventions your system uses. Although the external file name is the real name for the file, it is typically used only once in a program. The external file name is given as an argument to the function open, but after the file is opened, the file is always referred to by naming the stream that is connected to the file. Thus, within your program, the stream name serves as a second name for the file.

The sample program in Display 6.1 reads three numbers from one file and writes their sum, as well as some text, to another file.

Every file should be closed when your program is finished getting input from the file or sending output to the file. Closing a file disconnects the stream from the file. A file is closed with a call to the function close. The following lines from the program in Display 6.1 illustrate how to use the function close:

inStream.close( );
outStream.close( );

Notice that the function close takes no arguments. If your program ends normally but without closing a file, the system will automatically close the file for you. However, it is good to get in the habit of closing files for at least two reasons. First, the system will only close files for you if your program ends in a normal fashion. If your program ends abnormally due to an error, the file will not be closed and may be left in a corrupted state. If your program closes files as soon as it is finished with them, file corruption is less likely. A second reason for closing a file is that you may want your program to send output to a file and later read that output back into the program. To do this, your program should close the file after it is finished writing to the file, and then your program should connect the file to an input stream using the function open. (It is possible to open a file for both input and output, but this is done in a slightly different way and we will not be discussing this alternative.)

Display 6.1 Simple File Input/Output

 1    //Reads three numbers from the file infile.dat, sums the numbers,
 2    //and writes the sum to the file outfile.dat.
 3    //(A better version of this program will be given in  Display 6.2.)
 4    #include <fstream>
 5    int main( )
 6    {
 7        using namespace std;
 8        ifstream inStream;
 9        ofstream outStream;
10     
11        inStream.open("infile.dat");
12        outStream.open("outfile.dat");
13        int first, second, third;
14        inStream >> first >> second >> third;
15        outStream << "The sum of the first 3\n"
16                  << "numbers in infile.dat\n"
17                  << "is " << (first + second + third)
18                  << endl;
19        inStream.close( );
20        outStream.close( );
21        return 0;
22    }
infile.dat outfile.dat
(Not changed by program.) (After program is run.)
1
2
3
4
The sum of the first 3
numbers in infile.dat
is 6

There is no output to the screen and no input from the keyboard.

Introduction to Classes and Objects

The streams inStream and outStream discussed in the last section and the predefined streams cin and cout are objects. An object is a variable that has functions as well as data associated with it. For example, the streams inStream and outStream both have a function named open associated with them. Two sample calls of these functions, along with the declarations of the objects inStream and outStream, are given below:

ifstream inStream;
ofstream outStream;
inStream.open("infile.dat");
outStream.open("outfile.dat");

There is a reason for this peculiar notation. The function named open that is associated with the object inStream is a different function from the function named open that is associated with the object outStream. One function opens a file for input, and the other opens a file for output. Of course, these two functions are similar. They both “open files.” When we give two functions the same name, it is because the two functions have some intuitive similarity. However, these two functions named open are different functions, even if they may be only slightly different. When the compiler sees a call to a function named open, it must decide which of these two functions named open you mean. The compiler determines this by looking at the name of the object that precedes the dot, in this case, either inStream or outStream. A function that is associated with an object is called a member function. So, for example, open is a member function of the object inStream, and another function named open is a member of the object outStream.

As we have just seen, different objects can have different member functions. These functions may have the same names, as was true of the functions named open, or they may have completely different names. The type of an object determines which member functions the object has. If two objects are of the same type, they may have different values, but they will have the same member functions. For example, suppose you declare the following stream objects:

ifstream inStream, inStream2;
ofstream outStream, outStream2;

The functions inStream.open and inStream2.open are the same function. Similarly, outStream.open and outStream2.open are the same function (but they are different from the functions inStream.open and inStream2.open).

A type whose variables are objects—such as ifstream and ofstream—is called a class. Since the member functions for an object are completely determined by its class (that is, by its type), these functions are called member functions of the class (as well as being called members of the object). For example, the class ifstream has a member function called open, and the class ofstream has a different member function called open. The class ofstream also has a member function named precision, but the class ifstream has no member function named precision. You have already been using the member function precision with the stream cout, but we will discuss it in more detail later.

When you call a member function in a program, you always specify an object, usually by writing the object name and a dot before the function name, as in the following example:

inStream.open("infile.dat");

One reason for naming the object is that the function can have some effect on the object. In the preceding example, the call to the function open connects the file infile.dat to the stream inStream, so it needs to know the name of this stream.

In a function call, such as

inStream.open("infile.dat");

the dot is called the dot operator and the object named before the dot is referred to as the calling object. In some ways the calling object is like an additional argument to the function—the function can change the calling object as if it were an argument—but the calling object plays an even larger role in the function call. The calling object determines the meaning of the function name. The compiler uses the type of the calling object to determine the meaning of the function name. For example, in the earlier call to open, the type of the object inStream determines the meaning of the function name open.

The function name close is analogous to open. The classes ifstream and ofstream each have a member function named close. They both “close files,” but they close them in different ways because the files were opened and were manipulated in different ways. We will be discussing more member functions for the classes ifstream and ofstream later in this chapter.

Programming Tip Check Whether a File Was Opened Successfully

A call to open can be unsuccessful for a number of reasons. For example, if you open an input file and there is no file with the external name that you specify, then the call to open will fail. When this happens, you might not receive an error message and your program might simply proceed to do something unexpected. Thus, you should always follow a call to open with a test to see whether the call to open was successful and end the program (or take some other appropriate action) if the call to open was unsuccessful.

You can use the member function named fail to test whether a stream operation has failed. There is a fail member function for each of the classes ifstream and ofstream. The fail function takes no arguments and returns a bool value. A call to the function fail for a stream named inStream would be as follows:

inStream.fail( )

This is a Boolean expression that can be used to control a while loop or an if-else statement.

You should place a call to fail immediately after each call to open; if the call to open fails, the function fail will return true (that is, the Boolean expression will be satisfied). For example, if the following call to open fails, then the program will output an error message and end; if the call succeeds, the fail function returns false, so the program will continue.

An illustration shows a code segment:

fail is a member function, so it is called using the stream name and a dot. Of course, the call to inStream.fail refers only to a call to open of the form inStream.open, and not to any call to the function open made with any other stream as the calling object.

The exit statement shown earlier has nothing to do with classes and has nothing directly to do with streams, but it is often used in this context. The exit statement causes your program to end immediately. The exit function returns its argument to the operating system. To use the exit statement, your program must contain the following include directive:

#include <cstdlib>

When using exit, your program must also contain the following, normally either at the start of the file or at the start of the function body that uses exit:

using namespace std;

The function exit is a predefined function that takes a single integer argument. By convention, 1 is used as the argument if the call to exit was due to an error, and 0 is used otherwise.1 For our purposes, it makes no difference what integer you use, but it pays to follow this convention since it is important in more advanced programming.

Display 6.2 contains the program from Display 6.1 rewritten to include tests to see if the input and output files were opened successfully. It processes files in exactly the same way as the program in Display 6.1. In particular, assuming that the file infile.dat exists and has the contents shown in Display 6.1, the program in Display 6.2 will create the file outfile.dat that is shown in Display 6.1. However, if there were something wrong and one of the calls to open failed, then the program in Display 6.2 would end and send an appropriate error message to the screen. For example, if there were no file named infile.dat, then the call to inStream.open would fail, the program would end, and an error message would be written to the screen.

Display 6.2 File I/O with Checks on open

 1    //Reads three numbers from the file infile.dat, sums the numbers,
 2    //and writes the sum to the file outfile.dat.
 3    #include <fstream>
 4    #include <iostream>
 5    #include <cstdlib>
 6    int main( )
 7    {
 8        using namespace std;
 9        ifstream inStream;
10        ofstream outStream;
11        inStream.open("infile.dat");
12        if (inStream.fail( ))
13        {
14            cout << "Input file opening failed.\n";
15            exit(1);
16        }
17        outStream.open("outfile.dat");
18        if (outStream.fail( ))
19        {
20            cout << "Output file opening failed.\n";
21            exit(1);
22        }
23        int first, second, third;
24        inStream >> first >> second >> third;
25        outStream << "The sum of the first 3\n"
26                  << "numbers in infile.dat\n"
27                  << "is " << (first + second + third)
28                  << endl;
29        inStream.close( );
30        outStream.close( );
31        return 0;
32    }

Screen Output (If the file infile.dat does not exist)

Input file opening failed.

Notice that we used cout to output the error message; this is because we want the error message to go to the screen, as opposed to going to a file. Since this program uses cout to output to the screen (as well as doing file I/O), we have added an include directive for the header file iostream. (Actually, your program does not need to have #include <iostream> when the program has #include <fstream>, but it causes no problems to include it, and it reminds you that the program is using screen output in addition to file I/O.)

Techniques for File I/O

As we already noted, the operators >> and << work the same for streams connected to files as they do for cin and cout. However, the programming style for file I/O is different from that for I/O using the screen and keyboard. When reading input from the keyboard, you should prompt for input and echo the input, like this:

cout << "Enter the number: ";
cin >> theNumber;
cout << "The number you entered is " << theNumber;

When your program takes its input from a file, you should not include such prompt lines or echoing of input, because there is nobody there to read and respond to the prompt and echo. When reading input from a file, you must be certain the data in the file is exactly the kind of data the program expects. Your program then simply reads the input file assuming that the data it needs will be there when it is requested. If inFile is a stream variable that is connected to an input file and you wish to replace the previous keyboard/screen I/O shown with input from the file connected to inFile, then you would replace those three lines with the following line:

inFile >> theNumber;

You may have any number of streams opened for input or for output. Thus, a single program can take input from the keyboard and also take input from one or more files. The same program could send output to the screen and to one or more files. Alternatively, a program could take all of its input from the keyboard and send output to both the screen and a file. Any combination of input and output streams is allowed. Most of the examples in this book will use cin and cout to do I/O using the keyboard and screen, but it is easy to modify these programs so that the program takes its input from a file and/or sends its output to a file.

Self-Test Exercises

  1. Suppose you are writing a program that uses a stream called fin that will be connected to an input file, and a stream called fout that will be connected to an output file. How do you declare fin and fout? What include directive, if any, do you need to place in your program file?

  2. Suppose you are continuing to write the program discussed in the previous exercise and you want it to take its input from the file stuff1.dat and send its output to the file stuff2.dat. What statements do you need to place in your program in order to connect the stream fin to the file stuff1.dat and to connect the stream fout to the file stuff2.dat? Be sure to include checks to make sure that the openings were successful.

  3. Suppose that you are still writing the same program that we discussed in the previous two exercises and you reach the point at which you no longer need to get input from the file stuff1.dat and no longer need to send output to the file stuff2.dat. How do you close these files?

  4. Suppose you want to change the program in Display 6.1 so that it sends its output to the screen instead of the file outfile.dat. (The input should still come from the file infile.dat.) What changes do you need to make to the program?

  5. What include directive do you need to place in your program file if your program uses the function exit?

  6. Continuing Self-Test Exercise 5, what does exit(1) do with its argument?

  7. Suppose bla is an object, dobedo is a member function of the object bla, and dobedo takes one argument of type int. How do you write a call to the member function dobedo of the object bla using the argument 7?

  8. What characteristics of files do ordinary program variables share? What characteristics of files are different from ordinary variables in a program?

  9. Name at least three member functions associated with an iostream object, and give examples of usage of each.

  10. A program has read half of the lines in a file. What must the program do to the file to enable reading the first line a second time?

  11. In the text it says “a file has two names.” What are the two names? When is each name used?

Appending to a File (Optional)

When sending output to a file, your code must first use the member function open to open a file and connect it to a stream of type ofstream. The way we have done that thus far (with a single argument for the file name) always gives an empty file. If a file with the specified name already exists, its old contents are lost. There is an alternative way to open a file so that the output from your program will be appended to the file after any data already in the file.

To append your output to a file named important.txt, you would use a two-argument version of open, as illustrated by the following:

ofstream outStream;
outStream.open("important.txt", ios::app);

If the file important.txt does not exist, this will create an empty file with that name to receive your program’s output, but if the file already exists, then all the output from your program will be appended to the end of the file so that old data in the file is not lost. This is illustrated in Display 6.3.

Display 6.3 Appending to a File (Optional)

 1    //Appends data to the end of the file data.txt.
 2    #include <fstream>
 3    #include <iostream>
 4    
 5    int main( )
 6    {
 7         using namespace std;
 8     
 9         cout << "Opening data.txt for appending.\n";
10         ofstream fout;
11         fout.open("data.txt", ios::app);
12         if (fout.fail( ))
13         {
14             cout << "Input file opening failed.\n";
15             exit(1);
16         }
17     
18         fout << "5 6 pick up sticks.\n"
19              << "7 8 ain't C++ great!\n";
20     
21         fout.close( );
22         cout << "End of appending to file.\n";
23     
24          return 0;
25    }

Sample Dialogue

data.txt data.txt
(Before program is run.) (After program is run.)
1 2 buckle my shoe.
3 4 shut the door.
1 2 buckle my shoe.
3 4 shut the door.
5 6 pick up sticks.
7 8 ain't C++ great!

Screen Output

Opening data.txt for appending.
End of appending to file.

The second argument ios::app is a special constant that is defined in iostream and so requires the following include directive:

#include <iostream>

Your program should also include the following, normally either at the start of the file or at the start of the function body that uses ios::app:

using namespace std;

File Names as Input (Optional)

Thus far, we have written the literal file names for our input and output files into the code of our programs. We did this by giving the file name as the argument to a call to the function open, as in the following example:

inStream.open("infile.dat");

This can sometimes be inconvenient. For example, the program in Display 6.2 reads numbers from the file infile.dat and outputs their sum to the file outfile.dat. If you want to perform the same calculation on the numbers in another file named infile2.dat and write the sum of these numbers to another file named outfile2.dat, then you must change the file names in the two calls to the member function open and then recompile your program. A preferable alternative is to write your program so that it asks the user to type in the names of the input and output files. This way your program can use different files each time it is run.

A file name is a string and we will not discuss string handling in detail until Chapter 8. However, it is easy to learn enough about strings so that you can write programs that accept a file name as input. A string is just a sequence of characters. We have already used string values in output statements such as the following:

cout << "This is a string.";

We have also used string values as arguments to the member function open. Whenever you write a literal string, as in the cout statement shown, you must place the string in double quotes.

In order to read a file name into your program, you need a variable that is capable of holding a string. We discuss the details of strings in Chapter 8, but for now we will cover just enough to store a file name. A variable to hold a string value is declared as in the following example:

char fileName[16];

This declaration is the same as if you had declared the variable to be of type char, except that the variable name is followed by an integer in square brackets that specifies the maximum number of characters you can have in a string stored in the variable. This number must be one greater than the maximum number of characters in the string value. So, in our example, the variable fileName can contain any string that contains 15 or fewer characters. The name fileName can be replaced by any other identifier (that is not a keyword), and the number 16 can be replaced by any other positive integer.

You can input a string value to a string variable the same way that you input values of other types. For example, consider the following piece of code:

cout << "Enter the file name (maximum of 15 characters):\n";
cin >> fileName;
cout << "OK, I will edit the file " << fileName << endl;

A possible dialogue for this code is

Enter the file name (maximum of 15 characters): 
myfile.dat
OK, I will edit the file myfile.dat

Once your program has read the name of a file into a string variable, such as the variable fileName, it can use this string variable as the argument to the member function open. For example, the following will connect the input-file stream inStream to the file whose name is stored in the variable fileName (and will use the member function fail to check whether the opening was successful):

ifstream inStream;
inStream.open(fileName);
if (inStream.fail( ))
{
    cout << "Input file opening failed.\n";
    exit(1);
}

Note that when you use a string variable as an argument to the member function open, you do not use any quotes.

In Display 6.4 we have rewritten the program in Display 6.2 so that it takes its input from and sends its output to whatever files the user specifies. The input and output file names are read into the string variables inFileName and outFileName and then these variables are used as the arguments in calls to the member function open. Notice the declaration of the string variables. You must include a number in square brackets after each string variable name, as we did in Display 6.4.

Display 6.4 Inputting a File Name (Optional)

 1    //Reads three numbers from the file specified by the user, sums the numbers,
 2    //and writes the sum to another file specified by the user.
 3    #include <fstream>
 4    #include <iostream>
 5    #include <cstdlib>
 6   
 7    int main( )
 8    {
 9        using namespace std;
10        charinFileName[16], outFileName[16];
11        ifstream inStream;
12        ofstream outStream;
13     
14        cout << "I will sum three numbers taken from an input\n"
15             << "file and write the sum to an output file.\n";
16        cout << "Enter the input file name (maximum of 15 characters):\n";
17        cin >> inFileName;
18        cout << "Enter the output file name (maximum of 15 characters):\n";
19        cin >> outFileName;
20        cout << "I will read numbers from the file "
21             << inFileName << " and\n"
22             << "place the sum in the file "
23             << outFileName << endl;
24      
25        inStream.open(inFileName);
26        if (inStream.fail( ))
27        {
28            cout << "Input file opening failed.\n";
29            exit(1);
30        }
31      
32        outStream.open(outFileName);
33        if (outStream.fail( ))
34        {
35            cout << "Output file opening failed.\n";
36            exit(1);
37        }
38        int first, second, third;
39        inStream >> first >> second >> third;
40        outStream << "The sum of the first 3\n"
41                   << "numbers in " << inFileName << endl
42                   << "is " << (first + second + third)
43                   << endl;
44    
45        inStream.close( );
46       outStream.close( ); 
47
48       cout << "End of Program.\n";
49       return 0;
50    }
numbers.dat sum.dat
(Not changed by program.) (After program is run.)
1
2
3
4
The sum of the first 3
numbers in numbers.dat
is 6

Sample Dialogue

I will sum three numbers taken from an input
file and write the sum to an output file.
Enter the input file name (maximum of 15 characters):
numbers.dat
Enter the output file name (maximum of 15 characters):
sum.dat
I will read numbers from the file numbers.dat and
place the sum in the file sum.dat
End of Program.

String variables are not ordinary variables and cannot be used in all the ways you can use ordinary variables. In particular, you cannot use an assignment statement to change the value of a string variable.