File IO

Programming Workshop 2 (CSCI 1061U)

Faisal Qureshi

Faculty of Science, Ontario Tech University

http://vclab.science.uoit.ca


C++ provides iostream library to read from and write to files. iostream library defines three data types to constructs i/o streams attached to files.

Data Type Description
ofstream This data type represents the output file stream and is used to create files and to write information to files.
ifstream This data type represents the input file stream and is used to read information from files.
fstream This data type represents the file stream generally, and has the capabilities of both ofstream and ifstream which means it can create files, write information to files, and read information from files.

Once an file (IO) stream is ready, it behaves similarly to standard streams that you are already familiar with, such as cin, cout and cerr, with a few caveats that we discuss below.

Note: To perform file processing in C++, header files <iostream> and <fstream> must be included in your C++ source file.

Reading from a file

Opening a file for reading

Use

ifstream f("filename.txt");

or

ifstream f;
f.open("filename.txt");

To setup f as the read stream from file filename.txt. See the code below.

#include <iostream>
#include <fstream>
using namespace std;

int main()
{
    ifstream f("names.txt");
    if (f.is_open()) {
        cout << "Successfully opened \"names.txt\" for reading.\n"; 
        
        // Read contents here
        
        f.close(); // Don't forget to close the file stream
    }
    else {
        cerr << "Error opening \"names.txt\" for reading.\n";
    }

    return 0;
}

Reading file contents

Example: Numbers, one per line

Say we want to read the following file:

1342
23
354

This file contains 3 numbers. Each number is on its own line.

Choice 1

Use getline() method

...
if (f.is_open()) {
    string line;
    while ( getline(f, line) ) // getline() returns a true if 
                               // it successfully reads a line
                               // from the file; otherwise, it 
                               // returns false. 
    {
        cout << line << '\n';       
    }
    
    f.close(); // Don't forget to close the file stream
}
...

Source code

The above program will output

1342
23
354
Experiment

What will happen if the numbers are stored as

1342 23 354

What will be the output of the above program (choice 1)?

Choice 2

Use extraction operator >>

...
if (f.is_open()) {
    while ( !f.eof() ) // eof() method checks wether we
                       // have reached the end-of-file.
    {
        int num;
        f >> num;
        cout << num << '\n';
    }
    
    f.close(); // Don't forget to close the file stream
}
...

The above program will output

1342
23
354

Source code

Experiment

Lets repeat the experiment. What will happen if the numbers are stored as

1342 23 354

What will be the output of the above program (choice 2)?

Is it the same output? If not, why not?

Things to try

Writing to a file

Opening a file for writing

Use

ofstream f("filename.txt");

or

ofstream f;
f.open("filename.txt");

See the code below

#include <iostream>
#include <fstream>
using namespace std;

int main()
{
    ofstream f("names.txt");
    if (f.is_open()) {
        cout << "Successfully opened \"names.txt\" for writing.\n"; 
        
        // Writes contents to the file here
        
        f.close(); // Don't forget to close the file stream
    }
    else {
        cerr << "Error opening \"names.txt\" for writing.\n";
    }

    return 0;
}

Writing to a file: numbers, one per line

Lets save a list of numbers to a file using insertion operator << .

...
if (f.is_open()) {
    f << 3.14159 << '\n';
    f << 8321 << '\n';
    
    f.close(); // Don't forget to close the file stream
}
...

Source code

When you compile and run this program one of the following two will happen:

The contents of this file will be

3.14159
8321

Problem

The above code overwrites the file everytime it opens it for writing. We can solve this issue by telling the system to open file in the append mode. Replace

ofstream f("numbers.txt");

with

ofstream f("numbers.txt", ios::app);

ios::app flag sets up ofstream for appending.

Buffered Output

Output in C++ may be buffered. This means that anything that is output to a file stream may not be written to disk immediately. Instead several writes may be group together to improve performance (disk access is painfully slow). Typically buffered writes is not an issue; however, it can lead to problems in unique circumstances. For example, if a program crashes. The contents may not yet be written to the file and all changes may be lost. It is possible to force a write by flushing. Use flush() to force a write to the disk.

Note that closing a file also flushes a buffer, forcing a write to the disk of course.

Interestingly, std::endl which is often used in place of \n also forces a flush. Relying upon std::endl instead of \n to indicate a newline, for example, can adversaly effect the performance. Interesting, eh!

f << "CSCI 1061U\n" << "Programming workshop 2\n";
f.flush();  // forces a save to the file

Alternately,

f << "CSCI 1061U\n" << "Programming workshop 2" << endl;

Note: In practice, it is preferrable to use flush(). This gives a clear signal that you intend to force a write-to-the-file.

File modes

File stream constructors take an optional second parameter that allows you to specify information about how the file should be opened. This parameter is called mode, and the valid flags that it accepts live in the ios class.

ios file mode Meaning
app Opens the file in append mode
ate Seeks to the end of the file before reading/writing
binary Opens the file in binary mode (instead of text mode)
in Opens the file in read mode (default for ifstream)
nocreate Opens the file only if it already exists
noreplace Opens the file only if it does not already exist
out Opens the file in write mode (default for ofstream)
trunc Erases the file if it already exists

It is possible to specify multiple flags by bitwise ORing them together (using the | operator).

Binary files

ios:binary mode flag can be used to open a file for read/write in binary mode.

Insertion << and extraction >> operators are not meant to be used for binary mode files.

To understand the difference between the two modes, consider the following example. Say you want to write 3.14159 to a file. In text mode, this can be accomplished as f << 3.14159. The << operator will write ‘3’, ‘.’, ‘1’,‘4’,‘5’,‘9’ to the file, which will take 7 bytes. In binary mode, however, it will only take 32 bits (or 4 bytes), since it will be written as a double.

You’ll use read and write methods to read/write from binary files.

double pi = 3.14159;
ofstream f("pi.dat", ios::binary);
f.write(&pi, sizeof(double));

and

double d;
ifstream f("pi.dat", ios::binary);
f.read(&d, sizeof(double));

If there is a read error gcount() can be used to see how many bytes are actually read.

Source code showing storing and loading binary values to a file.

Random Access

It is possible to set the location in the file where you want to perform the next read or write.

  1. Move read location forward n bytes from the beginning: f.seekg( n ); It is also possible specify the beginning of a file using ios::beg.
  2. Move read location forward n bytes from the current location: f.seekg( n, ios::cur );
  3. Move read location back n bytes from the current location: f.seekg( n, ios::end );
  4. Find the current read location f.tellg();
  5. seekp() and tellp() can be used to move the write locations.

How to both and read from and write to a file?

Check out the following code that showcases the use of opening a file for both reading and writing.

Note that ios::in and ios::out flags are used together to specify that we plan to both read from and write to this file stream.

cout <<  "\nTask 4: change 50th number to 50000" << endl;
fstream f4("data", ios::binary | ios::in | ios::out);
if (f4.is_open()) {
    int d = 50000;
    cout << d << endl;

    f4.clear();
    f4.seekg(0);
    cout << f4.tellg() << endl;
        
    f4.seekp(sizeof(int)*49, ios::beg);
    cout << f4.tellp() << endl;

    f4.write((char*)&d, sizeof(int));
    f4.flush();
    
    if (f4.fail()) {
        cerr << "Error writing.";
    }
    f4.close();
}
else {
    cerr << "Error opening to change 50th number\n";
}

Source code that show cases reading and writing values to a file.

How to read a file when we don’t know how much data it contains?

Choice 1 (Preferred solution)

Use f.eof() to check whether the end-of-file has been reached as seen below

std::ifstream f("unknown.txt");
if (f.is_open()) {
    char c;
    while (!f.eof()) {
        f >> c;
        std::cout << "Read: " << c << std::endl;
    }
}
else {
    std::cout << "Error opening unknown.txt file" << std::endl; 
}

If unknown.txt contains

abcde f
gh i j
k

Then the above code will print

Read: a
Read: b
Read: c
Read: d
Read: e
Read: f
Read: g
Read: h
Read: i
Read: j
Read: k
Read: k

Note that the last character is printed twice. Can you figure out why? This is because end-of-file flag is set after

f >> c;

So while end-of-file flag is set, the code still prints out the stale value in c in the next line

std::cout << "Read: " << c << std::endl;

How to prevent the last character from printing twice in the above example?

f >> c;
if (f.good()) {
    std::cout << "Read: " << c << std::endl;
}

Here we check if the read operation has succeeded. This can be achieved by check if an error or fail bit was set on the filestream.

Source code

Choice 2

Another option is to use f.seekg() to go to the end-of-file and calculate its size. This is rarely used in practice.

How read and write from a stringstream

It is also possible to set up a stringstream object. This is used to read and write values to/from strings. The following code demonstrates stringstream in action.

#include <sstream>

stringstream s;
s << "x = " << 22;

cout << "1. Contents of the stringstream object." << endl;
cout << s.str() << endl;

cout << "\n2. Reading from stringstream object.";
char buf[32];
s.getline(buf, 32);
int actual_read = s.gcount();
cout << "Read " << actual_read << " characters." << endl;
cout << "buf = \n"
        << buf << endl;

cout << "\n3. Setting contents of a stringstream object." << endl;
s.str("Hello world");
cout << s.str() << endl;

Source code shows stringstream usage.

Exercises

  1. Write a program for storing and loading 3D vectors to a file.
  2. Write a program for storing and loading lastname, firstname to a file.

Credits

References