我必须用sprintf格式化std::string,并将其发送到文件流。我该怎么做呢?
当前回答
我不喜欢把事情搞复杂。这是基于iFreilicht的答案,但我减少了一些噪音,使它更有效。请注意,如果您计划在接口中使用此功能,可能会添加一些模糊输入检查。
#include <iostream>
#include <string>
template<typename... Ts>
std::string string_format( const std::string& format, Ts... Args )
{
const size_t n = std::snprintf( nullptr, 0, format.c_str(), Args ... ) + 1; // Extra space for '\0'
std::string ret(n, '\0');
std::snprintf( &ret.front(), n, format.c_str(), Args... );
return ret;
}
int main()
{
int a = 5;
char c = 'h';
double k = 10.3;
std::cout << string_format("%d, %c, %.2f", a, c, k) << "\n";
}
输出:
5, h, 10.30
试着自己
(*唯一的警告,我发现性能方面是没有办法默认初始化字符串存储。这很遗憾,因为我们不需要在这里将所有的值初始化为“\0”。)
其他回答
这里是内存使用(和执行速度)方面的最佳解决方案,不依赖于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字节,如果是,格式字符串只解析一次而不是两次。
这是我用来在我的程序中这样做的代码…这没什么特别的,但很管用……注意,您必须根据需要调整您的尺寸。我的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;
}
Poco Foundation库有一个非常方便的格式函数,它在格式字符串和值中都支持std::string:
道格:http://pocoproject.org/docs/Poco.html # 7308 来源:https://github.com/pocoproject/poco/blob/develop/Foundation/src/Format.cpp
如果缓冲区不够大,无法打印字符串,就会出现问题。在打印格式化消息之前,必须确定格式化字符串的长度。 我制作了自己的帮助器(在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();
c++ 20有std::format,它在API方面类似于sprintf,但完全是类型安全的,适用于用户定义的类型,并使用类似python的格式字符串语法。下面是如何格式化std::string并将其写入流的方法:
std::string s = "foo";
std::cout << std::format("Look, a string: {}", s);
或者,你可以使用{fmt}库格式化字符串,并将其写入标准输出或文件流:
fmt::print("Look, a string: {}", s);
至于sprintf或这里的大多数其他答案,不幸的是,它们使用了可变参数,并且本质上是不安全的,除非您使用类似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
免责声明:我是{fmt}和c++ 20 std::format的作者。
推荐文章
- 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