我必须用sprintf格式化std::string,并将其发送到文件流。我该怎么做呢?
当前回答
我喜欢的一个解决方案是,在使缓冲区足够大之后,用sprintf直接在std::string缓冲区中执行此操作:
#include <string>
#include <iostream>
using namespace std;
string l_output;
l_output.resize(100);
for (int i = 0; i < 1000; ++i)
{
memset (&l_output[0], 0, 100);
sprintf (&l_output[0], "\r%i\0", i);
cout << l_output;
cout.flush();
}
因此,创建std::string,调整它的大小,直接访问它的缓冲区…
其他回答
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"
你可以试试这个:
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 );
现代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++ 20 std::格式
它来了!该特性描述在: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);
}
g++-9 -std=c++2a的GCC 9.1.0仍然不支持它。
现有的fmt库在获得官方支持之前实现了它:https://github.com/fmtlib/fmt,如前所述:std::string格式化,如sprintf:
sudo apt install libfmt-dev
修改源代码以替换:
用<fmt/core.h> .h格式化> Std::format转换为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;
}
编译并运行:
g++ -std=c++11 -o main.out main.cpp -lfmt
./main.out
输出:
The answer is 42.
API将添加一个新的std::format头文件:
建议的格式化API定义在新的header <format>中,应该不会对现有代码产生影响。
十六进制格式{:x}
c++十六进制成本值?
前导零{:03}
用c++输出操作符打印前导零?
左对齐{:<},右对齐{:>},中对齐{:^}
打印cout时c++对齐<<
浮点精度{:.2}
我如何打印一个双值与全精度使用cout? 在c++中设置默认浮点打印精度
在正数{:+}上显示符号
如何打印正数与前缀+在c++
将布尔值显示为true和false: {:}
在c++中将bool转换为文本
这个问题已经解决了。但是,我认为这是c++中格式化字符串的另一种方式
class string_format {
private:
std::string _result;
public:
string_format( ) { }
~string_format( ) { std::string( ).swap( _result ); }
const std::string& get_data( ) const { return _result; }
template<typename T, typename... Targs>
void format( const char* fmt, T value, Targs... Fargs ) {
for ( ; *fmt != '\0'; fmt++ ) {
if ( *fmt == '%' ) {
_result += value;
this->format( fmt + 1, Fargs..., 0 ); // recursive call
return;
}
_result += *fmt;
}
}
friend std::ostream& operator<<( std::ostream& ostream, const string_format& inst );
};
inline std::string& operator+=( std::string& str, int val ) {
str.append( std::to_string( val ) );
return str;
}
inline std::string& operator+=( std::string& str, double val ) {
str.append( std::to_string( val ) );
return str;
}
inline std::string& operator+=( std::string& str, bool val ) {
str.append( val ? "true" : "false" );
return str;
}
inline std::ostream& operator<<( std::ostream& ostream, const string_format& inst ) {
ostream << inst.get_data( );
return ostream;
}
并测试这个类:
string_format fmt;
fmt.format( "Hello % and is working ? Ans: %", "world", true );
std::cout << fmt;
你可以在这里查一下
推荐文章
- decltype(auto)的一些用途是什么?
- Shared_ptr转换为数组:应该使用它吗?
- Printf与std::字符串?
- 禁用复制构造函数
- 不区分大小写的“in”
- 我如何得到一个字符串的前n个字符而不检查大小或出界?
- 只接受特定类型的c++模板
- 如何在PHP中截断字符串最接近于一定数量的字符?
- c#和Java中的泛型有什么不同?和模板在c++ ?
- Ruby数组到字符串的转换
- c++ 11中的递归lambda函数
- 在c++中指针使用NULL或0(零)吗?
- 为什么在Java和。net中不能修改字符串?
- 在c++中,如何将int值附加到字符串中?
- 如何创建一个日期对象从字符串在javascript