我必须格式化std::string
并将sprintf
其发送到文件流中。我怎样才能做到这一点?
41 回答
现代 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 日,部分支持在 2021 年 5 月 25 日发布的Visual Studio 2019 16.10和在此处跟踪的Clang 14中提供。在所有其他情况下,您可以求助于下面的 C++11 解决方案,或使用与具有相同语义的库。{fmt}
std::format
C++11
使用C++11 s 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下获得许可。
逐行解释:
目标:char*
通过使用 写入astd::snprintf
,然后将其转换为astd::string
。
首先,我们使用 中的特殊条件确定所需的 char 数组长度snprintf
。来自cppreference.com:
返回值
[...]如果结果字符串由于 buf_size 限制而被截断,如果没有施加限制,函数将返回本应写入的字符总数(不包括终止的空字节)。
这意味着所需的大小是字符数加一,因此空终止符将位于所有其他字符之后,并且可以再次被字符串构造函数切断。@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
. 通常建议这样做,因为您不必再次手动删除它。
请注意,这不是使用用户定义类型分配 a 的安全方法,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 的解决方案会很有用。 不要使用任何具有固定缓冲区长度的解决方案,那只是自找麻烦。
您不能直接执行此操作,因为您没有对底层缓冲区的写访问权(直到 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();
内部使用的 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
会使其变慢。
boost::format()
提供您想要的功能:
来自 Boost 格式库的概要:
格式对象是从格式字符串构造的,然后通过重复调用 operator% 给定参数。然后将这些参数中的每一个转换为字符串,然后根据格式字符串将这些字符串组合成一个字符串。
#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"
C++20在 API 方面std::format
类似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]);
Erik Aronesty 的回答在哪里string_format
实现。此代码可以编译,但是当您尝试运行它时很可能会崩溃:
$ g++ -Wall -Wextra -pedantic test.cc
$ ./a.out
Segmentation fault: 11
免责声明:我是 {fmt} 和 C++20 的作者std::format
。
我使用 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);
如果您只想要类似 printf 的语法(而不自己调用 printf),请查看Boost Format。
为了以std::string
'sprintf' 方式格式化,调用snprintf
(arguments nullptr
and 0
) 来获取所需的缓冲区长度。使用 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);
C++20std::format
它已经到了!该功能在: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 时,我会尝试一下,GCC 9.1.0g++-9 -std=c++2a
仍然不支持它。
API 将添加一个新std::format
标头:
建议的格式化 API 在新标头中定义,
<format>
应该对现有代码没有影响。
如果您需要 polyfill ,现有fmt
库声称可以实现它:https ://github.com/fmtlib/fmt
C++20 的实现
std::format
。
并且之前提到过:std::stringformatting like sprintf
十六进制格式{:x}
前导零{:03}
{:<}
左、右{:>}
、中对齐{:^}
浮点精度{:.2}
在正数上显示符号{:+}
将布尔值显示为true
和false
:{:}
[编辑: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; }
-functionPRINTSTRING(r)
是为了满足 GUI 或终端或任何特殊输出需要使用#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;
}
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
经过测试,生产质量答案
此答案使用符合标准的技术处理一般情况。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; }
可预测的线性效率
根据问题规范,两次通过是安全、可靠和可预测的可重用功能的必要条件。关于可重用函数中 vargs 大小分布的假设是不好的编程风格,应该避免。在这种情况下,vargs 的任意大的可变长度表示是算法选择的关键因素。
溢出时重试效率呈指数级低下,这是 C++11 标准委员会讨论上述提议时讨论的另一个原因,即在写缓冲区为空时提供空运行。
在上述生产就绪的实现中,第一次运行是这样一个空运行来确定分配大小。不发生分配。几十年来,printf 指令的解析和 vargs 的读取变得非常高效。可重用代码应该是可预测的,即使必须牺牲一些琐碎案例的低效率。
安全性和可靠性
Andrew Koenig 在剑桥活动的演讲后对我们中的一小群人说,“用户功能不应该依赖于利用故障来实现非异常功能。” 像往常一样,从那以后,他的智慧在记录中得到了体现。已修复和已关闭的安全错误问题通常表明在修复之前利用的漏洞的描述中存在重试黑客攻击。
这在关于 sprintf 的替代方案、C9X 修订提案、ISO IEC 文档 WG14 N645/X3J11 96-008中的空缓冲区功能的正式标准修订提案中有所提及。在动态内存可用性的限制内,每个打印指令“%s”插入的任意长字符串也不例外,不应被利用来产生“无异常功能”。
考虑该提案以及该答案第一段中链接到的 C++Reference.org 页面底部给出的示例代码。
此外,失败案例的测试很少像成功案例那样稳健。
可移植性
所有主要的操作系统供应商都提供完全支持 std::vsnprintf 作为 c++11 标准的一部分的编译器。出于多种原因,运行不再维护发行版的供应商产品的主机应配备 g++ 或 clang++。
堆栈使用
第一次调用 std::vsnprintf 时的堆栈使用量将小于或等于第二次调用的堆栈使用量,并且在第二次调用开始之前将被释放。如果第一次调用超出堆栈可用性,则 std::fprintf 也会失败。
这就是 google 的做法:StringPrintf
(BSD 许可)
和 facebook 的做法非常相似:StringPrintf
(Apache 许可)
两者都提供了便利StringAppendF
。
我对这个非常受欢迎的问题的两分钱。
成功返回后,这些函数返回打印的字符数(不包括用于结束输出到字符串的空字节)。
函数 snprintf() 和 vsnprintf() 写入的字节数不超过 size 字节(包括终止的空字节 ('\0'))。如果输出由于此限制而被截断,则返回值是字符数(不包括终止的空字节),如果有足够的空间可用,这些字符将被写入最终字符串。因此,大小或更大的返回值意味着输出被截断。
换句话说,一个健全的 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+1
的返回值是必需的std::snprintf
,因为我们需要为\0
字节留出空间。直观地说,在大多数架构上,缺少+1
将导致required
整数被部分覆盖0
。这将在评估为 的实际参数之后发生,因此效果不应该是可见的。required
std::snprintf
然而,这个问题可能会改变,例如编译器优化:如果编译器决定为required
变量使用寄存器怎么办?这种错误有时会导致安全问题。
根据 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()
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);
}
如果您在具有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;
}
string 没有你需要的东西,但是 std::stringstream 有。使用 stringstream 创建字符串,然后提取字符串。这是您可以做的事情的完整列表。例如:
cout.setprecision(10); //stringstream is a stream like cout
打印双精度或浮点数时将为您提供 10 个小数位的精度。
你可以试试这个:
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 );
这是我在我的程序中用来执行此操作的代码...这没什么花哨的,但它确实有用...注意,您必须根据需要调整您的大小。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;
}
从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
and delete[]
,并且在创建时还指定了大小std::string
。
这里不使用new
and的想法delete[]
是暗示在堆上使用堆栈,因为它不需要调用分配和释放函数,但是如果使用不当,在某些(可能是旧的,或也许只是脆弱的)系统。如果这是一个问题,我强烈建议使用new
anddelete[]
代替。请注意,这里唯一的问题是关于vsnprintf()
已经使用限制调用的分配,因此根据在第二个缓冲区上分配的大小指定限制也会阻止这些。)
我通常使用这个:
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
如果需要,请随意将此答案与原始答案合并。
更新 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
.
Poco Foundation库有一个非常方便的格式化函数,它支持 std::string 的格式化字符串和值:
您可以使用 iomanip 头文件在 cout 中格式化 C++ 输出。确保在使用任何帮助函数(如 setprecision、setfill 等)之前包含 iomanip 头文件。
这是我过去用来打印向量中的平均等待时间的代码片段,我已经“累积”了它。
#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上测试),你可以尝试使用它。
String.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();
这个可以试试。简单的。虽然确实不使用字符串类的细微差别。
#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;
}
_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;
}
笔记:
- 单独的 VC++ 代码分支是必要的,因为 VC++ 已决定弃用
snprintf
它将为上述其他高度投票的答案生成编译器警告。因为我总是在“警告为错误”模式下运行,所以它不适合我。 - 该函数接受
char *
而不是std::string
. 这是因为大多数时候这个函数会用文字字符串调用,实际上char *
不是std::string
。如果您确实有std::string
格式参数,则只需调用.c_str()
. - 函数的名称是 stringf 而不是 string_format 以跟上 printf、scanf 等。
- 它没有解决安全问题(即错误的参数可能会导致段错误而不是异常)。如果你需要这个,那么你最好使用 boost 或fmt库。我在这里的首选是 fmt,因为它只是一个可以放入项目的头文件和源文件,同时具有比 boost 更少奇怪的格式语法。但是,两者都与 printf 格式字符串不兼容,因此在这种情况下,下面仍然有用。
- stringf 代码通过GCC 严格模式编译。这需要额外的
#pragma
宏来抑制 GCC 警告中的误报。
上面的代码已经过测试,
我意识到这已被多次回答,但这更简洁:
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());
}
更新一些答案,不同之处在于 - 函数将正确接受 %s 的 std::string
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;
}
这是我的(简单的解决方案):
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,调整它的大小,直接访问它的缓冲区......
我试了一下,用正则表达式。我以 ints 和 const 字符串为例实现了它,但是您可以添加任何其他类型(POD类型,但使用指针可以打印任何内容)。
#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;
输出:
我是鲍勃,我有 3 只猫
这是一个特定于 Windows 的解决方案,旨在避免 Visual Studio 中的编译器警告而不使它们静音。有问题的警告是使用带有 va_start 的 std::string ,这会错误地产生警告,以及使用不推荐使用的 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 编写版本,希望有一天有人可以使其可移植。(怀疑需要替换_vsnwprintf
为类似的vsnwprintf
东西。)
_CRT_SECURE_NO_WARNINGS
您需要使用项目配置中的定义来禁用弃用警告。
我使用_vsnwprintf
第一个参数作为 anullptr
能够估计缓冲区大小,保留 wstring 缓冲区,然后将字符串直接格式化到缓冲区中。
不知道为什么需要禁用弃用警告,因为相同方法调用 ( _vsnwprintf_s
) 的安全版本不能nullptr
用作输入。嫌疑人需要报告给 Microsoft C++ 团队。
此版本应该与 -string
或wstring
类一起使用。
如果您发现任何错误或不一致,请再次询问,我会尝试修复它。
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
这个问题已经解决了。format string
但是,我认为这是另一种方式c++
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;
你可以在这里查看
对于视觉 C:
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());
}
Windows 和 Visual Studio 有一个极具吸引力的解决方案:CString。
CString str;
str.Format("Hello %s\n", "World");
str = "ABC";
str += "DEF";