std::string formatting like sprintf

前端 未结 30 2605
野趣味
野趣味 2020-11-22 04:42

I have to format std::string with sprintf and send it into file stream. How can I do this?

相关标签:
30条回答
  • 2020-11-22 05:07

    C++20 will include std::format which resembles sprintf in terms of API but is fully type-safe, works with user-defined types, and uses Python-like format string syntax. Here's how you will be able to format std::string and write it to a stream:

    std::string s = "foo";
    std::cout << std::format("Look, a string: {}", s);
    

    or

    std::string s = "foo";
    puts(std::format("Look, a string: {}", s).c_str());
    

    Alternatively, you could use the {fmt} library to format a string and write it to stdout or a file stream in one go:

    fmt::print(f, "Look, a string: {}", s); // where f is a file stream
    

    As for sprintf or most of the other answers here, unfortunately they use varargs and are inherently unsafe unless you use something like GCC's format attribute which only works with literal format strings. You can see why these functions are unsafe on the following example:

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

    where string_format is an implementation from the Erik Aronesty's answer. This code compiles, but it will most likely crash when you try to run it:

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

    Disclaimer: I'm the author of {fmt} and C++20 std::format.

    0 讨论(0)
  • 2020-11-22 05:07
    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);
    }
    
    0 讨论(0)
  • 2020-11-22 05:09

    You can't do it directly, because you don't have write access to the underlying buffer (until C++11; see Dietrich Epp's comment). You'll have to do it first in a c-string, then copy it into a std::string:

      char buff[100];
      snprintf(buff, sizeof(buff), "%s", "Hello");
      std::string buffAsStdStr = buff;
    

    But I'm not sure why you wouldn't just use a string stream? I'm assuming you have specific reasons to not just do this:

      std::ostringstream stringStream;
      stringStream << "Hello";
      std::string copyOfStr = stringStream.str();
    
    0 讨论(0)
  • 2020-11-22 05:09

    C++11 solution that uses vsnprintf() internally:

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

    A safer and more efficient (I tested it, and it is faster) approach:

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

    The fmt_str is passed by value to conform with the requirements of va_start.

    NOTE: The "safer" and "faster" version doesn't work on some systems. Hence both are still listed. Also, "faster" depends entirely on the preallocation step being correct, otherwise the strcpy renders it slower.

    0 讨论(0)
  • 2020-11-22 05:09

    boost::format() provides the functionality you want:

    As from the Boost format libraries synopsis:

    A format object is constructed from a format-string, and is then given arguments through repeated calls to operator%. Each of those arguments are then converted to strings, who are in turn combined into one string, according to the format-string.

    #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"
    
    0 讨论(0)
  • 2020-11-22 05:10

    This is how google does it: StringPrintf (BSD License)
    and facebook does it in a quite similar fashion: StringPrintf (Apache License)
    Both provide with a convenient StringAppendF too.

    0 讨论(0)
提交回复
热议问题