我必须用sprintf格式化std::string,并将其发送到文件流。我该怎么做呢?
当前回答
我对这个非常流行的问题的看法。
引用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作为实际参数计算之后发生,因此效果不应该可见。
然而,这个问题可以改变,例如编译器优化:如果编译器决定为所需的变量使用寄存器怎么办?这类错误有时会导致安全问题。
其他回答
我现在将为Visual Studio编写版本,希望有一天有人会让它变得可移植。(怀疑需要用vsnwprintf替换_vsnwprintf之类的东西。)
您需要使用项目配置中的define _CRT_SECURE_NO_WARNINGS来禁用已弃用的警告。
我使用_vsnwprintf与第一个参数作为nullptr能够估计缓冲区大小,保留wstring缓冲区,然后格式化字符串直接到缓冲区。
不确定为什么需要禁用已弃用警告,因为相同方法调用(_vsnwprintf_s)的安全版本不能使用nullptr作为输入。怀疑需要报告给微软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
这是可以尝试的。简单。虽然没有使用字符串类的细微差别。
#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;
}
我用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);
到目前为止,所有的答案似乎都有一个或多个这样的问题:(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;
}
注:
Separate VC++ code branch is necessary because VC++ has decided to deprecate snprintf which will generate compiler warnings for other highly voted answers above. As I always run in "warnings as errors" mode, its no go for me. The function accepts char * instead of std::string. This because most of the time this function would be called with literal string which is indeed char *, not std::string. In case you do have std::string as format parameter, then just call .c_str(). Name of the function is stringf instead of things like string_format to keepup with printf, scanf etc. It doesn't address safety issue (i.e. bad parameters can potentially cause seg fault instead of exception). If you need this then you are better off with boost or fmt libraries. My preference here would be fmt because it is just one header and source file to drop in the project while having less weird formatting syntax than boost. However both are non-compatible with printf format strings so below is still useful in that case. The stringf code passes through GCC strict mode compilation. This requires extra #pragma macros to suppress false positives in GCC warnings.
以上代码已在,
GCC 4.9.2 11 / c++ / C + + 14 vc++编译器19.0 铿锵声3.7.0
我对这个非常流行的问题的看法。
引用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作为实际参数计算之后发生,因此效果不应该可见。
然而,这个问题可以改变,例如编译器优化:如果编译器决定为所需的变量使用寄存器怎么办?这类错误有时会导致安全问题。
推荐文章
- 为什么我的程序不能在Windows 7下用法语编译?
- 是否有可能更新一个本地化的故事板的字符串?
- 为什么字符串类型的默认值是null而不是空字符串?
- 如何获取变量的类型?
- 什么是奇怪的重复模板模式(CRTP)?
- 在Python中包装长行
- 连接两个向量的最佳方法是什么?
- 在Windows批处理脚本中格式化日期和时间
- 在c++中,是通过值传递更好,还是通过引用到const传递更好?
- string. isnullorempty (string) vs. string. isnullowhitespace (string)
- 在STL中deque到底是什么?
- 如何检查字符串的特定字符?
- 在Android中设置TextView span的颜色
- Haskell:将Int转换为字符串
- Windows上最好的免费c++分析器是什么?