提问人:Max Frai 提问时间:2/26/2010 最后编辑:DeduplicatorMax Frai 更新时间:7/18/2023 访问量:1306963
std::string 格式,如 sprintf
std::string formatting like sprintf
答:
你不能直接执行此操作,因为你没有对底层缓冲区的写入权限(直到 C++11;请参阅 Dietrich Epp 的评论)。你必须先在 c-string 中执行此操作,然后将其复制到 std::string 中:
char buff[100];
snprintf(buff, sizeof(buff), "%s", "Hello");
std::string buffAsStdStr = buff;
但我不确定你为什么不直接使用字符串流?我假设你有具体的理由不这样做:
std::ostringstream stringStream;
stringStream << "Hello";
std::string copyOfStr = stringStream.str();
评论
char buf[100];
asprintf
std::string
free
double
%s
string 没有你需要的东西,但 std::stringstream 有。使用 stringstream 创建字符串,然后提取字符串。以下是您可以做的事情的完整列表。例如:
cout.setprecision(10); //stringstream is a stream like cout
在打印双精度或浮点数时,将为您提供 10 位小数的精度。
评论
如果你只想要一个类似 printf 的语法(不自己调用 printf),请查看 Boost Format。
评论
boost::format()
提供了您想要的功能:
根据 Boost 格式库概要:
格式对象由格式字符串构造,然后通过重复调用运算符来提供参数。 然后,这些参数中的每一个都转换为字符串,这些字符串又根据格式字符串组合成一个字符串。
#include <boost/format.hpp>
cout << boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50;
// prints "writing toto, x=40.230 : 50-th try"
评论
[编辑:20/05/25] 更好的是...: 在标题中:
// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>
void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }
-函数是用来满足GUI或终端或任何特殊输出需求的,默认为:PRINTSTRING(r)
#ifdef _some_flag_
void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }
[编辑 '17/8/31]添加可变参数模板版本“vtspf(..)”:
template<typename T> const std::string type_to_string(const T &v)
{
std::ostringstream ss;
ss << v;
return ss.str();
};
template<typename T> const T string_to_type(const std::string &str)
{
std::istringstream ss(str);
T ret;
ss >> ret;
return ret;
};
template<typename...P> void vtspf_priv(std::string &s) {}
template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
s+=type_to_string(h);
vtspf_priv(s, p...);
}
template<typename...P> std::string temp_vtspf(P...p)
{
std::string s("");
vtspf_priv(s, p...);
return s;
}
这实际上是有时阻碍 -运算符的逗号分隔版本(而不是),使用方式如下:<<
char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);
[编辑]改编为利用 Erik Aronesty 的回答(上图)中的技术:
#include <string>
#include <cstdarg>
#include <cstdio>
//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
int n, size=100;
bool b=false;
va_list marker;
while (!b)
{
s.resize(size);
va_start(marker, fmt);
n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
va_end(marker);
if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
}
}
//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
std::string ss;
int n, size=100;
bool b=false;
va_list marker;
while (!b)
{
ss.resize(size);
va_start(marker, fmt);
n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
va_end(marker);
if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
}
s += ss;
}
[上一个答案]
一个很晚的答案,但对于那些像我一样喜欢“sprintf”方式的人来说:我已经编写并使用了以下函数。如果你喜欢它,你可以扩展 %-options 以更接近 sprintf 选项;目前那里的那些足以满足我的需求。
你使用 stringf() 和 stringfappend() 与 sprintf 相同。请记住,...必须是 POD 类型。
//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
char *s, ch=0;
int n, i=0, m;
long l;
double d;
std::string sf = sformat;
std::stringstream ss;
m = sf.length();
while (i<m)
{
ch = sf.at(i);
if (ch == '%')
{
i++;
if (i<m)
{
ch = sf.at(i);
switch(ch)
{
case 's': { s = va_arg(marker, char*); ss << s; } break;
case 'c': { n = va_arg(marker, int); ss << (char)n; } break;
case 'd': { n = va_arg(marker, int); ss << (int)n; } break;
case 'l': { l = va_arg(marker, long); ss << (long)l; } break;
case 'f': { d = va_arg(marker, double); ss << (float)d; } break;
case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
case 'X':
case 'x':
{
if (++i<m)
{
ss << std::hex << std::setiosflags (std::ios_base::showbase);
if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
char ch2 = sf.at(i);
if (ch2 == 'c') { n = va_arg(marker, int); ss << std::hex << (char)n; }
else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
else if (ch2 == 'l') { l = va_arg(marker, long); ss << std::hex << (long)l; }
else ss << '%' << ch << ch2;
ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
}
} break;
case '%': { ss << '%'; } break;
default:
{
ss << "%" << ch;
//i = m; //get out of loop
}
}
}
}
else ss << ch;
i++;
}
va_end(marker);
sF = ss.str();
}
//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
va_list marker;
va_start(marker, sformat);
DoFormatting(stgt, sformat, marker);
}
//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
string sF = "";
va_list marker;
va_start(marker, sformat);
DoFormatting(sF, sformat, marker);
stgt += sF;
}
评论
fmt
内部使用的 C++ 11 解决方案:vsnprintf()
#include <stdarg.h> // For va_start, etc.
std::string string_format(const std::string fmt, ...) {
int size = ((int)fmt.size()) * 2 + 50; // Use a rubric appropriate for your code
std::string str;
va_list ap;
while (1) { // Maximum two passes on a POSIX system...
str.resize(size);
va_start(ap, fmt);
int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
va_end(ap);
if (n > -1 && n < size) { // Everything worked
str.resize(n);
return str;
}
if (n > -1) // Needed size returned
size = n + 1; // For null char
else
size *= 2; // Guess at a larger size (OS specific)
}
return str;
}
一种更安全、更高效(我测试过,而且速度更快)的方法:
#include <stdarg.h> // For va_start, etc.
#include <memory> // For std::unique_ptr
std::string string_format(const std::string fmt_str, ...) {
int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
std::unique_ptr<char[]> formatted;
va_list ap;
while(1) {
formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
strcpy(&formatted[0], fmt_str.c_str());
va_start(ap, fmt_str);
final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
va_end(ap);
if (final_n < 0 || final_n >= n)
n += abs(final_n - n + 1);
else
break;
}
return std::string(formatted.get());
}
按值传递,以符合 的要求。fmt_str
va_start
注意:“更安全”和“更快”的版本在某些系统上不起作用。因此,两者仍然被列出。此外,“更快”完全取决于预分配步骤是否正确,否则会使其变慢。strcpy
评论
size
vsnprintf()
这是我在程序中用于执行此操作的代码......这没什么花哨的,但它确实做到了......请注意,您必须根据需要调整尺寸。对我来说MAX_BUFFER是 1024。
std::string Format ( const char *fmt, ... )
{
char textString[MAX_BUFFER*5] = {'\0'};
// -- Empty the buffer properly to ensure no leaks.
memset(textString, '\0', sizeof(textString));
va_list args;
va_start ( args, fmt );
vsnprintf ( textString, MAX_BUFFER*5, fmt, args );
va_end ( args );
std::string retStr = textString;
return retStr;
}
评论
vsnprintf
我使用 vsnprintf 编写了自己的字符串,因此它返回字符串,而不必创建自己的缓冲区。
#include <string>
#include <cstdarg>
//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
int size = 512;
char* buffer = 0;
buffer = new char[size];
va_list vl;
va_start(vl, fmt);
int nsize = vsnprintf(buffer, size, fmt, vl);
if(size<=nsize){ //fail delete buffer and try again
delete[] buffer;
buffer = 0;
buffer = new char[nsize+1]; //+1 for /0
nsize = vsnprintf(buffer, size, fmt, vl);
}
std::string ret(buffer);
va_end(vl);
delete[] buffer;
return ret;
}
所以你可以像这样使用它
std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
评论
vsnprintf
std::unique_ptr<char[]> buffer (new char[size]);
你可以试试这个:
string str;
str.resize( _MAX_PATH );
sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11
str.resize( strlen( str.data() ) + 1 );
根据 Erik Aronesty 提供的答案:
std::string string_format(const std::string &fmt, ...) {
std::vector<char> str(100,'\0');
va_list ap;
while (1) {
va_start(ap, fmt);
auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
va_end(ap);
if ((n > -1) && (size_t(n) < str.size())) {
return str.data();
}
if (n > -1)
str.resize( n + 1 );
else
str.resize( str.size() * 2);
}
return str.data();
}
这避免了抛弃原始答案中结果的需要。const
.c_str()
评论
谷歌就是这样做的:StringPrintf(BSD许可证)和Facebook以非常相似的方式做到这一点:StringPrintf
(Apache许可证)
两者都提供了一个方便的。StringAppendF
inline void format(string& a_string, const char* fmt, ...)
{
va_list vl;
va_start(vl, fmt);
int size = _vscprintf( fmt, vl );
a_string.resize( ++size );
vsnprintf_s((char*)a_string.data(), size, _TRUNCATE, fmt, vl);
va_end(vl);
}
评论
_vscprintf
Poco Foundation 库有一个非常方便的格式函数,它支持 std::string 格式字符串和值:
- 文档:http://pocoproject.org/docs/Poco.html#7308
- 来源:https://github.com/pocoproject/poco/blob/develop/Foundation/src/Format.cpp
您可以使用 iomanip 头文件格式化 cout 中的 C++ 输出。 在使用任何帮助程序函数(如 setprecision、setfill 等。
这是我过去用来打印矢量中平均等待时间的代码片段,我已经“累积”了。
#include<iomanip>
#include<iostream>
#include<vector>
#include<numeric>
...
cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
cout << " and " << Q.size() << " tasks remaining" << endl;
以下是我们如何格式化 C++ 流的简要说明。http://www.cprogramming.com/tutorial/iomanip.html
如果缓冲区不够大,无法打印字符串,则可能会出现问题。在其中打印格式化邮件之前,必须确定格式化字符串的长度。 我为此制作了自己的助手(在 Windows 和 Linux GCC 上进行了测试),您可以尝试使用它。
字符串.cpp:http://pastebin.com/DnfvzyKP
String.h:http://pastebin.com/7U6iCUMa
字符串 .cpp:
#include <cstdio>
#include <cstdarg>
#include <cstring>
#include <string>
using ::std::string;
#pragma warning(disable : 4996)
#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
int length;
va_list apStrLen;
va_copy(apStrLen, ap);
length = vsnprintf(NULL, 0, format, apStrLen);
va_end(apStrLen);
if (length > 0) {
dst.resize(length);
vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
} else {
dst = "Format error! format: ";
dst.append(format);
}
}
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
va_list ap;
va_start(ap, format);
toString(dst, format, ap);
va_end(ap);
}
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
string dst;
va_list ap;
va_start(ap, format);
toString(dst, format, ap);
va_end(ap);
return dst;
}
///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
string dst;
toString(dst, format, ap);
return dst;
}
int main() {
int a = 32;
const char * str = "This works!";
string test(toString("\nSome testing: a = %d, %s\n", a, str));
printf(test.c_str());
a = 0x7fffffff;
test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
printf(test.c_str());
a = 0x80000000;
toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
printf(test.c_str());
return 0;
}
字符串.h:
#pragma once
#include <cstdarg>
#include <string>
using ::std::string;
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();
评论
vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
dst.resize(length+1); vsnprintf((char *)dst.data(), dst.size(), format, ap); dst.resize(length);
data
c_str
C++ 20 具有 std::format
,它在 API 方面类似于 API,但完全类型安全,适用于用户定义的类型,并使用类似 Python 的格式字符串语法。以下是格式化并将其写入流的方法:sprintf
std::string
std::cout << std::format("The answer is {}.", 42);
或者,您可以使用 {fmt} 库来格式化字符串并将其一次性写入文件流:stdout
fmt::print("The answer is {}.", 42);
至于这里的大多数其他答案,不幸的是,它们使用 varargs 并且本质上是不安全的,除非您使用像 GCC 属性这样的东西,它仅适用于文字格式字符串。您可以在以下示例中看到为什么这些函数不安全:sprintf
format
std::string format_str = "%s";
string_format(format_str, format_str[0]);
Erik Aronesty 的答案中的实现在哪里。此代码会编译,但当您尝试运行它时,它很可能会崩溃:string_format
$ g++ -Wall -Wextra -pedantic test.cc
$ ./a.out
Segmentation fault: 11
免责声明:我是 {fmt} 和 C++20 的作者。std::format
评论
error: 'fmt' has not been declared
fmt
我对这个非常受欢迎的问题有两分钱。
成功返回后,这些函数将返回打印的字符数(不包括用于结束字符串输出的空字节)。
函数 snprintf() 和 vsnprintf() 写入的字节数不超过 (包括终止的 null 字节 ('\0'))。如果输出由于此限制而被截断,则返回值是字符数(不包括终止的 null 字节),如果有足够的可用空间,该字符数将写入最终字符串。因此,大小或更大的返回值意味着输出被截断。
换句话说,一个健全的 C++11 实现应该是这样的:
#include <string>
#include <cstdio>
template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
char b;
size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
// See comments: the +1 is necessary, while the first parameter
// can also be set to nullptr
char bytes[required];
std::snprintf(bytes, required, fmt.c_str(), vs...);
return std::string(bytes);
}
它工作得很好:)
可变参数模板仅在 C++11 中受支持。Pixelpoint 的答案显示了使用旧编程风格的类似技术。
奇怪的是,C++没有这样的东西。他们最近添加了 to_string(),
在我看来,这是向前迈出的一大步。我想知道他们是否会最终添加一个操作员.......format
std::string
编辑
正如 alexk7 所指出的,在 的返回值上需要 A,因为我们需要为字节留出空间。直观地说,在大多数架构上,缺少 will 会导致整数被 .这将在计算为 的实际参数 之后发生,因此效果应该不可见。+1
std::snprintf
\0
+1
required
0
required
std::snprintf
然而,这个问题可能会改变,例如在编译器优化中:如果编译器决定对变量使用寄存器怎么办?这种错误有时会导致安全问题。required
评论
bytes
required
现代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 日,部分支持可从以下方式开始:
- Visual Studio 2019 16.10,2021-05-25 发布
- Clang 14,此处跟踪特征
- GCC 13.1,于 2023 年 4 月 26 日发布,但支持正式处于“实验阶段”。
在所有其他情况下,您可以求助于下面的 C++11 解决方案,或使用与 具有相同语义的 {fmt}
库。std::format
C++11
使用 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 下获得许可。
逐行说明:
目的:使用 写入 a,然后将其转换为 .char*
std::snprintf
std::string
首先,我们使用 中的特殊条件确定 char 数组的所需长度。从 cppreference.com:snprintf
返回值
[...]如果生成的字符串由于buf_size限制而被截断, 函数返回总字符数(不包括 终止 null-byte),如果限制是 不强加。
这意味着所需的大小是字符数加 1,以便 null 终止符将位于所有其他字符之后,并且可以再次被字符串构造函数切断。@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 ] );
在 C++14 中,您可以改用 make_unique
,这对于用户定义的类型是安全的。
auto buf = std::make_unique<char[]>( size );
之后,我们当然可以将其用于预期用途,并将格式化的字符串写入 .snprintf
char[]
std::snprintf( buf.get(), size, format.c_str(), args ... );
最后,我们从中创建一个 new 并返回一个,确保省略末尾的 null 终止符。std::string
return std::string( buf.get(), buf.get() + size - 1 );
您可以在此处查看实际操作示例。
如果您还想在参数列表中使用,请查看此要点。std::string
面向 Visual Studio 用户的其他信息:
如此答案中所述,Microsoft 重命名为(是的,没有)。MS 进一步将其设置为已弃用,并建议改用 _snprintf_s
,但不会接受缓冲区为零或小于格式化输出,如果发生这种情况,则不会计算输出长度。
因此,为了摆脱编译过程中的弃用警告,您可以在文件顶部插入以下行,其中包含以下用法:std::snprintf
_snprintf
std::
_snprintf_s
_snprintf
#pragma warning(disable : 4996)
最后的思考
这个问题的很多答案都是在 C++11 之前编写的,并且使用固定的缓冲区长度或 varg。除非你坚持使用旧版本的C++,否则我不建议使用这些解决方案。理想情况下,采用 C++20 方式。
由于此答案中的 C++11 解决方案使用模板,因此如果大量使用,它可以生成相当多的代码。但是,除非您正在为二进制文件空间非常有限的环境进行开发,否则这不会成为问题,并且在清晰度和安全性方面仍比其他解决方案有了巨大的改进。
如果空间效率非常重要,那么这两个带有 vargs 和 vsnprintf 的解决方案可能会很有用。不要使用任何具有固定缓冲区长度的解决方案,那只会自找麻烦。
评论
return string(&buf[0], size);
std::vector
return string(buf.get(), buf.get() + size);
return string(buf.get(), buf.get() + size - 1);
error: format not a string literal and no format arguments [-Werror=format-security]
format
snprintf()
template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
size_t size = snprintf(nullptr, 0, fmt, args...);
std::string buf;
buf.reserve(size + 1);
buf.resize(size);
snprintf(&buf[0], size + 1, fmt, args...);
return buf;
}
使用 C99 snprintf 和 C++11
评论
va_list
std::string
std::wstring
这可以尝试一下。简单。不过,实际上没有使用字符串类的细微差别。
#include <stdarg.h>
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <string>
#include <exception>
using namespace std;
//---------------------------------------------------------------------
class StringFormatter
{
public:
static string format(const char *format, ...);
};
string StringFormatter::format(const char *format, ...)
{
va_list argptr;
va_start(argptr, format);
char *ptr;
size_t size;
FILE *fp_mem = open_memstream(&ptr, &size);
assert(fp_mem);
vfprintf (fp_mem, format, argptr);
fclose (fp_mem);
va_end(argptr);
string ret = ptr;
free(ptr);
return ret;
}
//---------------------------------------------------------------------
int main(void)
{
string temp = StringFormatter::format("my age is %d", 100);
printf("%s\n", temp.c_str());
return 0;
}
如果你使用的是 asprintf(3) 的系统, 你可以很容易地包装它:
#include <iostream>
#include <cstdarg>
#include <cstdio>
std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
std::string format(const char *fmt, ...)
{
std::string result;
va_list ap;
va_start(ap, fmt);
char *tmp = 0;
int res = vasprintf(&tmp, fmt, ap);
va_end(ap);
if (res != -1) {
result = tmp;
free(tmp);
} else {
// The vasprintf call failed, either do nothing and
// fall through (will return empty string) or
// throw an exception, if your code uses those
}
return result;
}
int main(int argc, char *argv[]) {
std::string username = "you";
std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
return 0;
}
评论
format
std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
throw std::bad_alloc();
从 Dacav 和 pixelpoint 的答案中汲取了这个想法。我玩了一会儿,得到了这个:
#include <cstdarg>
#include <cstdio>
#include <string>
std::string format(const char* fmt, ...)
{
va_list vl;
va_start(vl, fmt);
int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
va_end(vl);
char buffer[size];
va_start(vl, fmt);
size = vsnprintf(buffer, size, fmt, vl);
va_end(vl);
return std::string(buffer, size);
}
通过理智的编程实践,我相信代码应该足够了,但是我仍然对更安全的替代方案持开放态度,这些替代方案仍然足够简单并且不需要 C++11。
这是另一个版本,它利用初始缓冲区来防止在初始缓冲区已经足够时进行第二次调用。vsnprintf()
std::string format(const char* fmt, ...)
{
va_list vl;
int size;
enum { INITIAL_BUFFER_SIZE = 512 };
{
char buffer[INITIAL_BUFFER_SIZE];
va_start(vl, fmt);
size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
va_end(vl);
if (size < INITIAL_BUFFER_SIZE)
return std::string(buffer, size);
}
size += sizeof('\0');
char buffer[size];
va_start(vl, fmt);
size = vsnprintf(buffer, size, fmt, vl);
va_end(vl);
return std::string(buffer, size);
}
(原来这个版本和Piti Ongmongkolkul的答案差不多,只是没有使用和,还在创建时指定了大小。new
delete[]
std::string
这里不使用 and 的想法意味着在堆上使用堆栈,因为它不需要调用分配和释放函数,但是如果使用不当,在某些(可能是旧的,或者可能只是易受攻击的)系统中缓冲溢出可能是危险的。如果这是一个问题,我强烈建议改用 AND。请注意,这里唯一关心的是已经使用限制调用的分配,因此根据第二个缓冲区上分配的大小指定限制也会阻止这些限制。new
delete[]
new
delete[]
vsnprintf()
为了以“sprintf”方式格式化,请调用 (arguments and ) 以获取所需的缓冲区长度。使用 C++11 可变参数模板编写函数,如下所示:std::string
snprintf
nullptr
0
#include <cstdio>
#include <string>
#include <cassert>
template< typename... Args >
std::string string_sprintf( const char* format, Args... args ) {
int length = std::snprintf( nullptr, 0, format, args... );
assert( length >= 0 );
char* buf = new char[length + 1];
std::snprintf( buf, length + 1, format, args... );
std::string str( buf );
delete[] buf;
return str;
}
使用 C++11 支持进行编译,例如在 GCC 中:g++ -std=c++11
用法:
std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
评论
char buf[length + 1];
char* buf = new char[length + 1];
char[]
char*
new
string_sprintf("value: %020000000d",5)
new char[length + 1]
到目前为止,这里的所有答案似乎都存在以下一个或多个问题:(1)它可能不适用于VC++(2)它需要额外的依赖项,如boost或fmt(3)它过于复杂的自定义实现,可能没有经过很好的测试。
下面的代码解决了上述所有问题。
#include <string>
#include <cstdarg>
#include <memory>
std::string stringf(const char* format, ...)
{
va_list args;
va_start(args, format);
#ifndef _MSC_VER
//GCC generates warning for valid use of snprintf to get
//size of result string. We suppress warning with below macro.
#ifdef __GNUC__
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#endif
size_t size = std::snprintf(nullptr, 0, format, args) + 1; // Extra space for '\0'
#ifdef __GNUC__
# pragma GCC diagnostic pop
#endif
std::unique_ptr<char[]> buf(new char[ size ] );
std::vsnprintf(buf.get(), size, format, args);
return std::string(buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
#else
int size = _vscprintf(format, args);
std::string result(size, 0);
vsnprintf_s((char*)result.data(), size + 1, _TRUNCATE, format, args);
return result;
#endif
va_end(args);
}
int main() {
float f = 3.f;
int i = 5;
std::string s = "hello!";
auto rs = stringf("i=%d, f=%f, s=%s", i, f, s.c_str());
printf("%s", rs.c_str());
return 0;
}
笔记:
- 单独的 VC++ 代码分支是必要的,因为 VC++ 已决定弃用该分支,这将为上述其他高投票答案生成编译器警告。由于我总是在“警告作为错误”模式下运行,因此它不适合我。
snprintf
- 该函数接受而不是 。这是因为大多数时候这个函数会用文字字符串来调用,这确实是 ,而不是 。如果您确实有 format 参数,则只需调用 .
char *
std::string
char *
std::string
std::string
.c_str()
- 该函数的名称是 stringf,而不是像 string_format to keep up with printf, scanf 等。
- 它没有解决安全问题(即错误的参数可能会导致 seg 错误而不是异常)。如果你需要这个,那么你最好使用boost或fmt库。我在这里的偏好是 fmt,因为它只是一个要放入项目的标题和源文件,同时具有比 boost 更奇怪的格式语法。但是,两者都与 printf 格式字符串不兼容,因此在这种情况下,下面仍然有用。
- stringf 代码通过 GCC 严格模式编译。这需要额外的宏来抑制 GCC 警告中的误报。
#pragma
上面的代码已经过测试,
评论
我通常用这个:
std::string myformat(const char *const fmt, ...)
{
char *buffer = NULL;
va_list ap;
va_start(ap, fmt);
(void)vasprintf(&buffer, fmt, ap);
va_end(ap);
std::string result = buffer;
free(buffer);
return result;
}
缺点:并非所有系统都支持 vasprint
评论
下面略微修改了@iFreilicht答案的版本,更新为 C++14(使用函数而不是原始声明)并添加了对参数的支持(基于 Kenny Kerr 的文章)make_unique
std::string
)
#include <iostream>
#include <memory>
#include <string>
#include <cstdio>
template <typename T>
T process_arg(T value) noexcept
{
return value;
}
template <typename T>
T const * process_arg(std::basic_string<T> const & value) noexcept
{
return value.c_str();
}
template<typename ... Args>
std::string string_format(const std::string& format, Args const & ... args)
{
const auto fmt = format.c_str();
const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
auto buf = std::make_unique<char[]>(size);
std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
auto res = std::string(buf.get(), buf.get() + size - 1);
return res;
}
int main()
{
int i = 3;
float f = 5.f;
char* s0 = "hello";
std::string s1 = "world";
std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
}
输出:
i = 3, f = 5.000000, s = hello world
如果需要,请随时将此答案与原始答案合并。
经过测试,生产质量答案
此答案使用符合标准的技术处理一般情况。在页面底部附近的 CppReference.com 上给出了相同的方法作为示例。与示例不同的是,此代码符合问题的要求,并在机器人和卫星应用中进行了现场测试。它还改进了评论。设计质量将在下文中进一步讨论。
#include <string>
#include <cstdarg>
#include <vector>
// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {
// initialize use of the variable argument array
va_list vaArgs;
va_start(vaArgs, zcFormat);
// reliably acquire the size
// from a copy of the variable argument array
// and a functionally reliable call to mock the formatting
va_list vaArgsCopy;
va_copy(vaArgsCopy, vaArgs);
const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
va_end(vaArgsCopy);
// return a formatted string without risking memory mismanagement
// and without assuming any compiler or platform specific behavior
std::vector<char> zc(iLen + 1);
std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
va_end(vaArgs);
return std::string(zc.data(), iLen); }
#include <ctime>
#include <iostream>
#include <iomanip>
// demonstration of use
int main() {
std::time_t t = std::time(nullptr);
std::cerr
<< std::put_time(std::localtime(& t), "%D %T")
<< " [debug]: "
<< vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
<< std::endl;
return 0; }
可预测的线性效率
根据问题规范,两次通过是安全、可靠和可预测的可重用功能的必要条件。关于可重用函数中 varg 大小分布的假设是糟糕的编程风格,应避免。在这种情况下,任意大的变量长度表示是选择算法的关键因素。
溢出时重试效率呈指数级低,这是 C++11 标准委员会讨论上述建议时讨论的另一个原因,即在写入缓冲区为 null 时提供试运行。
在上面的生产就绪实现中,第一次运行就是这样的试运行,以确定分配大小。不进行分配。几十年来,printf 指令的解析和 vargs 的读取已经变得非常高效。可重用的代码应该是可预测的,即使必须牺牲微不足道的低效率。
安全可靠
安德鲁·柯尼希(Andrew Koenig)在剑桥的一次活动上发表演讲后对我们中的一小群人说:“用户功能不应该依赖于利用故障来获得非异常功能。像往常一样,他的智慧在记录中得到了证明。已修复和已关闭的安全 bug 问题通常表明在修复之前利用的漏洞的描述中存在重试黑客攻击。
这在 sprintf 的替代方案、C9X 修订提案、ISO IEC 文档 WG14 N645/X3J11 96-008 中对空缓冲区功能的正式标准修订提案中有所提及。在动态内存可用性的约束下,每个打印指令插入的任意长字符串“%s”不是例外,不应被利用它来产生“异常功能”。
考虑该提案以及本答案第一段中链接到的 C++Reference.org 页面底部给出的示例代码。
此外,失败案例的测试很少像成功案例那样强大。
可移植性
所有主要的 OS 供应商都提供完全支持 std::vsnprintf 的编译器,作为 c++11 标准的一部分。出于多种原因,运行不再维护发行版的供应商的产品的主机应配备 g++ 或 clang++。
堆栈使用
对 std::vsnprintf 的第一次调用中的堆栈使用量将小于或等于第二次调用,并且将在第二次调用开始之前释放。如果第一次调用超过堆栈可用性,则 std::fprintf 也会失败。
评论
std::vector<char>
std::string
更新 1:添加了测试fmt::format
我围绕这里介绍的方法进行了自己的调查,并获得了与这里提到的截然相反的结果。
我在 4 种方法中使用了 4 个函数:
- 变调功能 +
vsnprintf
+std::unique_ptr
- 变调功能 +
vsnprintf
+std::string
- 可变参数模板函数 +
std::ostringstream
+std::tuple
+utility::for_each
fmt::format
库中的函数fmt
对于已使用的测试后端。googletest
#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>
#include <fmt/format.h>
inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
size_t str_len = (std::max)(fmt_str.size(), string_reserve);
// plain buffer is a bit faster here than std::string::reserve
std::unique_ptr<char[]> formatted;
va_list ap;
va_start(ap, fmt_str);
while (true) {
formatted.reset(new char[str_len]);
const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);
if (final_n < 0 || final_n >= int(str_len))
str_len += (std::abs)(final_n - int(str_len) + 1);
else
break;
}
va_end(ap);
return std::string(formatted.get());
}
inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
size_t str_len = (std::max)(fmt_str.size(), string_reserve);
std::string str;
va_list ap;
va_start(ap, fmt_str);
while (true) {
str.resize(str_len);
const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);
if (final_n < 0 || final_n >= int(str_len))
str_len += (std::abs)(final_n - int(str_len) + 1);
else {
str.resize(final_n); // do not forget to shrink the size!
break;
}
}
va_end(ap);
return str;
}
template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
std::ostringstream ss;
if (string_reserve) {
ss.rdbuf()->str().reserve(string_reserve);
}
std::tuple<Args...> t{ args... };
utility::for_each(t, [&ss](auto & v)
{
ss << v;
});
return ss.str();
}
实现取自此处:迭代元组for_each
#include <type_traits>
#include <tuple>
namespace utility {
template <std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I == sizeof...(Tp), void>::type
for_each(std::tuple<Tp...> &, const FuncT &)
{
}
template<std::size_t I = 0, typename FuncT, typename... Tp>
inline typename std::enable_if<I < sizeof...(Tp), void>::type
for_each(std::tuple<Tp...> & t, const FuncT & f)
{
f(std::get<I>(t));
for_each<I + 1, FuncT, Tp...>(t, f);
}
}
测试:
TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
for (size_t i = 0; i < 1000000; i++) {
std::ostringstream ss;
ss << "test test test" << "+" << 12345 << "\n";
const std::string v = ss.str();
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
for (size_t i = 0; i < 1000000; i++) {
std::ostringstream ss;
ss.rdbuf()->str().reserve(256);
ss << "test test test" << "+" << 12345 << "\n";
const std::string v = ss.str();
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_fmt_format_positional)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
TEST(ExternalFuncs, test_fmt_format_named)
{
for (size_t i = 0; i < 1000000; i++) {
const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
}
}
这。UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR
未起诉.hpp:
#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var) ::utility::unused_param(&var)
namespace utility {
extern const volatile void * volatile g_unused_param_storage_ptr;
extern void
#ifdef __GNUC__
__attribute__((optimize("O0")))
#endif
unused_param(const volatile void * p);
}
未使用的 .cpp:
namespace utility {
const volatile void * volatile g_unused_param_storage_ptr = nullptr;
void
#ifdef __GNUC__
__attribute__((optimize("O0")))
#endif
unused_param(const volatile void * p)
{
g_unused_param_storage_ptr = p;
}
}
结果:
[ RUN ] ExternalFuncs.test_string_format_on_unique_ptr_0
[ OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN ] ExternalFuncs.test_string_format_on_unique_ptr_256
[ OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN ] ExternalFuncs.test_string_format_on_std_string_0
[ OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN ] ExternalFuncs.test_string_format_on_std_string_256
[ OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[ OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[ OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[ OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[ OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN ] ExternalFuncs.test_fmt_format_positional
[ OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN ] ExternalFuncs.test_fmt_format_named
[ OK ] ExternalFuncs.test_fmt_format_named (392 ms)
正如你所看到的,通过 + 实现等于 ,但比通过 + 更快,而 + 比通过 .vsnprintf
std::string
fmt::format
vsnprintf
std::unique_ptr
std::ostringstream
在 中编译并运行的测试。Visual Studio 2015 Update 3
Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB
C++20 std::格式
它来了!该功能在以下位置进行了描述:http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html 并使用类似 Python 的语法。.format()
我希望用法是这样的:
#include <format>
#include <string>
int main() {
std::string message = std::format("The answer is {}.", 42);
}
GCC 9.1.0 仍然不支持它。g++-9 -std=c++2a
现有库在获得官方支持之前实现它: https://github.com/fmtlib/fmt 如前所述: std::string 格式化,如 sprintf 在 Ubuntu 上安装 22.04:fmt
sudo apt install libfmt-dev
修改源以替换:
<format>
跟<fmt/core.h>
std::format
自fmt::format
主 .cpp
#include <string>
#include <iostream>
#include <fmt/core.h>
int main() {
std::string message = fmt::format("The answer is {}.", 42);
std::cout << message << std::endl;
}
并使用以下命令进行编译和运行:
g++ -std=c++11 -o main.out main.cpp -lfmt
./main.out
输出:
The answer is 42.
API 将添加一个新的标头:std::format
建议的格式设置 API 在新标头中定义,应该不会对现有代码产生影响。
<format>
十六进制格式 {:x}
前导零 {:03}
左对齐 {:<}
,右对齐 {:>}
,居中 {:^}
浮点精度 {:.2}
显示正数上的符号 {:+}
将布尔值显示为 true
和 false
:{:}
(这在 VC++ VS2019、VS2022 上运行良好)
C++ 17 解决方案(这将适用于 std::string 和 std::wstring):
分配一个缓冲区,格式化到其中,然后将其复制到另一个字符串中是无效的。可以创建格式化字符串大小的 std::string 并直接格式化到该字符串缓冲区中:
#include <string>
#include <stdexcept>
#include <cwchar>
#include <cstdio>
#include <type_traits>
template<typename T, typename ... Args>
std::basic_string<T> string_format(T const* const format, Args ... args)
{
int size_signed{ 0 };
// 1) Determine size with error handling:
if constexpr (std::is_same_v<T, char>) { // C++17
size_signed = std::snprintf(nullptr, 0, format, args ...);
}
else {
size_signed = std::swprintf(nullptr, 0, format, args ...);
}
if (size_signed <= 0) {
throw std::runtime_error("error during formatting.");
}
const auto size = static_cast<size_t>(size_signed);
// 2) Prepare formatted string:
std::basic_string<T> formatted(size, T{});
if constexpr (std::is_same_v<T, char>) { // C++17
std::snprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
}
else {
std::swprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
}
return formatted; // Named Return Value Optimization (NRVO), avoids an unnecessary copy.
}
另外:通常,format 参数是 char[] / wchar_t[] & 创建 std::string 对象效率不高。传递 char* 或 wchar_t* & 如果你已经有一个 std::string 对象,你仍然可以将其用作 your_string.c_str()。例:
int main()
{
int i{ 0 };
// The format parameter is a char[] / wchar_t[]:
const std::string title1 = string_format("story[%d].", ++i); // => "story[1]"
const std::wstring title2 = string_format(L"story[%d].", ++i); // => L"story[2]"
// If you already have a std::string object:
const std::string format1{ "story[%d]." };
const std::string title3 = string_format(format1.c_str(), ++i); // => "story[3]"
const std::wstring format2{ L"story[%d]." };
const std::wstring title4 = string_format(format2.c_str(), ++i); // => L"story[4]"
}
评论
std::format
C++20
C++20
boost
std::format
C++20
boost::format
std::format
{}
%
printf
boost::format
{fmt}
std::format