Потоковый ввод-вывод в файлы

Программирование микроконтроллеров / Потоковый ввод-вывод в файлы

 

Работа с файлами с использованием конструкций языка Си была рассмотрена здесь.

Для программиста открытый файл представляется как последовательность считываемых или записываемых данных. При открытии файла с ним связывается поток ввода-вывода. Выводимая информация записывается в поток, вводимая информация считывается из потока.

Для работы с файлами необходимо подключить заголовочный файл <fstream>. В нем определены несколько классов и подключены заголовочные файлы

  • <ifstream> — файловый ввод ;
  • <ofstream> — файловый вывод.

Файловый ввод-вывод аналогичен стандартному вводу-выводу, единственное отличие – это то, что ввод-вывод выполнятся не на экран, а в файл.

Если ввод-вывод на стандартные устройства выполняется с помощью объектов cin и cout, то для организации файлового ввода-вывода достаточно создать собственные объекты, которые можно использовать аналогично этим операторам.

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

  • создать объект класса fstream (возможно, ofstream или ifstream);
  • связать объект класса fstream с файлом, который будет использоваться для операций ввода-вывода;
  • осуществить операции ввода-вывода в файл;
  • закрыть файл.
1
2
3
4
5
6
7
8
9
10
#include <fstream>
using namespace std;
int main()
{
  ofstream fout;
  fout.open("file.txt");
  fout << "Привет, мир!";
  fout.close();
  return 0;
}

В результате будет создан файл
Работа с файлами в C++

Режимы открытия файлов устанавливают характер использования файлов. Для установки режима в классе ios предусмотрены константы, которые определяют режим открытия файлов.

Константа Описание
ios::in открыть файл для чтения
ios::out открыть файл для записи
ios::ate при открытии переместить указатель в конец файла
ios::app открыть файл для записи в конец файла
ios::trunc удалить содержимое файла, если он существует
ios::binary открытие файла в двоичном режиме

Режимы открытия файлов можно устанавливать непосредственно при создании объекта или при вызове метода open().

ofstream fout("file.txt", ios::app);
fout.open("file.txt", ios::app);

Режимы открытия файлов можно комбинировать с помощью поразрядной логической операции ИЛИ |, например:

ios::out | ios::in - открытие файла для записи и чтения.

Произвольный доступ к файлу

Система ввода-вывода С++ позволяет осуществлять произвольный доступ с использованием методов seekg() и seekp().

  • ifstream &seekg(Смещение, Позиция);
  • ofstream &seekp(Смещение, Позиция);

Смещение определяет область значений в пределах файла (long int).

Система ввода-вывода С++ обрабатывает два указателя, ассоциированные с каждым файлом:

  • get pointer g - определяет, где именно в файле будет производиться следующая операция ввода;
  • put pointer p - определяет, где именно в файле будет производиться следующая операция вывода.

Позиция смещения определяется как

Позиция Значение
ios::beg Начало файла
ios::cur Текущее положение
ios::end Конец файла

Всякий раз, когда осуществляются операции ввода или вывода, соответствующий указатель автоматически перемещается.
С помощью методов seekg() и seekp() можно получить доступ к файлу в произвольном месте.

Можно определить текущую позицию файлового указателя, используя следующие функции:

  • streampos tellg() - позиция для ввода
  • streampos tellp() - позиция для вывода

 
Пример на С++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
  system("chcp 1251");
  system("cls");
  char s[80];
  fstream inOut;
  inOut.open("file.txt", ios::out);
  inOut << "строчка текста" << endl;
  inOut.seekp(8, ios::beg);
  inOut << "еще строчка текста";
  inOut.close();
  inOut.open("file.txt", ios::in);
  inOut.seekg(-6, ios::end);
  inOut >> s;
  inOut.close();
  cout << s;
  cin.get();
  return 0;
}

В результате выполнения первой части программы будет создан файл
Создание файла
Вторая часть программы выведет в консоль
Считывание данных из файла

Ещё один пример. Допустим, нам нужно заполнять таблицу

ФИО Дата рождения Хобби
     

Причем каждая вновь введенная строка должна размещаться в таблице непосредственно под "шапкой".

Алгоритм решения задачи следующий:

  • формируем очередную строку для вывода
  • открываем файл для чтения, считываем из него данные и сохраняем их в массив строк
  • закрываем файл
  • открываем файл для записи
  • выводим "шапку" таблицы
  • выводим новую строку
  • выводим все сохраненные строки обратно в файл, начиная со строки после шапки

Пример на C++

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include <iostream>
#include <fstream>
using namespace std;
#define LINES 100 // максимальное количество строк в файле
int main() {
  system("chcp 1251");
  system("cls");
  char line[LINES][100];
  char str[30];
  char s[] = "|                              |                |                              |";
  // Ввод данных для размещаемой строки
  cout << "ФИО: ";
  cin.getline(str, 30); // вводим ФИО
  for (int i = 0; str[i] != '\0'; i++) // копируем в строку без 0
    s[i + 2] = str[i];               // начиная с указанной позиции
  cout << "Дата: ";
  cin.getline(str,30);
  for (int i = 0; str[i] != '\0'; i++)
    s[i + 33] = str[i];
  cout << "Хобби: ";
  cin.getline(str,30);
  for (int i = 0; str[i] != '\0'; i++)
    s[i + 50] = str[i];
  
  fstream inOut;
  inOut.open("file.txt",  ios::in); // открываем файл для ввода
  // Считываем из файла имеющиеся данные
  int count = 0;
  while (inOut.getline(line[count], 100)) count++;
  inOut.close(); // закрываем файл
  
  inOut.open("file.txt", ios::out); // открываем файл для вывода
  inOut << "--------------------------------------------------------------------------------" << endl;
  inOut << "|   ФИО                        |  Дата          | Хобби                        |" << endl;
  inOut << "--------------------------------------------------------------------------------" << endl;
  inOut << s << endl; // выводим сформированную строку
  inOut << "--------------------------------------------------------------------------------" << endl;
  // Выводим обратно в файл все строки кроме "шапки" (первые 3 строки)
  for (int j = 3; j < count; j++)
  {
    inOut << line[j] << endl;
  }
  inOut.close();
  cin.get();
  return 0;
}

Результат выполнения:
Ввод строки таблицы
Ввод строки таблицы
Ввод строки таблицы

Полученный файл данных:
Файл данных


Назад: Программирование микроконтроллеров

Комментариев к записи: 31

  • Здравствуйте, подскажите как считать данные из файла, сохранённых в следующем виде: Check Type | Check name | Cost check | 12 |Full |50000 | 38 |Half |25000 | 156 |Special |79000 | и записать данные в структуру. Например: Поле "Struct->CheckType" имеет значение "12" Поле "Struct->CheckName" имеет значение "Full" Поле "Struct->CostCheck" имеет значение "50000"

    • Елена Вставская
      Считывать по строкам, далее строку разделять по символу | и присваивать значения полям структуры. Если поля структуры целочисленные, то предусмотреть ещё перевод из строки в число.

  • Здравствуйте. Возникла проблема, когда пытался переделать ваш код под себя. Выдается очень странный баг, хотя программа компилируется. Я добавил возможность использования программы повторно, без перезапуска консоли, через цикл while. И немного изменил шапку и ее ввод. В общем вот код. Если получится найти причину, буду очень благодарен.
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    #include <iostream>
    #include <fstream>
    using namespace std;
    #define LINES 100 
    int main() {
      system("chcp 1251");
      system("cls");
      int check = 1;
      char line[LINES][100];
      char str[41];
      char s[] = "|                    |                    |                                        |                    |                    |                    |                         |";
      while (check == 1) {
        cout << "Имя : ";
        cin.getline(str, 20);
        for (int i = 0; str[i] != '\0'; i++)
          s[i + 1] = str[i];
        cout << "Фамилия : ";
        cin.getline(str, 20);
        for (int i = 0; str[i] != '\0'; i++)
          s[i + 22] = str[i];
        cout << "Адрес проживания : ";
        cin.getline(str, 40);
        for (int i = 0; str[i] != '\0'; i++)
          s[i + 43] = str[i];
        cout << "Город : ";
        cin.getline(str, 20);
        for (int i = 0; str[i] != '\0'; i++)
          s[i + 84] = str[i];
        cout << "Почтовый индекс : ";
        cin.getline(str, 20);
        for (int i = 0; str[i] != '\0'; i++)
          s[i + 105] = str[i];
        cout << "Номер телефона : ";
        cin.getline(str, 20);
        for (int i = 0; str[i] != '\0'; i++)
          s[i + 126] = str[i];
        cout << "Адрес эл. почты : ";
        cin.getline(str, 25);
        for (int i = 0; str[i] != '\0'; i++)
          s[i + 147] = str[i];

        fstream abonent;
        abonent.open("file.txt", ios::in);

        int count = 0;
        while (abonent.getline(line[count], 100)) count++;
        abonent.close();

        abonent.open("file.txt", ios::app);
        abonent.seekg(0, ios::end);
        if (abonent.tellg() == 0) {
          abonent << "-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------" << endl;
          abonent << "|        Имя         |      Фамилия       |            Адрес проживания            |       Город        |  Почтовый индекс   |   Номер телефона   |     Адрес эл. почты     |" << endl;
          abonent << "-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------" << endl;
        }
        abonent << s << endl;
        abonent << "-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------" << endl;
        abonent.close();
        cout << "Продолжить? ";
        cin >> check;
      }
      cin.get();
      return 0;
    }

    • Елена Вставская
      Пока вижу 2 бага, не знаю, какой Вы имеете в виду. 1. Когда вводим данные второго и последующих лиц, программа "глотает" Имя. 2. Если, допустим, фамилия второго человека окажется короче, чем у первого, то она будет дополнена "остатком" предыдущей фамилии.


  • Николай
    Подскажите, а можно определить середину строки s, в файле при помощи tellg/tellp или seekg/seekp?

    • Елена Вставская
      Считать всю строку. Найти ее длину с помощью strlen() и сместить курсор на "минус" половину этого значения от текущей позиции.

      • Николай
        Наверно не то что Вы имели ввиду?
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        #include <iostream>
        using namespace std;
        int main()
        {
            string buf;
            ifstream infile;
            infile.open("text.txt", ios::in);
            if(infile.is_open()){
                infile >> buf;
                short n = strlen(buf);
                short k=0;
                if(n%2) {
                    k = n/2;
                    cout << buf[k];
                }
                else cout << buf[k] << " " << buf[k+1];
            }
            else{
                cout << "File error."<< endl;
                return 1;
            }
            infile.close();
        return 0;
        }

  • Николай
    Спасибо за статью. Познавательно. Пробую использовать данный вывод в файл, но возникла проблема: Файл создается и очищается (проверено) но запись в файл не происходит, и после выполнения данного кода
    1
    2
    3
    4
    5
    6
    7
    std::string textoutbuf = "yjjfhjshdsjhf dkcfjdhj jfcjh 111111";
    textoutbuf.push_back('\n');
    //m_sock.fileLog << "yjjfhjshdsjhf dkcfjdhj jfcjh 111111";
    m_sock.fileLog << textoutbuf;
    INT qqq = GetLastError();
    m_sock.fileLog.flush();//очистить поток
    m_sock.fileLog.close();
    файл всегда нулевой длины. Что уже не пробовал, читал форумы но ответа пока не нашел. Может Вы подскажете в чем может быть проблема? Спасибо.

    • Елена Вставская
      Возможно, я чего-то не учитываю, поскольку приведен только фрагмент кода. Но такая реализация позволит получить файл с данными:
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      #include <fstream>
      using namespace std;
      int main()
      {
        ofstream m_sock;
        m_sock.open("C://1/file.txt");
        char textoutbuf[] = "yjjfhjshdsjhf dkcfjdhj jfcjh 111111\n";
        m_sock << textoutbuf;
        m_sock << "yjjfhjshdsjhf dkcfjdhj jfcjh 111111";
        m_sock.flush();
        m_sock.close();
        return 0;
      }


  • А как записать данные в файл с названием, определяемым самим пользователем? Насколько я понял, в качестве названия файла можно использовать только константу (?)

    • Елена Вставская
      Нет, Вы можете ввести имя файла в ходе выполнения программы.
      1
      2
      3
      4
      5
      char filename[40];
      cout >> "filename: ";
      cin << filename;
      inOut.open(filename,  ios::in);
      ...

  • извините, я наверно вас уже заколебал, с этой 4 строчкой... Но мне надо прочитать строчку из файла и вывести ее в консоль. Т.е. В файле есть 4 строка мне ее надо прочитать и вывести на экран. inOut.open("file.txt",ios::out); for(int i=0; i<4; i++) cout<<line[i]; выводит белиберду. То есть я хочу теперь эту табличку на экране увидеть, но не всю, а только ее часть, например строку 4.
    for(int i=0; i<100; i++) cout<<line[4][100]; Тоже не  работает

  • 1
    while ( inOut.getline (line[count], 100) ) count++;
    А как происходит перебор строк? Я как понимаю, мы читаем строку в массив line[1]; Потом читаем еще раз строку, но в массив line[2]; и т.д. до line[100]. А где мы говорим, что мы не читаем одну и туже строчку то? Как встать то на строчку 5? И прочитать что в строке 5 У нас Петров Петр.

    • Почему если в файле "file.txt" есть информация, то данный код не просто читает он удаляет все из файла?

      • Елена Вставская
        При считывании строки указатель позиции в файле автоматически смещается на её длину. Поэтому если мы считаем 4 строки, то окажемся на начале 5-ой. Считав её, мы получим нужную нам информацию.
        Если данные из файла считываются, то они не удаляются. Удаление данных происходит только при открытии файла в режиме записи (строка 32 последнего примера в статье).

        • Спасибо большое за ответы. Но а как мне получить строку под номером 4? Допустим я не знаю что в файле, и я хочу прочитать только 4 строчку. line[4] выводит все четвертые буквы всех строк, но не всю строку целиком. Может как то можно получить массив строк и массив символов за раз? Например my_line_txt[4][5] - четвертая строка 5 символ.

          • Елена Вставская
            line[4] - это и есть 4-ая строка. Первый индекс в массиве line[][] - это номер строки, второй - номер символа в строке.
            1
            2
            3
            int i;
            for(i=0; i<4; i++) inOut.getline(line[i], 100);
            inOut.getline(line[i], 100); // вот Ваша 4-ая строка


  • Подскажите пожалуйста (нигде найти не могу) Как выводить в файл строки сверху. было То, как зверь, она завоет, То заплачет, как дитя, Надо вставить сверху строчки, что бы получилось так: Буря мглою небо кроет, Вихри снежные крутя; То, как зверь, она завоет, То заплачет, как дитя, Только не говорите что для этого отдельный файл создавать надо....:)

    • Если использовать флаг ios::beg Да, он ставит курсор в начало файла. Но он удалит весь текст который там был. И вставить строчку не получиться. Вместо вставки происходит замена.

      • Елена Вставская
        Я, конечно, не буду говорить, что нужно создавать отдельный файл :) Но придётся считать всю информацию из файла и где-то её сохранить. Потом открыть файл для записи. Записать недостающие строки сверху, а потом снова выгрузить сохраненное содержимое файла.

        • А если надо  заполнить такую таблицу что бы последние введенные данные были наверху. То есть, есть шапка которую нельзя трогать. файл 1 "с шапкой" файл 2 "с новой строкой" файл 3 = файл 1+файл2; // итоговый наш файл файл 2 = получили новую строку файл 4 = скопировать все с 4 строчки и ниже из файла №3 файл 3 = встаем на четвертую строчку и добавляем файл №2 с нашей строкой файл 3 = копируем все из файла 4. Только так можно это реализовать? Как можно установить курсор на 4 строчку? ----------------------------------------------------------------------------------- | Фамилия  | Имя  |  Отчество  |   дата рождения  |   хобби   | ----------------------------------------------------------------------------------- |_________|_____|__________|_______________|________| |_________|_____|__________|_______________|________| |_________|_____|__________|_______________|________| |_________|_____|__________|_______________|________|

          • Как можно установить курсор на 4 строчку? И как можно скопировать все что ниже 4 строчки. Или удалить все что выше 4 строчки.

          • Елена Вставская
            Ответила в тексте статьи


  • Блог программиста
    Хорошая статья, но: 1) в последнем листинге используются операторы >> и <<, в статье не объясняется как именно они работают; 2) не упоминается функция getline - это единственный способ считать строку целиком; 3) в начале упоминается про бинарные файлы, но не описано как с ними работать; 4) если не всегда, то очень часто, входной файл обрабатывается до тех пор, пока не будут считаны все элементы. В статье нет слова про feof; 5) что если файл не удалось открыть? - там целый комплекс флажков есть и еще можно объект сравнить с нулем (есть перегруженный оператор для этого, который проверяет набор каких-то флажков) - так очень часто делают; 6) я не знаю что случится при попытке переместить каретку чтения в файле на недоступную позицию, но думаю, что вылетит исключение. Я могу посмотреть в документации, конечно, но мог бы и у вас прочитать (так-то в документации про seekp тоже написано); 7) я думаю, стоит упомянуть, что если файл не закрывать - то он закроется сам когда объект будет разрушен. Ну потому, что так-то, мне кажется, файлы не часто закрывают - это нужно делать только если есть вероятность, что программа завершится настолько аварийно, что до деструктора файла дело не дойдет. ЗЫ:. вот этот system("cls") я бы лично не использовал (как и system вообще) - некроссплатформенно это... По крайней мере по возможности его стоит избегать, никакой острой необходимости вставлять его в такие учебные статьи я не вижу ((

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *