我必须用sprintf格式化std::string,并将其发送到文件流。我该怎么做呢?
当前回答
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);
}
其他回答
我对这个非常流行的问题的看法。
引用printf类函数的manpage:
Upon successful return, these functions return the number of characters printed (excluding the null byte used to end output to strings). The functions snprintf() and vsnprintf() do not write more than size bytes (including the terminating null byte ('\0')). If the output was truncated due to this limit then the return value is the number of characters (excluding the terminating null byte) which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated.
换句话说,一个正常的c++ 11实现应该是这样的:
#include <string>
#include <cstdio>
template <typename... Ts>
std::string fmt (const std::string &fmt, Ts... vs)
{
char b;
size_t required = std::snprintf(&b, 0, fmt.c_str(), vs...) + 1;
// See comments: the +1 is necessary, while the first parameter
// can also be set to nullptr
char bytes[required];
std::snprintf(bytes, required, fmt.c_str(), vs...);
return std::string(bytes);
}
它工作得很好:)
只有c++ 11支持可变参数模板。pixelpoint的答案显示了使用较旧的编程风格的类似技术。
奇怪的是,c++没有这样一个开箱即用的东西。他们最近添加了to_string(),在我看来这是向前迈出的一大步。我想知道他们是否最终会给std::string添加一个.format操作符…
Edit
正如alexk7指出的那样,std::snprintf的返回值需要A +1,因为我们需要为\0字节留出空间。直观地说,在大多数体系结构上,缺少+1将导致所需的整数部分被0覆盖。这将在std::snprintf的required作为实际参数计算之后发生,因此效果不应该可见。
然而,这个问题可以改变,例如编译器优化:如果编译器决定为所需的变量使用寄存器怎么办?这类错误有时会导致安全问题。
为了以'sprintf'方式格式化std::string,调用snprintf(参数nullptr和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);
这里是内存使用(和执行速度)方面的最佳解决方案,不依赖于RVO,如果字符串大小大于零,也可以执行追加,还会自动调整std::string的大小。
宏解决方案IMO更好,现代编译器将警告如果格式字符串不匹配的类型。该函数版本不会出现此警告,因为编译器无法看到snprintf。宏版本也更短,它也需要一个更少的包含。
来自:
https://github.com/ericcurtin/twincam
宏观的解决方案:
#include <string.h>
#include <string>
// function that will sprintf to a C++ string starting from std::string::size()
// so if you want to completely overwrite a string or start at a specific point
// use std::string::clear() or std::string::resize(). str is a std::string.
#define STRING_PRINTF(str, ...) \
do { \
const int size = snprintf(NULL, 0, __VA_ARGS__); \
const size_t start_of_string = str.size(); \
str.resize(start_of_string + size); \
snprintf(&str[start_of_string], str.size() + 1, __VA_ARGS__); \
} while (0)
函数的解决方案:
#include <stdarg.h> // For va_start, etc.
#include <string.h>
#include <string>
// function that will sprintf to a C++ string starting from std::string::size()
// so if you want to completely overwrite a string or start at a specific point
// use std::string::clear() or std::string::resize()
int string_printf(std::string& str, const char* const fmt, ...) {
c_va_list c_args;
va_start(c_args.args, fmt);
c_va_list tmpa;
va_copy(tmpa.args, c_args.args);
// Get addtional size required
int size = vsnprintf(NULL, 0, fmt, tmpa.args);
if (size < 0) {
return -1;
}
const size_t start_of_string = str.size();
str.resize(start_of_string + size);
// plus 1 so the null terminator gets included
size = vsnprintf(&str[start_of_string], str.size() + 1, fmt, c_args.args);
return size;
}
更优解:
#define STRING_PRINTF(str, ...) \
do { \
const size_t write_point = str.size(); \
str.resize(write_point + 127); \
const int size = snprintf(&str[write_point], 128, __VA_ARGS__); \
str.resize(write_point + size); \
if (size < 128) { \
break; \
} \
\
snprintf(&str[write_point], size + 1, __VA_ARGS__); \
} while (0)
这是一个更优的解决方案,假设sprintf小于128字节,如果是,格式字符串只解析一次而不是两次。
c++ 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会使其变慢。
Poco Foundation库有一个非常方便的格式函数,它在格式字符串和值中都支持std::string:
道格:http://pocoproject.org/docs/Poco.html # 7308 来源:https://github.com/pocoproject/poco/blob/develop/Foundation/src/Format.cpp
推荐文章
- decltype(auto)的一些用途是什么?
- Shared_ptr转换为数组:应该使用它吗?
- Printf与std::字符串?
- 禁用复制构造函数
- 不区分大小写的“in”
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 只接受特定类型的c++模板
- 如何在PHP中截断字符串最接近于一定数量的字符?
- c#和Java中的泛型有什么不同?和模板在c++ ?
- Ruby数组到字符串的转换
- c++ 11中的递归lambda函数
- 在c++中指针使用NULL或0(零)吗?
- 为什么在Java和。net中不能修改字符串?
- 在c++中,如何将int值附加到字符串中?
- 如何创建一个日期对象从字符串在javascript