在C ++中使用带有std :: string的sprintf

时间:2016-04-28 08:16:16

标签: c++ string c++11

我在C ++ 11中使用sprintf函数,方法如下:

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

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

6 个答案:

答案 0 :(得分:14)

您的构造 - 写入到从c_str()收到的缓冲区 - 是未定义的行为,即使您事先检查了字符串的容量。 (返回值是指向 const char的指针,函数本身标记为 const ,原因。)

不要将C和C ++,特别是混合在一起,而不是写入内部对象表示。 (这打破了非常基本的OOP。)使用C ++,类型安全,不会遇到转换说明符/参数不匹配,如果没有别的。

std::ostringstream s;
s << "Type=" << INDEX_RECORD_TYPE_SERIALIZATION_HEADER
  << " Version=" << FORMAT_VERSION
  // ...and so on...
  ;
std::string output = s.str();

替代:

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

答案 1 :(得分:8)

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

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
  • std::string缓冲区在C ++ 11之前并不保证是连续的,这依赖于该保证。如果您需要支持使用std::string的绳索实现的异乎寻常的C ++ 11前符合平台,那么您最好先将冲刺转移到std::vector<char>然后再复制字符串的向量。
  • 这仅适用于在大小计算和格式之间未修改参数的情况;使用变量的本地副本或线程同步原语来实现多线程代码。

答案 2 :(得分:1)

你的代码错了。 reserve为字符串分配内存,但不会更改其大小。写入c_str返回的缓冲区也不会改变其大小。所以字符串仍然认为它的大小是0,你只是在字符串缓冲区的未使用空间中写了一些东西。 (可能。从技术上讲,代码具有未定义的行为,因为写入c_str是未定义的,因此任何事情都可能发生。)

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

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

答案 3 :(得分:1)

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

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

它还提供了比iostreams和printf更好的界面。例如:

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

请参阅:
https://github.com/fmtlib/fmt
http://fmtlib.net/latest/api.html#printf-formatting-functions

答案 4 :(得分:1)

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

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

答案 5 :(得分:0)

是的,有!

在C中,更好的方法是将文件与 null 设备相关联,并为其创建所需输出的虚拟request.Headers.Authorization.Parameter = "VXNlcjpQYXNzd29yZA==",以了解如果需要多少空间,实际打印。然后为其分配适当的缓冲区和printf相同的数据。

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