Форум программистов, компьютерный форум CyberForum.ru

С++ для начинающих

Войти
Регистрация
Восстановить пароль
 
Рейтинг: Рейтинг темы: голосов - 31, средняя оценка - 4.94
zer0mail
2332 / 1958 / 192
Регистрация: 03.07.2012
Сообщений: 7,021
Записей в блоге: 1
#1

Чтение текстовых файлов для новичков (getline) - C++

18.05.2014, 18:29. Просмотров 5455. Ответов 3
Метки нет (Все метки)

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

Сначала общая схема: открывается некий текстовый файл, в буфер считывается порция данных,
затем эта порция обрабатывается. Цикл чтение-обработка порции выполняется, пока не будет достигнут конец файла.

Для реализации этой схемы надо:
1. Выделить буфер для чтения
2. Открыть исходный файл
3. Читаем очередную порцию
Анализируем, что прочитали и в зависимости от результатов анализа:
- обрабатываем строку
- прекращаем обработку или продолжаем чтение

Сначала на примере getline(buf, size)
buf - буфер, куда считаются символы (каждый символ занимает 1 байт) из файла
size - размер считываемой порции. Поскольку прочитанные данные ВСЕГДА ДОПОЛНЯЮТСЯ нулевым байтом,
количество прочитанных символов максимум size-1.

Проблема 1: размер буфера должен быть не меньше size (иначе может произойти программный сбой,
поскольку getline не знает точного размера buf и пишет максимум size байтов с адреса buf)

Проблема 2: что такое "считываемая порция", как определить, что в файле "эта порция" закончилась и началась
"новая порция"? В текстовом файле разделителем порций служит LF (перевод строки, код этого символа=10).
Если откроем текстовый файл текстовым редактором, то сами символы LF он не показывает,
но каждую "порцию" показывает на отдельной строке. Поэтому прочитанные getline данные - это
та же самая строка, которая видна в текстовом редакторе (но при условии, что size > длины строки).


Пусть в файле строка: "АБВГДЕЖЗ";
getline(buf,9) прочитает "АБВГДЕЖЗ", после 'З' запишет 0-й байт (т.е. вся строка прочитана)
getline(buf,8) прочитает "АБВГДЕЖ" , после 'Ж' запишет 0-й байт (последний символ 'З' останется непрочитанным)

Если строка прочитана полностью, то мы ее обрабатываем и читаем следующую.
Проблема 3: мы видим в буфере строку "АБВГДЕЖЗ" (длина size-1), но как узнать - это полностью прочитанная
строка из файла или в файле более длинная строка и прочитано только ее начало?
Для этого надо прочитать флаг fail:
- ложь строка прочитана полностью
- истина строка прочитана не полностью

Проблема 4: Если строка не влезает в буфер, то может произойти ЗАЦИКЛИВАНИЕ программы. После установки флага fail
функция getline() не будет читать данные из файла, она будет просто обнулять первый байт буфера и все.
Чтобы чтение возобновилось, надо сбросить флаг вызовом clear() и тогда getline() дочитает остаток
длинной строки и последующие строки. Поэтому, когда буфер маленький, а строка очень длинная, то придется
вызывать getline() и clear() многократно, но зато мы сможет используя небольшой буфер, читать строки
любой длины. Правда, останется проблема правильной обработки таких длинных строк...

Проблема 5: мы не хотим заморачиваться - влезает строка в буфер или не влезает, а просто читаем и обрабатываем то,
что прочитали. Часто так и делают и это работает, когда буфер заведомо больше всех строк файла. Например, буфер
1024 байтов (или даже 4000), а файл - обычный. Но если строка длиннее - ЗАЦИКЛИВАНИЕ.

С порциями в основном разобрались. Но осталась самая последняя порция, которая завершается не LF, а концом файла.
Пусть в файле строка: "АБВГДЕЖЗ" и сразу конец файла (нет LF)
getline(buf,8) - строка с терминальным символом не влезает в буфер. Эта ситуация рассмотрена выше.

getline(buf,9) - строка влезает в буфер. Строка будет прочитана в буфер и установится флаг eof.

Проблема 6: Не обработали то, что надо
Иногда программист, встретив eof, прекращает чтение файла, но забывает обработать последнюю строку.
И все вроде бы работает правильно... Почему?
Потому что часто после последней строки стоит невидимый символ LF, а конец файла сразу после LF.
В этом случае обрабатывается порция перед последним LF (как описано выше), а дальше данных нет.
А раз между последним LF и концом файла нет данных, то обрабатывать нечего и нет ошибки.
Чтобы узнать, были прочитаны данные или не были, когда достигнут конец файла, надо прочитать флаг fail:
- ложь данные прочитаны
- истина данных нет (но в этом случае все равно в первый байт буфера getline запишет 0).

Проблема 7: Обработали то, чего не было.
Встретив eof программист обрабатывает строку в буфере и заканчивает чтение файла.
Это работает правильно, если при обработке пустой строки ничего никуда не пишется.

Символ LF (перевод строки) играет особую роль разделителя строк: если хватает буфера, функция getline
читает данные строго между ними (или до конца файла). Однако эту роль разделителя может играть и произвольный символ:
getline(buf, size, ';') читает данные порциями, но разделителем порций служит символ ';'.

Проблема 8: В этом случае LF попадает в буфер (код 10) и надо его учитывать.
Символ-разделитель строк не заносится в буфер чтения!

Пример простой программы, которая копирует текстовый файл, построчно считывая его.
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
#include "stdafx.h"
#include <iostream>
#include <fstream>
using namespace std;
int main()
{
    bool fb,ff,fe;              // флаги
    char buf[9];                // буфер, куда будут считываться данные
    strcpy(buf,"+******-");     // заполним буфер, чтобы потом видеть какие именно данные считались
    char *name="b:\input.txt";  // имя открываемого файла
    setlocale(LC_ALL, "Rus");   // чтобы консольный вывод шел кириллицей
    ifstream ifs(name);         // попробуем открыть файл
    ofstream ofs("b:\write.txt");           // попробуем открыть файл
    if (ifs.fail()) {
        // упс, не удалось
        cout<< "Не удалось открыть файл: " <<name;
        return 1;
    }
    // файл открыт, читаем построчно
    while(1) {
        ifs.getline(buf,9); // читаем в буфер 8 символов + терминальный символ (он ВСЕГДА пишется в буфер)
        // читаем флаги и анализируем (тут есть ПОДВОДНЫЕ КАМНИ)
        fb=ifs.bad();           // если установлен, что-то пошло совсем не так...
        ff=ifs.fail();          // дополнительный флаг для анализа
        fe=ifs.eof();           // достигнут конец файла
        ofs<<buf;               // выводим прочитанную строку
        if (fe) break;          // конец файла достигнут, больше читать нечего
        if (ff) {
            // прочитана часть строки, сбросим fail
            ifs.clear(); 
        }
        else {
            // прочитан конец строки LF
            ofs<<endl;          // добавляем перевод строки
        }
    }
}
// более простой но работающий пример цикла чтения-записи:
C++
1
2
3
4
5
6
7
    // СОКРАЩЕННЫЙ, НО РАБОТАЮЩИЙ ПРИМЕР
    // после завершения цикла последняя строка не запишется а длинные строки будут разорваны 
    while(!ifs.getline(buf,9).eof()) {  // читаем в буфер 8 символов + терминальный символ (он ВСЕГДА пишется в буфер)
        ofs<<buf<<endl; // выводим прочитанную строку
        if (ifs.fail()) ifs.clear();    // если закомментировать, длинные строки приведут к зацикливанию
    }
    ofs<<buf; // выводим последнюю строку (без LF!)
Similar
Эксперт
41792 / 34177 / 6122
Регистрация: 12.04.2006
Сообщений: 57,940
18.05.2014, 18:29
Здравствуйте! Я подобрал для вас темы с ответами на вопрос Чтение текстовых файлов для новичков (getline) (C++):

Как написать программу.(чтение текстовых файлов) - C++
Создать файл, состоящий из записей. Каждая запись есть фамилии, имена и отчества студентов. Напишите программу, которая считывает записи из...

программа для шифрования и расшифровки текстовых файлов - C++
программа для шифрования и расшифровки текстовых файлов.

Getline чтение из файла - C++
#include &lt;iostream&gt; using std::cout; using std::cin; using std::endl; using std::ios; using std::cerr; #include &lt;fstream&gt; ...

Чтение из файла, не используя getline - C++
Вот ВЕСЬ код: #include &lt;iostream&gt; #include &lt;string&gt; #include &lt;fstream&gt; #include &lt;iomanip&gt; #include &lt;Windows.h&gt; using...

string, getline() чтение строк - C++
Здравствуйте, помогите пожалуйста написать часть кода. Задача состоит в том, чтобы прочитать данные из строк, отдельные поля дат отделяются...

Чтение из файла: getline() не работает - C++
У меня такая ситуация: Есть два файла: words1.txt и cities-source.txt. Прилагаются. Берем первую строку из words1 и начинаем...

Надоела реклама? Зарегистрируйтесь и она исчезнет полностью.
zss
Модератор
Эксперт С++
6358 / 5922 / 1919
Регистрация: 18.12.2011
Сообщений: 15,218
Завершенные тесты: 1
18.05.2014, 19:26 #2
Цитата Сообщение от zer0mail Посмотреть сообщение
char *name="b:\input.txt"; // имя открываемого файла
1. Вряд ли b: т.к. оно было зарезервировано для флоппи дисков
2. Слаш надо дублировать.
3. Под массив имеет смысл выделить память
C++
1
char name[]="c:\\temp\\input.txt";
Добавлено через 3 минуты
Цитата Сообщение от zer0mail Посмотреть сообщение
while(1) { ifs.getline(buf,9);
Беда в том, что, если в строке больше 8 символов, то дальнейший ввод прекращается
(следующий getline вернет пустую строку) и возвращается false.
Рекомендую buf сделать побольше, а после ввода принудительно писать терминальный символ
C++
1
buf[9]=0;
zer0mail
2332 / 1958 / 192
Регистрация: 03.07.2012
Сообщений: 7,021
Записей в блоге: 1
19.05.2014, 07:17  [ТС] #3
Цитата Сообщение от zss Посмотреть сообщение
1. Вряд ли b: т.к. оно было зарезервировано для флоппи дисков
2. Слаш надо дублировать.
3. Под массив имеет смысл выделить память
1. Флоппи у меня нет, а b: RAM-диск
2. Согласен, в name записалось b:input.txt
3. Способ задать имя файла не принципиален
Цитата Сообщение от zss Посмотреть сообщение
Рекомендую buf сделать побольше, а после ввода принудительно писать терминальный символКод C++
1 buf[9]=0;
1.буфер специально сделан маленьким, чтобы показать, как можно правильно прочитать и обработать длиные строки (не влезающие в буфер).
Цель темы - не просто прочитать файл, а научиться правильно работать с getline и узнать про подводные камни.
2.getline всегда сама пишет терминальный символ
3.для моего буфера buf[9] за его границей (тоже распростаненная ошибка у новичков).
Ilot
Модератор
Эксперт С++
1809 / 1166 / 226
Регистрация: 16.05.2013
Сообщений: 3,070
Записей в блоге: 5
Завершенные тесты: 1
19.05.2014, 08:30 #4
C++
1
2
// прочитана часть строки, сбросим fail
ifs.clear();
Строго говоря это сброс всех флагов. Если уж есть желание сбросить именно флаг failbit то следует писать так:
C++
1
ifs.clear(ifs.rdstate() & ~ios::failbit);
MoreAnswers
Эксперт
37091 / 29110 / 5898
Регистрация: 17.06.2006
Сообщений: 43,301
19.05.2014, 08:30
Привет! Вот еще темы с ответами:

Чтение с текстового файла getline()-ом - C++
Приветствую Всех! У меня возникла проблема с getline() Я должен прочитать из текстового файла строки по порядку в цикле...

Использование массивов и текстовых файлов//это не для слабаков..и если толком ничего не знаете то даже не пытайтесь решить - C++
1. Сформировать массив из произведений положительных элементов каждой строки массива С; если их в строке нет, результат должен быть равен...

Как начать чтение файла сначала после Getline с первой строчки в fstream? - C++
Пишу программу,которая читает строки их текстового файла и передаёт их в поле Edit1 поочерёдно,по нажатию кнопки.Когда строки...

Подскажите пожалуйста хорошую литературу для изучения с++, для новичков - C++
Чтобы подошла человеку , который не сталкивался с программированием , а вот сейчас ему очень срочно и быстро нужно освоить хотя бы главные...


Искать еще темы с ответами

Или воспользуйтесь поиском по форуму:
Yandex
Объявления
19.05.2014, 08:30
Ответ Создать тему
Опции темы

КиберФорум - форум программистов, компьютерный форум, программирование
Powered by vBulletin® Version 3.8.9
Copyright ©2000 - 2017, vBulletin Solutions, Inc.
Рейтинг@Mail.ru