Чтение и запись данных в файл в текстовом режиме

Практический курс по C/C++: https://stepik.org/course/193691

Продолжаем тему работы с файлами с помощью классов:

  • ifstream – для чтения данных из файла;
  • ofstream – для записи данных в файл;
  • fstream – для записи и чтения данных из файла.

Пусть в программе открывается файл на запись в текстовом режиме доступа:

#include <iostream>
#include <fstream>
 
using std::ios;
 
int main()
{
    std::ofstream ofs("out_course.dat");    // открытие файла
 
    ofs.close();            // закрытие файла
    return 0;
}

Чтобы поместить в него какие-либо данные базовых типов, достаточно воспользоваться операцией <<, например, следующим образом:

    if(ofs.is_open()) {
        ofs << 10 << -5.34 << -34 << std::endl;
    }

Обратите внимание, что мы можем по цепочке прописывать сохраняемые в файл данные, а также использовать стандартную функцию endl для перевода строки и очистки буфера выходного файлового потока.

Если после выполнения программы посмотрим на содержимое файла out_course.dat, то увидим строку:

10-5.34-34

Как вы понимаете, эти данные будет сложно прочитать, так как между числами нет никакого разделения. Давайте добавим пробел:

    if(ofs.is_open()) {
        ofs << 10 << " " << -5.34 << " " << -34 << std::endl;
    }

Теперь данные в файле четко разделены между собой через пробел:

10 -5.34 -34

и их относительно просто можно прочитать. Для этого воспользуемся файловым потоком, открытым с помощью класса ifstream, следующим образом:

int main()
{
    ...
 
    // ------------- чтение данных из файла --------------------------------
    int data_i1 {}, data_i2 {};
    double data_d1 {};
 
    std::ifstream ifs("out_course.dat");
    
    if(ifs.is_open()) {
        ifs >> data_i1 >> data_d1 >> data_i2;
    }
    std::cout << data_i1 << " " << data_d1 << " " << data_i2 << std::endl;
 
    ifs.close();            // закрытие файла
 
    return 0;
}

Как видите, для чтения данных произвольного типа используется операция >>, которую также можно вызывать по цепочке. При этом пробел автоматически воспринимается, как разделитель между данными, подобно тому, как это было для функции scanf() языка Си. И, разумеется, формат читаемых данных должен совпадать с типом переменных, в которые происходит чтение. Например, если совпадений по типам не будет:

    if(ifs.is_open()) {
        ifs >> data_i1 >> data_i2 >> data_d1;
    }

То данные будут прочитаны некорректно.

Запись и чтение строк

Но это были числа. Что если нам нужно записывать и считывать строки целиком? Нет ничего проще. Запись строк происходит совершенно так же, как и запись чисел. Например:

    if(ofs.is_open()) {
        ofs << 10 << " " << -5.34 << " " << -34 << "\n";
        ofs << "I'm Sergey Balakirev" << std::endl;
    }

В выходном файле видим эту строку. А вот чтение имеет свои нюансы. Опять же, операция >> работает по аналогии с функцией scanf(), а значит, читает строку до первого пробела. Например:

    std::string str;
 
    if(ifs.is_open()) {
        ifs >> data_i1 >> data_d1 >> data_i2;
        ifs >> str;
    }
 
    std::cout << str << std::endl;

Увидим фрагмент:

I'm

Причем, обратите внимание, так как объект-строка str основывается на динамическом массиве, то выход за его пределы при чтении длинных строк происходить не будет. То есть, это безопасная операция. А вот если бы мы воспользовались обычным массивом символов:

char msg[100];
ifs >> msg;

то вполне можно выйти за его пределы и записать информацию в недопустимые ячейки памяти. Работа такой программы становится непредсказуемой. Об этом всегда следует помнить, при работе со строками.

Итак, вернем прежний вариант строки str и спрашивается, как же все-таки прочитать всю строку до символа перевода строки, либо до конца файла? Для этого можно воспользоваться уже знакомой нам функцией getline() следующим образом (не забываем прописать: #include <string>):

    if(ifs.is_open()) {
        ifs >> data_i1 >> data_d1 >> data_i2;
        std::getline(ifs, str);
        std::getline(ifs, str);
    }

Обратите внимание, мне пришлось дважды вызвать функцию getline() для чтения следующей строки. Дело в том, что после чтения числовых данных указатель файла ведет на символ переноса строки, поэтому первая функция его читает и выдает пустую строку, а следующая уже читает с новой строки все символы до следующего перевода строки. Поэтому, для пропуска всех пустых строк, можно записать следующую конструкцию:

    if(ifs.is_open()) {
        ifs >> data_i1 >> data_d1 >> data_i2;
        str = "";
        while(str.length() == 0)
            std::getline(ifs, str);
    }

На следующем занятии мы с вами посмотрим, как происходит работа с файлами в бинарном режиме и для каких данных его целесообразно применять.

Практический курс по C/C++: https://stepik.org/course/193691

Видео по теме