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


当前回答

我通常用这个:

std::string myformat(const char *const fmt, ...)
{
        char *buffer = NULL;
        va_list ap;

        va_start(ap, fmt);
        (void)vasprintf(&buffer, fmt, ap);
        va_end(ap);

        std::string result = buffer;
        free(buffer);

        return result;
}

缺点:并非所有系统都支持vasprint

其他回答

我用vsnprintf写了我自己的,所以它返回字符串,而不是必须创建我自己的缓冲区。

#include <string>
#include <cstdarg>

//missing string printf
//this is safe and convenient but not exactly efficient
inline std::string format(const char* fmt, ...){
    int size = 512;
    char* buffer = 0;
    buffer = new char[size];
    va_list vl;
    va_start(vl, fmt);
    int nsize = vsnprintf(buffer, size, fmt, vl);
    if(size<=nsize){ //fail delete buffer and try again
        delete[] buffer;
        buffer = 0;
        buffer = new char[nsize+1]; //+1 for /0
        nsize = vsnprintf(buffer, size, fmt, vl);
    }
    std::string ret(buffer);
    va_end(vl);
    delete[] buffer;
    return ret;
}

所以你可以用它

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

从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.)

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

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

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

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

这是我用来在我的程序中这样做的代码…这没什么特别的,但很管用……注意,您必须根据需要调整您的尺寸。我的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;
}