【问题标题】:Check if a stream ends with a newline检查流是否以换行符结尾
【发布时间】:2016-09-26 13:22:51
【问题描述】:

我想检查一个流(实际上是一个 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)。我一般可以这样做吗?

【问题讨论】:

  • 文件流必须以换行符结尾。此测试将检测一个换行符或以两个或多个换行符结尾的流。
  • 不以换行符结尾的文本流在 C 或 C++ 中不是有效的输入流。不需要对该流的输入操作进行合理处理。
  • 所以使用 std::stringstream the_stream(""); the_stream
  • @PeteBecker 在 C 或 C++ 中,文件可以以它喜欢的任何内容结尾,文件流和文本流也是如此。如果最后的换行符不存在,std::getline()fgets() 都会正确运行。
  • 不要使用 in_avail。它仅显示非阻塞读取的可用字符数(0 == 下一次读取可能会或可能不会发送字符)

标签: c++ stream ifstream eof


【解决方案1】:

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


需要考虑两个位:fail 位和 eof 位。 std::getline 确实,来自 [string.io]:

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

sentry 确实如此,来自 [istream::sentry]:

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

考虑到所有这些,让我们来看两个例子:


案例 1:"hello\n"。第一次调用getline()the_stream.good() 是真的,我们通过\n 提取字符,流仍然是good(),我们进入循环体,line 设置为"hello"

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


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

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

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


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

  • 您可以在执行任何其他操作之前先通过peek() 查看第一个字符是否为traits::eof()
  • 您可以计算进入循环的次数并检查它是否非零。
  • 您可以将line 初始化为一些哨兵非空值。在循环结束时,只有当流以分隔符结束时,该行才会为空。

【讨论】:

  • 关于hello\n 的例子,是的,我同意我会得到一个字符串,但是while 循环会进行额外的迭代,因为没有达到eof。在这次迭代中,我得到一个分配给字符串参数的空字符串。所以对我来说,我似乎不需要明确的 eof 检查。
  • @Jens 不,你没有。对getline() 的下一次调用将以失败的流结束,因此您不会进入循环。
  • 我同意我不进入循环,但是line在进入循环之前已经被分配了一个新值,不是吗?
  • @Jens 是的,这将是一个空字符串,因为getline 被调用了两次。
  • @Jens 这是一个更彻底的答案。
【解决方案2】:

您的代码有效。

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

#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 << "empty\n";
    std::istringstream empty;
    assert(StreamEndsWithNewline(empty) == false);

    std::cout << "empty_line\n";
    std::istringstream empty_line("\n");
    assert(StreamEndsWithNewline(empty_line) == true);

    std::cout << "line\n";
    std::istringstream line("Line\n");
    assert(StreamEndsWithNewline(line) == true);

    std::cout << "unterminated_line\n";
    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;
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2014-08-28
    • 1970-01-01
    • 1970-01-01
    • 2022-01-25
    • 2013-03-21
    • 1970-01-01
    • 2023-02-06
    相关资源
    最近更新 更多