我必须格式化std::string
用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();
vsnprintf()
内部使用的C++ 11解决方案:
#include// 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// For va_start, etc. #include // 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 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
会使其变慢.
利用C++ 11 std::snprintf
,这变得非常简单和安全.我看到很多关于这个问题的答案显然是在C++ 11之前编写的,它使用固定的缓冲区长度和varg,我不建议出于安全性,效率和清晰度的原因.
#include#include #include #include template std::string string_format( const std::string& format, Args ... args ) { size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0' std::unique_ptr buf( new char[ size ] ); 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下获得许可.
目标:char*
通过使用 写入astd::snprintf
然后将其转换为astd::string
.
首先,我们确定char数组的所需长度.
来自cppreference.com:
返回值
[...]如果由于buf_size限制而导致结果字符串被截断,则函数将返回已写入的字符总数(不包括终止空字节),如果未强制执行限制.
这意味着所需的大小是字符数加1,因此空终止符将位于所有其他字符之后,并且可以再次被字符串构造函数截断.@ alexk7在评论中解释了这个问题.
然后,我们分配一个新的字符数组并将其分配给a std::unique_ptr
.通常建议这样做,因为您不必delete
再次手动操作.
请注意,这不是一种unique_ptr
使用用户定义类型分配的安全方法,因为如果构造函数抛出异常,则无法释放内存!
在那之后,我们当然可以使用snprintf
它的预期用途并将格式化的字符串写入char[]
,然后创建并返回一个新的std::string
.
你可以在这里看到一个实例.
如果您还想std::string
在参数列表中使用,请查看此要点.
Visual Studio用户的其他信息:
正如在这个答案中解释的那样,微软改名std::snprintf
为_snprintf
(是的,没有std::
).MS进一步将其设置为已弃用并建议使用_snprintf_s
,但_snprintf_s
不会接受缓冲区为零或小于格式化输出,并且如果发生这种情况则不会计算输出长度.因此,为了在编译期间摆脱弃用警告,您可以在文件顶部插入以下行,其中包含以下内容_snprintf
:
#pragma warning(disable : 4996)
boost::format()
提供您想要的功能:
从Boost格式库概要:
格式对象由格式字符串构造,然后通过重复调用operator%给出参数.然后根据格式字符串将每个参数转换为字符串,然后将字符串组合成一个字符串.
#includecout << boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50; // prints "writing toto, x=40.230 : 50-th try"
不幸的是,这里的大多数答案都使用了varargs,除非你使用GCC的std::format
属性,它只适用于文字格式字符串,否则它本身就是不安全的.您可以在以下示例中看到这些函数为何不安全:
std::string s = "foo"; std::cout << std::format("Look, a string: {}", s);
sprintf
Erik Aronesty的答案在哪里实施.此代码编译,但当您尝试运行它时,它很可能会崩溃:
std::string s = "foo"; puts(std::format("Look, a string: {}", s).c_str());
可以实现安全性std::string
并将其扩展为stdout
使用(可变参数)模板进行格式化.这已经在{fmt}库中完成,它为sprintf
返回提供了一个安全的替代方法format
:
fmt::print(f, "Look, a string: {}", s); // where f is a file stream
{fmt}跟踪参数类型,如果类型与格式规范不匹配,则不存在分段错误,只有异常或编译时错误(如果使用string_format
格式字符串检查).
免责声明:我是{fmt}的作者.
如果您只想要类似printf的语法(不自行调用printf),请查看Boost Format.
我使用vsnprintf编写了自己的,所以它返回字符串而不必创建自己的缓冲区.
#include#include //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);
为了以std::string
'sprintf'方式格式化,调用snprintf
(arguments nullptr
和0
)以获得所需的缓冲区长度.使用C++ 11可变参数模板编写函数,如下所示:
#include#include #include 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);
[edit '17/8/31]添加一个可变模板版'vtspf(..)':
templateconst std::string type_to_string(const T &v) { std::ostringstream ss; ss << v; return ss.str(); }; template const T string_to_type(const std::string &str) { std::istringstream ss(str); T ret; ss >> ret; return ret; }; template void vtspf_priv(std::string &s) {} template void vtspf_priv(std::string &s, H h, P...p) { s+=type_to_string(h); vtspf_priv(s, p...); } template 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#include #include //============================================================================= 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 0) && ((b=(n [上一个答案]
一个非常晚的答案,但对于那些像我一样喜欢'sprintf'方式的人:我写过并且正在使用以下功能.如果你喜欢它,你可以扩展%-options以更接近sprintf的那些; 目前那些足以满足我的需求.你使用与sprintf相同的stringf()和stringfappend().请记住,...的参数必须是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
10> PW...:这就是谷歌的做法:(
StringPrintf
BSD许可证)
和Facebook以非常相似的方式做到:(StringPrintf
Apache许可证)
两者都提供了方便StringAppendF
.
11> Dacav..:我对这个非常受欢迎的问题的两分钱.
引用类似函数的联机帮助页
printf
:成功返回后,这些函数返回打印的字符数(不包括用于结束输出到字符串的空字节).
函数snprintf()和vsnprintf()写入的字节数不超过大小(包括终止空字节('\ 0')).如果输出由于此限制而被截断,则返回值是字符数(不包括终止空字节),如果有足够的空间,则该字符数将被写入最终字符串.因此,大小或更大的返回值意味着输出被截断.
换句话说,理智的C++ 11实现应该如下:
#include#include template 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指出的那样,
+1
返回值需要Astd::snprintf
,因为我们需要为\0
字节留出空间.直观地说,在大多数缺少的架构上+1
都会导致required
整数被a部分覆盖0
.这将在评估required
为实际参数后发生std::snprintf
,因此效果不应该是可见的.然而,这个问题可能会改变,例如编译器优化:如果编译器决定使用寄存器作为
required
变量怎么办?这种错误有时会导致安全问题.
使用"char bytes [required]"将在堆栈而不是堆上分配,在大型格式字符串上可能会有危险.考虑使用新的代替.晏
12> 小智..:templatestd::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
13> Douglas Dase..:测试,生产质量答案
这个答案处理符合标准的技术的一般情况.在其页面底部附近的CppReference.com上给出了相同的方法作为示例.与他们的示例不同,此代码符合问题的要求,并在机器人和卫星应用程序中进行现场测试.它还改进了评论.设计质量将在下面进一步讨论.
#include#include #include // 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 zc(iLen + 1); std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs); va_end(vaArgs); return std::string(zc.data(), iLen); } #include #include #include // 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; } 可预测的线性效率
根据问题规范,两次通过是安全,可靠和可预测的可重用功能的必需品.关于可重用函数中vargs大小分布的假设是糟糕的编程风格,应该避免.在这种情况下,vargs的任意大的可变长度表示是算法选择的关键因素.
在溢出时重试是指数效率低的,这是当C++ 11标准委员会讨论上述提议在写缓冲区为空时提供干运行时讨论的另一个原因.
在上面的生产就绪实现中,第一次运行是这样的干运行以确定分配大小.没有分配.几十年来,printf指令的解析和vargs的读取已经非常有效.可重复使用的代码应该是可预测的,即使必须牺牲一些微不足道的低效率.
安全性和可靠性
安德鲁·科尼格在剑桥大学的一次活动讲座后对我们的一小部分人说:"用户功能不应该依赖于利用失败来实现无与伦比的功能." 像往常一样,自那以后,他的智慧在记录中得到了证实.固定和封闭的安全性错误问题通常表示在修复之前利用漏洞的描述中重试黑客.
这在sprintf的替代品中的空缓冲特征的正式标准修订提案,C9X修订提案,ISO IEC文件WG14 N645/X3J11 96-008中提到.在动态内存可用性的约束下,每个print指令插入一个任意长的字符串"%s",这不是一个例外,不应该被利用来产生"Unexceptional functions".
请考虑与本答案第一段中链接的C++ Reference.org页面底部给出的示例代码一起提出的建议.
此外,对失败案例的测试很少能够成功案例.
可移植性
所有主要操作系统供应商都提供完全支持std :: vsnprintf的编译器,作为c ++ 11标准的一部分.运行不再维护发行版的供应商产品的主机应该提供g ++或clang ++,原因有很多.
堆栈使用
第一次调用std :: vsnprintf时的堆栈使用将小于或等于第二次调用,并且它将在第二次调用开始之前释放.如果第一个调用超过堆栈可用性,那么std :: fprintf也会失败.
14> Ciro Santill..:C ++ 20
std::format
已经到了!该功能在http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html中进行了描述,并使用类似Python的
.format()
语法。我希望用法将是这样的:
#include#include int main() { std::string message = std::format("The answer is {}.", 42); } 当GCC支持到达时,我会尝试一下,GCC 9.1.0
g++-9 -std=c++2a
仍然不支持它。该API将添加一个新的
std::format
标头:建议的格式API在新的标头中定义,
对现有代码没有影响。
fmt
如果需要polyfill ,现有的库声称可以实现它:https : //github.com/fmtlib/fmtC ++ 20的实现
std::format
。并在前面提到过:std :: string格式如sprintf
15> ChetS..:根据Erik Aronesty提供的答案:
std::string string_format(const std::string &fmt, ...) { std::vectorstr(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()
.
16> 小智..: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); }