C++ std::string 格式,如 sprintf

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2342162/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-27 23:08:19  来源:igfitidea点击:

std::string formatting like sprintf

c++stringformattingstdstringc++-standard-library

提问by Max Frai

I have to format std::stringwith sprintfand send it into file stream. How can I do this?

我不得不格式std::stringsprintf和发送到文件流。我怎样才能做到这一点?

采纳答案by Doug T.

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:

您不能直接执行此操作,因为您没有对底层缓冲区的写访问权限(直到 C++11;请参阅 Dietrich Epp 的评论)。您必须先在 c 字符串中执行此操作,然后将其复制到 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();

回答by iFreilicht

Modern C++ makes this super simple.

现代 C++ 使这变得非常简单。

C++20

C++20

C++20introduces std::format, which allows you to do exactly that. It uses replacement fields similar to those in python:

C++20引入了std::format,它允许您做到这一点。它使用类似于python 中的替换字段:

#include <iostream>
#include <format>

int main() {
    std::cout << std::format("Hello {}!\n", "world");
}

Check out the full documentation! It's a huge quality-of-life improvement.

查看完整文档!这是一个巨大的生活质量改善。



C++11

C++11

With C++11s std::snprintf, this already became a pretty easy and safe task.

使用C++11s std::snprintf,这已经成为一项非常简单和安全的任务。

#include <memory>
#include <string>
#include <stdexcept>

template<typename ... Args>
std::string string_format( const std::string& format, Args ... args )
{
    size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1; // Extra space for '
size_t size = snprintf( nullptr, 0, format.c_str(), args ... ) + 1;
' if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); } std::unique_ptr<char[]> buf( new char[ size ] ); snprintf( buf.get(), size, format.c_str(), args ... ); return std::string( buf.get(), buf.get() + size - 1 ); // We don't want the '
if( size <= 0 ){ throw std::runtime_error( "Error during formatting." ); }
' inside }

The code snippet above is licensed under CC0 1.0.

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

Line by line explanation:

逐行解释:

Aim:Write to a char*by using std::snprintfand then convert that to a std::string.

目标:char*通过 using 写入 astd::snprintf,然后将其转换为 astd::string

First, we determine the desired length of the char array using a special condition in snprintf. From cppreference.com:

首先,我们使用 中的特殊条件确定 char 数组的所需长度snprintf。从cppreference.com

Return value

[...] If the resulting string gets truncated due to buf_size limit, function returns the total number of characters (not including the terminating null-byte) which would have been written, if the limit was not imposed.

返回值

[...] 如果结果字符串因 buf_size 限制而被截断,则函数返回本应写入的字符总数(不包括终止空字节),如果未施加限制。

This means that the desired size is the number of characters plus one, so that the null-terminator will sit after all other characters and that it can be cut off by the string constructor again. This issue was explained by @alexk7 in the comments.

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

std::unique_ptr<char[]> buf( new char[ size ] );

snprintfwill return a negative number if an error occurred, so we then check whether the formatting worked as desired. Not doing this could lead to silent errors or the allocation of a huge buffer, as pointed out by @ead in the comments.

snprintf如果发生错误,将返回一个负数,因此我们然后检查格式是否按预期工作。正如@ead 在评论中指出的那样,不这样做可能会导致无提示错误或分配巨大的缓冲区。

snprintf( buf.get(), size, format.c_str(), args ... );

Next, we allocate a new character array and assign it to a std::unique_ptr. This is generally advised, as you won't have to manually deleteit again.

接下来,我们分配一个新的字符数组并将其分配给 a std::unique_ptr。通常建议这样做,因为您不必delete再次手动操作。

Note that this is not a safe way to allocate a unique_ptrwith user-defined types as you can not deallocate the memory if the constructor throws an exception!

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

return std::string( buf.get(), buf.get() + size - 1 );

After that, we can of course just use snprintffor its intended use and write the formatted string to the char[].

之后,我们当然可以仅用snprintf于其预期用途并将格式化的字符串写入char[].

#pragma warning(disable : 4996)

Finally, we create and return a new std::stringfrom that, making sure to omit the null-terminator at the end.

最后,我们创建并返回一个新的std::string,确保省略最后的空终止符。

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


You can see an example in action here.

您可以在此处查看示例。



If you also want to use std::stringin the argument list, take a look at this gist.

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



Additional information for Visual Studiousers:

Visual Studio用户的其他信息:

As explained in this answer, Microsoft renamed std::snprintfto _snprintf(yes, without std::). MS further set it as deprecated and advises to use _snprintf_sinstead, however _snprintf_swon't accept the buffer to be zero or smaller than the formatted output and will not calculate the outputs length if that occurs. So in order to get rid of the deprecation warnings during compilation, you can insert the following lineat the top of the file which contains the use of _snprintf:

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

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


Final thoughts

最后的想法

A lot of answers to this question were written before the time of C++11 and use fixed buffer lengths or vargs. Unless you're stuck with old versions of C++, I wouldn't recommend using those solutions. Ideally, go the C++20 way.

这个问题的很多答案都是在 C++11 之前写的,并且使用固定的缓冲区长度或 vargs。除非您坚持使用旧版本的 C++,否则我不建议使用这些解决方案。理想情况下,采用 C++20 方式。

Because the C++11 solution in this answer uses templates, it can generate quite a bit of code if it is used a lot. However, unless you're developing for an environment with very limited space for binaries, this won't be a problem and is still a vast improvement over the other solutions in both clarity and security.

因为这个答案中的C++11解决方案使用了模板,所以如果经常使用它可以生成相当多的代码。但是,除非您正在为二进制文件空间非常有限的环境进行开发,否则这不会成为问题,并且在清晰度和安全性方面仍然比其他解决方案有很大的改进。

If space efficiency is super important, these two solution with vargs and vsnprintfcan be useful. DO NOT USEany solutions with fixed buffer lengths, that is just asking for trouble.

如果空间效率非常重要,那么这两个带有 vargs 和 vsnprintf 的解决方案会很有用。 不要使用任何具有固定缓冲区长度的解决方案,这只是在自找麻烦。

回答by Erik Aronesty

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

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

#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"

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

一种更安全、更高效(我测试过,而且速度更快)的方法:

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

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

fmt_str是按值传递与要求相符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 strcpyrenders it slower.

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

回答by kennytm

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

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

As from the Boost format libraries synopsis:

来自 Boost 格式库的概要:

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.

格式对象由格式字符串构造而成,然后通过重复调用 operator% 为其提供参数。然后根据格式字符串将这些参数中的每一个转换为字符串,然后将字符串组合成一个字符串。

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

回答by vitaut

C++20 will include std::formatwhich resembles sprintfin 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::stringand write it to a stream:

C++20 将包括std::formatsprintf在 API 方面类似但完全类型安全,使用用户定义的类型,并使用类似 Python 的格式字符串语法。以下是如何格式化std::string并将其写入流的方法:

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

or

或者

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

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

或者,您可以使用{fmt} 库格式化字符串并将其写入stdout或一次性写入文件流:

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


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

至于sprintf这里的其他大多数答案,不幸的是,它们使用可变参数并且本质上是不安全的,除非您使用 GCC 的format属性之类的东西,它只适用于文字格式字符串。您可以在以下示例中了解为什么这些函数不安全:

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

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

其中,string_format从埃里克Aronesty的答案的实现。这段代码可以编译,但是当您尝试运行它时很可能会崩溃:

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

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

免责声明:我是 {fmt} 和 C++20 的作者std::format

回答by Timo Geusch

If you only want a printf-like syntax (without calling printf yourself), have a look at Boost Format.

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

回答by user2622016

In order to format std::stringin a 'sprintf' manner, call snprintf(arguments nullptrand 0) to get length of buffer needed. Write your function using C++11 variadic template like this:

为了以std::string'sprintf' 方式格式化,调用snprintf(arguments nullptrand 0) 来获取所需的缓冲区长度。使用 C++11 可变参数模板编写您的函数,如下所示:

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

Compile with C++11 support, for example in GCC: g++ -std=c++11

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

Usage:

用法:

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);

回答by Piti Ongmongkolkul

I wrote my own using vsnprintf so it returns string instead of having to create my own buffer.

我使用 vsnprintf 编写了自己的代码,因此它返回字符串,而不必创建自己的缓冲区。

// `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; }

So you can use it like

所以你可以像这样使用它

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

回答by slashmais

[edit: 20/05/25] better still...:
In header:

[编辑:20/05/25] 更好...:
在标题中:

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

The PRINTSTRING(r)-function is to cater for GUI or terminal or any special output needs using #ifdef _some_flag_, the default is:

所述PRINTSTRING(r)-function是为应付GUI或终端或使用任何特殊的输出需求#ifdef _some_flag_,默认为:

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


[edit '17/8/31] Adding a variadic templated version 'vtspf(..)':

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

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

which is effectively a comma-delimited version (instead) of the sometimes hindering <<-operators, used like this:

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

//=============================================================================
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;
}



[edit] Adapted to make use of the technique in Erik Aronesty's answer (above):

[编辑] 适应使用 Erik Aronesty 的回答中的技术(上图):

##代码##

[previous answer]
A very late answer, but for those who, like me, do like the 'sprintf'-way: I've written and are using the following functions. If you like it, you can expand the %-options to more closely fit the sprintf ones; the ones in there currently are sufficient for my needs. You use stringf() and stringfappend() same as you would sprintf. Just remember that the parameters for ... must be POD types.

[上一个答案]
一个很晚的答案,但对于那些像我一样喜欢“sprintf”方式的人:我已经编写并正在使用以下函数。如果你喜欢它,你可以扩展 %-options 以更贴近 sprintf 选项;目前那里的那些足以满足我的需要。您可以像使用 sprintf 一样使用 stringf() 和 stringfappend()。请记住, ... 的参数必须是 POD 类型。

##代码##

回答by PW.

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 StringAppendFtoo.

这就是 google 的做法:StringPrintf(BSD 许可证)
和 facebook 以非常相似的方式做到这一点:StringPrintf(Apache 许可证)
两者都提供了方便StringAppendF