Std::string格式,如sprintf

我必须格式化std::stringsprintf,并将其发送到文件流。我该怎么做呢?

1182807 次浏览

你不能直接这样做,因为你没有对底层缓冲区的写访问权(直到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();

String没有你需要的东西,但是std::stringstream有。使用stringstream创建字符串,然后提取字符串。在这里是一个关于你可以做的事情的综合列表。例如:

cout.setprecision(10); //stringstream is a stream like cout

将在打印双精度或浮点数时提供10位小数点后的精度。

如果您只想要一个类似printf的语法(不需要自己调用printf),请查看提升格式

boost::format()提供了你想要的功能:

Boost格式库简介如下:

format对象由format-string构造,然后通过反复调用运算符%给出参数。 然后,这些参数中的每一个都被转换为字符串,这些字符串又根据format-string组合成一个字符串
#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"

[edit: 20/05/25] better still 标题:
< / p >

// `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; }

# eyz0 -函数是为了满足GUI或终端使用#ifdef _some_flag_的任何特殊输出需求,默认值是:

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

[edit '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;
}

它实际上是一个逗号分隔的版本(而不是),有时会阻碍# eyz0 -操作符,使用如下:

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;
}
< p >[之前回答]
这是一个很晚的回答,但对于那些像我一样喜欢“sprintf”方式的人来说:我已经编写并正在使用以下函数。如果你喜欢它,你可以扩展%-选项以更接近sprintf选项;现在里面的已经足够我用了。 使用stringf()和stringfappend()与使用sprintf相同。只要记住参数…

//=============================================================================
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;
}

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会使它变慢。

这是我用来在我的程序中这样做的代码…这没什么特别的,但很管用……注意,您必须根据需要调整您的尺寸。我的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写了我自己的,所以它返回字符串,而不是必须创建我自己的缓冲区。

#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);

你可以试试这个:

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();
}

这避免了从原始答案中.c_str()的结果中丢弃const的需要。

以下是我的(简单的解决方案):

std::string Format(const char* lpszFormat, ...)
{
// Warning : "vsnprintf" crashes with an access violation
// exception if lpszFormat is not a "const char*" (for example, const string&)


size_t  nSize     = 1024;
char    *lpBuffer = (char*)malloc(nSize);


va_list lpParams;


while (true)
{
va_start(lpParams, lpszFormat);


int nResult = vsnprintf(
lpBuffer,
nSize,
lpszFormat,
lpParams
);


va_end(lpParams);


if ((nResult >= 0) && (nResult < (int)nSize) )
{
// Success


lpBuffer[nResult] = '\0';
std::string sResult(lpBuffer);


free (lpBuffer);


return sResult;
}
else
{
// Increase buffer


nSize =
(nResult < 0)
? nSize *= 2
: (nResult + 1)
;


lpBuffer = (char *)realloc(lpBuffer, nSize);
}
}
}

我喜欢的一个解决方案是,在使缓冲区足够大之后,用sprintf直接在std::string缓冲区中执行此操作:

#include <string>
#include <iostream>


using namespace std;


string l_output;
l_output.resize(100);


for (int i = 0; i < 1000; ++i)
{
memset (&l_output[0], 0, 100);
sprintf (&l_output[0], "\r%i\0", i);


cout << l_output;
cout.flush();
}

因此,创建std::string,调整它的大小,直接访问它的缓冲区…

这是谷歌是如何做到的:StringPrintf (BSD许可证)
facebook也采用了类似的方式:StringPrintf (Apache License)
两者都提供了方便的StringAppendF

# EYZ0:

std::wstring stringFormat(const wchar_t* fmt, ...)
{
if (!fmt) {
return L"";
}


std::vector<wchar_t> buff;
size_t size = wcslen(fmt) * 2;
buff.resize(size);
va_list ap;
va_start(ap, fmt);
while (true) {
int ret = _vsnwprintf_s(buff.data(), size, _TRUNCATE, fmt, ap);
if (ret != -1)
break;
else {
size *= 2;
buff.resize(size);
}
}
va_end(ap);
return std::wstring(buff.data());
}
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);
}

少的基础库有一个非常方便的格式函数,它在格式字符串和值中都支持std::string:

  • 道格:# EYZ0
  • 来源:# EYZ0
你可以在cout中使用iomanip头文件格式化c++输出。 在使用类似的任何helper函数之前,请确保包含iomanip头文件 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++流的简要描述。 # EYZ0 < / p >
如果缓冲区不够大,无法打印字符串,就会出现问题。在打印格式化消息之前,必须确定格式化字符串的长度。 我做了自己的帮助器(在Windows和Linux 海湾合作委员会上测试),你可以尝试使用它

String.cpp: http://pastebin.com/DnfvzyKP
String.h: # EYZ0 < / p >

String.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;
}

String.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();

c++ 20有std::format,它在API方面类似于sprintf,但完全是类型安全的,可以使用用户定义的类型,并使用类似python的格式字符串语法。下面是如何格式化std::string并将其写入流:

std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);

或者,你可以使用{fmt}库格式化字符串,并将其写入stdout或文件流:

fmt::print("Look, a string: {}", s);

至于sprintf或这里的大多数其他答案,不幸的是,它们使用可变参数,并且本质上是不安全的,除非你使用像GCC的format属性这样的东西,它只适用于文字格式字符串。你可以在下面的例子中看到为什么这些函数是不安全的:

std::string format_str = "%s";
string_format(format_str, format_str[0]);

其中string_format是Erik Aronesty回答的一个实现。这段代码可以编译,但是当你试图运行它时,它很可能会崩溃:

$ g++ -Wall -Wextra -pedantic test.cc
$ ./a.out
Segmentation fault: 11

免责声明:我是{fmt}和c++ 20 std::format的作者。

我对这个非常流行的问题的看法。

引用类似# eyz0的函数:

成功返回后,这些函数返回打印的字符数(不包括用于结束输出到字符串的空字节)。

函数snprintf()和vsnprintf()写入的字节不超过size(包括结束空字节('\0'))。如果由于这个限制而导致输出被截断,那么返回值就是在有足够空间的情况下写入最终字符串的字符数(不包括终止空字节)。因此,返回值大于或等于size意味着输出被截断了。

换句话说,一个正常的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(),在我看来这是向前迈出的一大步。我想知道他们最终是否会在std::string中添加.format操作符…

编辑

正如alexk7指出的那样,在std::snprintf的返回值上需要一个+1,因为我们需要为\0字节留出空间。直观地说,在大多数架构中,缺少+1将导致required整数被0部分覆盖。这将发生作为std::snprintf的实际参数的required的评估,因此效果应该是不可见的。

然而,这个问题可以改变,例如编译器优化:如果编译器决定为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

检查编译器支持页面,看看它是否在您的标准库实现中可用。截至2021年11月28日,部分支持在Visual Studio 2019 16.10,即发布日期:2021-05-25铿锵声14这里可以看到。在所有其他情况下,您可以求助于下面的c++ 11解决方案,或使用与std::format具有相同语义的{fmt}


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下获得许可。

逐行解释:

使用std::snprintf写入char*,然后将其转换为std::string

首先,使用snprintf中的特殊条件确定所需的char数组长度。从# EYZ1:

返回值

< p >[…如果结果字符串由于buf_size限制而被截断, 函数返回字符总数(不包括 终止空字节),如果限制为 不是。< / p >

这意味着所需的大小是+ 1字符的数量,因此空结束符将位于所有其他字符之后,并且可以再次被字符串构造函数截断。@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 ... );

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

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

您可以在在这里中看到一个示例。


如果您还想在参数列表中使用std::string,请查看这个要点


Visual Studio用户的附加信息:

正如在这个答案中解释的那样,微软将std::snprintf重命名为_snprintf(是的,没有std::)。MS进一步将其设置为弃用,并建议使用_snprintf_s代替,但是_snprintf_s将不会接受缓冲区为零或小于格式化输出,如果发生这种情况,将不会计算输出长度。 因此,为了消除编译过程中的弃用警告,您可以在包含使用_snprintf:

的文件顶部插入下面一行
#pragma warning(disable : 4996)

最终的想法

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

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

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

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

我试了一下,用正则表达式。我为int和const字符串实现了它作为一个例子,但你可以添加任何其他类型(圆荚体类型,但有指针你可以打印任何东西)。

#include <assert.h>
#include <cstdarg>


#include <string>
#include <sstream>
#include <regex>


static std::string
formatArg(std::string argDescr, va_list args) {
std::stringstream ss;
if (argDescr == "i") {
int val = va_arg(args, int);
ss << val;
return ss.str();
}
if (argDescr == "s") {
const char *val = va_arg(args, const char*);
ss << val;
return ss.str();
}
assert(0); //Not implemented
}


std::string format(std::string fmt, ...) {
std::string result(fmt);
va_list args;
va_start(args, fmt);
std::regex e("\\{([^\\{\\}]+)\\}");
std::smatch m;
while (std::regex_search(fmt, m, e)) {
std::string formattedArg = formatArg(m[1].str(), args);
fmt.replace(m.position(), m.length(), formattedArg);
}
va_end(args);
return fmt;
}

下面是一个使用它的例子:

std::string formatted = format("I am {s} and I have {i} cats", "bob", 3);
std::cout << formatted << std::endl;

输出:

我是鲍勃,我有三只猫

这是可以尝试的。简单。虽然没有使用字符串类的细微差别。

#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;
}

Dacavpixelpoint的回答中获得灵感。我玩了一下,得到了这个:

#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);
}

有了< em >的< / em >编程实践,我相信代码应该足够了,但是我仍然对更安全的替代方案持开放态度,这些替代方案仍然足够简单,不需要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的回答是类似,只是它不使用newdelete[],并且在创建std::string时指定了大小。

这里不使用newdelete[]的想法是暗示在堆上使用堆栈,因为它不需要调用分配和回收函数,但是如果使用不当,在一些(可能是旧的,或者只是脆弱的)系统中可能会有缓冲区溢出的危险。如果这是一个问题,我强烈建议使用newdelete[]代替。注意,这里唯一需要关注的是分配,因为vsnprintf()已经被限制调用了,所以根据第二个缓冲区分配的大小指定一个限制也可以防止这些。)

为了以“sprintf”方式格式化std::string,调用snprintf(参数nullptr0)来获得所需的缓冲区长度。使用c++ 11可变模板编写函数,如下所示:

#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);
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();

非常简单的解决方案。

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);

到目前为止,所有的答案似乎都有一个或多个这样的问题:(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, _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;
}

注:

  1. 单独的vc++代码分支是必要的,因为vc++已经决定弃用snprintf,它将为上面其他高投票的答案生成编译器警告。因为我总是在“警告为错误”模式下运行,这对我来说是行不通的。
  2. 函数接受char *而不是std::string。这是因为大多数情况下,这个函数会被字面字符串调用,实际上是char *,而不是std::string。如果您确实有std::string作为格式参数,那么只需调用.c_str()
  3. 函数的名字是stringf,而不是string_format与printf, scanf等保持一致。
  4. 它没有解决安全问题(即坏的参数可能会导致seg故障而不是异常)。如果你需要这个,那么你最好使用boost或fmt库。在这里我更喜欢fmt,因为它只是一个头文件和源文件,而没有boost那么奇怪的格式语法。然而,两者都不兼容printf格式字符串,所以在这种情况下,下面的内容仍然有用。
  5. stringf代码通过GCC严格模式编译。这需要额外的#pragma宏来抑制GCC警告中的假阳性。

以上代码已在,

我通常用这个:

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(使用make_unique函数而不是原始声明),并增加了对std::string参数的支持(基于Kenny Kerr的文章)

#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大小分布的假设是糟糕的编程风格,应该避免。在这种情况下,vargs的任意大变长表示是选择算法的关键因素。

在溢出时重试效率是指数级的,这是c++ 11标准委员会在讨论上述建议时讨论的另一个原因,即在写缓冲区为空时提供一个排练。

在上述生产就绪实现中,第一次运行就是这样的一个演练,以确定分配大小。没有发生分配。几十年来,printf指令的解析和vargs的读取已经变得非常高效。可重用代码应该是可预测的,即使必须牺牲一些微不足道的低效率。

安全性和可靠性

Andrew Koenig在剑桥的一次活动上演讲后对我们一小群人说:“用户功能不应该依赖于利用失败来实现普通的功能。”像往常一样,他的智慧在此后的记录中被证明是正确的。已修复和已关闭的安全错误问题通常表明在修复之前所利用的漏洞的描述中存在重试黑客。

sprintf的替代方案,C9X修订建议, ISO IEC文件WG14 N645/X3J11 96-008中null缓冲区特性的正式标准修订建议中提到了这一点。在动态内存可用性的限制范围内,每个打印指令插入任意长的字符串“%s”并不是一个例外,不应该利用它来产生“非异常功能”。

考虑这个建议以及在c++ Reference.org页面底部给出的示例代码,该页面链接到这个答案的第一段。

同样,失败案例的测试很少像成功案例那样健壮。

可移植性

所有主要的os供应商提供的编译器都完全支持std::vsnprintf作为c++11标准的一部分。运行不再维护发行版的供应商的产品的主机应该提供g++或clang++,原因有很多。

堆栈使用

第一次调用std::vsnprintf时使用的堆栈将小于或等于第二次调用时使用的堆栈,并且将在第二次调用开始前释放。如果第一次调用超过堆栈可用性,那么std::fprintf也会失败。

我知道这个问题已经被回答过很多次了,但下面这个更简洁:

std::string format(const std::string fmt_str, ...)
{
va_list ap;
char *fp = NULL;
va_start(ap, fmt_str);
vasprintf(&fp, fmt_str.c_str(), ap);
va_end(ap);
std::unique_ptr<char[]> formatted(fp);
return std::string(formatted.get());
}

例子:

#include <iostream>
#include <random>


int main()
{
std::random_device r;
std::cout << format("Hello %d!\n", r());
}

参见http://rextester.com/NJB14150

更新1:添加fmt::format测试

我对这里介绍的方法进行了自己的研究,得到了与这里提到的完全相反的结果。

我用了4个函数/ 4个方法:

  • 变进函数+ vsnprintf + std::unique_ptr
  • 变进函数+ vsnprintf + std::string
  • 可变参数模板函数+ std::ostringstream + std::tuple + utility::for_each
  • fmt库中的fmt::format函数

对于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);
}
}

# EYZ0。

# EYZ0:

#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);


}

# EYZ0:

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;
}


}

# EYZ0:

[ 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中运行。

更新了一些答案,不同的是-函数将正确接受std::string为%s

namespace format_helper
{


template <class Src>
inline Src cast(Src v)
{
return v;
}


inline const char *cast(const std::string& v)
{
return v.c_str();
}
};


template <typename... Ts>
inline std::string stringfmt (const std::string &fmt, Ts&&... vs)
{
using namespace format_helper;
char b;
size_t required = std::snprintf(&b, 0, fmt.c_str(), cast(std::forward<Ts>(vs))...);//not counting the terminating null character.
std::string result;
//because we use string as container, it adds extra 0 automatically
result.resize(required , 0);
//and snprintf will use n-1 bytes supplied
std::snprintf(const_cast<char*>(result.data()), required + 1, fmt.c_str(), cast(std::forward<Ts>(vs))...);


return result;
}

生活:# EYZ0

这是一个特定于Windows的解决方案,旨在避免Visual Studio中的编译器警告而不消除它们。所讨论的警告是针对使用std::string和va_start,这会错误地产生警告,以及针对使用已弃用的printf变量。

template<typename ... va>
std::string Format( const std::string& format, va ... args )
{
std::string s;
s.resize( _scprintf( format.c_str(), args ... ) + 1 );
s.resize( _snprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
return s;
}


template<typename ... va>
std::wstring Format( const std::wstring& format, va ... args )
{
std::wstring s;
s.resize( _scwprintf( format.c_str(), args ... ) + 1 );
s.resize( _snwprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
return s;
}


std::string s = Format( "%hs %d", "abc", 123 );
std::wstring ws = Format( L"%hs %d", "abc", 123 );

我现在将为Visual Studio编写版本,希望有一天有人会让它变得可移植。(怀疑需要替换_vsnwprintfvsnwprintf和类似的东西。)

您需要从项目配置中使用define _CRT_SECURE_NO_WARNINGS来禁用已弃用警告。

我使用_vsnwprintf第一个参数作为nullptr能够估计缓冲区大小,保留wstring缓冲区,然后格式化字符串直接到缓冲区。

不确定为什么需要禁用已弃用的警告,因为相同方法调用(_vsnwprintf_s)的安全版本不能使用nullptr作为输入。怀疑需要报告给微软c++团队。

这个版本应该同时使用stringwstring类。

如果你发现任何错误或不一致,请再问一次,我会尽力修复它。

stringHelpers.h:


#pragma once
#include <string>


//
//  Formats string/wstring according to format, if formatting fails (e.g. invalid %s pointer - returns empty string)
//
template <typename T>
std::basic_string<T> sFormat(const T* format, ...)
{
va_list args;
va_start(args, format);
int size;


if constexpr (std::is_same_v<T, char>)
size = vsnprintf(nullptr, 0, format, args);
else
size = _vsnwprintf(nullptr, 0, format, args);


size++; // Zero termination
std::basic_string<T> s;
s.resize(size);


if constexpr (std::is_same_v<T, char>)
vsnprintf(&s[0], size, format, args);
else
_vsnwprintf(&s[0], size, format, args);


va_end(args);
return s;
}

以上是代码示例,可以复制。我将维护工作版本在我自己的仓库在github:

https://github.com/tapika/cppscriptcore/blob/master/SolutionProjectModel/helpers.h#L12

20 # EYZ0 c++

它来了!该特性在: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);
}

带有g++-9 -std=c++2a的GCC 9.1.0仍然不支持它。

现有的fmt库在获得官方支持之前实现了它:https://github.com/fmtlib/fmt,正如前面提到的:Std::string格式,如sprintf

sudo apt install libfmt-dev

修改源代码以替换:

  • <format><fmt/core.h>
  • std::formatfmt::format

main.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}

c++ cout十六进制值?< / >

前导零{:03}

用c++输出操作符打印前导零?< / >

左对齐{:<},右对齐{:>},中间对齐{:^}

打印时c++对齐cout <<

浮点精度{:.2}

在正数{:+}上显示签名

如何在c++中打印带有前缀+的正数

显示布尔值为truefalse: {:}

在c++中将bool转换为文本

Windows和Visual Studio有一个非常有吸引力的解决方案:CString。

CString str;
str.Format("Hello %s\n", "World");
str = "ABC";
str += "DEF";

这个问题已经解决了。但是,我认为这是c++format string的另一种方式

class string_format {
private:
std::string _result;
public:
string_format( ) { }
~string_format( ) { std::string( ).swap( _result ); }
const std::string& get_data( ) const { return _result; }
template<typename T, typename... Targs>
void format( const char* fmt, T value, Targs... Fargs ) {
for ( ; *fmt != '\0'; fmt++ ) {
if ( *fmt == '%' ) {
_result += value;
this->format( fmt + 1, Fargs..., 0 ); // recursive call
return;
}
_result += *fmt;
}
}
friend std::ostream& operator<<( std::ostream& ostream, const string_format& inst );
};
inline std::string& operator+=( std::string& str, int val ) {
str.append( std::to_string( val ) );
return str;
}
inline std::string& operator+=( std::string& str, double val ) {
str.append( std::to_string( val ) );
return str;
}
inline std::string& operator+=( std::string& str, bool val ) {
str.append( val ? "true" : "false" );
return str;
}
inline std::ostream& operator<<( std::ostream& ostream, const string_format& inst ) {
ostream << inst.get_data( );
return ostream;
}

并测试这个类:

string_format fmt;
fmt.format( "Hello % and is working ? Ans: %", "world", true );
std::cout << fmt;

你可以检查它在这里

我不喜欢把事情搞复杂。这是基于iFreilicht的答案,但我减少了一些噪音,使它更有效。请注意,如果您计划在接口中使用此功能,可能会添加一些模糊输入检查。

#include <iostream>
#include <string>


template<typename... Ts>
std::string string_format( const std::string& format, Ts... Args )
{
const size_t n = std::snprintf( nullptr, 0, format.c_str(), Args ... ) + 1; // Extra space for '\0'
std::string ret(n, '\0');
std::snprintf( &ret.front(), n, format.c_str(), Args... );
return ret;
}


int main()
{
int a = 5;
char c = 'h';
double k = 10.3;
std::cout << string_format("%d, %c, %.2f", a, c, k) << "\n";
}

输出:

5, h, 10.30

自己试试

(*唯一的警告,我发现性能方面是没有办法默认初始化字符串存储。这很遗憾,因为我们不需要在这里将所有的值初始化为“\0”。)

这里是内存使用(和执行速度)方面的最佳解决方案,不依赖于RVO,如果字符串大小大于零,也可以执行追加,还会自动调整std::string的大小。

宏解决方案IMO更好,现代编译器将警告如果格式字符串不匹配的类型。该函数版本不会出现此警告,因为编译器无法看到snprintf。宏版本也更短,它也需要一个更少的包含。

来自:

https://github.com/ericcurtin/twincam

宏观的解决方案:

#include <string.h>
#include <string>


// function that will sprintf to a C++ string starting from std::string::size()
// so if you want to completely overwrite a string or start at a specific point
// use std::string::clear() or std::string::resize(). str is a std::string.
#define STRING_PRINTF(str, ...)                                   \
do {                                                            \
const int size = snprintf(NULL, 0, __VA_ARGS__);              \
const size_t start_of_string = str.size();                    \
str.resize(start_of_string + size);                           \
snprintf(&str[start_of_string], str.size() + 1, __VA_ARGS__); \
} while (0)

函数的解决方案:

#include <stdarg.h>  // For va_start, etc.
#include <string.h>
#include <string>


// function that will sprintf to a C++ string starting from std::string::size()
// so if you want to completely overwrite a string or start at a specific point
// use std::string::clear() or std::string::resize()
int string_printf(std::string& str, const char* const fmt, ...) {
c_va_list c_args;


va_start(c_args.args, fmt);


c_va_list tmpa;
va_copy(tmpa.args, c_args.args);


// Get addtional size required
int size = vsnprintf(NULL, 0, fmt, tmpa.args);
if (size < 0) {
return -1;
}


const size_t start_of_string = str.size();
str.resize(start_of_string + size);


// plus 1 so the null terminator gets included
size = vsnprintf(&str[start_of_string], str.size() + 1, fmt, c_args.args);
return size;
}

更优解:

#define STRING_PRINTF(str, ...)                                     \
do {                                                              \
const size_t write_point = str.size();                          \
str.resize(write_point + 127);                                  \
const int size = snprintf(&str[write_point], 128, __VA_ARGS__); \
str.resize(write_point + size);                                 \
if (size < 128) {                                               \
break;                                                        \
}                                                               \
\
snprintf(&str[write_point], size + 1, __VA_ARGS__);             \
} while (0)

这是一个更优的解决方案,假设sprintf小于128字节,如果是,格式字符串只解析一次而不是两次。

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]"
}