STOF、STOI、STOD 与提取器操作员

stof, stoi, stod vs extractor operator

提问人:Kikadass 提问时间:9/11/2023 最后编辑:wohlstadKikadass 更新时间:9/11/2023 访问量:178

问:

我想知道现代 C++ 中最推荐哪种方式将字符串转换为数字。双精度型、浮点型或整数型。

我遇到了 C++11,但我一直在我创建的帮助程序函数中使用提取器运算符。 但是,现在有了 C++11 // 存在。因此,我应该停止使用我的助手功能吗?我尝试研究这两种方法的不同之处,但我无法清楚地看到它们的优缺点。stofstringstreamstofstoistod

template <typename T>
bool stringToValue(const std::string& item, T& value)
{
  T tmp;
  std::istringstream iss(item);
  if ((iss >> std::dec >> tmp).fail())
  {
    return false;
  }
  else
  {
    value = tmp;
    return true;
  }
}

我也有兴趣保持尽可能高的精度。

补充一点,我在这里找到了所有可能的选项: https://www.techiedelight.com/convert-a-string-to-a-float-in-cpp/

它们在效率或精确度方面没有进行比较。

注意:我已经看到在 C++17 中使用了 gcc 11(我相信)。我们(我和我的团队)最近迁移到了 C++17,但是,我们停留在 gcc 9.4.0。因此,可悲的是,我们不能使用那个。std::from_chars

C++ 性能 类型转换 精度

评论

4赞 HolyBlackCat 9/11/2023
std::from_chars 是当今最热门的东西。所有其他替代方案都有需要解决的怪癖(主要是不完整的输入字符串验证,例如静默忽略前导空格)。您的实现也有同样的问题,此外,它还忽略了字符串末尾的垃圾。
2赞 HolyBlackCat 9/11/2023
std::strtol & std::strtod 和其他是下一个最好的东西。它们需要一些手动工作来做正确的事情,所以一定要为你的包装器编写大量的测试(字符串开头的空格、末尾的空格、末尾的垃圾、数字在两个方向上都超出范围(分别用于有符号、无符号和浮点)等)。
2赞 Alan Birtles 9/11/2023
请注意,如果区域设置对应用程序很重要,则不使用区域设置。首先要问的问题是性能真的重要吗?您的程序目前是否花费大量时间解析数字?如果没有,那么请坚持使用您已经有效的解决方案。所有解析器都具有相同的精度(它们甚至可能调用相同的底层转换例程)std::from_chars
1赞 HolyBlackCat 9/11/2023
@AlanBirtles 题外话:我不记得有一次与语言环境相关的数字解析是有用的。我的语言环境中的小数点是 而不是 ,但让面向用户的应用程序只理解是愚蠢的。唯一合理的选择是在解析之前替换 ->,而不考虑区域设置(在面向用户的应用程序中),或者完全不支持(在文本文件格式中)。Microsoft 试图接受整个区域设置,现在 Excel 无法直接在我的区域设置中打开 CSV 文件,因为它们在已被视为小数点时从 to 切换为字段分隔符。,.,,.,,;,
2赞 Jan Schultke 9/11/2023
另见 stackoverflow.com/q/7663709/5740428;但那里的答案也没有真正比较不同的方法

答:

1赞 Jan Schultke 9/11/2023 #1

在 C++ 中有许多解析数字的方法。如需全面比较,请参阅此答案

简而言之,所有函数都应提供相同程度的精度。 它们很可能都使用相同的基础例程在某个时间点解析浮点数。

主要区别在于它们提供的接口,以及该接口的便利性/开销。有些函数(如具有可怕的错误处理和模糊的名称),但由于不使用区域设置功能,并且不使用任何动态多态性,因此应该比流更有效。std::atoi

如有疑问,请进行基准测试以比较它们的性能。没有一个功能严格优于所有其他功能。

何时(不)使用std::sto*

单独使用系列函数是完全可以的,但它们并不能构成项目中随处可见的良好“基本效用”。它们在失败时抛出异常,并且不确定输入是否为数字(即可能失败)的“尝试解析”会很慢,因为异常处理成本非常高。std::sto*

如果你不在乎,你只需要一些简单和安全的东西(虽然不是通用的),函数就可以了。std::sto*

赞成的论点std::istringstream

流的开销很高,标头相对较大。乍一看,它们确实没有吸引力,但如果您的目标是制作“通用解析函数”,那么流比任何其他方法都更容易,因为它应该适用于所有方法。 您可以继续使用当前函数。<sstream>>>

但是,如果 include of 的成本太高,或者您负担不起流的运行时开销,那么您必须回退到下一个最好的东西,即函数系列:<sstream>std::strto*

bool stringToValue(const std::string& str, long& out, int base = 10) {
    errno = 0;
    char* end = nullptr;
    out = std::strtol(str.data(), str.data() + str.size(), &end, base);
    return str.data() != end && errno == 0;
}

bool stringToValue(const std::string& str, int& out, int base = 10) {
    errno = 0;
    char* end = nullptr; // note: there is no std::strtoi, so we use std::strtol
    out = std::strtol(str.data(), str.data() + str.size(), &end, base);
    return str.data() != end && errno == 0;
}

bool stringToValue(const std::string& str, float& out) {
    errno = 0;
    char* end = nullptr;
    out = std::strtof(str.data(), str.data() + str.size(), &end, base);
    return str.data() != end && errno == 0;
}

// ...

使用模板,我们可以避免复制和粘贴每个算术类型的实现。但是,替换流仍然需要付出很多努力。