【问题标题】:Finding end of file while reading from it读取文件时查找文件结尾
【发布时间】:2011-11-23 11:58:05
【问题描述】:
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 循环。我不知道为什么 while 没有中断。

【问题讨论】:

  • 我也试过用 eof() 没用。
  • 为什么你认为它在阅读时会出现段错误?什么是 Gtable?您访问不存在的索引的可能性更大吗?调试器告诉你导致段错误的确切位置和指令是什么?
  • 它可能会因其他原因出现段错误,但无论如何我都不想在文件完成后执行任何操作。我很确定它在尝试读取文件的空白部分时会出现段错误。 Gtable 是一个对象数组。这个数组被初始化为 100 个槽,只是为了确保我不会超出索引!
  • @TylerPaff:嗯,你知道任何类型的eof()good() 只会在读取失败后 被设置,并且你通常应该检查是否读取成功,之后通过eof()询问原因?
  • 确实知道标准 C++ 库提供自动调整容器大小的功能,是吗?你为什么要处理笨拙且容易出错的数组?另外,我们能看到 Gtable 是一个数组的定义吗?

标签: c++ fstream


【解决方案1】:

您在 while 循环中的条件是错误的。 ios::eof() 不是 预测;它只会在流尝试后设置 (内部)读取超出文件末尾。您必须在每次检查后检查
输入。

处理您的案例的经典方法是定义一个&gt;&gt; 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()

istreamostream 的基类定义了三个 “错误”位:failbitbadbiteofbit。它是 了解这些设置的时间很重要: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() 返回真,我们 可以使用其他函数来确定原因:

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() 是一个错误——不要问我为什么有这个函数)。

【讨论】:

  • 这当然是最好的答案,但你必须意识到这是一个家庭作业问题。 OP 很可能是编程新手,不知道/不允许使用字符串流、标准模板库容器或重载运算符。
  • 差不多。我不知道字符串流或 stl。谢谢你的回答,但我恐怕无法使用它。
  • 作业与否,C++ 中唯一可接受的解决方案是为正在读取的类型编写 &gt;&gt; 运算符,以便 1)输入是原子的(您要么读取数据,要么不读取数据) t) 和 2) 您可以使用标准输入循环习语进行输入。和getline,然后istringstream 应该是您了解iostream 的第一件事,因为它是用于简单解析的 标准习语。它不是在生产代码中使用的,但对于家庭作业类型问题(或其他快速代码)几乎是(或应该是)通用的。
  • 很棒的编辑谢谢。我可以问你为什么有 good() 吗? =p
  • @TylerPfaff 在标准中,出于历史原因。为什么要创建它(在第一个实现中),我不知道。更一般地说,我发现 iostream 中的错误报告和处理是一件似乎没有经过深思熟虑和设计的事情。 (但是,对于 C 语言中的 FILE*,也可以这样说。)
【解决方案2】:

略慢但更简洁的方法:

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

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

【讨论】:

  • graph.cpp:25: error: variable âstd::istringstream sinâ 有初始化程序但类型不完整
  • 你需要添加&lt;sstream&gt; header
  • 谢谢,我没用过。
  • @Nim 但是你仍然犯了最初的错误之一。在检查slot 是否在界限内之前,您正在输入Gtable[slot]。恐怕这是一个致命错误——你不能指望输入失败(即使输入与你的表有完全相同的条目数,你仍然索引到 Gtable 与无效索引。这可能不是他的代码失败的原因,但如果 Gtable 正在做边界检查,那就是。
  • @JamesKanze,当然 - 这样做的主要目的是修复流的使用方式,Gtable 的任何问题完全取决于 OP 来解决(毕竟这是他的作业! ) - 根据我在其他一个 cmets 中收集到的数据,他将这个尺寸定为 100 左右,这应该绰绰有余......
【解决方案3】:

在您从文件末尾实际读取之前,该文件不会失败。这直到fin&gt;&gt;Gtable[slot].Name; 行才会发生。由于您的检查在此之前,good 仍然可以返回 true。

一种解决方案是添加额外的失败检查,如果是,则跳出循环。

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

这仍然不能很好地处理输入文件中的格式错误;为此,您应该像其他一些答案中提到的那样逐行阅读。

【讨论】:

  • -1 因为答案是错误的。 ios::good() 作为一个函数完全没有价值,因为即使在成功输入后它也可能返回false
  • 老实说,我从来没有使用过ios::good,因为我总是直接测试流(即if(fin) {),但是从文档中我看不到它在成功输入后如何返回false .据我所知,如果没有设置eofbitfailbitbadbitios::good 返回 true,这几乎是预期的行为。
  • @zennehoy:许多类型的成功读取(例如int)即使在成功读取期间也可以设置eofbit
  • @zennehoy 您做得正确且惯用。第一个测试应该始终针对ios::fail()(这是使用流作为布尔值所做的)。一旦ios::fail() 返回真,ios::eof()ios::bad() 可用于确定原因(但在许多学校项目中,您可以假设失败是由于文件结尾,然后就可以完成)。在检查失败之前使用ios::eof() 是一个错误。任何使用ios::good(), perios 都是错误的。
  • @zennehoy eofbit 如果对streambuf::sgetc 的调用返回EOF,则设置。这可能是由于预读而发生的:尝试从 "123" 读取单个 int (没有尾随空格或换行符),您会明白我的意思。将流用作布尔值是基于ios::fail(),它只考虑failbitbadbit,而不考虑eofbit
【解决方案4】:

尝试在 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() 函数(例如void fillTable(int table_size)),并在每次读取之前检查slot 是否在界限内。

【讨论】:

  • @Tyler 我猜你正在越界访问 GTable。 Gtable 中是否有足够的“插槽”?如果您向我们展示您如何声明 Gtable 可能会有所帮助。
  • 在读取失败之前,您仍在使用GTable[slot] 的地址。他的代码的整个逻辑是错误的:如果GTable 具有固定大小,您必须在尝试阅读之前检查此内容。 (更传统的方法是让GTable 成为std::vector,读入一个临时的,如果读成功则push_back
  • @James 我同意并知道这一点。这就是为什么我说最好看看 Gtable 到底是什么。
  • @jrok 添加给你看。
  • @jrok 数组在构造函数中被初始化为 100 个元素。我不相信我会超越这一点。
【解决方案5】:

*针对 James 的评论进行了编辑 - 代码现在使用 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();

这个解决方案是可取的(在我看来),因为它不依赖于添加随机 breaks 在循环内,循环条件是一个简单的 good() 检查。这使得 代码更容易理解。

【讨论】:

  • -1 因为答案是错误的。 (你永远不会使用ios::eof() 作为循环控制。)
  • 你能解释一下为什么不吗?编辑:显然,除了 EOF 之外,文件输入可能会失败,但对于典型的家庭作业练习,通常不需要进行深入的错误处理。
  • 我已经更改了我的代码以使用 good() 检查,这确实更有意义。我还是觉得代码的结构比处处使用break更有效。
  • 因为当ios::eof() 变为真时没有很好的定义。如果ios::eof() 返回true,则保证进一步输入将失败,但如果返回false,则无法保证进一步输入。 ios::good() 包括!ios::eof(),所以也好不到哪里去。 (ios::eof() 可以在 after 失败后使用,以了解失败是由于文件结束还是其他原因。ios::good() 那时甚至没有用,因为失败后它总是返回 false。)
猜你喜欢
  • 2019-05-10
  • 2013-10-24
  • 1970-01-01
  • 1970-01-01
  • 2014-05-19
  • 2013-07-14
  • 1970-01-01
  • 2012-06-12
  • 1970-01-01
相关资源
最近更新 更多