C++ 程序行为

C++ program behavior

演示了当输入后有附加字符时,C++ 无法检测浮点数中的输入问题。

234.5 在使用 cin 时工作正常,但 234.t(演示输入时手指滑动)会读取 234. 并保留 t.

想象一下当某些输入失败而其他输入失败时我的惊讶。注意:这些并不是复杂的验证器,只是一个向学生介绍输入可能难以验证的演示。

在 C++ 14 下的 Clion Mac 上编译的程序

#include <iostream>
#include <fstream>

int main() {

    float number = 0.0;
    int counter = 0;
    std::ifstream inData;
    inData.open("input.txt");

    inData >> number;
    for(int i = 0; i < 26;i++ ){
        if(inData.good()) {
            std::cout << "Good Row number: " << i << " " << static_cast<char>(i+97) << std::endl;
        } else {
            std::cout << "\tFail Row number: " << i << " " << static_cast<char>(i+97) << std::endl;
            inData.clear();
            inData.ignore(1, '\n');
        }

        inData >> number;
    }

    return 0;
}

注意:出于好奇,我运行使用这个输入文件

234.a
234.b
234.c
234.d
234.e
234.f
234.g
234.h
234.i
234.j
234.k
234.l
234.m
234.n
234.o
234.p
234.q
234.r
234.s
234.t
234.u
234.v
234.w
234.x
234.y
234.z

这是输出:

    Fail Row number: 0 a
    Fail Row number: 1 b
    Fail Row number: 2 c
    Fail Row number: 3 d
    Fail Row number: 4 e
    Fail Row number: 5 f
Good Row number: 6 g
    Fail Row number: 7 h
Good Row number: 8 i
    Fail Row number: 9 j
    Fail Row number: 10 k
Good Row number: 11 l
    Fail Row number: 12 m
Good Row number: 13 n
    Fail Row number: 14 o
Good Row number: 15 p
    Fail Row number: 16 q
Good Row number: 17 r
    Fail Row number: 18 s
    Fail Row number: 19 t
Good Row number: 20 u
    Fail Row number: 21 v
    Fail Row number: 22 w
Good Row number: 23 x
    Fail Row number: 24 y
Good Row number: 25 z

有人知道发生了什么事吗?

评论说明: -是的,使用 'a' 与 97 相比更好,但部分课程是强调字符具有十进制值,因此它们很容易比较。

这似乎是解析浮点数的不一致之一,偶尔会出现在标准库的实现中(参见 1169, 2381)。

考虑以下修改后的代码段

#include <iostream>
#include <sstream>
#include <string>

int main()
{
    float number{};
    std::string test{ "234.0" };
    
    for(char ch{'a'}; ch <= 'z'; ++ch )
    {
        test.back() = ch;
        std::istringstream ss{ test };
        
        if (ss >> number)
            std::cout << ch << ' ';
    }
    std::cout << '\n';
}

Here 你可以看到,当使用 Clang 和 -stdlib=libc++ 编译时,它会产生以下输出

g h j k l m o q r s t u v w y z 

-stdlib=libstdc++ 给出

a b c d f g h i j k l m n o p q r s t u v w x y z     // Only 'e' is rejected

使用 strtof, all the cases are rejected (see e.g. here).

operator>>

(5) Extracts a floating-point value potentially skipping preceding whitespace. The value is stored to a given reference value. This function behaves as a FormattedInputFunction. After constructing and checking the sentry object, which may skip leading whitespace, extracts a floating-point value by calling std::num_get::get().

标准指定 std::num_get::get()28.4.3.2 的行为 [locale.num.get].

分为三个阶段

  1. 确定转换说明符
  2. 从 in 中提取字符并为阶段 1 中确定的转换规范所期望的格式确定相应的 char 值。
  3. 存储结果

stage 2的细节提到

If it is not discarded, then a check is made to determine if c is allowed as the next character of an input field of the conversion specifier returned by Stage 1. If so, it is accumulated.

stage 3

The sequence of chars accumulated in stage 2 (the field) is converted to a numeric value by the rules of one of the functions declared in the header :
[...]
For a float value, the function strtof.
[...]
The numeric value to be stored can be one of:

  • zero, if the conversion function does not convert the entire field.
  • [...]
  • the converted value, otherwise.

The resultant numeric value is stored in val.
If the conversion function does not convert the entire field, or if the field represents a value outside the range of representable values, ios_­base​::​failbit is assigned to err.

根据之前的结果,似乎阶段 2 在两个测试库中都没有正确实现。