现代 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可能有用。不使用任何具有固定缓冲区长度的解决方案都是自找麻烦。