std :: string格式,如sprintf
我必须使用sprintf
格式化std::string
并将其发送到文件stream中。 我该怎么做?
你不能直接这样做,因为你没有对底层缓冲区的写访问权限(直到C ++ 11;参见Dietrich Epp的评论 )。 你必须首先在一个cstring中,然后将它复制到一个std :: string:
char buff[100]; snprintf(buff, sizeof(buff), "%s", "Hello"); std::string buffAsStdStr = buff;
但我不知道为什么你不会只使用stringstream? 我假设你有具体的理由不只是这样做:
std::ostringstream stringStream; stringStream << "Hello"; std::string copyOfStr = stringStream.str();
#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; }
更安全,更高效(我testing了它,速度更快)方法:
#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会使其更慢。
使用C ++ 11 std::snprintf
,这变成了一个非常简单和安全的任务。 我看到很多关于这个问题的答案,这些答案显然是在使用固定缓冲区长度和vargs的C ++ 11之前编写的,为了安全,有效和清晰的原因,我不build议这样做。
#include <memory> #include <iostream> #include <string> #include <cstdio> using namespace std; //Don't if you're in a header-file template<typename ... Args> string string_format( const std::string& format, Args ... args ) { size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0' unique_ptr<char[]> buf( new char[ size ] ); snprintf( buf.get(), size, format.c_str(), args ... ); return string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside }
上面的代码片段在CC0 1.0下获得许可。
逐行解释:
目标:使用std::snprintf
写入char*
,然后将其转换为std::string
。
首先,我们确定char数组的期望长度。
来自cppreference.com :
返回值
[…]如果由于buf_size限制而导致结果string被截断,则函数将返回如果没有施加该限制,则会写入的字符总数(不包括终止的空字节)。
这意味着所需的大小是字符数加1 ,这样空终止符就会位于所有其他字符之后,并且可以被string构造函数再次截断。 这个问题在评论中由@ alexk7解释。
然后,我们分配一个新的字符数组,并将其分配给一个std::unique_ptr
。 一般build议这样做,因为您不必再次手动delete
它。
请注意,这是不是一个安全的方式来分配一个unique_ptr
与用户定义的types,因为如果构造函数抛出一个exception,你不能释放内存!
之后,我们当然可以使用snprintf
作为它的用途,并将格式化的string写入char[]
,然后创build并返回一个新的std::string
。
你可以在这里看到一个例子。
Visual Studio用户的附加信息:
正如在这个答案中所解释的那样,Microsoft将std::snprintf
重命名为_snprintf
(是的,没有使用std::
_snprintf
。 MS进一步将其设置为不build议使用,并build议使用_snprintf_s
,但是_snprintf_s
不会接受缓冲区为零或小于格式化的输出,并且如果发生这种情况将不计算输出长度。 因此,为了摆脱编译过程中的弃用警告,可以在包含_snprintf
的文件顶部插入以下行 :
#pragma warning(disable : 4996)
boost::format()
提供了你想要的function:
从Boost格式的图书馆大纲:
格式对象是由格式string构造的,然后通过重复调用运算符%给出参数。 然后将这些参数中的每一个转换为string,根据格式string将这些string合并为一个string。
cout << boost::format("writing %1%, x=%2% : %3%-th try") % "toto" % 40.23 % 50; // prints "writing toto, x=40.230 : 50-th try"
不幸的是,这里的大多数答案都使用可变参数,这些参数本质上是不安全的,除非你使用像GCC format
属性那样只能用于字面格式string的东西。 你可以看到为什么这些函数在下面的例子中是不安全的:
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
可以实现一个安全的printf
并使用(variadic)模板将其扩展为格式std::string
。 这已经在fmt库中完成了,它提供了一个替代sprintf
返回std::string
的安全选项:
std::string format_str = "The answer is %d"; std::string result = fmt::sprintf(format_str, 42);
fmt跟踪参数types,如果types不符合格式规范,则不存在分段错误,只是一个例外。
免责声明 :我是这个图书馆的作者。
如果你只想要一个类似于printf的语法(不需要自己调用printf),可以看看Boost格式 。
我用vsnprintf编写了自己的代码,所以它返回string,而不必创build自己的缓冲区。
#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);
添加一个可变模板版本'vtspf(..)':编辑'17 / 8/31'
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; }
这实际上是一个逗号分隔的版本(而不是)有时候会妨碍<<
-operators,这样使用:
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的; 那里的那些已经足够满足我的需要了。 你可以像使用sprintf一样使用stringf()和stringfappend()。 请记住,参数…必须是PODtypes。
//============================================================================= 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; }
为了以'sprintf'的方式格式化std::string
,调用snprintf
(参数nullptr
和0
)来获得需要的缓冲区的长度。 用C ++ 11 variadic模板编写你的函数,像这样:
#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); }
用C ++ 11编译支持,例如在GCC中: g++ -std=c++11
用法:
std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
这是谷歌如何做到这一点: StringPrintf
(BSD许可证)
和Facebook以非常相似的方式: StringPrintf
(Apache许可证)
两者都提供了一个方便的StringAppendF
。
我在这个非常受欢迎的问题上有两分钱。
引用printf
类函数的manpage :
成功返回后,这些函数返回打印的字符数(不包括用于结束输出的空字节)。
函数snprintf()和vsnprintf()不会写入多于大小的字节(包括终止空字节('\ 0'))。 如果输出由于这个限制而被截断,那么返回值就是如果有足够的空间可用的话,将被写入最终string的字符数(不包括终止空字节)。 因此,大小或更大的返回值意味着输出被截断。
换句话说,一个理智的C ++ 11实现应该如下:
#include <string> #include <cstdio> template <typename... Ts> std::string fmt (const std::string &fmt, Ts... vs) { char b; unsigned 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); }
它工作得很好:)
variables模板仅在C ++ 11中受支持。 像素点的答案显示了使用较旧的编程风格的类似技术。
奇怪的是,C ++没有这样的开箱即用的东西。 他们最近添加了to_string()
,这在我看来是一个很大的进步。 我想知道如果他们最终将添加一个.format
运算符到std::string
…
编辑
正如alexk7指出的那样, std::snprintf
的返回值需要+1
,因为我们需要为\0
字节留出空间。 直观地说,在大多数架构中,缺less+1
将导致required
整数部分被0
覆盖。 在评估std::snprintf
实际参数之后会发生这种情况,所以效果不应该是可见的。
然而,这个问题可能会改变,例如编译器优化:如果编译器决定使用一个寄存器为required
variables呢? 这是有时会导致安全问题的那种错误。
string没有你所需要的,但是std :: stringstream呢。 使用stringstream创buildstring,然后提取string。 这里是你可以做的事情的全面清单。 例如:
cout.setprecision(10); //stringstream is a stream like cout
打印双精度或浮点数时会给你十位精度。
基于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()
的结果中.c_str()
const
。
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); }
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
如果你在一个有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 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; }
从达卡夫和像素点的答案 。 我玩了一下,得到这个:
#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); }
(事实certificate,这个版本与Piti Ongmongkolkul的答案类似,只是它不使用new
和delete[]
,而且在创buildstd::string
时指定了一个大小。
这里不使用new
和delete[]
的想法是暗示在堆上使用堆栈,因为它不需要调用分配和释放函数,但是如果没有正确使用,可能会有一些危险的缓冲区溢出也许是旧的,也许只是脆弱的)系统。 如果这是一个问题,我强烈build议使用new
和delete[]
来代替。 请注意,这里唯一关心的是分配,因为vsnprintf()
已经被调用了限制,所以根据第二个缓冲区上分配的大小来指定一个限制也会阻止这些。)
非常非常简单的解决scheme。
std::string strBuf; strBuf.resize(256); int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...); strBuf.resize(iCharsPrinted);
我通常使用这个:
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
Feel free to merge this answer with the original one if desired.
One solution I've favoured is to do this with sprintf directly into the std::string buffer, after making said buffer big enough:
#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(); }
So, create the std::string, resize it, access its buffer directly…
Poco Foundation library has a very convenient format function, which supports std::string in both the format string and the values:
You can format C++ output in cout using iomanip header file. Make sure that you include iomanip header file before you use any of the helper functions like setprecision, setfill etc.
Here is a code snippet I have used in the past to print the average waiting time in the vector, which I have "accumulated".
#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;
Here is a brief description of how we can format C++ streams. http://www.cprogramming.com/tutorial/iomanip.html
There can be problems, if the buffer is not large enough to print the string. You must determine the length of the formatted string before printing a formatted message in there. I make own helper to this (tested on Windows and Linux GCC ), and you can try use it.
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();
I gave it a try, with regular expressions . I implemented it for ints and const strings as an example, but you can add whatever other types ( POD types but with pointers you can print anything).
#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; }
Here is an example of use of it:
std::string formatted = format("I am {s} and I have {i} cats", "bob", 3); std::cout << formatted << std::endl;
输出:
I am bob and I have 3 cats
this can be tried out. 简单。 really does not use nuances of the string class though.
#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();
All the answers so far here seems to have problems: (1) it may not work on VC++ (2) it requires additional dependencies like boost or fmt (3) its too complicated custom implementation and probably not tested well.
Below code addresses all above issues.
#include <string> #include <cstdarg> #include <memory> std::string format(const char* format, ...) { va_list args; va_start(args, format); #ifndef _MSC_VER size_t size = std::snprintf( nullptr, 0, format, args) + 1; // Extra space for '\0' 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 = format("i=%d, f=%f, s=%s", i, f, s.c_str()); printf("%s", rs.c_str()); return 0; }
笔记:
- Separate VC++ code branch is necessary because of non-compliance with standards.
- The function accepts
char *
instead ofstd::string
. This because most of the time this function would be called with literal string which is indeedchar *
, notstd::string
. In case you do havestd::string
as format parameter, then just call.c_str()
. - Name of the function is format instead of things like string_format because the parameters and return type already tells you what it formats.
Tested on,
- GCC 4.9.2/C++11/C++14
- VC++ compiler ver 19.0
- Clang 3.7.0