一 问题
使用C++ ifstream来读取文件时,发现在读到文件结尾时会多读一行。测试代码如下,
#include <iostream>
#include <fstream>
#include <string>
int main(void)
{
std::ifstream inf("data.txt");
if (!inf) {
std::cerr << "open data.txt fail\n";
return 1;
}
std::string oneline;
while (inf.eof() == false) { // 问题点
std::getline(inf, oneline);
std::cout << oneline << "\n";
}
return 0;
}
data.txt内容如下,
输出如下,多读了一行,为空。
二 解决办法
方法1
直接判断std::getline()的返回值,
#include <iostream>
#include <fstream>
#include <string>
int main(void)
{
std::ifstream inf("data.txt");
if (!inf) {
std::cerr << "open data.txt fail\n";
return 1;
}
std::string oneline;
while (std::getline(inf, oneline)) {
std::cout << oneline << "\n";
}
return 0;
}
方法2
使用文件流的peek()方法来代替eof()方法,如下,
#include <iostream>
#include <fstream>
#include <string>
int main(void)
{
std::ifstream inf("data.txt");
if (!inf) {
std::cerr << "open data.txt fail\n";
return 1;
}
std::string oneline;
while (inf.peek() != EOF) { // 关键行
std::getline(inf, oneline);
std::cout << oneline << "\n";
}
return 0;
}
输出如下,
可见最后一行得到了正确的处理。
方法3
先调用std::getline()去读取,然后先判断eof()再决定是否打印,
#include <iostream>
#include <fstream>
#include <string>
int main(void)
{
std::ifstream inf("data.txt");
if (!inf) {
std::cerr << "open data.txt fail\n";
return 1;
}
std::string oneline;
while (true)
{
std::getline(inf, oneline);
if (inf.eof() == true)
break;
std::cout << oneline << "\n";
}
return 0;
}
三 原因分析
《C++ primer 5th》17.5.2节对文件流的peek()方法描述如下,
peek返回输入流中下一个字符的副本,但不会将它从流中删除。peek返回的值仍然留在流中。
所以,通过peek()来提前预判下一个字符是不是文件结束符EOF,就可以让我们提前知道文件流是否已经到了结尾,就可以正确结束输入。
为什么eof()方法不行呢?下面来分析下,上面用于测试的data.txt,其实际结构如下,
文件最后会有个EOF(End Of File)表示文件内容的结束。
当使用std::getline()读取完4444后,此时如果使用peek(),则返回值是EOF,而peek()方法的功能是预判下一个字符,由此可以知道std::getline()读完4444后,文件句柄的位置并不在EOF处,而是在EOF之前的那个字符位置上,此时使用eof()方法会返回false,然后调用std::getline()去读取EOF,就会导致错误,参数oneline就会变成空字符串,所以就会多打印一个空行。
如果有写的不对的地方,希望能留言指正,谢谢阅读。