我必须用sprintf格式化std::string,并将其发送到文件流。我该怎么做呢?


当前回答

更新1:增加了fmt::格式测试

我对这里介绍的方法进行了自己的研究,得到了与这里提到的完全相反的结果。

我用了4个函数/ 4个方法:

可变变量函数+ vsnprintf + std::unique_ptr 可变变量函数+ vsnprintf + std::string 可变变量模板函数+ std::ostringstream + std::tuple +实用程序::for_each 来自Fmt库的Fmt::format函数

对于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实现从这里开始:遍历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更快,而vsnprintf+std::unique_ptr比通过std::ostringstream更快。

测试在Visual Studio 2015 Update 3中编译,运行于Windows 7 x64 / Intel酷睿i7-4820K CPU @ 3.70GHz / 16GB。

其他回答

如果缓冲区不够大,无法打印字符串,就会出现问题。在打印格式化消息之前,必须确定格式化字符串的长度。 我制作了自己的帮助器(在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();

下面是@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

如果需要,可以随意将这个答案与原始答案合并。

c++ 20 std::格式

它来了!该特性描述在: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);
}

g++-9 -std=c++2a的GCC 9.1.0仍然不支持它。

现有的fmt库在获得官方支持之前实现了它:https://github.com/fmtlib/fmt,如前所述:std::string格式化,如sprintf:

sudo apt install libfmt-dev

修改源代码以替换:

用<fmt/core.h> .h格式化> Std::format转换为fmt::format

main.cpp

#include <string>
#include <iostream>

#include <fmt/core.h>

int main() {
    std::string message = fmt::format("The answer is {}.", 42);
    std::cout << message << std::endl;
}

编译并运行:

g++ -std=c++11 -o main.out main.cpp -lfmt
./main.out

输出:

The answer is 42.

API将添加一个新的std::format头文件:

建议的格式化API定义在新的header <format>中,应该不会对现有代码产生影响。

十六进制格式{:x}

c++十六进制成本值?

前导零{:03}

用c++输出操作符打印前导零?

左对齐{:<},右对齐{:>},中对齐{:^}

打印cout时c++对齐<<

浮点精度{:.2}

我如何打印一个双值与全精度使用cout? 在c++中设置默认浮点打印精度

在正数{:+}上显示符号

如何打印正数与前缀+在c++

将布尔值显示为true和false: {:}

在c++中将bool转换为文本

非常简单的解决方案。

std::string strBuf;
strBuf.resize(256);
int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
strBuf.resize(iCharsPrinted);

我不喜欢把事情搞复杂。这是基于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”。)