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


当前回答

为了以'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);

其他回答

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的作者。

下面是@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++ 17解决方案(这将工作于std::string和std::wstring):

分配一个缓冲区,格式化它,然后复制到另一个字符串是不高效的。可以创建格式化字符串大小的std::string,并直接格式化到字符串缓冲区中:

#include <string>
#include <stdexcept>
#include <cwchar>
#include <cstdio>
#include <type_traits>

template<typename T, typename ... Args>
std::basic_string<T> string_format(T const* const format, Args ... args)
{
    int size_signed{ 0 };

    // 1) Determine size with error handling:    
    if constexpr (std::is_same_v<T, char>) { // C++17
        size_signed = std::snprintf(nullptr, 0, format, args ...);
    }
    else {
        size_signed = std::swprintf(nullptr, 0, format, args ...);
    }  
    if (size_signed <= 0) {
        throw std::runtime_error("error during formatting.");
    }
    const auto size = static_cast<size_t>(size_signed);

    // 2) Prepare formatted string:
    std::basic_string<T> formatted(size, T{});
    if constexpr (std::is_same_v<T, char>) { // C++17
        std::snprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
    }
    else {
        std::swprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
    }

    return formatted; // Named Return Value Optimization (NRVO), avoids an unnecessary copy. 
}

此外:通常,format参数是char[] / wchar_t[] &创建std::string对象效率不高。传递char*或wchar_t* &如果你已经有一个std::string对象,你仍然可以使用它作为your_string.c_str()。例子:

int main()
{
    int i{ 0 };

    // The format parameter is a char[] / wchar_t[]:

    const std::string title1 = string_format("story[%d].", ++i); // => "story[1]"

    const std::wstring title2 = string_format(L"story[%d].", ++i); // => L"story[2]"

    // If you already have a std::string object:

    const std::string format1{ "story[%d]." };
    const std::string title3 = string_format(format1.c_str(), ++i); // => "story[3]"

    const std::wstring format2{ L"story[%d]." };
    const std::wstring title4 = string_format(format2.c_str(), ++i); // => L"story[4]"  
}
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);
}

我喜欢的一个解决方案是,在使缓冲区足够大之后,用sprintf直接在std::string缓冲区中执行此操作:

#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();
}

因此,创建std::string,调整它的大小,直接访问它的缓冲区…