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


当前回答

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

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

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

其他回答

你不能直接这样做,因为你没有对底层缓冲区的写访问权(直到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();

我试了一下,用正则表达式。我为int和const字符串实现了它作为一个例子,但你可以添加任何其他类型(POD类型,但有指针,你可以打印任何东西)。

#include <assert.h>
#include <cstdarg>

#include <string>
#include <sstream>
#include <regex>

static std::string
formatArg(std::string argDescr, va_list args) {
    std::stringstream ss;
    if (argDescr == "i") {
        int val = va_arg(args, int);
        ss << val;
        return ss.str();
    }
    if (argDescr == "s") {
        const char *val = va_arg(args, const char*);
        ss << val;
        return ss.str();
    }
    assert(0); //Not implemented
}

std::string format(std::string fmt, ...) {
    std::string result(fmt);
    va_list args;
    va_start(args, fmt);
    std::regex e("\\{([^\\{\\}]+)\\}");
    std::smatch m;
    while (std::regex_search(fmt, m, e)) {
        std::string formattedArg = formatArg(m[1].str(), args);
        fmt.replace(m.position(), m.length(), formattedArg);
    }
    va_end(args);
    return fmt;
}

下面是一个使用它的例子:

std::string formatted = format("I am {s} and I have {i} cats", "bob", 3);
std::cout << formatted << std::endl;

输出:

我是鲍勃,我有三只猫

c++ 11内部使用vsnprintf()的解决方案:

#include <stdarg.h>  // For va_start, etc.

std::string string_format(const std::string fmt, ...) {
    int size = ((int)fmt.size()) * 2 + 50;   // Use a rubric appropriate for your code
    std::string str;
    va_list ap;
    while (1) {     // Maximum two passes on a POSIX system...
        str.resize(size);
        va_start(ap, fmt);
        int n = vsnprintf((char *)str.data(), size, fmt.c_str(), ap);
        va_end(ap);
        if (n > -1 && n < size) {  // Everything worked
            str.resize(n);
            return str;
        }
        if (n > -1)  // Needed size returned
            size = n + 1;   // For null char
        else
            size *= 2;      // Guess at a larger size (OS specific)
    }
    return str;
}

一种更安全、更有效的方法(我测试过,它更快):

#include <stdarg.h>  // For va_start, etc.
#include <memory>    // For std::unique_ptr

std::string string_format(const std::string fmt_str, ...) {
    int final_n, n = ((int)fmt_str.size()) * 2; /* Reserve two times as much as the length of the fmt_str */
    std::unique_ptr<char[]> formatted;
    va_list ap;
    while(1) {
        formatted.reset(new char[n]); /* Wrap the plain char array into the unique_ptr */
        strcpy(&formatted[0], fmt_str.c_str());
        va_start(ap, fmt_str);
        final_n = vsnprintf(&formatted[0], n, fmt_str.c_str(), ap);
        va_end(ap);
        if (final_n < 0 || final_n >= n)
            n += abs(final_n - n + 1);
        else
            break;
    }
    return std::string(formatted.get());
}

fmt_str是按值传递的,以符合va_start的要求。

注意:“更安全”和“更快”的版本在某些系统上不起作用。因此,两家公司仍在上市。此外,“更快”完全取决于预分配步骤是否正确,否则strcpy会使其变慢。

这是一个特定于Windows的解决方案,旨在避免Visual Studio中的编译器警告而不消除它们。所讨论的警告是针对使用std::string和va_start,这会错误地产生警告,以及针对使用已弃用的printf变量。

template<typename ... va>
std::string Format( const std::string& format, va ... args )
{
    std::string s;
    s.resize( _scprintf( format.c_str(), args ... ) + 1 );
    s.resize( _snprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
    return s;
}

template<typename ... va>
std::wstring Format( const std::wstring& format, va ... args )
{
    std::wstring s;
    s.resize( _scwprintf( format.c_str(), args ... ) + 1 );
    s.resize( _snwprintf_s( s.data(), s.capacity(), _TRUNCATE, format.c_str(), args ... ) );
    return s;
}

std::string s = Format( "%hs %d", "abc", 123 );
std::wstring ws = Format( L"%hs %d", "abc", 123 );

String没有你需要的东西,但是std::stringstream有。使用stringstream创建字符串,然后提取字符串。这里有一个关于你可以做的事情的全面列表。例如:

cout.setprecision(10); //stringstream is a stream like cout

将在打印双精度或浮点数时提供10位小数点后的精度。