检查流是否以换行符结尾
Check if a stream ends with a newline
我想检查流(实际上是 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),其中将分配空字符串到行参数。
必须单独处理 "empty" 流的特殊情况。
它似乎适用于 windows (vs2010)。一般这样可以吗?
tldr;是的,这保证有效,除非流最初是空的。
有两个位需要考虑:fail
位和 eof
位。 std::getline
确实如此,来自 [string.io]:
After constructing a sentry
object, if the
sentry converts to true, calls str.erase()
and then extracts characters from is and appends them to str
as if by calling str.append(1, c)
[...] If the function extracts no characters, it calls is.setstate(ios::failbit)
而 sentry
确实如此,来自 [istream::sentry]:
Effects: If is.good()
is false
, calls is.setstate(failbit)
. Otherwise, prepares for formatted or unformatted input. [...] If is.rdbuf()->sbumpc()
or is.rdbuf()->sgetc()
returns traits::eof()
, the function calls 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()
同时检查 eofbit
和failbit
)。由于这次失败,我们不进入调用 str.erase()
的 getline()
的第一步。由于这次失败,failbit
被设置,所以我们再次不进入循环体。
循环结束时,line
仍然是"goodbye"
。
案例 3:""
。这里,getline()
将不提取任何字符,因此设置了 failbit
并且永远不会进入循环,并且 line
始终为空。有几种方法可以将这种情况与情况 1 区分开来:
- 您可以预先
peek()
查看第一个字符是否为 traits::eof()
,然后再执行任何其他操作。
- 您可以计算进入循环的次数并检查它是否为非零。
- 您可以将
line
初始化为某个标记非空值。在循环结束时,只有当流以定界符结束时,该行才会为空。
您的代码有效。
但是,您可以尝试搜索流并只测试最后一个字符或丢弃读取的字符:
#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;
}
我想检查流(实际上是 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),其中将分配空字符串到行参数。
必须单独处理 "empty" 流的特殊情况。
它似乎适用于 windows (vs2010)。一般这样可以吗?
tldr;是的,这保证有效,除非流最初是空的。
有两个位需要考虑:fail
位和 eof
位。 std::getline
确实如此,来自 [string.io]:
After constructing a
sentry
object, if the sentry converts to true, callsstr.erase()
and then extracts characters from is and appends them tostr
as if by callingstr.append(1, c)
[...] If the function extracts no characters, it callsis.setstate(ios::failbit)
而 sentry
确实如此,来自 [istream::sentry]:
Effects: If
is.good()
isfalse
, callsis.setstate(failbit)
. Otherwise, prepares for formatted or unformatted input. [...] Ifis.rdbuf()->sbumpc()
oris.rdbuf()->sgetc()
returnstraits::eof()
, the function callssetstate(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()
同时检查 eofbit
和failbit
)。由于这次失败,我们不进入调用 str.erase()
的 getline()
的第一步。由于这次失败,failbit
被设置,所以我们再次不进入循环体。
循环结束时,line
仍然是"goodbye"
。
案例 3:""
。这里,getline()
将不提取任何字符,因此设置了 failbit
并且永远不会进入循环,并且 line
始终为空。有几种方法可以将这种情况与情况 1 区分开来:
- 您可以预先
peek()
查看第一个字符是否为traits::eof()
,然后再执行任何其他操作。 - 您可以计算进入循环的次数并检查它是否为非零。
- 您可以将
line
初始化为某个标记非空值。在循环结束时,只有当流以定界符结束时,该行才会为空。
您的代码有效。
但是,您可以尝试搜索流并只测试最后一个字符或丢弃读取的字符:
#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;
}