std::string 格式如 sprintf

发布于 2024-08-23 01:48:45 字数 225 浏览 6 评论 0 原文

我必须使用 std::string ://en.cppreference.com/w/cpp/io/c/fprintf">sprintf 并将其发送到文件流中。我该怎么做?

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

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(30

衣神在巴黎 2024-08-30 01:48:45

现代 C++ 使这变得超级简单。

C++20

C++20 引入 std::format,它可以让你做到这一点。它使用类似于 python 中的替换字段

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

< em>来自 cppreference.com 的代码、CC BY-SA 和 GFDL

查看编译器支持页面以查看它是否在您的标准库实现中可用。

截至 2023 年 7 月 18 日,从以下版本开始提供部分支持:

在所有其他情况下,您可以求助于下面的 C++11 解决方案,或使用 {fmt}< /code> 库,其语义与 std::format 相同。


C++11

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

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

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

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

逐行解释:

目标:使用std::snprintf写入char*,然后将其转换为<代码>std::string。

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

返回值

[...] 如果结果字符串由于 buf_size 限制而被截断,
函数返回字符总数(不包括
终止空字节),如果限制是,则会被写入
不强加。

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

int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

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

if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

因为我们知道 size_s 不能为负数,所以我们使用静态转换将其从有符号 int 转换为无符号 size_t。这样,即使是最迂腐的编译器也不会抱怨下一行将发生的转换。

size_t size = static_cast<size_t>( size_s );

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

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

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

"https://en.wikipedia.org/wiki/C%2B%2B14" rel="noreferrer">C++14,您可以改用 make_unique,对于用户定义的类型是安全的。

auto buf = std::make_unique<char[]>( size );

之后,我们当然可以仅使用 snprintf 来实现其预期用途,并将格式化字符串写入 char[] 中。

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

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

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

您可以在此处查看实际示例。


如果您还想在参数列表中使用 std::string,请查看 这个要点


针对 Visual Studio 用户的其他信息:

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

#pragma warning(disable : 4996)

最终想法

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

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

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

Modern C++ makes this super simple.

C++20

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

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

Code from cppreference.com, CC BY-SA and GFDL

Check out the compiler support page to see if it's available in your standard library implementation.

As of 2023-07-18, partial support is available starting from:

In all other cases, you can resort to the C++11 solution below, or use the {fmt} library, which has the same semantics as std::format.


C++11

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

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

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

The code snippet above is licensed under CC0 1.0.

Line by line explanation:

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

First, we determine the desired length of the char array using a special condition in snprintf. From 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.

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.

int size_s = std::snprintf( nullptr, 0, format.c_str(), args ... ) + 1;

snprintf will 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.

if( size_s <= 0 ){ throw std::runtime_error( "Error during formatting." ); }

Because we know that size_s can't be negative, we use a static cast to convert it from a signed int to an unsigned size_t. This way, even the most pedantic compiler won't complain about the conversions that would otherwise happen on the next lines.

size_t size = static_cast<size_t>( size_s );

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 delete it again.

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

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

In C++14, you could instead use make_unique, which is safe for user-defined types.

auto buf = std::make_unique<char[]>( size );

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

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

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

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

You can see an example in action here.


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


Additional information for Visual Studio users:

As explained in this answer, Microsoft renamed std::snprintf to _snprintf (yes, without std::). MS further set it as deprecated and advises to use _snprintf_s instead, however _snprintf_s won'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 line at the top of the file which contains the use of _snprintf:

#pragma warning(disable : 4996)

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.

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.

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

ぇ气 2024-08-30 01:48:45

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

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();
无所的.畏惧 2024-08-30 01:48:45

内部使用 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的要求。

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

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.

放飞的风筝 2024-08-30 01:48:45

C++20 有 std::format ,类似于 sprintf 就 API 而言,但它是完全类型安全的,可与用户定义的类型一起使用,并使用类似 Python 的格式字符串语法。以下是如何格式化 std::string 并将其写入流:

std::cout << std::format("The answer is {}.", 42);

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

fmt::print("The answer is {}.", 42);

至于 sprintf 或大多数其他答案不幸的是,它们使用可变参数并且本质上是不安全的,除非您使用类似 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

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

C++20 has 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 can format std::string and write it to a stream:

std::cout << std::format("The answer is {}.", 42);

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("The answer is {}.", 42);

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.

じее 2024-08-30 01:48:45

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"

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"
红ご颜醉 2024-08-30 01:48:45

经过测试的生产质量答案

该答案使用符合标准的技术处理一般情况。底部附近的 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; }

可预测的线性效率

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

溢出时重试的效率呈指数级下降,这是 C++11 标准委员会讨论上述在写入缓冲区为空时提供试运行的提议时讨论的另一个原因。

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

安全性和可靠性

Andrew Koenig 在剑桥活动中发表演讲后对我们一小群人说:“用户功能不应该依赖于利用故障来实现普通功能。”和往常一样,他的智慧自此以后在记录中得到了真实的体现。已修复和已关闭的安全错误问题通常表明在修复之前所利用的漏洞的描述中存在重试黑客行为。

sprintf、C9X 修订提案、ISO IEC 文档 WG14 N6​​45/X3J11 96-008 的替代方案。在动态内存可用性的限制内,每个打印指令插入的任意长字符串“%s”不是一个例外,并且不应该被利用来产生“非异常功能”。

请考虑该提案以及本答案第一段中链接的 C++Reference.org 页面底部给出的示例代码。

此外,失败案例的测试很少像成功案例那样稳健。

可移植性

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

堆栈使用

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

Tested, Production Quality Answer

This answer handles the general case with standards compliant techniques. The same approach is given as an example on CppReference.com near the bottom of their page. Unlike their example, this code fits the question's requirements and is field tested in robotics and satellite applications. It also has improved commenting. Design quality is discussed further below.

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

Predictable Linear Efficiency

Two passes are necessities for a secure, reliable, and predictable reusable function per the question specifications. Presumptions about the distribution of sizes of vargs in a reusable function is bad programming style and should be avoided. In this case, arbitrarily large variable length representations of vargs is a key factor in choice of algorithm.

Retrying upon overflow is exponentially inefficient, which is another reason discussed when the C++11 standards committee discussed the above proposal to provide a dry run when the write buffer is null.

In the above production ready implementation, the first run is such a dry run to determine allocation size. No allocation occurs. Parsing of printf directives and the reading of vargs has been made extremely efficient over decades. Reusable code should be predictable, even if a small inefficiency for trivial cases must be sacrificed.

Security and Reliability

Andrew Koenig said to a small group of us after his lecture at a Cambridge event, "User functions shouldn't rely on the exploitation of a failure for unexceptional functionality." As usual, his wisdom has been shown true in the record since. Fixed and closed security bug issues often indicate retry hacks in the description of the hole exploited prior to the fix.

This is mentioned in the formal standards revision proposal for the null buffer feature in Alternative to sprintf, C9X Revision Proposal, ISO IEC Document WG14 N645/X3J11 96-008. An arbitrarily long string inserted per print directive, "%s," within the constraints of dynamic memory availability, is not an exception, and should not be exploited to produce, "Unexceptional functionality."

Consider the proposal along side the example code given at the bottom of the C++Reference.org page linked to in the first paragraph of this answer.

Also, the testing of failure cases is rarely as robust of success cases.

Portability

All major O.S. vendors provide compilers that fully support std::vsnprintf as part of the c++11 standards. Hosts running products of vendors that no longer maintain distributions should be furnished with g++ or clang++ for many reasons.

Stack Use

Stack use in the 1st call to std::vsnprintf will be less than or equal to that of the 2nd, and and it will be freed before the 2nd call begins. If the first call exceeds stack availability, then std::fprintf would fail too.

影子的影子 2024-08-30 01:48:45

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

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

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

So you can use it like

std::string mystr = format("%s %d %10.5f", "omg", 1, 10.5);
谁人与我共长歌 2024-08-30 01:48:45

为了以“sprintf”方式格式化 std::string,请调用 snprintf(参数 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 str;
}

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

用法:

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

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

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

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

Usage:

  std::cout << string_sprintf("%g, %g\n", 1.23, 0.001);
戏舞 2024-08-30 01:48:45

C++20 std::format

它已经到来!该功能的描述如下:http://www .open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html 并使用类似 Python 的 .format() 语法。

我预计用法如下:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

GCC 9.1.0 with g++-9 -std=c++2a 仍然不支持它。

现有的 fmt 库在获得官方支持之前就实现了它:https://github.com /fmtlib/fmt 如前所述: std::string 格式如 sprintf 在 Ubuntu 22.04 上安装:

sudo apt install libfmt-dev

修改源以替换:

  • std::formatfmt::format

main.cpp

#include <string>
#include <iostream>

#include <fmt/core.h>

int main() {
    std::string message = fmt::format("The answer is {}.", 42);
    std::cout << message << std::endl;
}

并编译并运行:

g++ -std=c++11 -o main.out main.cpp -lfmt
./main.out

输出:

The answer is 42.

API 将添加一个新的 std::format 标头:

建议的格式化 API 在新标头 中定义,并且不会对现有代码产生影响。

十六进制格式{:x}

C++ cout 十六进制值?

前导零 {:03}

使用 C++ 输出运算符打印前导零?

左对齐 {:<},右 { :>},居中{:^}

打印 cout 时的 C++ 对齐方式 <<

浮点精度 {:.2}

在正数上显示符号 {:+}

如何在 C++ 中打印带前缀 + 的正数

将布尔值显示为 truefalse{:}

在 C++ 中将 bool 转换为文本

C++20 std::format

It has arrived! The feature is described at: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p0645r9.html and uses a Python-like .format() syntax.

I expect that the usage will be like:

#include <format>
#include <string>

int main() {
    std::string message = std::format("The answer is {}.", 42);
}

GCC 9.1.0 with g++-9 -std=c++2a still doesn't support it.

The existing fmt library implements it for before it gets official support: https://github.com/fmtlib/fmt as previously mentioned at: std::string formatting like sprintf Install on Ubuntu 22.04:

sudo apt install libfmt-dev

Modify source to replace:

  • <format> with <fmt/core.h>
  • std::format to fmt::format

main.cpp

#include <string>
#include <iostream>

#include <fmt/core.h>

int main() {
    std::string message = fmt::format("The answer is {}.", 42);
    std::cout << message << std::endl;
}

and compile and run with:

g++ -std=c++11 -o main.out main.cpp -lfmt
./main.out

Output:

The answer is 42.

The API will add a new std::format header:

The proposed formatting API is defined in the new header <format> and should have no impact on existing code.

Hexadecimal format {:x}

C++ cout hex values?

Leading zeroes {:03}

Print leading zeros with C++ output operator?

Alignment left {:<}, right {:>}, center {:^}

C++ alignment when printing cout <<

Floating point precision {:.2}

Show sign on positive numbers {:+}

How to print positive numbers with a prefix + in C++

Show booleans as true and false: {:}

Converting bool to text in C++

百变从容 2024-08-30 01:48:45

如果您只想要类似 printf 的语法(无需自己调用 printf),请查看 增强格式

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

一抹微笑 2024-08-30 01:48:45

[编辑:2025年5月20日]更好...:
在标头中:

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

[编辑 '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”方式的人来说:我已经编写并正在使用以下函数。如果你喜欢,你可以扩展 %-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;
}

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

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

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

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

[edit '17/8/31] Adding a variadic templated version '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;
}

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

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

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

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

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

//=============================================================================
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;
}
哀由 2024-08-30 01:48:45
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

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

Using C99 snprintf and C++11

可是我不能没有你 2024-08-30 01:48:45

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

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.

故乡的云 2024-08-30 01:48:45

我对这个非常受欢迎的问题有两点看法。

引用 printf 函数的手册页

成功返回后,这些函数将返回打印的字符数(不包括用于结束字符串输出的空字节)。

函数 snprintf() 和 vsnprintf() 写入的内容不会超过 size 字节(包括终止空字节 ('\0'))。如果输出由于此限制而被截断,则返回值是在有足够空间可用的情况下将写入最终字符串的字符数(不包括终止空字节)。因此,返回值 size 或更大意味着输出被截断。

换句话说,一个健全的 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 支持可变参数模板。 Pixelpoint 的答案显示了使用旧编程风格的类似技术。

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

编辑

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

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

My two cents on this very popular question.

To quote the manpage of printf-like functions:

Upon successful return, these functions return the number of characters printed (excluding the null byte used to end output to strings).

The functions snprintf() and vsnprintf() do not write more than size bytes (including the terminating null byte ('\0')). If the output was truncated due to this limit then the return value is the number of characters (excluding the terminating null byte) which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated.

In other words, a sane C++11 implementation should be the following:

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

It works quite well :)

Variadic templates are supported only in C++11. The answer from pixelpoint show a similar technique using older programming styles.

It's weird that C++ does not have such a thing out of the box. They recently added to_string(), which in my opinion is a great step forward. I'm wondering if they will add a .format operator to the std::string eventually...

Edit

As alexk7 pointed out, A +1 is needed on the return value of std::snprintf, since we need to have space for the \0 byte. Intuitively, on most architectures missing the +1 will cause the required integer to be partially overwritten with a 0. This will happen after the evaluation of required as actual parameter for std::snprintf, so the effect should not be visible.

This problem could however change, for instance with compiler optimization: what if the compiler decides to use a register for the required variable? This is the kind of errors which sometimes result in security issues.

绝不服输 2024-08-30 01:48:45

如果您的系统具有 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;
}

If you are on a system that has asprintf(3), you can easily wrap it:

#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;
}
允世 2024-08-30 01:48:45

基于 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 的需要。

Based on the answer provided by 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();
}

This avoids the need to cast away const from the result of .c_str() which was in the original answer.

离鸿 2024-08-30 01:48:45
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);
}
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);
}
一张白纸 2024-08-30 01:48:45

string 没有您需要的内容,但 std::stringstream 有。使用字符串流创建字符串,然后提取字符串。 这里是您可以执行的操作的完整列表。例如:

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

打印双精度型或浮点型时,将为您提供 10 位小数的精度。

string doesn't have what you need, but std::stringstream does. Use a stringstream to create the string and then extract the string. Here is a comprehensive list on the things you can do. For example:

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

will give you 10 decimal places of precision when printing a double or float.

关于从前 2024-08-30 01:48:45

我通常使用这个:

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

I usually use this:

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

Disadvantage: not all systems support vasprint

空袭的梦i 2024-08-30 01:48:45

你可以试试这个:

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

You could try this:

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 );
苦行僧 2024-08-30 01:48:45

这是我在程序中用来执行此操作的代码...这没什么花哨的,但它确实有效...注意,您必须根据需要调整大小。我的 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;
}

This is the code I use to do this in my program... It's nothing fancy, but it does the trick... Note, you will have to adjust your size as applicable. MAX_BUFFER for me is 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;
}
上课铃就是安魂曲 2024-08-30 01:48:45

这个想法来自 Dacav Pixelpoint的答案。我玩了一下并得到了这个:

#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的答案类似,只是它不使用new delete[],并在创建 std::string 时指定大小,

这里不使用 new 和 。 delete[] 意味着在堆上使用堆栈,因为它不需要调用分配和释放函数,但是如果使用不当,某些缓冲区溢出可能会很危险(也许如果这是一个问题,我强烈建议使用 newdelete[] 请注意,这里唯一的问题是分配。因为 vsnprintf() 已经被调用并带有限制,因此根据第二个缓冲区上分配的大小指定限制也会阻止这些限制。)

Took the idea from Dacav and pixelpoint's answer. I played around a bit and got this:

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

With sane programming practice I believe the code should be enough, however I'm still open to more secure alternatives that are still simple enough and would not require C++11.


And here's another version that makes use of an initial buffer to prevent second call to vsnprintf() when initial buffer is already enough.

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

(It turns out that this version is just similar to Piti Ongmongkolkul's answer, only that it doesn't use new and delete[], and also specifies a size when creating std::string.

The idea here of not using new and delete[] is to imply usage of the stack over the heap since it doesn't need to call allocation and deallocation functions, however if not properly used, it could be dangerous to buffer overflows in some (perhaps old, or perhaps just vulnerable) systems. If this is a concern, I highly suggest using new and delete[] instead. Note that the only concern here is about the allocations as vsnprintf() is already called with limits, so specifying a limit based on the size allocated on the second buffer would also prevent those.)

疯狂的代价 2024-08-30 01:48:45

下面是 @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

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

Below slightly modified version of @iFreilicht answer, updated to C++14 (usage of make_unique function instead of raw declaration) and added support for std::string arguments (based on 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";
}

Output:

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

Feel free to merge this answer with the original one if desired.

薄暮涼年 2024-08-30 01:48:45

更新 1:添加了 fmt::format 测试,

我自己对此处介绍的方法进行了调查,并获得了与此处提到的截然相反的结果。

我在 4 种方法中使用了 4 个函数:

  • 可变参数函数 + vsnprintf + std::unique_ptr
  • 可变参数函数 + vsnprintf + std:: string
  • 可变参数模板函数 + std::ostringstream + std::tuple + utility::for_each
  • fmt:: fmt 库中的 format 函数

对于 googletest 使用的测试后端。

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

for_each 实现取自此处:迭代元组

#include <type_traits>
#include <tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

测试:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR

unsued.hpp

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

unused.cpp

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

结果

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

正如您通过vsnprintf+看到的实现std::string 等于 fmt::format,但比通过 vsnprintf+std::unique_ptr 更快,后者比通过 std::ostringstream 更快。

测试在 Visual Studio 2015 Update 3 中编译,并在 Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB 上运行。

UPDATE 1: added fmt::format tests

I've took my own investigation around methods has introduced here and gain diametrically opposite results versus mentioned here.

I have used 4 functions over 4 methods:

  • variadic function + vsnprintf + std::unique_ptr
  • variadic function + vsnprintf + std::string
  • variadic template function + std::ostringstream + std::tuple + utility::for_each
  • fmt::format function from fmt library

For the test backend the googletest has used.

#include <string>
#include <cstdarg>
#include <cstdlib>
#include <memory>
#include <algorithm>

#include <fmt/format.h>

inline std::string string_format(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);

    // plain buffer is a bit faster here than std::string::reserve
    std::unique_ptr<char[]> formatted;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        formatted.reset(new char[str_len]);

        const int final_n = vsnprintf(&formatted[0], str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else
            break;
    }

    va_end(ap);

    return std::string(formatted.get());
}

inline std::string string_format2(size_t string_reserve, const std::string fmt_str, ...)
{
    size_t str_len = (std::max)(fmt_str.size(), string_reserve);
    std::string str;

    va_list ap;
    va_start(ap, fmt_str);

    while (true) {
        str.resize(str_len);

        const int final_n = vsnprintf(const_cast<char *>(str.data()), str_len, fmt_str.c_str(), ap);

        if (final_n < 0 || final_n >= int(str_len))
            str_len += (std::abs)(final_n - int(str_len) + 1);
        else {
            str.resize(final_n); // do not forget to shrink the size!
            break;
        }
    }

    va_end(ap);

    return str;
}

template <typename... Args>
inline std::string string_format3(size_t string_reserve, Args... args)
{
    std::ostringstream ss;
    if (string_reserve) {
        ss.rdbuf()->str().reserve(string_reserve);
    }
    std::tuple<Args...> t{ args... };
    utility::for_each(t, [&ss](auto & v)
    {
        ss << v;
    });
    return ss.str();
}

The for_each implementation is taken from here: iterate over tuple

#include <type_traits>
#include <tuple>

namespace utility {

    template <std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I == sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> &, const FuncT &)
    {
    }

    template<std::size_t I = 0, typename FuncT, typename... Tp>
    inline typename std::enable_if<I < sizeof...(Tp), void>::type
        for_each(std::tuple<Tp...> & t, const FuncT & f)
    {
        f(std::get<I>(t));
        for_each<I + 1, FuncT, Tp...>(t, f);
    }

}

The tests:

TEST(ExternalFuncs, test_string_format_on_unique_ptr_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_unique_ptr_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(0, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_std_string_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format2(256, "%s+%u\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(0, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_on_variadic_tuple_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = string_format3(256, "test test test", "+", 12345, "\n");
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_0)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_string_format_on_string_stream_inline_256)
{
    for (size_t i = 0; i < 1000000; i++) {
        std::ostringstream ss;
        ss.rdbuf()->str().reserve(256);
        ss << "test test test" << "+" << 12345 << "\n";
        const std::string v = ss.str();
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_positional)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{0:s}+{1:d}\n", "test test test", 12345);
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

TEST(ExternalFuncs, test_fmt_format_named)
{
    for (size_t i = 0; i < 1000000; i++) {
        const std::string v = fmt::format("{first:s}+{second:d}\n", fmt::arg("first", "test test test"), fmt::arg("second", 12345));
        UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(v);
    }
}

The UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR.

unsued.hpp:

#define UTILITY_SUPPRESS_OPTIMIZATION_ON_VAR(var)   ::utility::unused_param(&var)

namespace utility {

    extern const volatile void * volatile g_unused_param_storage_ptr;

    extern void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p);

}

unused.cpp:

namespace utility {

    const volatile void * volatile g_unused_param_storage_ptr = nullptr;

    void
#ifdef __GNUC__
    __attribute__((optimize("O0")))
#endif
        unused_param(const volatile void * p)
    {
        g_unused_param_storage_ptr = p;
    }

}

RESULTS:

[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_0
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_0 (556 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_unique_ptr_256
[       OK ] ExternalFuncs.test_string_format_on_unique_ptr_256 (331 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_0
[       OK ] ExternalFuncs.test_string_format_on_std_string_0 (457 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_std_string_256
[       OK ] ExternalFuncs.test_string_format_on_std_string_256 (279 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_0 (1214 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_on_variadic_tuple_256 (1325 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_0
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_0 (1208 ms)
[ RUN      ] ExternalFuncs.test_string_format_on_string_stream_inline_256
[       OK ] ExternalFuncs.test_string_format_on_string_stream_inline_256 (1302 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_positional
[       OK ] ExternalFuncs.test_fmt_format_positional (288 ms)
[ RUN      ] ExternalFuncs.test_fmt_format_named
[       OK ] ExternalFuncs.test_fmt_format_named (392 ms)

As you can see implementation through the vsnprintf+std::string is equal to fmt::format, but faster than through the vsnprintf+std::unique_ptr, which is faster than through the std::ostringstream.

The tests compiled in Visual Studio 2015 Update 3 and run at Windows 7 x64 / Intel Core i7-4820K CPU @ 3.70GHz / 16GB.

海之角 2024-08-30 01:48:45

到目前为止,这里的所有答案似乎都存在一个或多个以下问题:(1)它可能无法在 VC++ 上工作(2)它需要额外的依赖项,例如 boost 或 fmt(3)它的自定义实现过于复杂,并且可能没有经过很好的测试。

下面的代码解决了上述所有问题。

#include <string>
#include <cstdarg>
#include <memory>

std::string stringf(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    #ifndef _MSC_VER
        
        //GCC generates warning for valid use of snprintf to get
        //size of result string. We suppress warning with below macro.
        #ifdef __GNUC__
        #pragma GCC diagnostic push
        #pragma GCC diagnostic ignored "-Wformat-nonliteral"
        #endif
        
        size_t size = std::snprintf(nullptr, 0, format, args) + 1; // Extra space for '\0'

        #ifdef __GNUC__
        # pragma GCC diagnostic pop
        #endif
        
        std::unique_ptr<char[]> buf(new char[ size ] ); 
        std::vsnprintf(buf.get(), size, format, args);
        return std::string(buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
    #else
        int size = _vscprintf(format, args);
        std::string result(size, 0);
        vsnprintf_s((char*)result.data(), size + 1, _TRUNCATE, format, args);
        return result;
    #endif
    va_end(args);
}    

int main() {
    float f = 3.f;
    int i = 5;
    std::string s = "hello!";
    auto rs = stringf("i=%d, f=%f, s=%s", i, f, s.c_str());
    printf("%s", rs.c_str());
    return 0;
}

注意:

  1. 单独的 VC++ 代码分支是必要的,因为 VC++ 已决定弃用 snprintf ,这将为上面其他高度投票的答案生成编译器警告。由于我总是在“警告作为错误”模式下运行,所以它不适合我。
  2. 该函数接受 char * 而不是 std::string。这是因为大多数时候这个函数会使用文字字符串调用,该字符串实际上是 char *,而不是 std::string。如果您确实有 std::string 作为格式参数,则只需调用 .c_str() 即可。
  3. 函数的名称是 stringf 而不是 string_format 之类的名称,以与 printf、scanf 等保持一致。
  4. 它没有解决安全问题(即错误的参数可能会导致 seg 错误而不是异常)。如果您需要这个,那么您最好使用 boost 或 fmt 库。我在这里更喜欢 fmt,因为它只是一个可以放入项目中的头文件和源文件,同时比 boost 具有更少奇怪的格式语法。然而,两者都不兼容 printf 格式字符串,因此下面的内容在这种情况下仍然有用。
  5. stringf代码通过GCC严格模式编译。这需要额外的#pragma 宏来抑制 GCC 警告中的误报。

以上代码已在

All the answers so far here seems to have one or more of these problems: (1) it may not work on VC++ (2) it requires additional dependencies like boost or fmt (3) its too complicated custom implementation and probably not tested well.

Below code addresses all of above issues.

#include <string>
#include <cstdarg>
#include <memory>

std::string stringf(const char* format, ...)
{
    va_list args;
    va_start(args, format);
    #ifndef _MSC_VER
        
        //GCC generates warning for valid use of snprintf to get
        //size of result string. We suppress warning with below macro.
        #ifdef __GNUC__
        #pragma GCC diagnostic push
        #pragma GCC diagnostic ignored "-Wformat-nonliteral"
        #endif
        
        size_t size = std::snprintf(nullptr, 0, format, args) + 1; // Extra space for '\0'

        #ifdef __GNUC__
        # pragma GCC diagnostic pop
        #endif
        
        std::unique_ptr<char[]> buf(new char[ size ] ); 
        std::vsnprintf(buf.get(), size, format, args);
        return std::string(buf.get(), buf.get() + size - 1 ); // We don't want the '\0' inside
    #else
        int size = _vscprintf(format, args);
        std::string result(size, 0);
        vsnprintf_s((char*)result.data(), size + 1, _TRUNCATE, format, args);
        return result;
    #endif
    va_end(args);
}    

int main() {
    float f = 3.f;
    int i = 5;
    std::string s = "hello!";
    auto rs = stringf("i=%d, f=%f, s=%s", i, f, s.c_str());
    printf("%s", rs.c_str());
    return 0;
}

Notes:

  1. Separate VC++ code branch is necessary because VC++ has decided to deprecate snprintf which will generate compiler warnings for other highly voted answers above. As I always run in "warnings as errors" mode, its no go for me.
  2. The function accepts char * instead of std::string. This because most of the time this function would be called with literal string which is indeed char *, not std::string. In case you do have std::string as format parameter, then just call .c_str().
  3. Name of the function is stringf instead of things like string_format to keepup with printf, scanf etc.
  4. It doesn't address safety issue (i.e. bad parameters can potentially cause seg fault instead of exception). If you need this then you are better off with boost or fmt libraries. My preference here would be fmt because it is just one header and source file to drop in the project while having less weird formatting syntax than boost. However both are non-compatible with printf format strings so below is still useful in that case.
  5. The stringf code passes through GCC strict mode compilation. This requires extra #pragma macros to suppress false positives in GCC warnings.

Above code was tested on,

流云如水 2024-08-30 01:48:45

(这在 VC++ VS2019、VS2022 上运行良好)

C++17 解决方案(这适用于 std::string 和 std::wstring):

分配缓冲区效率不高,格式化成它&amp;将其复制到另一个字符串后。可以按照格式化字符串的大小创建 std::string &直接格式化到该字符串缓冲区:

#include <string>
#include <stdexcept>
#include <cwchar>
#include <cstdio>
#include <type_traits>

template<typename T, typename ... Args>
std::basic_string<T> string_format(T const* const format, Args ... args)
{
    int size_signed{ 0 };

    // 1) Determine size with error handling:    
    if constexpr (std::is_same_v<T, char>) { // C++17
        size_signed = std::snprintf(nullptr, 0, format, args ...);
    }
    else {
        size_signed = std::swprintf(nullptr, 0, format, args ...);
    }  
    if (size_signed <= 0) {
        throw std::runtime_error("error during formatting.");
    }
    const auto size = static_cast<size_t>(size_signed);

    // 2) Prepare formatted string:
    std::basic_string<T> formatted(size, T{});
    if constexpr (std::is_same_v<T, char>) { // C++17
        std::snprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
    }
    else {
        std::swprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
    }

    return formatted; // Named Return Value Optimization (NRVO), avoids an unnecessary copy. 
}

此外:通常,格式参数是 char[] / wchar_t[] &创建 std::string 对象效率不高。传递 char* 或 wchar_t* &如果您已经有一个 std::string 对象,您仍然可以将其用作 your_string.c_str()。例子:

int main()
{
    int i{ 0 };

    // The format parameter is a char[] / wchar_t[]:

    const std::string title1 = string_format("story[%d].", ++i); // => "story[1]"

    const std::wstring title2 = string_format(L"story[%d].", ++i); // => L"story[2]"

    // If you already have a std::string object:

    const std::string format1{ "story[%d]." };
    const std::string title3 = string_format(format1.c_str(), ++i); // => "story[3]"

    const std::wstring format2{ L"story[%d]." };
    const std::wstring title4 = string_format(format2.c_str(), ++i); // => L"story[4]"  
}

(This runs fine on VC++ VS2019, VS2022)

C++17 solution (this will work for both std::string & for std::wstring):

It is not efficient to allocate a buffer, format into it & after copy it into the another string. It is possible to create std::string in the size of the formatted string & format directly into that string buffer:

#include <string>
#include <stdexcept>
#include <cwchar>
#include <cstdio>
#include <type_traits>

template<typename T, typename ... Args>
std::basic_string<T> string_format(T const* const format, Args ... args)
{
    int size_signed{ 0 };

    // 1) Determine size with error handling:    
    if constexpr (std::is_same_v<T, char>) { // C++17
        size_signed = std::snprintf(nullptr, 0, format, args ...);
    }
    else {
        size_signed = std::swprintf(nullptr, 0, format, args ...);
    }  
    if (size_signed <= 0) {
        throw std::runtime_error("error during formatting.");
    }
    const auto size = static_cast<size_t>(size_signed);

    // 2) Prepare formatted string:
    std::basic_string<T> formatted(size, T{});
    if constexpr (std::is_same_v<T, char>) { // C++17
        std::snprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
    }
    else {
        std::swprintf(formatted.data(), size + 1, format, args ...); // +1 for the '\0' (it will not be part of formatted).
    }

    return formatted; // Named Return Value Optimization (NRVO), avoids an unnecessary copy. 
}

In addition: Often, the format parameter is a char[] / wchar_t[] & it is not efficient to create a std::string object. Pass char* or wchar_t* & if you already have a std::string object, you still can use it as your_string.c_str(). Example:

int main()
{
    int i{ 0 };

    // The format parameter is a char[] / wchar_t[]:

    const std::string title1 = string_format("story[%d].", ++i); // => "story[1]"

    const std::wstring title2 = string_format(L"story[%d].", ++i); // => L"story[2]"

    // If you already have a std::string object:

    const std::string format1{ "story[%d]." };
    const std::string title3 = string_format(format1.c_str(), ++i); // => "story[3]"

    const std::wstring format2{ L"story[%d]." };
    const std::wstring title4 = string_format(format2.c_str(), ++i); // => L"story[4]"  
}
她说她爱他 2024-08-30 01:48:45

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

  • Doc : http://pocoproject.org/docs/Poco.html#7308
  • 来源:< a href="https://github.com/pocoproject/poco/blob/develop/Foundation/src/Format.cpp" rel="nofollow">https://github.com/pocoproject/poco/blob/develop/基础/src/Format.cpp

Poco Foundation library has a very convenient format function, which supports std::string in both the format string and the values:

a√萤火虫的光℡ 2024-08-30 01:48:45

您可以使用 iomanip 头文件在 cout 中格式化 C++ 输出。
确保在使用任何辅助函数之前包含 iomanip 头文件,例如
set precision、setfill 等。

这是我过去用来打印向量中的平均等待时间的代码片段,这是我“累积”的。

#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

You can format C++ output in cout using iomanip header file.
Make sure that you include iomanip header file before you use any of the helper functions like
setprecision, setfill etc.

Here is a code snippet I have used in the past to print the average waiting time in the vector, which I have "accumulated".

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

Here is a brief description of how we can format C++ streams.
http://www.cprogramming.com/tutorial/iomanip.html

梦里南柯 2024-08-30 01:48:45

如果缓冲区不够大,无法打印字符串,则可能会出现问题。在其中打印格式化消息之前,您必须确定格式化字符串的长度。
我为此制作了自己的助手(在 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();

There can be problems, if the buffer is not large enough to print the string. You must determine the length of the formatted string before printing a formatted message in there.
I make own helper to this (tested on Windows and Linux GCC), and you can try use it.

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();
回忆凄美了谁 2024-08-30 01:48:45

这个可以尝试一下。简单的。但实际上并没有使用字符串类的细微差别。

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

this can be tried out. simple. really does not use nuances of the string class though.

#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;
}
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文