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


当前回答

以下是我的(简单的解决方案):

std::string Format(const char* lpszFormat, ...)
{
    // Warning : "vsnprintf" crashes with an access violation
    // exception if lpszFormat is not a "const char*" (for example, const string&)

    size_t  nSize     = 1024;
    char    *lpBuffer = (char*)malloc(nSize);

    va_list lpParams;

    while (true)
    {
        va_start(lpParams, lpszFormat);

        int nResult = vsnprintf(
            lpBuffer,
            nSize,
            lpszFormat,
            lpParams
        );

        va_end(lpParams);

        if ((nResult >= 0) && (nResult < (int)nSize) )
        {
            // Success

            lpBuffer[nResult] = '\0';
            std::string sResult(lpBuffer);

            free (lpBuffer);

            return sResult;
        }
        else
        {
            // Increase buffer

            nSize =
                  (nResult < 0)
                ? nSize *= 2
                : (nResult + 1)
            ;

            lpBuffer = (char *)realloc(lpBuffer, nSize);
        }
    }
}

其他回答

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会使其变慢。

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

你可以试试这个:

string str;
str.resize( _MAX_PATH );

sprintf( &str[0], "%s %s", "hello", "world" );
// optionals
// sprintf_s( &str[0], str.length(), "%s %s", "hello", "world" ); // Microsoft
// #include <stdio.h>
// snprintf( &str[0], str.length(), "%s %s", "hello", "world" ); // c++11

str.resize( strlen( str.data() ) + 1 );
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);
}

测试,生产质量答案

这个答案用符合标准的技术处理一般情况。CppReference.com页面底部附近给出了相同的方法作为示例。与他们的例子不同,这个代码符合问题的要求,并在机器人和卫星应用中进行了现场测试。它还改进了评论功能。设计质量将在下面进一步讨论。

#include <string>
#include <cstdarg>
#include <vector>

// requires at least C++11
const std::string vformat(const char * const zcFormat, ...) {

    // initialize use of the variable argument array
    va_list vaArgs;
    va_start(vaArgs, zcFormat);

    // reliably acquire the size
    // from a copy of the variable argument array
    // and a functionally reliable call to mock the formatting
    va_list vaArgsCopy;
    va_copy(vaArgsCopy, vaArgs);
    const int iLen = std::vsnprintf(NULL, 0, zcFormat, vaArgsCopy);
    va_end(vaArgsCopy);

    // return a formatted string without risking memory mismanagement
    // and without assuming any compiler or platform specific behavior
    std::vector<char> zc(iLen + 1);
    std::vsnprintf(zc.data(), zc.size(), zcFormat, vaArgs);
    va_end(vaArgs);
    return std::string(zc.data(), iLen); }

#include <ctime>
#include <iostream>
#include <iomanip>

// demonstration of use
int main() {

    std::time_t t = std::time(nullptr);
    std::cerr
        << std::put_time(std::localtime(& t), "%D %T")
        << " [debug]: "
        << vformat("Int 1 is %d, Int 2 is %d, Int 3 is %d", 11, 22, 33)
        << std::endl;
    return 0; }

可预测线性效率

根据问题规范,两个通道对于安全、可靠和可预测的可重用函数是必要的。对可重用函数中varg大小分布的假设是糟糕的编程风格,应该避免。在这种情况下,vargs的任意大变长表示是选择算法的关键因素。

在溢出时重试效率是指数级的,这是c++ 11标准委员会在讨论上述建议时讨论的另一个原因,即在写缓冲区为空时提供一个排练。

在上述生产就绪实现中,第一次运行就是这样的一个演练,以确定分配大小。没有发生分配。几十年来,printf指令的解析和vargs的读取已经变得非常高效。可重用代码应该是可预测的,即使必须牺牲一些微不足道的低效率。

安全性和可靠性

Andrew Koenig在剑桥的一次活动上演讲后对我们一小群人说:“用户功能不应该依赖于利用失败来实现普通的功能。”像往常一样,他的智慧在此后的记录中被证明是正确的。已修复和已关闭的安全错误问题通常表明在修复之前所利用的漏洞的描述中存在重试黑客。

在Alternative to sprintf, C9X revision proposal, ISO IEC Document WG14 N645/X3J11 96-008中,空缓冲区特性的正式标准修订建议中提到了这一点。在动态内存可用性的限制范围内,每个打印指令插入任意长的字符串“%s”并不是一个例外,不应该利用它来产生“非异常功能”。

考虑这个建议以及在c++ Reference.org页面底部给出的示例代码,该页面链接到这个答案的第一段。

同样,失败案例的测试很少像成功案例那样健壮。

可移植性

所有主要的os供应商提供的编译器都完全支持std::vsnprintf作为c++11标准的一部分。运行不再维护发行版的供应商的产品的主机应该提供g++或clang++,原因有很多。

堆栈使用

第一次调用std::vsnprintf时使用的堆栈将小于或等于第二次调用时使用的堆栈,并且将在第二次调用开始前释放。如果第一次调用超过堆栈可用性,那么std::fprintf也会失败。