File I/O
Think about how useless Excel, Word, or PowerPoint would be if you couldn't save the documents. Or how infuriating a game would be if you had to start over every time you played it. This chapter teaches you how to work with files to save data between program runs.
File I/O (input/output) refers to operations that manage files. These include reading data from a file, writing data to a file, and actions like creating, deleting, and renaming files.
File IO is the only way to save data between program runs, as our program won't change its file while running.
We'll work a lot with the classes std::ifstream and std::ofstream. These are "input file stream" and "output file stream", respectively. You can create stream objects to input and output data to a file.
Both std::ifstream and std::ofstream are located inside the <fstream> header.
Output Streams
You can create and edit files using std::ofstream.
Start by declaring a std::ofstream object and calling the .open("filename") member function to open a file to write. If the file doesn't already exist, the program will create it. You can write to it after opening it by using the insertion operator (<<).
std::ofstream example;
example.open("file_name.txt");
example << "Some data to push into the file";
Passing the filename to a std::ofstream constructor automatically calls .open().
// This code does the same as the code above
std::ofstream example("file_name.txt");
example << "Some data to push into the file";
Try running this code and see how a new file called file_name.txt is created with the content Some data to push into the file.
When you finish using a std::ofstream object, call .close() to free up the system resources allocated for handling the output operations.
.close() ensures that any buffered data (data held in temporary storage) is flushed (ensuring all the buffered data is processed/saved). The std::ofstream destructor will also automatically call close() when it goes out of scope.
Here is an example of a program that saves a high score of some game to a file.
// Lets us to use the ofstream class
#include <fstream>
int main() {
// Open/Create the file "highscore.txt" to write to.
std::ofstream outFile("highscore.txt");
// This is the data we want to store
int highscore = 200;
// Write the value of highscore to the opened file using the << operator.
outFile << highscore;
// Close the outFile stream, releasing the resources.
outFile.close();
}
Input Streams
You can read files using std::ifstream.
ifstream has a lot in common with ofstream and the std::cin object.
Similarly to std::ofstream, you can open a file for reading using .open("filename") or a constructor.
You can use the extraction operator (>>) on an open ifstream to push data into a variable.
You should call .close() when you finish using an ifstream.
Example program reading a high score from a document created by the code from the last section:
#include <fstream>
int main() {
// Opens the file `highscore.txt` for editing (we could also use inFile.open("highscore.txt"));
std::ifstream inFile("highscore.txt");
// Store the data from "highscore.txt" into our variable
int highscore;
inFile >> highscore;
// Close the file to free up system resources
inFile.close();
// Should print 200
std::cout << highscore;
}
Getline
ifstream has the same behavior with whitespaces as std::cin. You can use getline() for ifstreams in the same way as you would for std::cin.
std::ifstream file("file.txt");
std::string buffer;
getline(file, buffer);
getline(file, buffer) will place the first line of the file into the variable buffer. Calling it again will put the second line into the buffer, and so on.
getline() returns true if it finds the following line and false if it fails to find a line. You can use getline with a while loop to go through the file line by line.
#include <iostream>
#include <fstream>
#include <string>
int main() {
// Open file for reading
std::ifstream file("example.txt");
// Declare the string we want to store each line on
std::string line;
// std::getline will return false when it fails to retrieve another line
while (std::getline(file, line)) { // Read each line
// Output the line
std::cout << line << "\n";
}
file.close();
}
The most efficient way to use ifstreams is to handle the data as you read it. If you need to store the entire file for later, you can fill a std::vector<std::string> with all the lines from a file. This is rarely optimal, but it might make managing data more accessible.
Get & the internal position marker
An ifstream needs to know the position from which to read. I'll refer to this position as the internal position marker.
getline() sets the second argument to the data from the internal position marker to the next newline character(\n) or end of the file. This process moves the internal position marker to after the new line character, which causes the next getline call to give you the next line.
get
The get() function returns the next character, moving the internal marker one char forwards.
This program reads each character in a file and outputs them individually, separated by -.
// Open the file
std::ifstream file("example.txt");
char ch;
// Read each character until end-of-file
while (file.get(ch)) {
std::cout << ch << "-";
}
If the file example.txt contains the data Hello!, the output will be H-e-l-l-o-!-
eof
If you ever need to check if the internal marker is at the end-of-file (eof), you can use the .eof() function.
int lineCount = 0;
char ch;
// Read characters from the file until the end of file is reached
while (!file.eof()) {
file.get(ch);
if (ch == '\n')
lineCount++;
}
}
Moving the internal position marker
You can move the internal position marker where you want to read/write. You move the internal position marker using seekg() (for inputs) and seekp() (for outputs). Their use is identical.
istream& seekg (streamoff off, ios_base::seekdir way);
seekg moves the position marker off bytes from the base position given in way.
way can take any of these constant values:
ios_base::beg: the beginning of the stream
ios_base::cur: current position in the stream
ios_base::end: the end of the stream
std::string line;
std::ifstream is ("example.txt");
is.seekg (4, is.beg); // Move 4 bytes into the file
std::getline(is, line); // Get the rest of the line
You can get the internal position marker by using tellg() (for inputs) and tellp() (for outputs).
You can use this with seekg() to get the length of a file.
std::ifstream is ("test.txt", std::ifstream::binary);
is.seekg (0, is.end); // set position to the end
int length = is.tellg(); // get position (currently at the end)
is.seekg (0, is.beg); // go back to the start to handle file
Writing to a location other than the end of a std::ofstream will overwrite the content already there. If you write "sup" to the start of a file that says "bye friend :)", the file will end up saying "sup friend :)".
std::filesystem
You need to configure your compiler to a modern version of C++ (C++17 or newer) to use std::filesystem.
std::filesystem is a namespace located inside <filesystem>. It is filled with tools that help us manage files.
std::filesystem::path is a class that holds paths to files or folders (also known as directories). You can initialize the file path with a string.
std::filesystem::path filePath = "example_dir/sample.txt";
Many functions from std::filesystem take a std::filesystem::path as their only argument.
std::filesystem::exists() returns true if the path leads to an existing file or directory.
std::filesystem::is_directory returns true if the path leads to a directory.
std::filesystem::file_size returns the size of the file.
std::filesystem::last_write_time returns the last modification time of a file.
std::filesystem::create_directories Creates a directory and all necessary parent directories. Returns true if it successfully creates them. The function will return false if the directory already exists. You can use std::filesystem::exists() to check if it exists before attempting creation.
std::filesystem::remove deletes a file or empty directory—returns true if deleted successfully.
std::filesystem::remove_all deletes a file or directory with all its contents—returns true if deleted successfully.
You can iterate over the contents of a directory by using std::filesystem::directory_iterator(directory_path)
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
// The directory you want to iterate over
fs::path directory_path("example_dir");
// Make sure the directory exists
if (fs::exists(directory_path) && fs::is_directory(directory_path)) {
// Iterate over the directory contents
for (const auto& entry : fs::directory_iterator(directory_path)) {
// Print the path of each entry
std::cout << entry.path() << std::endl;
if (entry.is_regular_file()) {
std::cout << "File size: " << fs::file_size(entry.path()) << " bytes" << std::endl;
} else if (entry.is_directory()) {
std::cout << "This is a directory" << std::endl;
}
}
} else {
std::cout << "Path is not a directory or does not exist." << std::endl;
}
}
Output Modes
The open functions (and the constructors) of std::ifstream/std::ofstream have a second parameter that takes flags specifying which mode to open a file in.
std::ofstream::ate lets you write to a file without overwriting it.
This flag places the internal position marker at the end of the stream.
std::ofstream file("example.txt", std::ios::ate);
file << "New data"; // Adding data without overwriting the file
std::ofstream::app lets you append data to the end of the file without the risk of overwriting existing data. It restricts writing to the end of the file only.
std::ofstream::binary opens a file in binary mode instead of text mode. This ensures that data is written exactly as is, without any alteration or formatting. This is crucial for non-text data like images, audio, or other binary files.
The I/O system may perform character translations in text mode. On windows systems, \n is converted to \r\n when writing in text mode, and vice versa when reading. This can disrupt files that need precise byte-level control.
You can "combine" flags using the | symbol. We will cover this syntax in a later section.
std::ofstream ofs ("test.txt", std::ios::app | std::ios::binary);
GOOD JOB!
You finished a chapter.
This would be a good time to practice what you've just learned.
Last ChapterPracticeNext Chapter