首页 文章

std :: string格式如sprintf

提问于
浏览
349

我必须使用sprintf格式化std::string并将其发送到文件流中 . 我怎样才能做到这一点?

30 回答

  • 98

    不幸的是,这里的大多数答案都使用了varargs,除非你使用像GCC的 format 属性这样只能使用文字格式的字符串,否则它本身就是不安全的 . 您可以在以下示例中看到这些函数为何不安全:

    std::string format_str = "%s";
    string_format(format_str, format_str[0]);
    

    其中 string_format 是Erik Aronesty的回答 . 此代码编译,但当您尝试运行它时,它很可能会崩溃:

    $ g++ -Wall -Wextra -pedantic test.cc 
    $ ./a.out 
    Segmentation fault: 11
    

    可以实现安全 printf 并将其扩展为使用(可变参数)模板格式化 std::string . 这已在fmt library中完成,它提供了 sprintf 返回 std::string 的安全替代方法:

    std::string format_str = "The answer is %d";
    std::string result = fmt::sprintf(format_str, 42);
    

    fmt跟踪参数类型,如果类型与格式规范不匹配,则不存在分段错误,只是例外 .

    Disclaimer :我是这个图书馆的作者 .

  • 10

    非常简单的解决方案 .

    std::string strBuf;
    strBuf.resize(256);
    int iCharsPrinted = sprintf_s((char *)strPath.c_str(), strPath.size(), ...);
    strBuf.resize(iCharsPrinted);
    
  • 235

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

    这实际上是一个逗号分隔的版本(相反)有时阻碍 << -operators,使用如下:

    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'的人:我写过并且正在使用以下功能 . 如果你喜欢它,你可以扩展%-options以更接近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;
    }
    
  • 257

    如果您使用的是具有asprintf(3)的系统,则可以轻松将其包装:

    #include <iostream>
    #include <cstdarg>
    #include <cstdio>
    
    std::string format(const char *fmt, ...) __attribute__ ((format (printf, 1, 2)));
    
    std::string format(const char *fmt, ...)
    {
        std::string result;
    
        va_list ap;
        va_start(ap, fmt);
    
        char *tmp = 0;
        int res = vasprintf(&tmp, fmt, ap);
        va_end(ap);
    
        if (res != -1) {
            result = tmp;
            free(tmp);
        } else {
            // The vasprintf call failed, either do nothing and
            // fall through (will return empty string) or
            // throw an exception, if your code uses those
        }
    
        return result;
    }
    
    int main(int argc, char *argv[]) {
        std::string username = "you";
        std::cout << format("Hello %s! %d", username.c_str(), 123) << std::endl;
        return 0;
    }
    
  • 3

    Dacavpixelpoint's answer接受了这个想法 . 我玩了一下,得到了这个:

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

    使用 sane 编程实践我相信代码应该足够了,但是我仍然对更安全的替代方案持开放态度,这些替代方案仍然很简单并且不需要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's answer类似,只是它不使用 newdelete[] ,并且在创建 std::string 时也指定了一个大小 .

    这里不使用 newdelete[] 的想法是暗示在堆上使用堆栈,因为它不需要调用分配和释放函数,但是如果没有正确使用,在某些情况下缓冲溢出可能是危险的(可能是旧的)或者可能只是易受攻击的系统 . 如果这是一个问题,我强烈建议使用 newdelete[] . 请注意,此处唯一关注的是分配,因为已经使用限制调用 vsnprintf() ,因此根据在第二个缓冲区上分配的大小指定限制也会阻止这些限制 . )

  • 1

    我对这个非常受欢迎的问题的两分钱 .

    引用manpage of printf-like functions

    成功返回后,这些函数返回打印的字符数(不包括用于结束输出到字符串的空字节) . 函数snprintf()和vsnprintf()写入的字节数不超过大小(包括终止空字节('\ 0')) . 如果输出因此限制而被截断,则返回值是字符数(不包括终止空字节),如果有足够的空间,则该字符数将被写入最终字符串 . 因此,大小或更大的返回值意味着输出被截断 .

    换句话说,理智的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中支持变量模板 . 像素点的答案显示了使用较旧编程样式的类似技术 .

    奇怪的是,C没有开箱即用的东西 . 他们最近添加了to_string(),我认为这是向前迈出的一大步 . 我想知道他们是否会在 std::string 最终添加 .format 运营商...

    编辑

    正如alexk7指出的那样, std::snprintf 的返回值需要A +1 ,因为我们需要为 \0 字节留出空间 . 直观地说,在大多数缺少 +1 的架构上都会导致 required 整数被 0 部分覆盖 . 这将在 required 评估为 std::snprintf 的实际参数后发生,因此效果不应该是可见的 .

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

  • 4
    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

  • 7
    _return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
    
  • 14

    在内部使用 vsnprintf() 的C 11解决方案:

    #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 的要求 .

    注意:"safer"和"faster"版本在某些系统上不起作用 . 因此两者仍然列出 . 此外,"faster"完全取决于预分配步骤是否正确,否则 strcpy 会使其变慢 .

  • 4

    利用C++11 std::snprintf,这将成为一项非常简单和安全的任务 . 我看到很多关于这个问题的答案显然是在C 11之前写的,它使用固定的缓冲长度和vargs,我不建议出于安全性,效率和清晰度的原因 .

    #include <memory>
    #include <iostream>
    #include <string>
    #include <cstdio>
    
    using namespace std; //Don't if you're in a header-file
    
    template<typename ... Args>
    string string_format( const std::string& format, Args ... args )
    {
        size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '\0'
        unique_ptr<char[]> buf( new char[ size ] ); 
        snprintf( buf.get(), size, format.c_str(), args ... );
        return string( buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
    }
    

    上面的代码段在CC0 1.0下获得许可 .

    逐行说明:

    Aim: 使用 std::snprintf 写入 char* ,然后将其转换为 std::string .

    首先,我们确定char数组的所需长度 .

    cppreference.com

    回归value [...]如果由于buf_size限制而导致结果字符串被截断,则函数将返回已写入的字符总数(不包括终止空字节),如果未强制执行限制 .

    这意味着所需的大小是字符数 plus one ,因此空终止符将位于所有其他字符之后,并且可以再次被字符串构造函数切断 . @ alexk7在评论中解释了这个问题 .

    然后,我们分配一个新的字符数组并将其分配给 std::unique_ptr . 通常建议这样做,因为您不必再次手动 delete .

    请注意,这不是一种使用用户定义类型分配 unique_ptr 的安全方法,因为如果构造函数抛出异常,则无法释放内存!

    之后,我们当然可以将 snprintf 用于其预期用途,并将格式化的字符串写入 char[] ,然后创建并返回一个新的 std::string .


    您可以在行动here中看到一个示例 .


    如果您还想在参数列表中使用 std::string ,请查看this gist .


    Visual Studio用户的其他信息:

    this answer中所述,Microsoft将 std::snprintf 重命名为 _snprintf (是的,没有 std:: ) . MS进一步将其设置为已弃用并建议使用_snprintf_s,但是 _snprintf_s 将不接受缓冲区为零或小于格式化输出,并且如果发生这种情况则不会计算输出长度 . 因此,为了在编译期间摆脱弃用警告,您可以在包含 _snprintf 的文件顶部插入the following line

    #pragma warning(disable : 4996)
    
  • 1

    如果您只想要类似printf的语法(不自行调用printf),请查看Boost Format .

  • 1

    我使用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);
    
  • 3

    这可以试试 . 简单 . 实际上并没有使用字符串类的细微差别 .

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

    如果缓冲区不足以打印字符串,则可能会出现问题 . 在那里打印格式化的消息之前,您必须确定格式化字符串的长度 . 我自己帮忙(在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();
    
  • 12

    您可以使用iomanip头文件格式化cout中的C输出 . 在使用setprecision,setfill等任何辅助函数之前,请确保包含iomanip头文件 .

    这是我过去使用的代码片段,用于打印向量中的平均等待时间,我已经“累积”了 .

    #include<iomanip>
    #include<iostream>
    #include<vector>
    #include<numeric>
    
    ...
    
    cout<< "Average waiting times for tasks is " << setprecision(4) << accumulate(all(waitingTimes), 0)/double(waitingTimes.size()) ;
    cout << " and " << Q.size() << " tasks remaining" << endl;
    

    以下是我们如何格式化C流的简要说明 . http://www.cprogramming.com/tutorial/iomanip.html

  • 10

    string没有你需要的东西,但是std :: stringstream呢 . 使用stringstream创建字符串,然后提取字符串 . Here是关于您可以做的事情的综合列表 . 例如:

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

    打印双面或浮动时,将给出10位小数的精度 .

  • 18

    这是我在我的程序中用来做这个的代码......这没什么特别的,但它确实有用......注意,你必须根据需要调整你的大小 . 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;
    }
    
  • 1

    谷歌就是这样做的:StringPrintf(BSD许可证)
    和Facebook以非常相似的方式做到:StringPrintf(Apache许可证)
    两者都提供了方便的 StringAppendF .

  • 6

    boost::format()提供您想要的功能:

    从Boost格式库概要:

    格式对象由格式字符串构造,然后通过重复调用operator%给出参数 . 然后根据格式字符串将每个参数转换为字符串,然后将字符串组合成一个字符串 .

    #include <boost/format.hpp>
    
    cout << boost::format("writing %1%,  x=%2% : %3%-th try") % "toto" % 40.23 % 50; 
    // prints "writing toto,  x=40.230 : 50-th try"
    
  • 4

    我试了一下,用regular expressions . 我为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;
    

    输出:

    我是鲍勃,我有3只猫

  • 1

    您可以对底层缓冲区具有写访问权限(直到C 11;请参阅Dietrich Epp的comment) . 您必须先在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();
    
  • 28

    我通常使用这个:

    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

  • 3

    Tested, Production Quality Answer

    这个答案处理符合标准的技术的一般情况 . 在页面底部附近的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; }
    
  • 15

    Poco Foundation库有一个非常方便的格式函数,它支持格式字符串和值中的std :: string:

  • 3

    根据答案由Erik Aronesty提供:

    std::string string_format(const std::string &fmt, ...) {
        std::vector<char> str(100,'\0');
        va_list ap;
        while (1) {
            va_start(ap, fmt);
            auto n = vsnprintf(str.data(), str.size(), fmt.c_str(), ap);
            va_end(ap);
            if ((n > -1) && (size_t(n) < str.size())) {
                return str.data();
            }
            if (n > -1)
                str.resize( n + 1 );
            else
                str.resize( str.size() * 2);
        }
        return str.data();
    }
    

    这避免了从原始答案中的 .c_str() 的结果中丢弃 const 的需要 .

  • 2

    要以'sprintf'方式格式化 std::string ,请调用 snprintf (arguments nullptr0 )以获取所需的缓冲区长度 . 使用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 std::move(str);
    }
    

    编译C 11支持,例如在GCC中: g++ -std=c++11

    用法:

    std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
    
  • 3

    下面稍微修改了@iFreilicht的答案版本,更新为C++14(使用 make_unique 函数而不是原始声明)并添加了对 std::string 参数的支持(基于Kenny Kerr article

    #include <iostream>
    #include <memory>
    #include <string>
    #include <cstdio>
    
    template <typename T>
    T process_arg(T value) noexcept
    {
        return value;
    }
    
    template <typename T>
    T const * process_arg(std::basic_string<T> const & value) noexcept
    {
        return value.c_str();
    }
    
    template<typename ... Args>
    std::string string_format(const std::string& format, Args const & ... args)
    {
        const auto fmt = format.c_str();
        const size_t size = std::snprintf(nullptr, 0, fmt, process_arg(args) ...) + 1;
        auto buf = std::make_unique<char[]>(size);
        std::snprintf(buf.get(), size, fmt, process_arg(args) ...);
        auto res = std::string(buf.get(), buf.get() + size - 1);
        return res;
    }
    
    int main()
    {
        int i = 3;
        float f = 5.f;
        char* s0 = "hello";
        std::string s1 = "world";
        std::cout << string_format("i=%d, f=%f, s=%s %s", i, f, s0, s1) << "\n";
    }
    

    输出:

    i = 3, f = 5.000000, s = hello world
    

    如果需要,请随意将此答案与原始答案合并 .

  • 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);
    }
    
  • 1

    你可以试试这个:

    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 );
    
  • 6

    我最喜欢的一个解决方案就是将sprintf直接放到std :: string缓冲区中,然后使缓冲区足够大:

    #include <string>
    #include <iostream>
    
    using namespace std;
    
    string l_output;
    l_output.resize(100);
    
    for (int i = 0; i < 1000; ++i)
    {       
        memset (&l_output[0], 0, 100);
        sprintf (&l_output[0], "\r%i\0", i);
    
        cout << l_output;
        cout.flush();
    }
    

    所以,创建std :: string,调整大小,直接访问它的缓冲区......

相关问题