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


当前回答

template<typename... Args>
std::string string_format(const char* fmt, Args... args)
{
    size_t size = snprintf(nullptr, 0, fmt, args...);
    std::string buf;
    buf.reserve(size + 1);
    buf.resize(size);
    snprintf(&buf[0], size + 1, fmt, args...);
    return buf;
}

使用C99 snprintf和c++ 11

其他回答

_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();

下面是@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::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的作者。

你不能直接这样做,因为你没有对底层缓冲区的写访问权(直到c++ 11;见Dietrich Epp的评论)。你必须先在c-string中执行,然后将其复制到std::string中:

  char buff[100];
  snprintf(buff, sizeof(buff), "%s", "Hello");
  std::string buffAsStdStr = buff;

但我不确定为什么不直接使用字符串流?我想你有特定的理由不这么做:

  std::ostringstream stringStream;
  stringStream << "Hello";
  std::string copyOfStr = stringStream.str();

我现在将为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