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


当前回答

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和Linux GCC上测试),您可以尝试使用它。

String.cpp: http://pastebin.com/DnfvzyKP String.h: http://pastebin.com/7U6iCUMa

String.cpp:

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

using ::std::string;

#pragma warning(disable : 4996)

#ifndef va_copy
#ifdef _MSC_VER
#define va_copy(dst, src) dst=src
#elif !(__cplusplus >= 201103L || defined(__GXX_EXPERIMENTAL_CXX0X__))
#define va_copy(dst, src) memcpy((void*)dst, (void*)src, sizeof(*src))
#endif
#endif

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw() {
  int length;
  va_list apStrLen;
  va_copy(apStrLen, ap);
  length = vsnprintf(NULL, 0, format, apStrLen);
  va_end(apStrLen);
  if (length > 0) {
    dst.resize(length);
    vsnprintf((char *)dst.data(), dst.size() + 1, format, ap);
  } else {
    dst = "Format error! format: ";
    dst.append(format);
  }
}

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw() {
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
}

///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw() {
  string dst;
  va_list ap;
  va_start(ap, format);
  toString(dst, format, ap);
  va_end(ap);
  return dst;
}

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw() {
  string dst;
  toString(dst, format, ap);
  return dst;
}


int main() {
  int a = 32;
  const char * str = "This works!";

  string test(toString("\nSome testing: a = %d, %s\n", a, str));
  printf(test.c_str());

  a = 0x7fffffff;
  test = toString("\nMore testing: a = %d, %s\n", a, "This works too..");
  printf(test.c_str());

  a = 0x80000000;
  toString(test, "\nMore testing: a = %d, %s\n", a, "This way is cheaper");
  printf(test.c_str());

  return 0;
}

String.h:

#pragma once
#include <cstdarg>
#include <string>

using ::std::string;

///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ap Variable argument list
///
void toString(string &dst, const char *format, va_list ap) throw();
///
/// \breif Format message
/// \param dst String to store formatted message
/// \param format Format of message
/// \param ... Variable argument list
///
void toString(string &dst, const char *format, ...) throw();
///
/// \breif Format message
/// \param format Format of message
/// \param ... Variable argument list
///
string toString(const char *format, ...) throw();

///
/// \breif Format message
/// \param format Format of message
/// \param ap Variable argument list
///
string toString(const char *format, va_list ap) throw();

[编辑:20/05/25]更好的是… 在标题:

// `say` prints the values
// `says` returns a string instead of printing
// `sayss` appends the values to it's first argument instead of printing
// `sayerr` prints the values and returns `false` (useful for return statement fail-report)<br/>

void PRINTSTRING(const std::string &s); //cater for GUI, terminal, whatever..
template<typename...P> void say(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); }
template<typename...P> std::string says(P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str(); return r; }
template<typename...P> void sayss(std::string &s, P...p) { std::string r{}; std::stringstream ss(""); (ss<<...<<p); r=ss.str();  s+=r; } //APPENDS! to s!
template<typename...P> bool sayerr(P...p) { std::string r{}; std::stringstream ss("ERROR: "); (ss<<...<<p); r=ss.str(); PRINTSTRING(r); return false; }

PRINTSTRING(r)-函数是为了满足GUI或终端或任何特殊输出需要使用#ifdef _some_flag_,默认是:

void PRINTSTRING(const std::string &s) { std::cout << s << std::flush; }

[edit '17/8/31]添加可变参数模板版本'vtspf(..)':

template<typename T> const std::string type_to_string(const T &v)
{
    std::ostringstream ss;
    ss << v;
    return ss.str();
};

template<typename T> const T string_to_type(const std::string &str)
{
    std::istringstream ss(str);
    T ret;
    ss >> ret;
    return ret;
};

template<typename...P> void vtspf_priv(std::string &s) {}

template<typename H, typename...P> void vtspf_priv(std::string &s, H h, P...p)
{
    s+=type_to_string(h);
    vtspf_priv(s, p...);
}

template<typename...P> std::string temp_vtspf(P...p)
{
    std::string s("");
    vtspf_priv(s, p...);
    return s;
}

它实际上是一个逗号分隔的版本(而不是)有时阻碍<<-操作符,像这样使用:

char chSpace=' ';
double pi=3.1415;
std::string sWorld="World", str_var;
str_var = vtspf("Hello", ',', chSpace, sWorld, ", pi=", pi);

[编辑]在Erik Aronesty的回答中使用了这个技巧(上图):

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

//=============================================================================
void spf(std::string &s, const std::string fmt, ...)
{
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        s.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)s.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) s.resize(n); else size*=2;
    }
}

//=============================================================================
void spfa(std::string &s, const std::string fmt, ...)
{
    std::string ss;
    int n, size=100;
    bool b=false;
    va_list marker;

    while (!b)
    {
        ss.resize(size);
        va_start(marker, fmt);
        n = vsnprintf((char*)ss.c_str(), size, fmt.c_str(), marker);
        va_end(marker);
        if ((n>0) && ((b=(n<size))==true)) ss.resize(n); else size*=2;
    }
    s += ss;
}

(之前的回答) 这是一个很晚的回答,但对于那些像我一样喜欢“sprintf”方式的人来说:我已经编写并正在使用以下函数。如果你喜欢它,你可以扩展%-选项以更接近sprintf选项;现在里面的已经足够我用了。 使用stringf()和stringfappend()与使用sprintf相同。只要记住参数…必须是POD类型。

//=============================================================================
void DoFormatting(std::string& sF, const char* sformat, va_list marker)
{
    char *s, ch=0;
    int n, i=0, m;
    long l;
    double d;
    std::string sf = sformat;
    std::stringstream ss;

    m = sf.length();
    while (i<m)
    {
        ch = sf.at(i);
        if (ch == '%')
        {
            i++;
            if (i<m)
            {
                ch = sf.at(i);
                switch(ch)
                {
                    case 's': { s = va_arg(marker, char*);  ss << s;         } break;
                    case 'c': { n = va_arg(marker, int);    ss << (char)n;   } break;
                    case 'd': { n = va_arg(marker, int);    ss << (int)n;    } break;
                    case 'l': { l = va_arg(marker, long);   ss << (long)l;   } break;
                    case 'f': { d = va_arg(marker, double); ss << (float)d;  } break;
                    case 'e': { d = va_arg(marker, double); ss << (double)d; } break;
                    case 'X':
                    case 'x':
                        {
                            if (++i<m)
                            {
                                ss << std::hex << std::setiosflags (std::ios_base::showbase);
                                if (ch == 'X') ss << std::setiosflags (std::ios_base::uppercase);
                                char ch2 = sf.at(i);
                                if (ch2 == 'c') { n = va_arg(marker, int);  ss << std::hex << (char)n; }
                                else if (ch2 == 'd') { n = va_arg(marker, int); ss << std::hex << (int)n; }
                                else if (ch2 == 'l') { l = va_arg(marker, long);    ss << std::hex << (long)l; }
                                else ss << '%' << ch << ch2;
                                ss << std::resetiosflags (std::ios_base::showbase | std::ios_base::uppercase) << std::dec;
                            }
                        } break;
                    case '%': { ss << '%'; } break;
                    default:
                    {
                        ss << "%" << ch;
                        //i = m; //get out of loop
                    }
                }
            }
        }
        else ss << ch;
        i++;
    }
    va_end(marker);
    sF = ss.str();
}

//=============================================================================
void stringf(string& stgt,const char *sformat, ... )
{
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(stgt, sformat, marker);
}

//=============================================================================
void stringfappend(string& stgt,const char *sformat, ... )
{
    string sF = "";
    va_list marker;
    va_start(marker, sformat);
    DoFormatting(sF, sformat, marker);
    stgt += sF;
}

我不喜欢把事情搞复杂。这是基于iFreilicht的答案,但我减少了一些噪音,使它更有效。请注意,如果您计划在接口中使用此功能,可能会添加一些模糊输入检查。

#include <iostream>
#include <string>

template<typename... Ts>
std::string string_format( const std::string& format, Ts... Args )
{
    const size_t n = std::snprintf( nullptr, 0, format.c_str(), Args ... ) + 1; // Extra space for '\0'
    std::string ret(n, '\0');
    std::snprintf( &ret.front(), n, format.c_str(), Args... );
    return ret;
}

int main()
{
    int a = 5;
    char c = 'h';
    double k = 10.3;
    std::cout << string_format("%d, %c, %.2f", a, c, k) << "\n";
}

输出:

5, h, 10.30

试着自己

(*唯一的警告,我发现性能方面是没有办法默认初始化字符串存储。这很遗憾,因为我们不需要在这里将所有的值初始化为“\0”。)

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

测试,生产质量答案

这个答案用符合标准的技术处理一般情况。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也会失败。