在C ++中将sprintf与std :: string一起使用

Using sprintf with std::string in C++

我通过以下方式在C ++ 11中使用sprintf函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
std::string toString()
{
    std::string output;
    uint32_t strSize=512;
    do
    {
        output.reserve(strSize);
        int ret = sprintf(output.c_str(),"Type=%u Version=%u ContentType=%u contentFormatVersion=%u magic=%04x Seg=%u",
            INDEX_RECORD_TYPE_SERIALIZATION_HEADER,
            FORAMT_VERSION,
            contentType,
            contentFormatVersion,
            magic,
            segmentId);

        strSize *= 2;
    } while (ret < 0);

    return output;
}

有没有比每次检查预留空间是否足够的方法更好的方法了? 为了将来添加更多东西的可能性。


即使事先检查了字符串的容量,您的构造(写入从c_str()接收的缓冲区)也是不确定的行为。 (由于某种原因,返回值是指向const char的指针,并且函数本身标记为const。)

不要混合使用C和C ++,尤其是不要用于编写内部对象表示形式。 (这打破了最基本的OOP。)使用C ++可以确保类型安全,并且不会遇到转换说明符/参数不匹配的情况(如果没有其他原因)。

1
2
3
4
5
6
std::ostringstream s;
s <<"Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
  <<" Version=" << FORMAT_VERSION
  // ...and so on...
  ;
std::string output = s.str();

选择:

1
2
3
4
std::string output ="Type=" + std::to_string( INDEX_RECORD_TYPE_SERIALIZATION_HEADER )
                   +" Version=" + std::to_string( FORMAT_VERSION )
                   // ...and so on...
                   ;


其他答案中显示的C ++模式更好,但是出于完整性考虑,这是sprintf的正确方法:

1
2
3
4
auto format ="your %x format %d string %s";
auto size = std::snprintf(nullptr, 0, format /* Arguments go here*/);
std::string output(size + 1, '\0');
std::sprintf(&output[0], format, /* Arguments go here*/);

注意

  • 您必须resize您的字符串。 reserve不会更改缓冲区的大小。在我的示例中,我直接构造了正确大小的字符串。
  • c_str()返回const char*。您可能无法将其传递给sprintf
  • 在C ++ 11之前,不能保证std::string缓冲区是连续的,这取决于该保证。如果您需要支持使用绳索实现std::string的特殊的C ++ 11兼容平台,那么最好先冲刺到std::vector,然后再将向量复制到字符串中。
  • 仅当未在尺寸计算和格式设置之间修改参数时,此方法才有效;对于多线程代码,请使用变量的本地副本或线程同步原语。


我们可以从这里https://stackoverflow.com/a/36909699/2667451和这里https://stackoverflow.com/a/7257307混合代码,结果将是这样的:

1
2
3
4
5
6
7
8
template <typename ...Args>
std::string stringWithFormat(const std::string& format, Args && ...args)
{
    auto size = std::snprintf(nullptr, 0, format.c_str(), std::forward<Args>(args)...);
    std::string output(size + 1, '\0');
    std::sprintf(&output[0], format.c_str(), std::forward<Args>(args)...);
    return output;
}

更好的方法是使用{fmt}库。例如:

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

与iostreams和printf相比,它还提供了更好的接口。例如:

1
std::string message = fmt::format("The answer is {}", 42);`

看到:
https://github.com/fmtlib/fmt
http://fmtlib.net/latest/api.html#printf-formatting-functions


您的代码是错误的。 reserve为该字符串分配内存,但不更改其大小。写入c_str返回的缓冲区也不会更改其大小。因此,字符串仍然认为其大小为0,并且您刚刚将某些内容写入了字符串缓冲区的未使用空间。 (可能是。从技术上讲,该代码具有未定义的行为,因为写入c_str是未定义的,所以任何事情都可能发生)。

您真正想要做的是忘记sprintf和类似的C样式函数,并使用C ++字符串格式化字符串流的方式:

1
2
3
4
5
std::ostringstream ss;
ss <<"Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
   <<" Version=" << FORAMT_VERSION
   << /* ... the rest ... */;
return ss.str();

就在这里!

在C语言中,更好的方法是将文件与空设备关联,并向其生成所需输出的伪printf,以了解如果实际打印将占用多少空间。然后为它分配适当的缓冲区和sprintf相同的数据。

在C ++中,您也可以将输出流与空设备相关联,并测试用std :: ostream :: tellp打印的字符数。但是,使用ostringstream是更好的解决方案–请参阅DevSolar或Angew的答案。