C++以二进制模式读取文件:遇到END OF FILE的问题

3

我正在学习C++,需要以二进制模式读取文件。下面是我按照C++参考文档所做的操作:

unsigned values[255];
unsigned total;
ifstream in ("test.txt", ifstream::binary);

while(in.good()){
    unsigned val = in.get();
    if(in.good()){
        values[val]++;
        total++;
        cout << val <<endl;
    }
}

in.close();

因此,我逐字节读取文件,直到in.good()为真。我在while循环的末尾放置了一些cout,以便了解发生了什么情况,以下是输出:

marco@iceland:~/workspace/huffman$ ./main 
97
97
97
97
10
98
98
10
99
99
99
99
10
100
100
10
101
101
10
221497852
marco@iceland:~/workspace/huffman$

现在,输入文件“test.txt”只是这样的:
aaaa
bb
cccc
dd
ee

一切都运行正常直到最后,出现了221497852这个问题。我猜测它可能与文件结尾有关,但是我无法找出问题所在。

我正在使用64位debian机器上的gedit和g++。

非常感谢您的帮助。

Marco


请将解决方案作为实际答案发布并接受它,而不是将其编辑到“问题”中。同时,请不要在标题中加入“已解决”。只需标记问题为已接受即可。 :) - undefined
好的,我不知道如何做。谢谢。 - undefined
3个回答

5

fstream::get 返回一个 int 值,这是其中的一个问题。

其次,您正在读取二进制数据,因此不应该使用格式化流。您应该使用fstream::read

// read a file into memory
#include <iostream>     // std::cout
#include <fstream>      // std::ifstream

int main () {

  std::ifstream is ("test.txt", std::ifstream::binary);
  if (is) {
    // get length of file:
    is.seekg (0, is.end);
    int length = is.tellg();
    is.seekg (0, is.beg);

    char * buffer = new char [length];

    std::cout << "Reading " << length << " characters... ";
    // read data as a block:
    is.read (buffer,length);

    if (is)
      std::cout << "all characters read successfully.";
    else
      std::cout << "error: only " << is.gcount() << " could be read";
    is.close();

    // ...buffer contains the entire file...

    delete[] buffer;
  }
  return 0;
}

get()是未格式化的,根据http://en.cppreference.com/w/cpp/io/basic_istream/get - undefined
字符实际上不就是整数吗?如果我使用get并将其返回值赋给一个无符号整数变量,会有什么问题呢? - undefined
这个答案是完全错误的。OP的代码不符合惯用法,也没有正确使用istream::get(),但我没有看到任何可能导致它无法工作的问题,除非istream::get()在未遇到文件末尾时返回一个负值。 - undefined
哪个答案是明显错误的?这是我所说的,不要像这样使用 fstream::get - undefined
@bash.d 事实上,istream::get() 返回一个 int 是问题的一部分。如果文件是二进制的,他应该使用 istream::read()。你的示例代码也不是一个很好的例子。 - undefined
显示剩余6条评论

2
这不是 istream::get() 的设计意图。 使用此函数的经典用法为:
for ( int val = in.get(); val != EOF; val = in.get() ) {
    //  ...
}

甚至更贴切的用语:
char ch;
while ( in.get( ch ) ) {
    //  ...
}

第一个循环实际上是从C继承而来的,其中in.get()相当于fgetc()
尽管如此,据我所知,您提供的代码应该可以工作。它不符合惯用法,也不是最佳的。
C++标准对于读取的字符值为负时应返回什么并不清楚。fgetc()要求在[0...UCHAR_MAX]范围内有一个值,我认为安全地假设这是意图。至少,这是我使用过的每个实现都这样做的。但这不会影响您的输入。根据实现解释标准的方式,in.get()的返回值必须在[0...UCHAR_MAX][CHAR_MIN...CHAR_MAX]范围内,或者必须是EOF(通常为-1)。 (我非常确定意图是要求[0...UCHAR_MAX],因为否则,您可能无法区分文件结束和有效字符。)
如果返回值是EOF(几乎总是-1),则应设置failbit,因此in.good()将返回false。不存在in.get()允许返回221497852的情况。我唯一可能想到的解释是,您的文件在文件末尾具有某个设置了第7位的字符,实现为此返回负数(但不是文件结束,因为它是一个字符),这导致values[val]中出现越界索引,并且这个越界索引以某种方式修改了val。或者您的实现有问题,并且在返回文件结束时没有设置failbit
要确保,请告诉我以下内容的结果:
std::ifstream in( "text.txt", std::ios_base::binary );
int ch = in.get();
while ( ch != std::istream::traits_type::eof() ) {
    std::cout << ch << std::endl;
    ch = in.get();
}

这可以避免可能无效索引和任何类型转换问题(尽管从intunsigned的转换是定义良好的)。另外,出于好奇(因为我只能在VC++中访问),您可以尝试将 in 替换为以下内容:
std::istringstream in( "\n\xE5" );

我希望能得到以下内容:

10
233

(假设使用8位字节和基于ASCII的编码集。这两者今天几乎是普遍的,但并非完全通用。)

我已经尝试了你上面的代码,结果输出是:none :D顺便说一下,我尝试改变了我的代码,用'\n'替换了endl。 现在输出几乎相同,只有最后一个数字不同,现在是10(ASCII码中的'\n'字符)。 所以我现在猜测可能是endl的问题。无论如何,我仍然无法理解最后的10个字符,因为文件末尾没有\n。 - undefined
@MarcoGalassi 你没有展示给我们其他的信息。上面的代码是从我的电脑上编译和运行的程序中复制粘贴过来的,并且是有效的;我以前几乎用相同的方法在g++中做过类似的事情。而使用'\n'还是std::endl只会影响是否立即刷新输出。 - undefined
这是代码。我正在使用g++ main.cpp -o main进行编译,然后使用./main运行程序。我认为问题可能出在文件末尾。我还读到过endl,它应该是带有刷新的\n,这就是为什么我无法理解输出变化的原因。你使用的是什么机器?Linux还是Windows?什么架构?(我想检查所有的差异) - undefined
@MarcoGalassi 我在这台机器上刚刚检查过,它是一台Windows机器,但我以前也在Linux和Sun Sparcs(在Solaris下)上做过完全相同的操作。如果我发布的第三个代码块不起作用,那么可能是你的安装出了问题。(我刚刚在Windows上运行了它,仔细创建了一个带有Unix换行符和没有最后一个eol的文件,我得到了你通过第二个101得到的输出,然后什么都没有。) - undefined
当你谈到“安装有问题”的时候,你指的是什么?是指g++?debian的安装?还是其他什么? - undefined
@MarcoGalassi 我不确定。可能是 g++。(也有可能是你进行编译的头文件与链接的库版本不一致所导致的问题。)如果我能记得并找到时间,我会在今晚重新检查我的 Linux 机器上的情况,但第三个循环,简单地包裹在 main 函数中,应该是能工作的。如果它不能工作,那么进一步分析你的代码也没有意义,因为安装本身就有问题。 - undefined

-1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接