读取文件时查找文件结尾

发布于 2024-12-17 15:17:15 字数 1005 浏览 2 评论 0原文

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 没有中断。

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

That's my code, basically it does exactly what I want it to but it keeps reading when the file is not good anymore. It'll input and output all the things I'm looking for, and then when the file is at an end, fin.good() apparently isn't returning false. Here is the text file.

A 2 B F

B 2 C G

C 1 H

H 2 G I

I 3 A G E

F 2 I E

and here is the output

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

Segmentation fault

-

Here's is Gtable's type.

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

I'm expecting it to stop after outputting 'E' which is the last char in the file. The program never gets into the for loop again after reading the last char. I can't figure out why the while isn't breaking.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

爱你不解释 2024-12-24 15:17:15

你在 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都不使用就成功
    已测试。)

  • 在不确保 slot 的情况下尝试读入 Gtable[slot]
    在边界内是错误的。

关于eof()good()

istreamostream的基类定义了三个
“错误”位:failbitbadbiteofbit。它是
理解这些设置的时间很重要:badbit 是在以下情况下设置的:
不可恢复的硬件错误(实际上从来没有,因为大多数
实现不能或没有检测到此类错误);并且 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()
是一个错误——不要问我为什么有这个函数)。

Your condition in the while loop is wrong. ios::eof() isn't
predictive; it will only be set once the stream has attempted
(internally) to read beyond end of file. You have to check after each
input.

The classical way of handling your case would be to define a >>
function for GTable, along the lines of:

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

(This was written rather hastily. In real code, I'd almost certainly
factor the inner loop out into a separate function.)

Note that:

  • We read line by line, in order to verify the format (and to allow
    resynchronization in case of error).

  • We set failbit in the source stream in case of an input error.

  • We skip empty lines (since your input apparently contains them).

  • We do not modify the target element until we are sure that the input
    is correct.

One we have this, it is easy to loop over all of the elements:

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

EDIT:

I'll point this out explicitly, because the other people responding seem
to have missed it: it is absolutely imperative to ensure that you have
the place to read into before attempting the read.

EDIT 2:

Given the number of wrong answers this question is receiving, I would
like to stress:

  • Any use of fin.eof() before the input is known to fail is wrong.

  • Any use of fin.good(), period, is wrong.

  • Any use of one of the values read before having tested that the input
    has succeeded is wrong. (This doesn't prevent things like fin >> a >>
    b
    , as long as neither a or b are used before the success is
    tested.)

  • Any attempt to read into Gtable[slot] without ensuring that slot
    is in bounds is wrong.

With regards to eof() and good():

The base class of istream and ostream defines three
“error” bits: failbit, badbit and eofbit. It's
important to understand when these are set: badbit is set in case of a
non-recoverable hardward error (practically never, in fact, since most
implementations can't or don't detect such errors); and failbit is set in
any other case the input fails—either no data available (end of
file), or a format error ("abc" when inputting an int, etc.).
eofbit is set anytime the streambuf returns EOF, whether this
causes the input to fail or not! Thus, if you read an int, and the
stream contains "123", without trailing white space or newline,
eofbit will be set (since the stream must read ahead to know where the
int ends); if the stream contains "123\n", eofbit will not be set.
In both cases, however, the input succeeds, and failbit will not be
set.

To read these bits, there are the following functions (as code, since I
don't know how to get a table otherwise):

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.)

Given this: the first check must always be fail() or one of the
operator (which are based on fail). Once fail() returns true, we
can use the other functions to determine why:

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
}

Practically speaking, any other use is an error (and any use of good()
is an error—don't ask me why the function is there).

微暖i 2024-12-24 15:17:15

稍慢但更干净的方法:

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

如果您仍然遇到问题,则与文件读取无关......

Slightly slower but cleaner approach:

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

If you still have issues, it's not with file reading...

兮颜 2024-12-24 15:17:15

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

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

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

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

The file won't fail until you actually read from past the end of file. This won't occur until the fin>>Gtable[slot].Name; line. Since your check is before this, good can still return true.

One solution would be to add additional checks for failure and break out of the loop if so.

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

This still does not handle formatting errors in the input file very nicely; for that you should be reading line by line as mentioned in some of the other answers.

晨光如昨 2024-12-24 15:17:15

尝试在 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 在每次读取之前都在边界内。

Try moving first two reads in the while condition:

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

Edit: As per James Kanze's comment, you're taking an adress past the end of Gtable array, which is what causes segfault. You could pass the size of Gtable as argument to your fillTable() function (f.ex. void fillTable(int table_size)) and check slot is in bounds before each read.

白鸥掠海 2024-12-24 15:17:15

*根据 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();

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

*Edited in response to James' comment - the code now uses a good() check instead of a
!eof() check, which will allow it to catch most errors. I also threw in an is_open()
check to ensure the stream is associated with the file.*

Generally, you should try to structure your file reading in a loop as follows:

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();

This solution is desirable (in my opinion) because it does not rely on adding random
breaks inside the loop, and the loop condition is a simple good() check. This makes the
code easier to understand.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文