Lab 4: Reading and Writing Files

C++ I/O

An introduction to file input and output (I/O) in C++. How to read and write text files, handle file errors, append data, and work with structured data.

§1 Introduction #

In C++, file handling is performed using streams. You can use file streams to read from and write to files. The standard library provides three main classes for file handling:

§2 Basic File Writing with ofstream #

To write to a file, you use the ofstream class. Here’s a simple example:

#include <iostream>
#include <fstream>  // Library needed for file operations

int main() {
  // Open a file named "output.txt", will create the file if it does not exist
  // store a reference to the filestream in the variable "myOutFile"
  std::ofstream myOutFile("output.txt");

  if (myOutFile.is_open()) { // check that the file was opened successfully
    // notice we write to "myOutfile" instead of cout
    myOutFile << "Hello, World!" << std::endl;
    myOutFile << "This is a file-writing example." << std::endl;
  } else {
    std::cout << "Unable to open file for writing!" << std::endl;
  }

  myOutFile.close();  // Always close the file after you are done with it
}

The constructor of ofstream opens a file (or creates it if it doesn’t exist). We then write to the file using the << operator, Finally close the file after we are done using it with .close().

Output:

$ g++ -o example main.cpp
$ ./example
  #program prints nothing. creates a file called "output.txt"
$ cat output.txt
Hello, World!
This is a file-writing example.

§3 Basic File Reading with ifstream #

To read from a file, you’ll use the ifstream class. Here’s a simple example:

#include <iostream>
#include <fstream>

int main() {
  std::ifstream inFile("output.txt");  // Open a file for reading, in variable "inFile"
  std::string line;

  if (inFile.is_open()) { // always check that the file was opened successfully
    // this while loop will read the file line-by-line until the end is reached,
    // the current line is stored in the variable "line"
    while (std::getline(inFile, line)) {
      std::cout << line << std::endl;  // Output the file's lines to the console
    }
  }
  else {
    std::cout << "Unable to open file for reading!" << std::endl;
  }

  inFile.close();  // Close the file after reading
}

The ifstream constructor opens the file for reading, then std::getline() is used to read the file line by line. Finally, always close the file after reading.

§4 Append Mode #

By Default, when you open a file with ofstream, the file is erased and overwritten. To append data to an existing file without overwriting its contents, you can open the file in append mode by using std::ios::app as a parameter to the ofstream constructor.

#include <iostream>
#include <fstream>

int main() {
  std::ofstream outFile("output.txt", std::ios::app);  // Open in append mode

  if (outFile.is_open()) {
    outFile << "This line is appended to the file." << std::endl;
  } else {
    std::cerr << "Unable to open file for appending!" << std::endl;
  }

  outFile.close();  // Close the file
}

Opening the file in std::ios::app mode ensures new data is added to the end of the file.

§5 Handling File Errors #

You should always check if a file was successfully opened by checking the .is_open() method or by checking the stream’s fail state with .fail().

#include <iostream>
#include <fstream>

int main() {
  // attempt to open a file that does not exist
  std::ifstream inFile("non_existent_file.txt");

  if (!inFile.is_open()) {  // Check if the file failed to open {if file did NOT open}}
    std::cerr << "Error: File could not be opened!" << std::endl;
    return 1; // return a non-zero exit code from main, signifying the program failed
  }

  // proceed to use file now that we confirmed it opened successfully
}
$ g++ -o example main.cpp
$ ls
example    main.cpp       // notice no file.txt is listed here...
$ ./example
Error: File could not be opened!

§6 Example: Reading and Writing Structured Data #

Let’s create a small program that both writes and reads structured data such as name and age.

#include <iostream>
#include <fstream>
#include <string>

int main() {
  std::ofstream outFile("people.txt");  // Writing structured data to a file

  if (outFile.is_open()) {
    outFile << "John Doe" << std::endl;
    outFile << 29 << std::endl;
    outFile << "Jane Smith" << std::endl;
    outFile << 24 << std::endl;
  } else {
    std::cerr << "Unable to open file for writing!" << std::endl;
  }
  outFile.close();
  // this section created the file "people.txt" with the following contents:
  // John Doe
  // 29
  // Jane Smith
  // 24

  std::ifstream inFile("people.txt");  // Reading structured data from the file
  // declare some variables to store data from the file.
  std::string name;
  int age;

  if (inFile.is_open()) {
    while (std::getline(inFile, name)) {  // read a line and store in variable "name"
      inFile >> age;  // read one more line into the variable "age"
      inFile.ignore();  // ignore the newline character after reading the integer
      // this effectively moves the reader to the next line

      std::cout << "Name: " << name << ", Age: " << age << std::endl;
    }
  } else {
    std::cerr << "Unable to open file for reading!" << std::endl;
  }

  inFile.close();
}
$ g++ -o example main.cpp
$ ./example
Name: John Doe, Age: 29
Name: Jane Smith, Age: 24

Here, we write names followed by their ages, separated by newlines into the people.txt file. Then we use std::getline() to read the name (since it may contain spaces), then use >> to read the integer (age) from the next line. Lastly, The inFile.ignore() is used to consume the newline after the integer, to prepare for reading the next line of the file, repeat as necessary.

§7 Modes of File Opening #

You can specify different modes when opening files:

You can combine these modes using the bitwise OR operator (|).

std::fstream file("data.txt", std::ios::in | std::ios::out | std::ios::app);

§8 Using fstream for Both Reading and Writing #

The fstream class in C++ allows you to perform both reading and writing operations on a file with a single object. It can be opened in various modes such as input (std::ios::in), output (std::ios::out), and more.

std::fstream file("filename.txt", std::ios::in | std::ios::out);

By combining these modes, you can perform both reading and writing operations.

In this example, we’ll write some data to a file and then read it back using the same fstream object.

#include <iostream>
#include <fstream>
#include <string>

int main() {
  // Wipe and open file for both reading and writing
  std::fstream file("sample.txt", std::ios::in | std::ios::out | std::ios::trunc);

  if (file.is_open()) {
    // Writing to the file
    file << "Hello, this is a test file." << std::endl;
    file << "It contains a few lines of text." << std::endl;

    // Move back to the beginning of the file before reading
    file.seekg(0);

    // Reading from the file
    std::string line;
    while (std::getline(file, line)) {
      std::cout << line << std::endl;
    }
  } else {
    std::cerr << "Unable to open the file!" << std::endl;
  }

  file.close();  // Close the file
  return 0;
}

Explanation:

You can also append new data to an existing file while keeping the old data intact:

#include <iostream>
#include <fstream>
#include <string>

int main() {
  // Open file for reading and for writing in append mode
  std::fstream file("sample.txt", std::ios::in | std::ios::out | std::ios::app);

  if (file.is_open()) {
    file << "This is an appended line." << std::endl;  // Appending data to the file

    file.seekg(0);  // Move back to the beginning of the file to read the contents

    // Reading the file contents
    std::string line;
    while (std::getline(file, line)) {
      std::cout << line << std::endl;
    }
  } else {
    std::cerr << "Unable to open the file!" << std::endl;
  }

  file.close();  // Close the file
  return 0;
}

§9 Reading Formatted Data #

By default, the getline() function uses the newline character (\n) as a delimiter, which means it reads each line of a file. However, we can modify the delimiter used by getline(), allowing us to extract structured data from a file based on a specific character.

For example, lets extract data in a comma-delimited file:

John Smith,25,123456A
Jane Doe,32,654321B
Emily Davis,29,789012C

#include <iostream>
#include <fstream>
#include <sstream>

int main() {
  std::ifstream inputFile("data.txt");
  if (!inputFile.is_open()) {  // exit with error if file failed to open
    std::cout << "Error opening file." << std::endl;
    return 1;
  }

  std::string line;  // create variable for `getline` to store data in
  while (getline(inputFile, line)) {
    // create a stringstream object from the extracted line
    // this can be passed into other `getline()` functions
    std::stringstream ss(line);
    // create variables to store data
    std::string name = "";
    int age = 0;
    std::string id = "";

    // store the first item in the `name` variable
    getline(ss, name, ',');

    // Then store the second item in the `age` variable
    std::string item; //temporary string for getline to store data in
    getline(ss, item, ',');
    age = stoi(item); // Convert string to integer

    // Finally store the third item in the `id` variable
    getline(ss, id, ',');

    std::cout << "Name: " << name << ", Age: " << age << ", ID: " << id << std::endl;
  }

  inputFile.close();
}

This will have the following output:

$ g++ -o test main.cpp && ./test
Name: John Smith, Age: 25, ID: 123456A
Name: Jane Doe, Age: 32, ID: 654321B
Name: Emily Davis, Age: 29, ID: 789012C

§10 Further Reading #

You can learn more about the C++ fstream library from the textbook in Chapter 5.12: Using Files for Data Storage.

This online tutorial has many more examples of how to read from files with C++: https://www.studyplan.dev/pro-cpp/file-streams#Reading%20from%20a%20File

§11 Questions #

  1. What happens if you try to open an existing file with ofstream without specifying the mode?
  1. What is the purpose of file.close()? What could happen if you forget to close a file after writing to it?
  1. What is the difference between ifstream and fstream, is there a situation where you should prefer to use ifstream over fstream?
  1. How can you detect if a file failed to open?
  1. When you open an existing file for writing, how can you ensure that data is appended to it rather than overwriting it? Write a line of code that would accomplish this.
  1. what is the value of the data string at the end of this code snippet?
    std::string str = "123;abc def;some other data";
    std::stringstream input(str);
    std::string data = "";
    getline(input, data, ';');
    getline(input, data, ';');
    std::cout << data << std::endl;
    
  1. In this function: getline(input, data, ';');. What is the data type of each parameter? explain what each parameter does and if it is required or optional.
    hint: read the docs: https://cplusplus.com/reference/string/string/getline/

§12 Assignment #

Open the assignment link from the “Lab 4” Canvas page. Follow the directions in the README.md file.

Be sure to commit and sync your changes!

§12.1 Deliverables #

  1. A text submission or PDF containing answers to the Lab questions and a link to your lab4 code repository.