std::string 格式如 sprintf

2023-11-29

我必须格式化std::string with sprintf并将其发送到文件流中。我怎样才能做到这一点?


现代 C++ 使这变得超级简单。

C++20

C++20介绍std::format,这使您可以做到这一点。它使用类似于的替换字段那些在Python中:

#include <iostream>
#include <format>
 
int main() {
    std::cout << std::format("Hello {}!\n", "world");
}

代码来自cppreference.com、CC BY-SA 和 GFDL

查看编译器支持页面查看它在您的标准库实现中是否可用。

截至 2023 年 7 月 18 日,部分支持从以下时间开始提供:

  • 视觉工作室 2019 16.10, 发布于 2021-05-25
  • Clang 14, 此处跟踪功能
  • GCC 13.1, 发布于 2023-04-26 though 支持正式为“实验性”.

在所有其他情况下,您可以求助于下面的 C++11 解决方案,或使用the {fmt} library,其语义与std::format.


C++11

With C++11s std::snprintf,这已经成为一项相当简单且安全的任务。

#include <memory>
#include <string>
#include <stdexcept>

template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
    int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
    if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
    auto size = static_cast<size_t>( size_s );
    std::unique_ptr<char[]> buf( new char[ size ] );
    std::snprintf( buf.get(), size, format.c_str(), args ... );
    return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
}

上面的代码片段已获得许可CC0 1.0.

逐行解释:

Aim:写信给一个char*通过使用std::snprintf然后将其转换为std::string.

首先,我们使用特殊条件确定 char 数组的所需长度snprintf. From cppreference.com:

返回值

[...] 如果结果字符串由于 buf_size 限制而被截断, 函数返回字符总数(不包括 终止空字节),如果限制是,则会被写入 不强加。

这意味着所需的大小是字符数plus one,这样空终止符将位于所有其他字符之后,并且可以再次被字符串构造函数截断。 @alexk7 在评论中解释了这个问题。

int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

snprintf如果发生错误,将返回负数,因此我们然后检查格式是否按预期工作。正如 @ead 在评论中指出的,不这样做可能会导致无提示错误或分配巨大的缓冲区。

if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

因为我们知道size_s不能为负数,我们使用静态转换将其从有符号的转换而来int给一个未签名的size_t。这样,即使是最迂腐的编译器也不会抱怨下一行将发生的转换。

size_t size = static_cast<size_t>( size_s );

接下来,我们分配一个新的字符数组并将其分配给std::unique_ptr。通常建议这样做,因为您不必再​​次手动删除它。

请注意,这不是一种安全的分配方法unique_ptr使用用户定义的类型,因为如果构造函数抛出异常,则无法释放内存!

std::unique_ptr<char[]> buf( new char[ size ] );

In C++14,你可以改为使用make_unique, which is对于用户定义的类型是安全的。

auto buf = std::make_unique<char[]>( size );

之后,我们当然可以使用snprintf以满足其预期用途并将格式化字符串写入char[].

std::snprintf( buf.get(), size, format.c_str(), args ... );

最后,我们创建并返回一个新的std::string由此,确保省略最后的空终止符。

return std::string( buf.get(), buf.get() + size - 1 );

您可以看到一个实际的例子here.


如果您也想使用std::string在参数列表中,看一下这个要点.


附加信息视觉工作室 users:

正如中所解释的这个答案, 微软更名std::snprintf to _snprintf(是的,没有std::)。 MS 进一步将其设置为已弃用并建议使用_snprintf_s然而,相反_snprintf_s不会接受缓冲区为零或小于格式化输出,如果发生这种情况,也不会计算输出长度。 因此,为了摆脱编译期间的弃用警告,您可以插入以下行在文件的顶部,其中包含使用_snprintf:

#pragma warning(disable : 4996)

最后的想法

这个问题的很多答案都是在 C++11 之前写的,并且使用固定缓冲区长度或 vargs。除非您坚持使用旧版本的 C++,否则我不建议使用这些解决方案。理想情况下,采用 C++20 方式。

由于本答案中的 C++11 解决方案使用模板,因此如果使用较多,它会生成相当多的代码。但是,除非您正在为二进制文件空间非常有限的环境进行开发,否则这不会成为问题,并且在清晰度和安全性方面仍然比其他解决方案有了巨大的改进。

如果空间效率非常重要,这两个解决方案与 vargs 和 vsnprintf可能有用。不使用任何具有固定缓冲区长度的解决方案都是自找麻烦。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

std::string 格式如 sprintf 的相关文章

随机推荐