File I/O

Reading from / writing to a file uses the same stream operators as we used to take input from a user or write to the screen.

To open a file, we use:

std::ofstream of;
of.open("file.txt");

alternately, we can pass the name of the file to the constructor of the class,

std::ofstream of("file.txt");

Once the file is open, we can send output to it using the stream operator <<:

double x{0.0};
of << x << std::endl;

When we are done with the file, we can close it:

of.close();

Note

C++ will manage closing the file when it goes out of scope (e.g. at the end of a function). However, you can still free it earlier if desired.

The alternate to open a file for reading is std::ifstream. Note that both of these are defined by the fstream header.

Here’s an example of writing our planet information out to a file:

Listing 26 output_example.cpp
#include <iostream>
#include <iomanip>
#include <fstream>
#include <vector>

struct Planet
{
    std::string name{};
    double a{};            // semi-major axis
    double e{};            // eccentricity
};

int main() {

    const std::vector<Planet> planets {{"Mercury",  0.3871, 0.2056},
                                       {"Venus",    0.7233, 0.0068},
                                       {"Earth",    1.0000, 0.0167},
                                       {"Mars",     1.5237, 0.0934},
                                       {"Jupiter",  5.2029, 0.0484},
                                       {"Saturn",   9.5370, 0.0539},
                                       {"Uranus",  19.189,  0.0473},
                                       {"Neptune", 30.070,  0.0086}};

    std::ofstream of("planets.txt");

    for (auto p : planets) {
        of << std::setw(12) << p.name
           << std::setw(12) << p.a
           << std::setw(12) << p.e << std::endl;
    }

    of.close();

}

Let’s now look at how to read that file back in.

There are 2 approaches we can take:

  • Just read in each piece of information as needed, e.g.:

    std::ifstream infile{"file.txt");
    
    infile >> x >> y >> z;
    
  • Read the file line-by-line into a string and then treat that string as a stream and read from it.

    The getline() function will do this for us:

    std::string line{};
    std::getline(infile, line);
    

    To interpret the string as a stream, we can use std::stringstream:

    std::stringstream ss(line);
    ss >> x >> y >> z;
    

Here’s an example of reading our file line by line. We rely on the fact when it reaches the end of the file, getline() will evaluate as false in a conditional.

Listing 27 input_example2.cpp
#include <iostream>
#include <iomanip>
#include <sstream>
#include <fstream>
#include <vector>

struct Planet
{
    std::string name{};
    double a{};            // semi-major axis
    double e{};            // eccentricity
};

int main() {

    std::vector<Planet> planets;

    std::ifstream data_file("planets.txt");

    if (! data_file.is_open()) {
        std::cout << "Error opening the file" << std::endl;
        return 1;
    }

    std::string line{};

    while (std::getline(data_file, line)) {
        Planet p;
        std::stringstream ss(line);
        ss >> p.name >> p.a >> p.e;
        planets.push_back(p);

    }

    data_file.close();

    for (auto p : planets) {
        std::cout << std::setw(12) << p.name
                  << std::setw(12) << p.a
                   << std::setw(12) << p.e << std::endl;
    }

}

try it…

A file stream object has an .eof() member that is true when we reach the end of a file. However, it is only set after a failed read from the file.

If we try using this in our while loop, it is possible that we will test as true and then fail to read in the loop body.

Tip

When you open a file for output using ofstream as shown above it overwrites an existing file of that name.

If we wish instead to append to an existing file, we would do:

std::ofstream of("file.txt", std::ios::app);

try it…

Let’s write our own version of wc. Here’s a text file: lorem-ipsum.txt.

Let’s write a C++ code the counts the number of lines, words, and characters from the input file.

We don’t know how to take a command line argument yet, so let’s just hardcode the file name into the program for now.

solution
Listing 28 wc_example.cpp
#include <iostream>
#include <fstream>

int main() {

    // open the file

    std::ifstream input_file("lorem-ipsum.txt");

    if (! input_file.is_open()) {
        std::cout << "Error: invalid file" << std::endl;
        return 1;
    }

    // loop over line by line
    std::string line{};

    unsigned int line_count{0};
    unsigned int char_count{0};
    unsigned int word_count{0};

    bool was_space = false;

    while (std::getline(input_file, line)) {

        // by design, getline drops the newline, so add it back here
        // so we can accurate capture line breaks and get the words right
        line += "\n";

        for (auto c : line) {
            bool test = (c == ' ' || c == '\n');
            if (test) {
                // if the previous character was a space too, then
                // just skip, otherwise, increment the word counter
                // and set it as a space
                if (!was_space) {
                    word_count += 1;
                    was_space = true;
                }
            } else {
                was_space = false;
            }

            char_count += 1;
        }

        line_count += 1;

    }

    std::cout << "number of lines = " << line_count << std::endl;
    std::cout << "number of words = " << word_count << std::endl;
    std::cout << "number of characters = " << char_count << std::endl;

}