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


当前回答

我知道这个问题已经被回答过很多次了,但下面这个更简洁:

std::string format(const std::string fmt_str, ...)
{
    va_list ap;
    char *fp = NULL;
    va_start(ap, fmt_str);
    vasprintf(&fp, fmt_str.c_str(), ap);
    va_end(ap);
    std::unique_ptr<char[]> formatted(fp);
    return std::string(formatted.get());
}

例子:

#include <iostream>
#include <random>

int main()
{
    std::random_device r;
    std::cout << format("Hello %d!\n", r());
}

参见http://rextester.com/NJB14150

其他回答

这是可以尝试的。简单。虽然没有使用字符串类的细微差别。

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

这是谷歌的做法: facebook也以类似的方式:StringPrintf (Apache许可证) 两者都提供了一个方便的StringAppendF。

我对这个非常流行的问题的看法。

引用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作为实际参数计算之后发生,因此效果不应该可见。

然而,这个问题可以改变,例如编译器优化:如果编译器决定为所需的变量使用寄存器怎么办?这类错误有时会导致安全问题。

到目前为止,所有的答案似乎都有一个或多个这样的问题:(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

从Dacav和pixelpoint的答案中获得灵感。我玩了一下,得到了这个:

#include <cstdarg>
#include <cstdio>
#include <string>

std::string format(const char* fmt, ...)
{
    va_list vl;

    va_start(vl, fmt);
    int size = vsnprintf(0, 0, fmt, vl) + sizeof('\0');
    va_end(vl);

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

通过合理的编程实践,我相信代码应该足够了,但是我仍然对更安全的替代方案持开放态度,这些替代方案仍然足够简单,不需要c++ 11。


下面是另一个版本,它使用初始缓冲区来防止在初始缓冲区已经足够多时再次调用vsnprintf()。

std::string format(const char* fmt, ...)
{

    va_list vl;
    int size;

    enum { INITIAL_BUFFER_SIZE = 512 };

    {
        char buffer[INITIAL_BUFFER_SIZE];

        va_start(vl, fmt);
        size = vsnprintf(buffer, INITIAL_BUFFER_SIZE, fmt, vl);
        va_end(vl);

        if (size < INITIAL_BUFFER_SIZE)
            return std::string(buffer, size);
    }

    size += sizeof('\0');

    char buffer[size];

    va_start(vl, fmt);
    size = vsnprintf(buffer, size, fmt, vl);
    va_end(vl);

    return std::string(buffer, size);
}

(事实证明,这个版本与Piti Ongmongkolkul的答案相似,只是它没有使用new和delete[],并且在创建std::string时指定了大小。

The idea here of not using new and delete[] is to imply usage of the stack over the heap since it doesn't need to call allocation and deallocation functions, however if not properly used, it could be dangerous to buffer overflows in some (perhaps old, or perhaps just vulnerable) systems. If this is a concern, I highly suggest using new and delete[] instead. Note that the only concern here is about the allocations as vsnprintf() is already called with limits, so specifying a limit based on the size allocated on the second buffer would also prevent those.)