Чтение и запись файлов в C++: ifstream, ofstream и fstream — практическое...

Чтение и запись файлов в C++: ifstream, ofstream и fstream — практическое руководство

Чтение и запись файлов в C++: ifstream, ofstream и fstream — практическое руководство

Этот материал — ответ на частый запрос «C++ чтение и запись файлов: ifstream/ofstream примеры». Разберём базовые классы потоков, режимы открытия, безопасные паттерны, чтение построчно и побайтно, а также двоичные файлы и обработку ошибок. Все примеры компилируются и подходят для начинающих и продолжающих.

Базовые классы для работы с файлами

  • std::ifstream — чтение (input file stream)
  • std::ofstream — запись (output file stream)
  • std::fstream — и чтение, и запись
  • RAII освобождает файл автоматически при разрушении объекта потока — закрывать вручную обычно не требуется.

    Открытие файла и проверка ошибок

    #include <fstream>
    #include <iostream>
    #include <string>
    
    int main() {
        std::ifstream in("input.txt");
        if (!in.is_open()) {
            std::cerr << "Не удалось открыть input.txtn";
            return 1;
        }
    
        std::string line;
        size_t lines = 0;
        while (std::getline(in, line)) {
            ++lines;
        }
        std::cout << "Строк в файле: " << lines << "n";
    }
    

    Если файл не открылся, проверьте путь, права и существование каталога.

    Режимы открытия файла

  • std::ios::in — чтение (по умолчанию для ifstream)
  • std::ios::out — запись (по умолчанию для ofstream)
  • std::ios::app — дозапись в конец
  • std::ios::trunc — обрезать файл при открытии
  • std::ios::binary — двоичный режим (важно на Windows)
  • std::ofstream out("log.txt", std::ios::app);           // дозапись
    std::ifstream binIn("data.bin", std::ios::binary);      // двоичный
    std::fstream  both("db.dat", std::ios::in | std::ios::out | std::ios::binary);
    

    На Windows текстовый режим меняет переводы строк. Для двоичных данных всегда используйте std::ios::binary.

    Чтение построчно и по словам

    #include <fstream>
    #include <iostream>
    #include <string>
    #include <limits>
    
    int main() {
        std::ifstream in("config.txt");
        if (!in) return 1;
    
        // Пример: сначала число, потом строка с пробелами
        int version = 0;
        in >> version;                 // читает до пробела/перевода строки
        in.ignore(std::numeric_limits<std::streamsize>::max(), 'n');
    
        std::string title;
        std::getline(in, title);        // корректно прочитает всю строку
    
        std::cout << "version=" << version << ", title=" << title << "n";
    }
    

    Альтернатива для пропуска пробелов перед getline: std::getline(in >> std::ws, line).

    Запись в файл: переносы строк и буферизация

    #include <fstream>
    #include <string>
    
    int main() {
        std::ofstream out("report.txt"); // trunc по умолчанию
        if (!out) return 1;
    
        out << "Отчёт за день" << 'n';  // 'n' быстрее, чем std::endl
        out << "Всего записей: " << 42 << 'n';
    }
    

    Совет: используйте 'n' вместо std::endl, чтобы не форсировать лишний flush.

    Работа с путями: std::filesystem

    #include <filesystem>
    #include <fstream>
    
    int main() {
        namespace fs = std::filesystem;
        fs::path p = "output/logs/app.log";
        fs::create_directories(p.parent_path());
    
        std::ofstream out(p, std::ios::app);
        out << "Started" << 'n';
    }
    

    Так вы избежите ошибок «No such file or directory», если каталога ещё нет.

    Считать весь файл в строку

    #include <fstream>
    #include <string>
    #include <iterator>
    
    std::string read_all(const std::string& path) {
        std::ifstream in(path, std::ios::binary);
        if (!in) return {};
        return std::string(
            std::istreambuf_iterator<char>(in),
            std::istreambuf_iterator<char>()
        );
    }
    

    Подходит для небольших файлов (напр., конфигураций, шаблонов).

    Двоичные файлы: чтение и запись

    #include <fstream>
    #include <type_traits>
    #include <vector>
    
    struct Record {
        int    id;
        double value;
    };
    static_assert(std::is_trivially_copyable_v<Record>);
    
    int main() {
        // Запись
        {
            std::ofstream out("data.bin", std::ios::binary);
            Record r{123, 45.67};
            out.write(reinterpret_cast<const char*>(&r), sizeof(r));
        }
    
        // Чтение
        {
            std::ifstream in("data.bin", std::ios::binary);
            Record r{};
            in.read(reinterpret_cast<char*>(&r), sizeof(r));
        }
    }
    

    Такой подход быстрый, но не переносимый между архитектурами из-за выравнивания/эндианности. Для устойчивых форматов используйте сериализацию (JSON/CBOR/Protobuf и пр.) или фиксированный протокол.

    Исключения вместо ручных проверок

    #include <fstream>
    #include <iostream>
    
    int main() {
        try {
            std::ifstream in;
            in.exceptions(std::ios::failbit | std::ios::badbit);
            in.open("input.txt", std::ios::binary);
    
            std::string s((std::istreambuf_iterator<char>(in)), {});
            std::cout << "Прочитано байт: " << s.size() << "n";
        } catch (const std::ios_base::failure& e) {
            std::cerr << "Ошибка ввода-вывода: " << e.what() << "n";
        }
    }
    

    Маска исключений заставляет поток бросать исключение при ошибках открытия/чтения/записи.

    Мини-пример: копирование файла с подсчётом строк

    #include <fstream>
    #include <iostream>
    #include <string>
    
    int main(int argc, char** argv) {
        if (argc < 3) {
            std::cerr << "Usage: copy_with_count <src> <dst>n";
            return 1;
        }
    
        std::ifstream in(argv[1]);
        if (!in) { std::cerr << "Не открыть: " << argv[1] << "n"; return 1; }
        std::ofstream out(argv[2]);
        if (!out) { std::cerr << "Не создать: " << argv[2] << "n"; return 1; }
    
        std::string line; size_t n = 0;
        while (std::getline(in, line)) {
            ++n;
            out << line << 'n';
        }
        std::cout << "Скопировано. Строк: " << n << "n";
    }
    

    Частые ошибки и как их избежать

  • Не проверяете открытие файла — всегда проверяйте is_open() или настраивайте исключения.
  • Смешиваете >> и std::getline — используйте ignore или std::ws.
  • Забываете std::ios::binary для двоичных данных на Windows — файлы портятся из-за перевода строк.
  • Чрезмерно используете std::endl — замените на 'n' для производительности.
  • Записываете «сырые» структуры в двоичный файл для обмена между системами — лучше используйте переносимые форматы.
  • Вывод

    Теперь вы умеете надёжно открывать файлы, читать по строкам, писать отчёты, работать с двоичными данными и обрабатывать ошибки. Эти приёмы покрывают 80% типовых задач «чтение и запись файлов в C++» и помогут избегать подводных камней на Windows и Linux.

    Хотите системно закрыть пробелы в языке и прокачать практику? Рекомендую посмотреть программу и стартовать обучение по курсу «Программирование на C++ с Нуля до Гуру» — узнать детали курса и забрать первые уроки.

    Источник

    НЕТ КОММЕНТАРИЕВ

    Оставить комментарий