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


当前回答

对于Visual C:

std::wstring stringFormat(const wchar_t* fmt, ...)
{
    if (!fmt) {
        return L"";
    }

    std::vector<wchar_t> buff;
    size_t size = wcslen(fmt) * 2;
    buff.resize(size);
    va_list ap;
    va_start(ap, fmt);
    while (true) {
        int ret = _vsnwprintf_s(buff.data(), size, _TRUNCATE, fmt, ap);
        if (ret != -1)
            break;
        else {
            size *= 2;
            buff.resize(size);
        }
    }
    va_end(ap);
    return std::wstring(buff.data());
}

其他回答

如果你在一个有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;
}

Boost::format()提供了你想要的功能:

Boost格式库简介如下:

format对象由format-string构造,然后通过反复调用运算符%来给出参数。 然后,每个参数都被转换为字符串,这些字符串又根据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"

现代c++使得这非常简单。

C + + 20

c++ 20引入了std::format,它允许你这样做。它使用的替换字段类似于python中的替换字段:

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

代码来自cppreference.com, CC BY-SA和GFDL

查看编译器支持页面,看看它是否在您的标准库实现中可用。截至2021年11月28日,Visual Studio 2019 16.10(于2021年05月25日发布)和Clang 14(可在此处跟踪)提供了部分支持。在所有其他情况下,您可以求助于下面的c++ 11解决方案,或使用{fmt}库,它具有与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许可的。

逐行解释:

目的:写入一个char*使用std::snprintf,然后将其转换为std::string。

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

返回值 […如果结果字符串由于buf_size限制而被截断, 函数返回字符总数(不包括 终止空字节),如果限制为 没有实施。

这意味着所需的大小是字符数加1,因此空结束符将位于所有其他字符之后,并且可以再次被字符串构造函数截断。@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不能为负,所以我们使用静态强制转换将size_t从有符号整型转换为无符号size_t。这样,即使是最迂腐的编译器也不会抱怨下一行可能发生的转换。

size_t size = static_cast<size_t>( size_s );

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

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

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

在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的解决方案可能会很有用。 不要使用任何具有固定缓冲长度的解决方案,那只是在自找麻烦。

更新了一些答案,不同的是-函数将正确接受std::string为%s

namespace format_helper
{

    template <class Src>
    inline Src cast(Src v)
    {
        return v;
    }

    inline const char *cast(const std::string& v)
    {
        return v.c_str();
    }
};

template <typename... Ts>
inline std::string stringfmt (const std::string &fmt, Ts&&... vs)
{
    using namespace format_helper;
    char b;
    size_t required = std::snprintf(&b, 0, fmt.c_str(), cast(std::forward<Ts>(vs))...);//not counting the terminating null character.
    std::string result;
    //because we use string as container, it adds extra 0 automatically
    result.resize(required , 0);
    //and snprintf will use n-1 bytes supplied
    std::snprintf(const_cast<char*>(result.data()), required + 1, fmt.c_str(), cast(std::forward<Ts>(vs))...);

    return result;
}

生活:http://cpp.sh/5ajsv

这是我用来在我的程序中这样做的代码…这没什么特别的,但很管用……注意,您必须根据需要调整您的尺寸。我的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;
}