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:
ifstream: Used to read data from files.ofstream: Used to write data to files.fstream: Used for both reading and writing data to/from files.
§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:
std::ios::in- Open for reading (this is default forifstream).std::ios::out- Open for writing (this is default forofstream).std::ios::app- Append mode.std::ios::binary- Binary mode (for raw data).std::ios::trunc- Truncate the file (this is default forofstream).std::ios::ate- Open and move to the end of the file.
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);
std::ios::in: Open the file for reading.std::ios::out: Open the file for writing.
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:
- Opening the file: The file is opened with
std::ios::in(read mode) andstd::ios::out(write mode). Thestd::ios::truncflag truncates the file, meaning any existing contents are cleared when opened. - Writing to the file: We use the
<<operator to write text into the file. - Seek to beginning: After writing, we use
file.seekg(0)to reset the file pointer to the first line before we start reading. This is necessary because the file pointer is at the end of the file after the write operations..seekg()allows you to move the Get (read) pointer to any location in the file..seekp()allows you to move the Put (write) pointer to any location in the file.
- Reading from the file: The
std::getline()function is used to read the file line by line (or optionally separated by any delimiter you choose), and write the content to a string variable.
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 #
- What happens if you try to open an existing file with
ofstreamwithout specifying the mode?
- What is the purpose of
file.close()? What could happen if you forget to close a file after writing to it?
- What is the difference between
ifstreamandfstream, is there a situation where you should prefer to useifstreamoverfstream?
- How can you detect if a file failed to open?
- 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.
- what is the value of the
datastring 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;
- 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 #
- A text submission or PDF containing answers to the Lab questions and a link to your
lab4code repository.