我必须使用std::string
格式化sprintf
并将其发送到文件流中。我怎么能这样做?
答案 0 :(得分:286)
你不能直接这样做,因为你没有对底层缓冲区的写权限(直到C ++ 11;参见Dietrich Epp的comment)。您必须先在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();
答案 1 :(得分:237)
内部使用vsnprintf()
的C ++ 11解决方案:
#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
会使其变慢。
答案 2 :(得分:194)
利用C++11 std::snprintf
,这变得非常简单和安全。我看到很多关于这个问题的答案显然是在C ++ 11之前编写的,它使用固定缓冲区长度和vargs,我不建议出于安全性,效率和清晰度的原因。
#include <memory>
#include <iostream>
#include <string>
#include <cstdio>
template<typename ... Args>
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<char[]> 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*
写信给std::snprintf
,然后将其转换为std::string
。
首先,我们确定char数组的所需长度。
返回值
[...]如果由于buf_size限制而导致结果字符串被截断, 函数返回总字符数(不包括 如果限制是,则会写入已写入的空字节 没有强加。
这意味着所需的大小是加一个的字符数,因此空终止符将位于所有其他字符之后,并且可以再次被字符串构造函数切断。 @ alexk7在评论中解释了这个问题。
然后,我们分配一个新的字符数组并将其分配给std::unique_ptr
。通常建议这样做,因为您不必再次手动delete
。
请注意,这不是一种使用用户定义类型分配unique_ptr
的安全方法,因为如果构造函数抛出异常,则无法释放内存!
之后,我们当然可以将snprintf
用于其预期用途,并将格式化的字符串写入char[]
,然后从中创建并返回一个新的std::string
。
您可以在行动here中看到一个示例。
如果您还想在参数列表中使用std::string
,请查看this gist。
Visual Studio用户的其他信息:
正如this answer中所述,Microsoft将std::snprintf
重命名为_snprintf
(是的,没有std::
)。 MS进一步将其设置为已弃用,并建议使用_snprintf_s
,但_snprintf_s
将不接受缓冲区为零或小于格式化输出,并且如果发生这种情况则不会计算输出长度。
因此,为了在编译期间摆脱弃用警告,您可以在文件顶部插入the following line,其中包含_snprintf
的使用:
#pragma warning(disable : 4996)
答案 3 :(得分:100)
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"
答案 4 :(得分:34)
不幸的是,这里的大多数答案都使用了varargs,除非你使用像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
可以使用(variadic)模板实现安全printf
并将其扩展为格式std::string
。这已在{fmt} library中完成,它提供了sprintf
返回std::string
的安全替代方法:
std::string format_str = "The answer is %d";
std::string result = fmt::sprintf(format_str, 42);
{fmt}跟踪参数类型,如果类型与格式规范不匹配,则不存在分段错误,只有异常或编译时错误(如果使用constexpr
格式字符串检查。< / p>
免责声明:我是{fmt}的作者。
答案 5 :(得分:18)
如果您只想要类似printf的语法(不自行调用printf),请查看Boost Format。
答案 6 :(得分:15)
我使用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);
答案 7 :(得分:14)
[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;
}
实际上是逗号分隔的版本(相反),有时会阻碍<<
- 运算符,使用如下:
char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);
[edit]适合在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的那些;目前那些足以满足我的需求。
你使用与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<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;
}
答案 8 :(得分:13)
要以'sprintf'方式格式化 BST PRE(BST x)
{
if(x->left!=NULL)
return TREE_MAXIMUM(x->left);
if(x== root)
return NULL;
BST goal = NULL, y = root;
while(1)
{
if(x->key <= y->key)
y=y->left;
else
{
goal=y;
y=y->right;
}
if(x==y)
{
return goal;
}
}
}
,请调用std::string
(参数snprintf
和nullptr
)以获取所需缓冲区的长度。使用C ++ 11可变参数模板编写函数,如下所示:
0
使用C ++ 11支持编译,例如在GCC中编译:{{1}}
用法:
#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 std::move(str);
}
答案 9 :(得分:10)
Google就是这样做的:StringPrintf
(BSD许可证)
和Facebook以非常相似的方式做到:StringPrintf
(Apache许可证)
两者都提供了方便的StringAppendF
。
答案 10 :(得分:9)
我对这个非常受欢迎的问题的两分钱。
引用manpage of printf
-like functions:
成功返回后,这些函数返回打印的字符数(不包括用于结束输出到字符串的空字节)。
函数snprintf()和vsnprintf()写入的字节数不超过大小(包括终止空字节('\ 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指出的那样,+1
的返回值需要std::snprintf
,因为我们需要为\0
字节留出空间。直观地说,在大多数缺少+1
的体系结构中,required
整数会被0
部分覆盖。 在评估required
作为std::snprintf
的实际参数后会发生,因此效果不应该是可见的。
然而,这个问题可能会改变,例如编译器优化:如果编译器决定使用required
变量的寄存器怎么办?这种错误有时会导致安全问题。
答案 11 :(得分:7)
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
答案 12 :(得分:6)
C ++ 20 std::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时,我会尝试一下,带有g++-9 -std=c++2a
的GCC 9.1.0仍然不支持。
API将添加一个新的std::format
标头:
建议的格式API在新的头文件
<format>
中定义,并且对现有代码没有影响。
如果您需要polyfill,则现有的fmt
库声称可以实现它:https://github.com/fmtlib/fmt
中提到过C ++ 20
std::format
的实现。
答案 13 :(得分:6)
经过测试,生产质量问题
这个答案处理符合标准的技术的一般情况。在页面底部附近的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的读取已经非常有效。可重复使用的代码应该是可预测的,即使必须牺牲一些微不足道的低效率。
安全性和可靠性
安德鲁科尼格在剑桥大学的一次活动上发表讲话后对我们的一小部分人说:“用户功能不应该依赖于利用失败来实现无与伦比的功能。”像往常一样,自那以后,他的智慧在记录中得到了证实。固定和封闭的安全性错误问题通常表示在修复之前利用漏洞的描述中重试黑客。Alternative to sprintf, C9X Revision Proposal, ISO IEC Document WG14 N645/X3J11 96-008中针对空缓冲区功能的正式标准修订提案中提到了这一点。在动态内存可用性的约束下,每个print指令插入一个任意长的字符串“%s”,这不是一个例外,不应该被利用来产生“Unexceptional functions。”
在本答案第一段中链接到的C ++ Reference.org页面底部给出的示例代码旁边考虑提案。
此外,对失败案例的测试很少能够成功案例。
<强>可移植性强>
所有主要的O.S.供应商提供的编译器完全支持std :: vsnprintf作为c ++ 11标准的一部分。运行不再维护发行版的供应商产品的主机应该提供g ++或clang ++,原因有很多。
堆叠使用
第一次调用std :: vsnprintf时的堆栈使用将小于或等于第二次调用,并且在第二次调用开始之前将被释放。如果第一个调用超过堆栈可用性,那么std :: fprintf也会失败。
答案 14 :(得分:5)
基于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()
。
答案 15 :(得分:5)
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);
}
答案 16 :(得分:3)
如果您使用的是具有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;
}
答案 17 :(得分:3)
string没有你需要的东西,但std :: stringstream有。使用stringstream创建字符串,然后提取字符串。 Here是一份关于你可以做的事情的综合清单。例如:
cout.setprecision(10); //stringstream is a stream like cout
打印双面或浮动时,会给你10个小数位。
答案 18 :(得分:3)
你可以试试这个:
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 );
答案 19 :(得分:2)
这是我用来在我的程序中执行此操作的代码...这没什么特别的,但是它可以解决问题...注意,您必须根据需要调整大小。我的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;
}
答案 20 :(得分:2)
从Dacav和pixelpoint's answer接受了这个想法。我玩了一下,得到了这个:
#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's answer类似,只是它不使用new
和delete[]
,并且在创建std::string
时也指定了尺寸}。
这里不使用new
和delete[]
的想法是暗示在堆上使用堆栈,因为它不需要调用分配和释放函数,但是如果没有正确使用它,它在一些(可能是旧的,或许只是易受攻击的)系统中缓冲溢出可能是危险的。如果这是一个问题,我强烈建议您使用new
和delete[]
。请注意,此处唯一关注的是分配,因为已使用限制调用vsnprintf()
,因此根据在第二个缓冲区上分配的大小指定限制也会阻止这些限制。)
答案 21 :(得分:2)
下面稍微修改过的@iFreilicht回答版本,更新为C++14(使用make_unique
函数而不是原始声明)并添加了对std::string
参数的支持(基于Kenny Kerr {{ 3}})
#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
如果需要,请随意将此答案与原始答案合并。
答案 22 :(得分:2)
我通常使用它:
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
答案 23 :(得分:1)
您可以使用iomanip头文件格式化cout中的C ++输出。 在使用任何辅助函数之前,请确保包含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 ++流的简要说明。 http://www.cprogramming.com/tutorial/iomanip.html
答案 24 :(得分:1)
Poco Foundation库有一个非常方便的格式函数,它支持格式字符串和值中的std :: string:
答案 25 :(得分:1)
更新一些答案,不同之处是-函数将正确接受%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;
}
答案 26 :(得分:1)
更新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
实现来自此处:iterate over tuple
#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
。
unsued.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);
}
unused.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
上运行的测试。
答案 27 :(得分:1)
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
答案 28 :(得分:1)
非常简单的解决方案。
std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);
答案 29 :(得分:0)
到目前为止,所有答案似乎都有一个或多个问题:(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;
}
注意:
snprintf
,这将为上面其他高度投票的答案生成编译器警告。因为我总是在“警告为错误”模式下运行,所以不适合我。char *
而不是std::string
。这是因为大部分时间都会使用文字字符串调用此函数,该字符串确实是char *
,而不是std::string
。如果您确实有std::string
作为格式参数,那么只需致电.c_str()
。#pragma
个宏来抑制GCC警告中的误报。以上代码已经过测试,
答案 30 :(得分:0)
我知道这个问题已经被回答了很多次,但这更加简洁:
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());
}
答案 31 :(得分:0)
如果缓冲区不足以打印字符串,则可能会出现问题。在此处打印格式化消息之前,必须确定格式化字符串的长度。 我为此做了自己的帮助(在Windows和Linux GCC上测试过),你可以尝试使用它。
String.cpp:http://pastebin.com/DnfvzyKP
String.h:http://pastebin.com/7U6iCUMa
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();
答案 32 :(得分:0)
这是我的(简单解决方案):
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);
}
}
}
答案 33 :(得分:0)
我最喜欢的一个解决方案就是将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,调整大小,直接访问其缓冲区......
答案 34 :(得分:0)
这可以试试。简单。实际上并没有使用字符串类的细微差别。
#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;
}
答案 35 :(得分:0)
我试了regular expressions。我为int和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只猫
答案 36 :(得分:-1)
这是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 );
答案 37 :(得分:-1)
我现在将为Visual Studio编写版本,希望有朝一日有人能使其移植。 (可以将_vsnwprintf
替换为vsnwprintf
之类的东西。)
您需要通过在项目配置中使用定义_CRT_SECURE_NO_WARNINGS
来禁用过时警告。
我将_vsnwprintf
与第一个参数用作nullptr
,以便能够估计缓冲区大小,保留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
答案 38 :(得分:-1)
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());
}
答案 39 :(得分:-3)
Windows和Visual Studio具有强大的吸引力解决方案:CString。
CString str;
str.Format("Hello %s\n", "World");
str = "ABC";
str += "DEF";