Три класса для файлов
В <fstream> живут три класса, все наследуются от потоков и работают через знакомые <</>>:
std::ifstream— только чтение;std::ofstream— только запись;std::fstream— и то, и другое.
#include <fstream>
#include <iostream>
#include <string>
std::ofstream out("log.txt"); // создаст или обрежет файл
out << "первая строка\n";
out.close(); // явно — или дождётся деструктора
std::ifstream in("log.txt");
std::string line;
while (std::getline(in, line)) { // читаем построчно
std::cout << line << "\n";
}
Файлы — RAII-объекты: деструктор сам закрывает дескриптор. Но проверка ошибок — ваша задача.
Проверка открытия и ошибок
std::ifstream in("data.txt");
if (!in.is_open()) { // или if (!in)
std::cerr << "не удалось открыть\n";
return 1;
}
Состояния потока: good(), eof(), fail(), bad(). Типичная правильная идиома чтения до конца:
std::string line;
while (std::getline(in, line)) { // ok
// обработка
}
// после цикла eof() — нормально; fail() без eof — ошибка формата
Антипаттерн while (!in.eof()) — он обрабатывает последнюю строку дважды: eof выставляется после неудачного чтения, а не до.
Запись и дозапись
std::ofstream out("log.txt"); // перезаписать
std::ofstream app("log.txt", std::ios::app); // дозапись в конец
std::ofstream tr("log.txt", std::ios::trunc); // явно обрезать
out << "x = " << 42 << "\n"; // форматированный вывод
out << std::fixed << std::setprecision(2) << 3.14159; // "3.14"
Важно: данные буферизуются. close() или деструктор сбрасывают буфер на диск. Для «напиши прямо сейчас» — out.flush() или std::endl (flush + newline).
Бинарный режим
Для не-текстовых данных открывают с std::ios::binary и работают через read/write сырыми байтами:
struct Header { uint32_t magic; uint32_t version; };
// запись структуры
std::ofstream out("data.bin", std::ios::binary);
Header h{0xDEADBEEF, 1};
out.write(reinterpret_cast<const char*>(&h), sizeof(h));
// чтение
std::ifstream in("data.bin", std::ios::binary);
Header h2{};
in.read(reinterpret_cast<char*>(&h2), sizeof(h2));
В бинарном режиме на Windows не происходит трансляция \n в \r\n — критично для строгих форматов.
Чтение всего файла сразу
// строка со всем содержимым
std::ifstream in("file.txt", std::ios::binary);
std::string content((std::istreambuf_iterator<char>(in)),
std::istreambuf_iterator<char>());
// или для больших файлов — через размер
in.seekg(0, std::ios::end);
auto size = in.tellg();
in.seekg(0, std::ios::beg);
std::string buf(size, '\0');
in.read(buf.data(), size);
std::filesystem: пути, папки, размер
С C++17 есть std::filesystem — кроссплатформенная работа с файловой системой:
#include <filesystem>
namespace fs = std::filesystem;
fs::exists("data.txt");
fs::file_size("data.txt");
fs::create_directories("logs/2026/10");
fs::copy_file("a.txt", "b.txt", fs::copy_options::overwrite_existing);
fs::remove("old.txt");
// обход директории
for (const auto& entry : fs::directory_iterator(".")) {
std::cout << entry.path() << " "
<< (entry.is_directory() ? "[dir]" : "") << "\n";
}
// склейка путей — переносимо
fs::path p = fs::path("dir") / "subdir" / "file.txt";
p.extension(); // ".txt"
p.stem(); // "file"
p.filename(); // "file.txt"
Позиционирование и строки вместо файлов
in.seekg(0); // в начало
in.seekg(-10, std::ios::end); // за 10 байт до конца
auto pos = in.tellg(); // текущая позиция
// std::stringstream — поток в памяти
std::stringstream ss;
ss << "x=" << 42;
std::string s = ss.str();
int x; ss >> x; // парсинг из строки
Типичные ошибки
while (!eof())— лишняя итерация; пишитеwhile (getline(...)).- Не проверять открытие — молчаливые пустые данные.
<<в бинарный файл — смешение текстового и бинарного вывода.- Чтение
readбез проверкиgcount()— частичное чтение не заметите. - Разделители путей
/vs\— используйтеstd::filesystem::pathили/(работает и на Windows).
Итог
Минимум: ifstream/ofstream для текста, ios::binary + read/write для байтов, getline в цикле, проверка is_open, std::filesystem для путей и директорий. Файлы — RAII, деструктор сам всё закроет; за вами остаётся только проверка ошибок и правильный режим.
Полный пример: логгер в файл
class Logger {
std::ofstream out_;
public:
Logger(const std::string& path) : out_(path, std::ios::app) {
if (!out_) throw std::runtime_error("не открыть лог: " + path);
}
void log(std::string_view msg) {
out_ << timestamp() << " " << msg << '\n';
// flush дорого — не делаем на каждую строку; destructor сделает при закрытии
}
~Logger() { out_ << "--- конец сессии ---\n"; } // автозакрытие
};
Работа с путями: std::filesystem подробнее
namespace fs = std::filesystem;
fs::path p = "/var/log/app/error.log";
p.filename(); // "error.log"
p.stem(); // "error"
p.extension(); // ".log"
p.parent_path(); // "/var/log/app"
fs::create_directories("/tmp/a/b/c"); // mkdir -p
for (const auto& e : fs::directory_iterator("/tmp"))
if (e.is_regular_file()) std::cout << e.path() << '\n';
fs::remove("temp.txt");
fs::rename("old.txt", "new.txt");
fs::copy_file("a", "b", fs::copy_options::overwrite_existing);
Парсинг простого конфига
std::unordered_map<std::string, std::string> readConfig(const std::string& path) {
std::unordered_map<std::string, std::string> cfg;
std::ifstream f(path);
std::string line;
while (std::getline(f, line)) {
auto pos = line.find('=');
if (pos == std::string::npos) continue;
cfg[line.substr(0, pos)] = line.substr(pos + 1);
}
return cfg;
}
Бинарная запись структуры — осторожно
Записывать struct напрямую через write(reinterpret_cast<char*>(&s), sizeof(s)) рискованно: padding-байты и порядок байт делают файл непереносимым между платформами и компиляторами. Для сериализации — явные форматы (JSON, protobuf, собственная упаковка полей).
Обработка ошибок стримов
Стримы имеют флаги: good(), fail(), bad(), eof(). eof становится true только после попытки чтения за концом — классическая ошибка while(!eof()) читает лишнюю итерацию. Правильно: while (getline(f, line)) или while (f >> x) — проверка самого чтения.