Поиск конца файла при чтении из него

void graph::fillTable()
{
  ifstream fin;
  char X;
  int slot=0;

  fin.open("data.txt");

  while(fin.good()){

  fin>>Gtable[slot].Name;
  fin>>Gtable[slot].Out;
  cout<<Gtable[slot].Name<<endl;
  for(int i=0; i<=Gtable[slot].Out-1;i++)
    {
      **//cant get here**
    fin>>X;
    cout<<X<<endl;
    Gtable[slot].AdjacentOnes.addFront(X);
    }
  slot++;
  }
 fin.close();
}

Это мой код, в основном он делает именно то, что я хочу, но он продолжает читать, когда файл уже не годится. Он будет вводить и выводить все, что я ищу, а затем, когда файл подходит к концу, fin.good(), по-видимому, не возвращает false. Вот текстовый файл.

A 2 B F

B 2 C G

C 1 H

H 2 G I

I 3 A G E

F 2 I E

и вот вывод

A
B
F
B
C
G
C
H
H
G
I
I
A
G
E
F
I
E

Segmentation fault

-

Вот тип Gtable.

struct Gvertex:public slist
  {
    char Name;
    int VisitNum;
    int Out;
    slist AdjacentOnes;
    //linked list from slist
  };

Я ожидаю, что он остановится после вывода «E», который является последним символом в файле. Программа никогда больше не входит в цикл for после чтения последнего символа. Я не могу понять, почему время не ломается.


person Tyler Pfaff    schedule 23.11.2011    source источник
comment
Я также пытался использовать eof() безрезультатно.   -  person Tyler Pfaff    schedule 23.11.2011
comment
почему вы думаете, что это segfaults при чтении? что такое гтабл? не более ли вероятно, что вы обращаетесь к несуществующему индексу? что вам сказал отладчик о точном месте и инструкции, ведущей к segfault?   -  person PlasmaHH    schedule 23.11.2011
comment
Это может быть связано с чем-то другим, но я не хочу выполнять какие-либо операции после завершения файла, независимо от того. Я почти уверен, что он ошибается при попытке прочитать пустую часть файла. Gtable — это массив объектов. Этот массив инициализирован до 100 слотов только для того, чтобы убедиться, что я не выйду из индекса!   -  person Tyler Pfaff    schedule 23.11.2011
comment
@TylerPaff: знаете ли вы, что любой тип eof() или good() устанавливается только после сбоя чтения, и что обычно вы должны проверять, успешно ли прочитано чтение, и после что спросить причину через eof() ?   -  person PlasmaHH    schedule 23.11.2011
comment
Вы знаете, что стандартная библиотека C++ обеспечивает автоматическое изменение размеров контейнеров, да? Почему вы возитесь с неуклюжими и подверженными ошибкам массивами? Кроме того, можем ли мы увидеть определение того, что Gtable является массивом?   -  person Karl Knechtel    schedule 23.11.2011
comment
@Karl Knetchel, вы можете посмотреть на теги, я бы хотел использовать контейнер с изменяемым размером!   -  person Tyler Pfaff    schedule 23.11.2011
comment
О, как жаль, что ваш инструктор — один из тех, кто считает, что изобретать велосипед плохо и без руководства имеет образовательную ценность. :(   -  person Karl Knechtel    schedule 23.11.2011
comment
:( На самом деле все мои инструктора учат именно так. Вы бы видели потрясающий векторный урок, который я провел в прошлом году сарказм   -  person Tyler Pfaff    schedule 23.11.2011
comment
@PlasmaHH Это еще хуже. Не указано, установлены ли eof() или good() до или после последней ошибки чтения. Если eof() равно true, следующее чтение завершится ошибкой, но не сообщается, было ли предыдущее чтение успешным или неудачным. И если eof() ложно, предыдущее чтение было успешным, но следующее чтение может быть успешным или неудачным. good() страдает от той же проблемы, потому что включает в себя eofbit.   -  person James Kanze    schedule 23.11.2011


Ответы (5)


Ваше условие в цикле while неверно. ios::eof() не предсказывает; он будет установлен только после того, как поток попытается (внутренне) прочитать за пределами конца файла. Вы должны проверять после каждого ввода
.

Классический способ обработки вашего случая состоял бы в том, чтобы определить функцию >> для GTable в соответствии с строками:

std::istream&
operator>>( std::istream& source, GTable& dest )
{
    std::string line;
    while ( std::getline( source, line ) && line.empty() ) {
    }
    if ( source ) {
        std::istringstream tmp( line );
        std::string name;
        int count;
        if ( !(tmp >> name >> count) ) {
            source.setstate( std::ios::failbit );
        } else {
            std::vector< char > adjactentOnes;
            char ch;
            while ( tmp >> ch ) {
                adjactentOnes.push_back( ch );
            }
            if ( !tmp.eof() || adjactentOnes.size() != count ) {
                source.setstate( std::ios::failbit );
            } else {
                dest.Name = name;
                dest.Out = count;
                for ( int i = 0; i < count; ++ i ) {
                    dest.AdjacentOnes.addFront( adjactentOnes[ i ] );
                }
            }
        }
    }
    return source;
}

(Это было написано довольно поспешно. В реальном коде я бы почти наверняка выделил внутренний цикл в отдельную функцию.)

Обратите внимание, что:

  • Читаем построчно, чтобы проверить формат (и разрешить повторную синхронизацию в случае ошибки).

  • Ставим failbit в исходном потоке на случай ошибки ввода.

  • Мы пропускаем пустые строки (поскольку ваш ввод явно их содержит).

  • Мы не изменяем целевой элемент, пока не убедимся, что ввод правильный.

У нас есть это, легко перебрать все элементы:

int slot = 0;
while ( slot < GTable.size() && fin >> GTable[ slot ] ) {
    ++ slot;
}
if ( slot != GTable.size )
    //  ... error ...

РЕДАКТИРОВАТЬ:

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

РЕДАКТИРОВАТЬ 2:

Учитывая количество неверных ответов на этот вопрос, я хотел бы подчеркнуть:

  • Любое использование fin.eof() до того, как известно, что ввод не пройден, является неправильным.

  • Любое использование fin.good(), точка, неправильно.

  • Любое использование одного из значений, считанных до проверки успешного ввода, является неправильным. (Это не предотвращает такие вещи, как fin >> a >> b, если ни a, ни b не используются до проверки успеха.)

  • Любая попытка прочитать Gtable[slot] без проверки того, что slot находится в границах, неверна.

Что касается eof() и good():

Базовый класс istream и ostream определяет три бита ошибки: failbit, badbit и eofbit. Важно понимать, когда они установлены: badbit устанавливается в случае неисправимой аппаратной ошибки (фактически, практически никогда, так как большинство реализаций не могут или не обнаруживают такие ошибки); а failbit устанавливается в любом другом случае сбоя ввода, будь то отсутствие доступных данных (конец файла), или ошибка формата ("abc" при вводе int и т.п.). eofbit устанавливается в любое время, когда streambuf возвращает EOF, независимо от того, приводит ли это к сбою ввода или нет! Таким образом, если вы читаете int, а поток содержит "123" без завершающих пробелов или новой строки, будет установлено eofbit (поскольку поток должен читать вперед, чтобы знать, где заканчивается int); если поток содержит "123\n", eofbit не будет установлен. Однако в обоих случаях ввод выполняется успешно, и failbit не будет установлено.

Для чтения этих битов есть следующие функции (в виде кода, так как иначе я не знаю, как получить таблицу):

eof():   returns eofbit
bad():   returns badbit
fail():  returns failbit || badbit
good():  returns !failbit && !badbit && !eofbit

operator!():      returns fail()
operator void*(): returns fail() ? NULL : this
    (typically---all that's guaranteed is that !fail() returns non-null.)

Учитывая это: первая проверка всегда должна быть fail() или одной из operator (которые основаны на fail). Как только fail() возвращает значение true, мы можем использовать другие функции, чтобы определить, почему:

if ( fin.bad() ) {
    //  Serious problem, disk read error or such.
} else if ( fin.eof() ) {
    //  End of file: there was no data there to read.
} else {
    //  Formatting error: something like "abc" for an int
}

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

person James Kanze    schedule 23.11.2011
comment
Это, безусловно, лучший ответ здесь, но вы должны понимать, что это вопрос домашнего задания. Вполне вероятно, что ОП новичок в программировании и не знает/не может использовать потоки строк, стандартные контейнеры библиотеки шаблонов или перегруженные операторы. - person Chris Parton; 23.11.2011
comment
Довольно много. Я не знаю о stringstreams или stl. Спасибо за ответ, но я боюсь, что не смогу его использовать. - person Tyler Pfaff; 23.11.2011
comment
Домашняя работа или нет, единственное приемлемое решение в C++ — это написать оператор >> для читаемого типа, чтобы 1) ввод был атомарным (вы либо читаете данные, либо нет), и 2) вы можете использовать оператор стандартная идиома цикла ввода для ввода. И getline, затем istringstream должны быть среди первых вещей, которые вы узнаете о iostream, так как это стандартная идиома для простого синтаксического анализа. Это не то, что используется в производственном коде, но оно (или должно быть) почти универсальным для домашних задач (или другого быстрого кода). - person James Kanze; 23.11.2011
comment
Классная редакция спасибо. Могу я спросить вас, почему good() существует? =р - person Tyler Pfaff; 24.11.2011
comment
@TylerPfaff В стандарте по историческим причинам. Для чего он был создан изначально (в первой реализации), я не знаю. В более общем плане я считаю, что сообщения об ошибках и их обработка в iostream — это единственная вещь, которая не кажется хорошо продуманной и разработанной. (Но то же самое можно сказать и о FILE* в C.) - person James Kanze; 24.11.2011

Немного более медленный, но более чистый подход:

void graph::fillTable()
{
  ifstream fin("data.txt");
  char X;
  int slot=0;

  std::string line;

  while(std::getline(fin, line))
  {
    if (line.empty()) // skip empty lines
      continue;

    std::istringstream sin(line);
    if (sin >> Gtable[slot].Name >> Gtable[slot].Out && Gtable[slot].Out > 0)
    {
      std::cout << Gtable[slot].Name << std::endl;
      for(int i = 0; i < Gtable[slot].Out; ++i)
      {
        if (sin >> X)
        {
          std::cout << X << std::endl;
          Gtable[slot].AdjacentOnes.addFront(X);
        }
      }
      slot++;
    }
  }
}

Если у вас все еще есть проблемы, это не с чтением файла...

person Nim    schedule 23.11.2011
comment
graph.cpp:25: ошибка: переменная âstd::istringstream sin имеет инициализатор, но неполный тип - person Tyler Pfaff; 23.11.2011
comment
вам нужно добавить заголовок <sstream> - person Nim; 23.11.2011
comment
Спасибо, я никогда не использовал его. - person Tyler Pfaff; 23.11.2011
comment
@Nim Но ты все еще делаешь одну из первоначальных ошибок. Вы вводите Gtable[slot] перед проверкой того, что slot находится в границах. Я боюсь, что это фатальная ошибка --- вы не можете рассчитывать на то, что входные данные раньше не сработают (и даже если входные данные содержат точно такое же количество записей, что и ваша таблица, вы все равно индексируете Gtable с недопустимым индексом Возможно, его код дает сбой не по этой причине, но если Gtable выполняет проверку границ, то так оно и есть. - person James Kanze; 23.11.2011
comment
@JamesKanze, конечно - главная цель этого - исправить способ использования потоков, любая проблема с Gtable полностью зависит от решения OP (в конце концов, это его домашняя работа!) - из того, что я собрал в одном из другие комментарии, он оценивает это примерно как 100, чего должно быть более чем достаточно... - person Nim; 23.11.2011
comment
@Nim Возможно, но я думаю, вы согласитесь, что вам никогда не следует рассчитывать на чтение внешних данных из файла для обеспечения соблюдения инвариантов кода :-). - person James Kanze; 23.11.2011
comment
@JamesKanze, да - действительно, очень глупо... ;) - person Nim; 23.11.2011

Файл не выйдет из строя до тех пор, пока вы не начнете читать с конца файла. Этого не произойдет до строки fin>>Gtable[slot].Name;. Так как ваша проверка предшествует этому, good все равно может вернуть true.

Одним из решений было бы добавить дополнительные проверки на сбой и выйти из цикла, если это так.

fin>>Gtable[slot].Name;
fin>>Gtable[slot].Out;
if(!fin) break;

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

person zennehoy    schedule 23.11.2011
comment
-1 потому что ответ неверный. ios::good() совершенно бесполезна как функция, потому что она может вернуть false даже после успешного ввода. - person James Kanze; 23.11.2011
comment
Честно говоря, я никогда не использовал ios::good, так как я всегда тестирую поток напрямую (т.е. if(fin) {), но из документов я не вижу, как он может возвращать false после успешного ввода. Насколько я могу судить, ios::good возвращает true, если не установлены eofbit, failbit и badbit, что вполне ожидаемо. - person zennehoy; 23.11.2011
comment
@zennehoy: успешное чтение многих типов (например, int) может установить eofbit даже во время успешного чтения. - person CB Bailey; 23.11.2011
comment
@zennehoy Ты делаешь это правильно и идиоматически. Первый тест всегда должен быть для ios::fail() (что и делает использование потока в качестве логического значения). Как только ios::fail() возвращает значение true, ios::eof() и ios::bad() можно использовать для определения причины (но во многих школьных проектах вы можете просто предположить, что ошибка связана с концом файла, и покончить с этим). Любое использование ios::eof() до проверки на сбой является ошибкой. Любое использование ios::good(), perios, является ошибкой. - person James Kanze; 23.11.2011
comment
@zennehoy eofbit устанавливается, если вызов streambuf::sgetc возвращает EOF. Это может произойти из-за упреждающего чтения: попробуйте прочитать одно целое из "123" (без завершающих пробелов или новой строки), и вы поймете, что я имею в виду. Использование потока в качестве логического значения основано на ios::fail(), который учитывает только failbit и badbit, а не eofbit. - person James Kanze; 23.11.2011
comment
@JamesKanze А, спасибо, узнал кое-что новое! Я никогда не смотрел внимательно на то, что происходит за if(stream), интересно, что он не проверяет eofbit, но имеет смысл для ввода, такого как "123". - person zennehoy; 23.11.2011
comment
@zennehoy eofbit немного неловкая ситуация. Первоначально, я думаю, это должно было быть в основном внутренним; в некоторых реализациях, если filebuf::overflow возвращал EOF один раз, это не гарантировалось во второй раз, поэтому istream запоминал, что видел EOF, и не возвращался к нему. (Но тогда почему eofbit в good()?) На практике я хотел бы видеть еще несколько битов ошибок: formatbit (для ошибок в формате) и eofbit, который устанавливается только в случае сбоя ввода. ИМХО, это облегчило бы проверку того, почему ввод не удался. Но у нас их нет. - person James Kanze; 23.11.2011

Попробуйте переместить первые два чтения в условие while:

// assuming Gtable has at least size of 1

while( fin>>Gtable[slot].Name && fin>>Gtable[slot].Out ) {
    cout<<Gtable[slot].Name<<endl;
    for(int i=0; i<=Gtable[slot].Out-1;i++) {
        fin>>X;
        cout<<X<<endl;
        Gtable[slot].AdjacentOnes.addFront(X);
    }
  slot++;

  //EDIT:

  if (slot == table_size) break;
}

Редактировать: согласно комментарию Джеймса Канзе, вы берете адрес за конец массива Gtable, что вызывает segfault. Вы можете передать размер Gtable в качестве аргумента вашей функции fillTable() (например, void fillTable(int table_size)) и проверить, что slot находится в границах перед каждым чтением.

person jrok    schedule 23.11.2011
comment
@Tyler Тайлер, я предполагаю, что вы получаете доступ к GTable за пределы допустимого. Достаточно ли слотов в Gtable? Это может помочь, если вы покажете нам, как вы объявляете Gtable. - person jrok; 23.11.2011
comment
Вы по-прежнему берете адрес GTable[slot] до того, как произойдет сбой чтения. Вся логика его кода неверна: если GTable имеет фиксированный размер, вы должны проверить это перед попыткой чтения. (Более традиционный подход заключается в том, чтобы GTable был std::vector, считывался во временный и push_back, если чтение прошло успешно. - person James Kanze; 23.11.2011
comment
@James Я согласен с этим и знаю об этом. Вот почему я сказал, что было бы неплохо увидеть, что такое Gtable. - person jrok; 23.11.2011
comment
@jrok добавил это для вас. - person Tyler Pfaff; 23.11.2011
comment
@jrok Массив инициализируется до 100 элементов в конструкторе. Я не верю, что иду дальше этого. - person Tyler Pfaff; 23.11.2011

*Отредактировано в ответ на комментарий Джеймса — теперь в коде используется проверка good() вместо проверки !eof(), что позволит отлавливать большинство ошибок. Я также добавил проверку is_open(), чтобы убедиться, что поток связан с файлом.*

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

ifstream fin("file.txt");
char a = '\0';
int b = 0;
char c = '\0';

if (!fin.is_open())
    return 1; // Failed to open file.

// Do an initial read. You have to attempt at least one read before you can
// reliably check for EOF.
fin >> a;

// Read until EOF
while (fin.good())
{
    // Read the integer
    fin >> b;

    // Read the remaining characters (I'm just storing them in c in this example)
    for (int i = 0; i < b; i++)
        fin >> c;

    // Begin to read the next line. Note that this will be the point at which
    // fin will reach EOF. Since it is the last statement in the loop, the
    // file stream check is done straight after and the loop is exited.
    // Also note that if the file is empty, the loop will never be entered.
    fin >> a;
}

fin.close();

Это решение желательно (на мой взгляд), потому что оно не полагается на добавление случайных break внутри цикла, а условием цикла является простая проверка good(). Это облегчает понимание кода.

person Chris Parton    schedule 23.11.2011
comment
-1 потому что ответ неверный. (Никогда не используйте ios::eof() в качестве элемента управления циклом.) - person James Kanze; 23.11.2011
comment
Не могли бы вы объяснить, почему нет? РЕДАКТИРОВАТЬ: Очевидно, что существуют способы, отличные от EOF, при которых ввод файла может завершиться ошибкой, но для типичных домашних упражнений обычно не требуется углубленная обработка ошибок. - person Chris Parton; 23.11.2011
comment
Я изменил свой код, чтобы использовать проверку good(), что действительно имеет больше смысла. Я все еще чувствую, что структура кода более эффективна, чем использование разрывов повсюду. - person Chris Parton; 23.11.2011
comment
Потому что когда ios::eof() становится истинным, это не совсем точно определено. Если ios::eof() возвращает true, вам гарантируется, что дальнейший ввод не удастся, но если он возвращает false, вам ничего не гарантируется в отношении дальнейшего ввода. ios::good() включает в себя !ios::eof(), так что это не лучше. (ios::eof() можно использовать после сбоя, чтобы узнать, был ли сбой вызван концом файла или какой-либо другой причиной. ios::good() тогда даже бесполезен, так как после сбоя он всегда будет возвращать false.) - person James Kanze; 23.11.2011