Введение: почему работа с файловой системой в C++ — это база
Любая серьёзная программа рано или поздно сталкивается с необходимостью сохранять данные на диск или читать их оттуда. Конфигурационные файлы, логи, базы данных, пользовательские документы — всё это требует умения взаимодействовать с файловой системой. В C++ для этого есть два основных подхода: классические потоки ввода/вывода (библиотека <fstream>) и современный, более высокоуровневый std::filesystem (начиная с C++17).
В этой статье мы разберём практические сценарии: от простого чтения и записи текстовых файлов до навигации по директориям и обработки ошибок. Все примеры написаны на C++17/20 и готовы к использованию в ваших проектах.
1. Основы: чтение и запись файлов с помощью fstream
Библиотека <fstream> предоставляет три основных класса: ifstream (входной поток — чтение), ofstream (выходной поток — запись) и fstream (двунаправленный поток). Рассмотрим их на примерах.
1.1. Запись в текстовый файл
Самый простой способ записать данные — использовать ofstream. Если файл не существует, он будет создан. Если существует — перезаписан (режим ios::trunc по умолчанию).
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ofstream outFile("example.txt");
if (!outFile) {
std::cerr << "Ошибка открытия файла для записи\n";
return 1;
}
outFile << "Привет, файловая система C++!\n";
outFile << "Строка номер 2\n";
outFile << 42 << " — это число.\n";
outFile.close(); // закрывать вручную необязательно, но хороший тон
std::cout << "Данные записаны.\n";
return 0;
}
1.2. Чтение из текстового файла
Для чтения используем ifstream. Самый надёжный способ — читать построчно через std::getline.
#include <iostream>
#include <fstream>
#include <string>
int main() {
std::ifstream inFile("example.txt");
if (!inFile) {
std::cerr << "Файл не найден или не может быть открыт\n";
return 1;
}
std::string line;
while (std::getline(inFile, line)) {
std::cout << "Прочитано: " << line << '\n';
}
inFile.close();
return 0;
}
1.3. Режимы открытия и бинарные файлы
Вы можете комбинировать флаги открытия: ios::app (дозапись), ios::binary (бинарный режим), ios::ate (перейти в конец). Для бинарных файлов используйте методы read() и write().
#include <fstream>
#include <vector>
// Пример: запись и чтение вектора чисел в бинарном формате
void saveVector(const std::string& filename, const std::vector<int>& data) {
std::ofstream file(filename, std::ios::binary);
if (!file) return;
file.write(reinterpret_cast<const char*>(data.data()), data.size() * sizeof(int));
}
std::vector<int> loadVector(const std::string& filename) {
std::ifstream file(filename, std::ios::binary);
if (!file) return {};
file.seekg(0, std::ios::end);
size_t size = file.tellg() / sizeof(int);
file.seekg(0, std::ios::beg);
std::vector<int> data(size);
file.read(reinterpret_cast<char*>(data.data()), size * sizeof(int));
return data;
}
2. Современный подход: std::filesystem (C++17)
Начиная с C++17, в стандартную библиотеку вошёл модуль <filesystem>, который позволяет работать с путями, файлами и директориями на высоком уровне, кроссплатформенно.
2.1. Проверка существования и получение информации о файле
Больше никаких системных вызовов! Всё делается через std::filesystem::exists, file_size, last_write_time и другие функции.
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
fs::path filePath = "example.txt";
if (fs::exists(filePath)) {
std::cout << "Файл: " << filePath << '\n';
std::cout << "Размер: " << fs::file_size(filePath) << " байт\n";
std::cout << "Расширение: " << filePath.extension() << '\n';
} else {
std::cout << "Файл не найден.\n";
}
return 0;
}
2.2. Создание, копирование и удаление файлов и директорий
Основные операции делаются в одну строку. Обратите внимание на обработку ошибок через исключения или перегрузку с std::error_code.
#include <iostream>
#include <filesystem>
namespace fs = std::filesystem;
int main() {
// Создание директории
fs::create_directories("backup/2024");
// Копирование файла
fs::copy("example.txt", "backup/2024/example_copy.txt", fs::copy_options::overwrite_existing);
// Переименование (перемещение)
fs::rename("old_name.txt", "new_name.txt");
// Удаление файла
fs::remove("temp.txt");
// Удаление директории рекурсивно (осторожно!)
// fs::remove_all("temp_dir");
std::cout << "Операции выполнены успешно.\n";
return 0;
}
2.3. Обход директорий: рекурсивный и нерекурсивный
Часто нужно пройтись по всем файлам в папке. std::filesystem предоставляет итераторы: directory_iterator