从中读取文件时查找文件结尾

时间:2011-11-23 11:58:05

标签: c++ fstream

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
  };

我希望在输出&#39; E&#39;之后停止它。这是文件中的最后一个字符。在读完最后一个字符后,程序永远不会再次进入for循环。我无法弄清楚为什么这段时间不会破裂。

5 个答案:

答案 0 :(得分: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之类的内容,只要在成功之前未使用ab 测试。)

  • 在未确保Gtable[slot]的情况下尝试阅读slot 在界限是错误的。

关于eof()good()

istreamostream的基类定义了三个 “错误”位:failbitbadbiteofbit。它的 重要的是要了解何时设置:badbit设置为a 不可恢复的硬性错误(实际上从来没有,实际上,因为大多数情况 实现不能或不能检测到这样的错误);并且failbit已设置 输入失败的任何其他情况 - 无数据可用(结束时) 文件),或格式错误(输入int时的"abc"等)。 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() 是一个错误 - 不要问我为什么功能在那里。)

答案 1 :(得分:3)

稍慢但更清洁的方法:

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++;
    }
  }
}

如果您仍有问题,则不是文件阅读......

答案 2 :(得分:2)

在您实际读取文件末尾之前,文件不会失败。直到fin>>Gtable[slot].Name;行才会发生这种情况。由于你的检查是在此之前,好的仍然可以返回。

一种解决方案是添加额外的故障检查,如果是这样的话就会中断。

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

这仍然不能很好地处理输入文件中的格式错误;为此你应该逐行阅读,如其他一些答案所述。

答案 3 :(得分:1)

尝试在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;
}

编辑:根据James Kanze的评论,你在Gtable数组的末尾开了一个地址,这就是导致段错误的原因。您可以将Gtable的大小作为参数传递给fillTable()函数(f.ex。void fillTable(int table_size)),并在每次读取之前检查slot是否在边界内。

答案 4 :(得分:0)

*编辑以回应James的评论 - 代码现在使用good()检查而不是a !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()检查。这使得 代码更容易理解。