检查流是否以换行符结束

Check if a stream ends with a newline

本文关键字:换行符 结束 是否 检查      更新时间:2023-10-16

我想检查一个流(实际上是ifstream)是否以换行符结束。我想到了这个:

bool StreamEndsWithNewline(std::basic_istream<char> & the_stream)
{
    if (the_stream.peek() == EOF) {
        the_stream.clear(); //clear flags set by peek()
        return false;
    }
    std::string line = "blah";
    while (std::getline(the_stream, line)) {
       // ...
    }
    return line.empty();
}

这个想法是,如果流的最后一行有一个结束字符n, while循环将进行一次额外的迭代(因为没有达到eof),其中空字符串将被分配给line参数。

必须单独处理"空"流的特殊情况。

它似乎在windows (vs2010)上工作。我可以这样做吗?

你的代码工作了。

但是,您可以尝试查找流并只测试最后一个字符或丢弃读取的字符:

#include <cassert>
#include <iostream>
#include <limits>
#include <sstream>
bool StreamEndsWithNewline(std::basic_istream<char>& stream) {
    const auto Unlimited = std::numeric_limits<std::streamsize>::max();
    bool result = false;
    if(stream) {
        if(std::basic_ios<char>::traits_type::eof() != stream.peek()) {
            if(stream.seekg(-1, std::ios::end)) {
                char c;
                result = (stream.get(c) && c == 'n');
                stream.ignore(Unlimited);
            }
            else {
                stream.clear();
                while(stream && stream.ignore(Unlimited, 'n')) {}
                result = (stream.gcount() == 0);
            }
        }
        stream.clear();
    }
    return result;
}
int main() {
    std::cout << "emptyn";
    std::istringstream empty;
    assert(StreamEndsWithNewline(empty) == false);
    std::cout << "empty_linen";
    std::istringstream empty_line("n");
    assert(StreamEndsWithNewline(empty_line) == true);
    std::cout << "linen";
    std::istringstream line("Linen");
    assert(StreamEndsWithNewline(line) == true);
    std::cout << "unterminated_linen";
    std::istringstream unterminated_line("Line");
    assert(StreamEndsWithNewline(unterminated_line) == false);
    std::cout << "Please enter ctrl-D: (ctrl-Z on Windows)";
    std::cout.flush();
    assert(StreamEndsWithNewline(std::cin) == false);
    std::cout << 'n';
    std::cout << "Please enter Return and ctrl-D (ctrl-Z on Windows): ";
    std::cout.flush();
    assert(StreamEndsWithNewline(std::cin) == true);
    std::cout << 'n';
    return 0;
}

;是的,这是保证工作的,除非流最初是空的。


有两个位需要考虑:fail位和eof位。std::getline does, from [string.io]:

在构造一个sentry对象之后,如果sentry转换为true,调用str.erase(),然后从is中提取字符并将它们附加到str,就像调用str.append(1, c)[…]如果函数没有提取字符,它调用is.setstate(ios::failbit)

sentry一样,from [istream::sentry]:

效果:如果is.good()false,则调用is.setstate(failbit)。否则,准备格式化或未格式化的输入。[…如果is.rdbuf()->sbumpc()或者is.rdbuf()->sgetc()返回traits::eof(),函数调用setstate(failbit | eofbit)

那么给出所有这些,让我们看两个例子:


案例1:"hellon"。第一次调用getline(), the_stream.good()为true,我们从n中抽取字符,流仍然是good(),我们进入循环体,将line设置为"hello"

第二次调用getline()时,流仍然是good(),因此sentry对象转换为true,我们调用str.erase()。尝试提取后续字符失败,因为我们已经完成了流,所以设置了failbit。这将导致返回getline()转换为false,因此我们不会第二次进入循环体。在循环结束时,line为空。


Case 2: "goodbye",无换行符。第一次调用getline(), the_stream.good()为true,我们提取字符直到到达eof()。流failbit还没有设置,所以我们仍然进入循环体,行设置为"goodbye"

第二次调用getline(), sentry对象的构造失败,因为is.good()为假(is.good()同时检查eofbitfailbit)。由于这个失败,我们没有进入getline()的第一步,它调用str.erase()。由于这个失败,设置了failbit,因此我们再次不进入循环体。

在循环结束时,line仍然是"goodbye"


案例3:""。这里,getline()将不提取任何字符,因此设置了failbit,并且永远不会进入循环,并且line始终为空。有几种方法可以将这种情况与情况1区分开来:

  • 在做其他事情之前,您可以预先设置peek(),以查看第一个字符是否为traits::eof()
  • 你可以计算你进入循环的次数,并检查它是否为零。
  • 您可以将line初始化为某个哨兵非空值。在循环结束时,只有流以分隔符结束时该行才为空。