-----

C++ File Input/Output

-----

Details of file I/O seem to be buried at the back, missing, or overly complicated in most C++ manuals. This page provides a quick reference for the most frequently used methods.

This page only discusses basic options that seem to be common to all my C++ references. Apparently there is a lot of variation from one manual to another, and from one implementation to another. I believe the methods below to be safe and portable, at least for ASCII (human-readable text) files.

Header files

To do input and output, you will need to load the iostream header file. You may also need to load the fstream (file I/O) and/or iomanip (format manipulation) header files. Put some/all of the following lines at the top of your code file (or in the header file for your program, if you are using one).

#include <iostream>  // I/O 
#include <fstream>   // file I/O
#include <iomanip>   // format manipulation
#include <string>

Getting a stream

Three streams just exist: cout (terminal output), cin (terminal input), and cerr (error output, which also goes to the terminal).

When writing error messages, use cerr rather than cout. In simple examples, the two appear to be the same. However, they behave differently when you use Unix redirection operators when you run your program. In particular, if you redirect output to a file, error messages printed to cerr will still appear on the user's terminal whereas error messages printed to cout will be mixed into the output file.

File streams are of type ifstream (input) or ofstream (output).

ifstream fp_in;  // declarations of streams fp_in and fp_out
ofstream fp_out;
fp_in.open("myfile.txt", ios::in);    // open the streams
fp_out.open("myfile.txt", ios::out);
fp_in.close();   // close the streams
fp_out.close(); 

A file should be closed if you are done with it, but the program will continue running for a while longer. This is particularly important when you intend to open a lot of files, as there may be a limit on how many you can have open at once. It is also a good idea if you intend to open a file for input, and then re-open the file for output.

Declaring the pointer and opening the file can be combined:

ifstream fp_in("myfile.txt", ios::in);  // declare and open

The parameters ios::in and ios::out specify the mode in which the file is to be opened. Depending on the implementation of C++, it may be possible to specify a variety of other mode options, such as appending to the end of an existing file, triggering an error rather than overwriting an existing file, or specifying that a file is binary for operating systems (e.g. MS-DOS) which distinguish binary and ASCII files.

Passing streams to functions

File streams must be passed to functions by reference, not by value.

void myfunction(ifstream &fp, ...)  // use this
void myfunction(ifstream fp, ...)   // not this

If you pass streams by value, the C++ compiler will not complain. However, mysterious bad things will start happening, often in parts of the code which don't appear to be related to the offending function.

Item by item input and output

If each input item is surrounded by whitespace (blanks, tabs, newlines), the items can be read easily using the extraction operator >>.

int myinteger;            // declarations
float myfloat;
char mychar;
string mystring;

fp_in >> myinteger;       // input from file pointer or standard input
cin >> myfloat;
fp_in >> mychar;
cin >> mystring;

The extraction operator works for numbers (ints, floats), characters (char), and strings (declared as arrays of type char or pointers to type char).

The extraction operator returns a zero value if it encounters a problem (typically, the end of the file). Therefore, it can be used as the test in an if statement or a while loop.

Numbers, characters, and strings can be written to a file, standard output, or the standard error using the insertion operator <<.

cout << "Value of myinteger " << myinteger << endl;
cout << "My string is " << mystring << " plus a null character\n" << flush;

To insert a line break, either insert the magic variable endl or write the end-of-line character ('\n') to the output.

To make a pointer print out as a pointer, not as whatever type of data it points to, cast it to the type (void *). To make a character print as a number, cast it to type int. Similarly, you can use a cast to convince C++ to print an integer as the corresponding character.

cout << (void *)ptr; 
cout << (int)ch; 
cout << (char)ival;

Buffering and flush

When you send output to a stream, it does not necessarily get printed immediately. Rather, it may wait in a buffer until some unspecified event, e.g. buffer full enough, reading from input, or exit from program. The details may vary.

Buffering makes it faster to print a large amount of output, particularly if you are producing the output bit-by-bit (e.g. one character at a time). However, it is a nuisance for output used in debugging (e.g. a statement that informs you that step 3 of your algorithm has been finished).

Forcing all buffered output to actually be printed is known as "flushing" the stream. A flush can be forced by calling the flush function associated with each output stream, inserting the magic variable flush into the stream, or inserting endl.

cout << flush;
cout.flush();
cout << endl;

Other input operations

All of the following are illustrated using the standard input, but they work just the same on file streams.

cin.get(char &ch)
Puts the next input character in the variable ch. Returns an integer value, which is zero if it encountered a problem (e.g. end of file).
cin.getline(char *buffer, int length)
Reads characters into the string buffer, stopping when (a) it has read length-1 characters or (b) when it finds an end-of-line character ('\n') or the end of the file. Stores a null character ('\0') after the last character read.
cin.read(char *buffer, int n)
Reads n bytes (or until the end of the file) from the stream into the buffer.
cin.gcount()
Returns the number of characters read by a preceding get, getline, or read command.
cin.ignore(int n)
Remove the next n characters (or until end of file) from the input stream, throwing them away into the Great Bit Bucket.
cin.putback(char ch)
Puts character ch back onto the stream. Bad things will happen if this character is not the one most recently extracted from the stream.

These operations all return zero if something goes wrong, e.g. they hit the end of the file. Therefore, they can be used as the condition in an if statement or while loop.

In addition, there are two more input operations, get and peek. These functions return EOF (which is secretly -1) if they encounter the end of the file. The output of these functions should be put into an integer (not a char) variable.

cin.get()
Returns the next character in the stream.
cin.peek()
Returns the next character in the stream but does not remove it from the stream.

The following functions can be used to test the status of a stream. They return an integer, which is either zero or non-zero.

cin.good()
Returns 0 if the stream has encountered problems such as reading the end of file, non-existent file.
cin.bad()
Returns non-zero value if the stream is totally unusable, e.g. the file cannot be opened (but not if the stream has merely hit the end of the file).
cin.eof()
Returns a non-zero value if the stream has hit the end of the file.

Notice that the stream's status will change to bad (not good, eof, etc) after the first read request which encounters a problem. So, to use one of these functions, you attempt to do what you wanted to do (e.g. open the file, read the next number from the file, ...). If the action can't succeed, the program won't crash, though some of your variables might not contain the values you intended. Next, use the status function to check whether the action succeeded.

Other output operations

Other options for writing data to an output stream are:

cout.put(char ch)
Puts ch onto the stream.
cout.write(char *str, int n)
Puts n characters onto the stream, reading them from the string str.

The function setf can be used to change formatting parameters for an output stream. For example, the following causes numbers to be left justified.

cout.setf(ios::left);       \\ set option
cout.unsetf(ios::left);     \\ unset option 

The most obviously useful parameters are:

ios::left
Left justify output.
ios::right
Right justify output.
ios::scientific
Print floating point numbers using scientific notation.
ios::fixed
Print floating point numbers using fixed point notation.
ios::showpoint
Print a decimal point for all floating point numbers, even when it's not needed (e.g. the number is exactly an integer).

The precision of numbers can be changed as follows. You can also set the width, i.e. the minimum number of spaces used to print the next. These features are used, for example, to make output items line up in columns when printed. Both of these features require that you include the iomanip header file.

cout << setprecision(2);  \\ print two digits after decimal point
cout.precision(2);        \\ an alternative syntax

cout << setw(8);   \\ make item occupy 8 characters
cout.width(8);     \\ an alternative syntax

Setting the width to k forces the item to occupy at least k characters. If its printed representation is shorter, blanks will be added so that it occupies k characters. If its printed representation is longer, it will occupy more than k characters.

When you reset parameters such as the precision, it is not clear whether the new value lasts only for the next item printed or whether it persists for subsequent values. Apparently this varies with the parameter and the C++ compiler. Therefore, if you care about the value of some formatting parameter, explicitly set it to the right thing before you output each item.

For examples of usage, and how the formatting options affect the printing of numbers, look at Bob Keller's format sampler.

Repositioning and error states

In general, it is possible to move to any position in a file stream. This is a capability most programmers use rarely, if at all. For fairly obvious reasons, don't try to use repositioning operations on the standard input, output, or error streams.

The most likely reason for using a repositioning command is to rewind a file to the start, so that you can read its contents again. This is done as follows:

fp_in.clear();              // forget we hit the end of file
fp_in.seekg(0, ios::beg);   // move to the start of the file

This page is maintained by Margaret Fleck.