18. File Input and Output
Input and output for our programs has been from the keyboard and to the screen, respectively. Sometimes, however, we need a program to read data from a file (file input) and/or write data to another file (file output). The mechanics of reading from and writing to files is discussed in this section. The example at the end of this section provides a complete program using file I/O. We’ll take a look at the activities needed for file input and output by examining portions of the program before taking a look at the complete program.
Both file input and output are discussed throughout this section as an understanding of both is necessary. A particular program, however, may only need to read from, or write to, a file without doing both.
Files
The files our programs will use are called text files. Text files contain data and no formatting information. Input files can be created using a text editor such as Windows Notepad. Output files will be created by our programs and Notepad used to check their contents. There is nothing about a file which makes it an input or an output file. How a program uses a file determines its type. If a program reads from a file, for example, the file is an input file.
The contents of the input file used by the example program at the end of this section are as follows:
3.4 7.1
0.5 -2.8
12.6 0.6
Each row of data in a file is called a record. The file, named data.txt, has three records with each record containing two floating-point values. The program recognizes there are two values in each record due to the space placed between the floating-point values. The file can be created by opening Notepad, typing the data in each record, and saving the file with the name expected by the program. Be sure to press Enter after typing the last record’s data before saving the file.
Here are the contents of the output file, output.txt, created by the program:
First Second Sum Product
3.4 7.1 10.5 24.1
0.5 -2.8 -2.3 -1.4
12.6 0.6 13.2 7.6
The data written to the file, its formatting, and the name of the file itself, are a result of the program’s code.
Functionally, the program reads the data from a record in data.txt, calculates the sum and product of the values read, and writes the values read and the results of the calculations to output.txt. Formatting includes the column headings, right alignment of the columns of data, and displaying all values to one decimal place.
Every input file and output file must be given a name like any file saved to a disk. The name chosen should be meaningful and not include spaces or special characters. Although a particular operating system may be able to handle spaces or particular special characters, using either can cause problems, so it is best to avoid them. Common file extensions for input and output files are .txt and .dat. It is not considered good practice to use file extensions which already have a particular meaning (e.g., .cpp or .exe).
Streams
A running program needs a way to retrieve data from its source and a way to send data to a specific destination. For example, if a user types a number, the program needs a means of retrieving the data typed. In C++, the connection between a running program and the source or destination of data is called a stream.
Two default streams are available for input and output. The standard input stream is used to retrieve data entered at the keyboard. We access this stream in our programs by using std::cin, the standard input stream object. Similarly, the standard output stream is used to display data to the screen and is accessed by our programs when we use std::cout, the standard output stream object.
If we want a program to read data from a source other than the keyboard, say a file, or write data to a file, we need to establish a new stream for each file. The established streams will connect the running program with the files to enable the program to read from or write to a file, as needed. Part of establishing a stream is the creation of a stream object (see below).
Common Activities
A number of activities must occur for both file input and file output to work. These are discussed below. The actions specific to input and output are covered afterwards.
Create a Stream Object
As mentioned above, a stream must be established between a file and a running program. A stream object is used by a program to access a stream. We are given std::cin and std::cout to access the default streams. A stream connected to a file requires that we create a stream object the program can use to access the stream and, therefore, the file.
Creating a stream object is very similar to declaring a variable:
std::ifstream input;
std::ofstream output;
The data type std::ifstream is used to create a stream object for input. Similarly, std::ofstream is used to create a stream object for output. In order to use std::ifstream or std::ofstream, a new include statement must be placed at the top of the source code file:
#include <fstream>
Stream object names (input and output in the example) should follow the guidelines for naming variables from the Variables section.
Open the File
Once a stream object is created, a program has a means for accessing a file, but one additional step is needed first. Every file to be read from, or written to, must be opened. It is the act of opening a file that creates a stream (i.e., establishes the connection between program and file).
Here is an example that shows both an input and an output file being opened:
input.open("data.txt");
output.open("output.txt");
The names of the files to open are in double quotes inside the parentheses. Nothing is required to indicate a file intended for input or output. The data type of the stream object (e.g., std::ifstream for input) indicates how the file will be used.
Some important points:
- The input file must be in a specific location to enable the program to find and open it. The location of the file depends upon the tools you are using.
- The output file will be created in the default location specified by the tools you are using.
- If the input file cannot be opened (e.g., it was never created or its name is spelled incorrectly), the open will fail.
- If the output file does not exist, it will be created and given the name in the double quotes.
- If the output file already exists, it will be opened and its contents immediately deleted.
Do not create an output stream object for a file meant for input. The file’s contents will be deleted when it is opened.
To have a program use an input or output file in a location other than its default location, add the file’s path inside the quotes:
input.open("C:\\temp\\info.txt");
This shows info.txt in the temp directory on the primary hard drive (C:) being opened. Note that two backslashes are used in the path instead of one.
Test for a Successful File Open
The first action a program should take after opening a file is check whether the file was successfully opened. If the attempt to open a file failed, using the stream object to access the file will also fail.
This compound condition tests whether both files are opened successfully:
if (!input.is_open() || !output.is_open()) {
std::cout << "Unable to open a file!" << std::endl;
} else {
...
}
The is_open() function returns true if the file is open, and false otherwise. Using the NOT logical operator (!) reverses the truth value of what is_open() returns (remember: !false == true). If either file is not open, the test will evaluate to true, the error message will display, and no attempt will be made to use the files. If both files are open, the test will evaluate to false and the statements in the “false” part of the if statement will be executed.
Work with the File
Once a file is known to be open, it can be used by the program for input or output, as the case may be. The details of file input and file output are discussed later in this section.
Close the File
As soon as a file is no longer needed by a program it should be closed. When a file is closed the stream established between the file and the program is eliminated. If the file is an output file, the data written to the file will be saved. A closed file cannot be used by a program unless it is reopened. These statements close the input and output files:
input.close();
output.close();
File Input
There are two parts to file input: reading data and determining whether all data has been read.
Reading data from a file is very similar to reading data from the keyboard. Suppose a program needs to read an integer typed by the user into a variable named number. The following statement would accomplish this:
std::cin >> number;
Now suppose the integer was coming instead from a file. The following statement would read the integer from a file:
input >> number;
As can be seen, the only difference is the stream object which determines the source of the data.
An input file always has an end. No file will contain an unlimited amount of data. A program reading from a file therefore needs to know when the end of the file has been reached so it does not continue trying to read from the file. In C++, the eof() function is used to check whether the end of a file has been reached. This function returns true when the end has been reached, otherwise it returns false. The check should be performed after each attempt to read from a file. As long as the end is not reached, the program can continue to read from the file.
Since the test is performed repeatedly, and the program should keep reading from the file until its end is reached, eof() is usually used in a loop’s test and the not logical operator used to indicate repetition should continue as long as eof() returns false.
Here is the general form for structuring a program for file input:
read data
while (!input.eof()) {
process data
read more data
}
The program’s steps, as a result, are:
- Perform an initial read from the file.
- Test whether the end of the file has been reached.
If the end of the file has not been reached:
- Process the data read from the file.
- Read the next data from the file.
- Go back to step 2.
- If the end of the file has been reached, stop reading from the file.
The test performed after the first read verifies the start of input is successful. The step to process the data consists of the code needed to utilize the data read.
The stream object keeps track of the location of the next data to read from the file.
File Output
Writing to a file is actually very similar to displaying data to the screen. For screen output, std::cout is used and the data formatted with newlines, std::setw, std::setprecision, and std::fixed. For file output, the stream object we create (output in the example) is used and data formatted with newlines, std::setw, std::setprecision, and std::fixed. Formatting output to a file works the same as formatting output to the screen. This includes using a newline to go to the next line on the screen which in a file moves output to the next record. The only real difference is instead of using the stream object which sends data to the screen, std::cout, we use the stream object we created using std::ofstream.
An example is shown below:
output << std::fixed << std::setprecision(2);
output << "Price: " << 2.5 << std::endl;
In this case, “Price: ” (without the quotes) and the amount 2.50 would be written to a new record in the file.
A Complete Program
The program given at the end of this section performs both file input and output. Much of its code has already been discussed in the examples above. The program reads a record from data.txt (described previously), adds and multiplies the numbers read, and writes the numbers read and the results of the calculations to output.txt. The data written to the file is formatted into right aligned columns each with its own heading. The complete contents of output.txt, after program execution ends, are shown earlier in this section. Step through the program.
Some additional points:
- The program has three
includestatements (why?). - The code to execute when the files are successfully opened is in the “false” part of the
ifstatement. If opening either file fails, the only code executed would be thestd::coutstatement in the “true” part of theifstatement and thereturnstatement at the end ofmain(). - Both
std::fixedandstd::setprecisionare used once and impact the output of all floating-point point values to the file. This corresponds to how they work with data formatted for output to the screen. - The input statements read all of a record’s data (i.e., both floating-point values). This is a common approach for file input as it is frequently necessary for a program to have all a record’s data for the processing step. It is also possible to read the two values using separate input statements. Either approach is fine.
- Both files are closed after exiting the loop because access to neither file is needed after this point. Keep in mind that this may not always be the case. For example, sometimes all data is read from a file and processing requires that the output file remain open a while longer.
A Complete Program using File I/O
#include <iostream>
#include <iomanip>
#include <fstream>
int main()
{
double first, // First number read from a record
second, // Second number read from a record
sum, // Stores sum of the two numbers
product; // Stores product of the two numbers
std::ifstream input; // Stream object used for input
std::ofstream output; // Stream object used for output
input.open("data.txt");
output.open("output.txt");
if (!input.is_open() || !output.is_open()) {
std::cout << "Unable to open a file!" << std::endl;
} else {
output << "First Second Sum Product"
<< std::endl;
output << std::fixed << std::setprecision(1);
input >> first >> second;
while (!input.eof()) {
sum = first + second;
product = first * second;
output << std::setw(5) << first
<< std::setw(8) << second
<< std::setw(8) << sum
<< std::setw(8) << product
<< std::endl;
input >> first >> second;
}
input.close();
output.close();
}
return 0;
}