我必须用sprintf格式化std::string,并将其发送到文件流。我该怎么做呢?
当前回答
现代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的解决方案可能会很有用。 不要使用任何具有固定缓冲长度的解决方案,那只是在自找麻烦。
其他回答
_return.desc = (boost::format("fail to detect. cv_result = %d") % st_result).str();
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
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的解决方案可能会很有用。 不要使用任何具有固定缓冲长度的解决方案,那只是在自找麻烦。
你不能直接这样做,因为你没有对底层缓冲区的写访问权(直到c++ 11;见Dietrich Epp的评论)。你必须先在c-string中执行,然后将其复制到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();
推荐文章
- Python __str__与__unicode__
- 为什么STL如此严重地基于模板而不是继承?
- 查找当前可执行文件的路径,不包含/proc/self/exe
- bash:错误的替换
- 未定义对静态constexpr char的引用[]
- 在c#中检查字符串是否只包含数字的最快方法
- 在c++中,restrict关键字是什么意思?
- c++中类似于java的instanceof
- 我怎么能强迫一个长字符串没有任何空白被包装?
- include_directories和target_include_directories在CMake中的区别是什么?
- 转换JSON字符串到JSON对象c#
- std::make_pair与std::pair的构造函数的目的是什么?
- 将查询字符串解析为数组
- 如何追加一个字符到std::字符串?
- 删除字符串中的字符列表