试图理解 cin 的行为

Trying to understand cin behavior

我正在编写软件,从 stdin 获取(巨大的)输入流并将其读入浮点向量。我想捕获流中包含逗号等字符的情况,要么无法接受它,要么干脆忽略所有不能解析为浮点数的内容(无论哪个更容易实现,我没有偏好)。我注意到以下行为:当我调用

echo "1.4, -0.7 890 23e-3" | ./cintest

这个版本

#include <iostream>
using std::endl;
using std::cin;
using std::cout;

int main ( int argc, const char* argv[] ){
    float val;
    while (cin >> val) {
        cout << val << endl;
    }
    return 0;
}

打印

1.4

而这个版本

#include <iostream>
using std::endl;
using std::cin;
using std::cout;

int main ( int argc, const char* argv[] ){
    float val;
    while (cin) {
        cin >> val;
        cout << val << endl;
    }
    return 0;
}

打印

1.4
0

没有逗号,打印第一个

1.4
-0.7
890
0.023

而第二个打印

1.4
-0.7
890
0.023
0.023

有人可以解释一下这是怎么回事吗?

您的结果与 >> 失败时有关。

在这两个版本中,您读取值并到达逗号(或 EOF)。读取显然失败了,因为 , 和 EOF 不是 >> 可以解析的有效整数。所以 >> 的 return 值(流本身)转换为 false,并且您在第一个版本中退出循环(这是它应该如何工作)。

然而,在第二个版本中(这不是您通常应该做的),您仍然打印出 val 中的任何值。在 C++11 之前的 C++ 中,val 保持不变;因为 C++11 >> 在失败时写入 0

TL;DR: 你的第二个版本停止循环很晚,并写了一遍垃圾。

你的代码的第一个版本

    while (cin >> val) {

尝试解析一个浮点数,然后 然后 检查流状态是否良好。 (具体来说,它调用 operator>> to do the extraction, which will set failbit on error, and then uses the bool conversion 来测试 failbit)。

因此,如果流状态不好(因为无法将 , 转换为浮点数),则不会进入循环体。因此它在第一次失败的转换时终止。

第二个版本

    while (cin) {
    cin >> val;

检查流状态是否良好(这只是告诉您 先前的 转换成功),然后尝试解析浮点数,然后假定此操作成功而不进行检查。它应该在使用浮点值之前检查转换后的流状态,在这种情况下,浮点值是上一次迭代遗留下来的。

在正确的实现中,当转换失败时,您应该检查是否 fail() 为 true 而 eof() 为 false(即转换失败的原因不是文件结束) .在这种情况下,使用 ignore() 丢弃输入 - 您可以要求 whitespace(并忽略直到下一个 space),或者忽略一个字符并重试。

请注意,上面链接的 ignore 文档包含具有正确错误处理的示例代码。如果我们选择在转换失败时跳过单个字符,您的代码将变为:

for(;;) {
    float val;
    std::cin >> val;

    if (std::cin.eof() || std::cin.bad()) {
        break;
    } else if (std::cin.fail()) {
        std::cin.clear(); // unset failbit
        std::cin.ignore(1); // skip next char
    } else {
        std::cout << val << '\n';
    }
}

std::cin 是 std::istream

的实例化

当任何位置存在逗号或任何无效数据类型时,运算符 >> 都会失败。因此您的代码打印了 'val'.

的最后一个已知值

此处链接是 'std::istream >>'

的参考

http://www.cplusplus.com/reference/istream/istream/operator%3E%3E/

因为第二个你有一个bug。

您应该始终检查格式化输入 operator>> 是否确实有效。

所以这段代码:

    cin >> val;
    cout << val << endl;

应该写成:

    if (cin >> val) {
        cout << val << endl;
    }

如果operator>>失败。然后它将在流中设置一个失败位,并且不将任何值放入 val。所以没有必要打印 val 因为什么都没有放进去。

这就是为什么您的第二个版本在没有数据可供读取时打印垃圾的原因。读取失败,然后打印出一个值。然后你尝试重新启动循环(失败)。

第一个工作正常。

while (cin >> val) {
    cout << val << endl;
}

因为您读取了一个值,然后在进入循环之前检查读取是否有效。