有比 std::stoi 更严格的版本吗?

3

我刚刚发现(让我很惊讶)以下输入不会导致std::stoi抛出异常:

3.14
3.14helloworld

违反了最小惊讶原则-因为这些都不是有效的整数格式值。

请注意,更令人惊讶的是,3.8被转换为值3

是否有一个更严格的std::stoi版本,当输入确实无效时会抛出异常?还是我必须自己编写?

另外,为什么C++标准库要以这种方式实现 std::stoi ?这个函数唯一的实际用途是在随机垃圾输入中拼命尝试获取一些整数值-这似乎不是一个非常有用的函数。

这是我的解决方法。

static int convertToInt(const std::string& value)
{
    std::size_t index;
    int converted_value{std::stoi(value, &index)};

    if(index != value.size())
    {
        throw std::runtime_error("Bad format input");
    }

    return converted_value;
}

10
stoi 和相关函数的行为就像输入操作一样。如果你有int foo; cin >> foo;并输入3.14作为输入,你会在 foo 中得到 3,正如使用 stoi 时一样。你可以使用 stoi 的第二个参数来确认是否所有的输入都被转换或者没有。 - NathanOliver
4
你考虑过该函数在处理字符串后为什么有一个size_t*类型的参数吗?你尝试过在指向的size_t变量中检查值吗?你明白这个和所进行的转换有什么关系吗?如果不理解,是否尝试查阅文档以便了解呢? - Karl Knechtel
3
在没有流的情况下应用流处理假设(“要读取的值是流中的第一件事,剩余的数据留在流中以便稍后处理”)有点奇怪。保持解析函数行为的一致性和使它们在特定任务中表现最佳都是有价值的,而在这方面,一致性胜出。 - Ben Voigt
1
请注意,你发布的“解决方法”是不一致的,因为你的程序接受前导空格(在 std::stoi 中被忽略),但在结尾处抛出异常。因此,在抛出异常之前,你可能需要使用 std::isspace 测试结尾处的输入。查看我的另一个答案的示例,了解如何操作。 - Andreas Wenzel
2
@AndreasWenzel 因此,在抛出异常之前,您可能希望使用std::isspace测试尾随输入-即使对于大多数人来说,这也不够严格,例如在“3 hello”中,报告的位置将是空格,但不是尾随空格。相反,我会从报告的位置开始,并使用string::find_first_not_of()std::find_if()检查输入字符串中是否有任何未解析的非空白字符,然后如果发现任何未解析的字符,则抛出异常。 - Remy Lebeau
显示剩余3条评论
2个回答

6
你的问题的答案是:

有没有比std::stoi更严格的版本?

答案是:标准库中没有。
这里所描述的,std::stoi的行为和CPP参考文档中解释的一样:

丢弃任何空格字符(由调用std::isspace识别),直到找到第一个非空格字符,然后尽可能多地获取字符以形成有效的基数n(其中n = base)整数表示,并将它们转换为整数值。有效的整数值由以下部分组成:......

如果你希望有一个也许更健壮的std::stoi版本来适应你的特殊需求,你确实需要编写自己的函数。
有许多潜在的实现方案,因此没有一个“正确”的解决方案。它取决于你的需求和编程风格。
我只是展示了(众多可能的)示例解决方案之一:
#include <iostream>
#include <string>
#include <utility>
#include <regex>

// Some example. Many many other different soultions possible
std::pair<int, bool> stoiSpecial(const std::string s) {

    int result{};
    bool validArgument{};

    if (std::regex_match(s, std::regex("[+-]?[0-9]+"))) {
        try {
            result = stoi(s);
            validArgument = true;
        }
        catch (...) {};
    }
    return {result, validArgument };
}

// Some test code
int main() {
    
    std::string valueAsString{};
    std::getline(std::cin,valueAsString);

    if (const auto& [result, validArgument] = stoiSpecial(valueAsString); validArgument)
        std::cout << result << '\n';
    else
        std::cerr << "\n\n*** Error: Invalid Argument\n\n";
}

很好地使用了regex_match,这实际上提供了一种更简单的解决问题的方法。它也适用于字符串和浮点格式,尽管我在主要问题中没有提到这一点。关于regex_match - 我如何匹配可打印字符串?"[\s-~]+"会起作用吗?我假设\s表示空格字符。我还没有实际检查如何匹配空格字符。我假设有一种匹配空格的方法,不会匹配制表符和换行符之类的东西。 - FreelanceConsultant
换句话说,匹配从[空格]到~ [波浪号]的所有 ASCII 字符。 - FreelanceConsultant

4
你需要自己编写代码,因为你的需求与C和C++中所有“字符串转整数”功能定义的一致性相冲突。首先,你需要确定“有效整数”的定义。你接受前导0(八进制)、前导0x(十六进制)和/或前导0b(二进制)吗?你接受前导空格吗?如果两者都可以,那么你的解决方法就足够好了。否则,你需要检查字符串的第一个字符是否是isdigit并且非空。
我刚刚发现(令我惊讶的是),以下输入不会导致std :: stoi引发异常:
在使用任何您不熟悉的函数之前阅读一个好的参考资料是一个相当基本的要求。
该参考资料非常清楚地说明,在跳过任何前导空格后,它将采用“尽可能多的字符”来形成“有效的[...]整数表示”,并且第二个参数“将接收第一个未转换的字符的地址”。
违反最小惊奇原则-因为这些都不是有效的格式整数值之一。
请注意,也许更令人惊讶的是,3.8转换为值3。
是否有更严格的std :: stoi版本,可以在输入确实不是有效整数时引发异常?还是我必须自己编写?
这里存在一个重要问题:您做出了一些假设,没有通过参考进行核实,现在却坚持认为自己更懂。您观察到的行为不仅与所有C++的istream operator>>std::sto*家族以及C的*scanfstrto*ato*家族内部一致,而且也是Java的Scanner.nextInt()、C#的int.TryParse、Perl的int和其他十几种语言中类似函数的工作方式。 (顺便说一句,各种浮点数解析函数也是如此。)

为什么std::stoi是这样实现的?

因为这是最适合一般用例的最高效实现。

这个函数唯一实际的用途是从随机垃圾输入中拼命尝试获取一些整数值,这似乎不是很有用的函数。

考虑:

4;3.14;16

那显然不是“随机垃圾输入”,而是分号分隔的数据——你会同意这种情况很常见。如果“读取整数”在非数字输入时抛出异常,就像你建议的那样,我们将至少需要抛出两个异常来解析这个非常普通的输入行。或者,我们必须两次处理该输入,首先找到分号/行尾(并且可能需要写入输入字符串或设置多个临时变量),然后第二次进行解析。这将非常低效。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接