一、c++中的I/O流
在传统的c语言编程中,程序员们可能对printf情有独衷,至少在调试时可以打个Log,以方便测试是否和实际的设计逻辑保持一致。但是这个printf其实是有不少的问题和安全隐患的,所以在c++中提供了iostream这个类,但是说实话,这个类好像还不如printf用得更好。很多人对这个类表示了极大的不友好,但是c++一直对流处理没怎么做更强大的升级,反正搞到c++这个地界儿的,基本都不是什么善茬。
但是c++11大幅升级c++标准以来,在c++20中,终于对此有了一些具体的改变,增加了formatting这个库,下面就介绍一下这个库的应用。
二、formatting
c++20中的formatting这个格式化库,其实是对printf族函数的一个替代方案,同时,也对STL中的IO流有一个补充。其实,有过其它语言开发的程序员可能一眼就看出来,它和c#等语言的Format没有什么不同,一直是编程语言发展的方向,即应用简单化、明确化。给程序员以一个高效、安全的应用库。看一下它的基本定义:
//format
template< class... Args >
std::string format( /*format_string<Args...>*/ fmt, const Args&... args );
template< class... Args >
std::wstring format( /*wformat_string<Args...>*/ fmt, const Args&... args );
template< class... Args >
std::string format( const std::locale& loc,
/*format_string<Args...>*/ fmt, const Args&... args );
template< class... Args >
std::wstring format( const std::locale& loc,
/*wformat_string<Args...>*/ fmt, const Args&... args );
//vformat
std::string vformat(std::string_view fmt, std::format_args args);
std::wstring vformat(std::wstring_view fmt, std::wformat_args args);
std::string vformat(const std::locale& loc, std::string_view fmt, std::format_args args);
std::wstring vformat(const std::locale& loc, std::wstring_view fmt, std::wformat_args args);
formatting提供了下列函数:
formatting还提供了扩展性的支持:
下面看几个简单的例子,这里面有几句话特别有意思,先看一下文档上怎么说的:
fmt-未指定类型的形参,其初始化仅若实参可转换成 std::string_view (对于 (1,3) )或 std::wstring_view (对于 (2,4) ),而转换结果是常量表达式和 Args 的合法格式字符串才合法。格式字符串由以下内容组成:
通常字符(除了 { 与 } ),它们被不加修改地复制到输出,
转义序列 {{ 与 }} ,它们在输出中被分别替换成 { 与 } ,以及
替换域。
每个替换域拥有下列格式:
引入的 { 字符;
(可选) arg-id ,一个非负数;
(可选) 冒号( : )后随格式说明;
终止的 } 字符。
arg-id 指定用于格式化其值的 args 中的参数的下标;若省略 arg-id ,则按顺序使用参数。格式字符串中的 arg-id 必须全部存在或全部被省略。混合手动和自动指定下标是错误。
格式说明由对应参数特化的 std::formatter 定义。
对于基本类型和标准字符串类型,格式说明为标准格式说明;
对于标准日期和时间类型,格式说明为 chrono 格式说明;
对于用户定义类型,格式说明由用户定义的 std::formatter 特化决定。”
再深入看格式定义中又有一句话:
“标准格式说明:对于基本类型和字符串类型,格式说明基于 Python 中的格式说明”。
三、例程
下面看几个例子:
#include <format>
#include <iostream>
#include <string>
#include <string_view>
template <typename... Args>
std::string dyna_print(std::string_view rt_fmt_str, Args&&... args) {
return std::vformat(rt_fmt_str, std::make_format_args(args...));
}
int main() {
std::cout << std::format("Hello {}!\n", "world");
std::string fmt;
for (int i{}; i != 3; ++i) {
fmt += "{} "; // 构造格式化字符串
std::cout << fmt << " : ";
std::cout << dyna_print(fmt, "alpha", 'Z', 3.14, "unused");
std::cout << '\n';
}
}
运行结果:
Hello world!
{} : alpha
{} {} : alpha Z
{} {} {} : alpha Z 3.14
再看一个formatted_size的例子:
#include <format>
#include <iostream>
#include <vector>
#include <string_view>
int mainty()
{
using namespace std::literals::string_view_literals;
constexpr auto fmt_str{ "Hubble's H{0} {1} {2:*^4} miles/sec/mpc."sv };
constexpr auto sub_zero{ "₀"sv }; // { "\u2080"sv } => { 0xe2, 0x82, 0x80 };
constexpr auto aprox_equ{ "≅"sv }; // { "\u2245"sv } => { 0xe2, 0x89, 0x85 };
constexpr int Ho{ 42 }; // H₀
const auto min_buffer_size = std::formatted_size(fmt_str, sub_zero, aprox_equ, Ho);
std::cout << "Min buffer size = " << min_buffer_size << '\n';
// 以 std::vector 为动态缓冲区。注:缓冲区不包含尾随 '\0' 。
std::vector<char> buffer(min_buffer_size);
std::format_to_n(buffer.data(), buffer.size(), fmt_str, sub_zero, aprox_equ, Ho);
std::cout << "Buffer: \"" << std::string_view{ buffer.data(), min_buffer_size } << "\"\n";
// 或者我们能通过添加尾随 '\0' 直接打印缓冲区。
buffer.push_back('\0');
std::cout << "Buffer: \"" << buffer.data() << "\"\n";
return 0;
}
int main()
{
mainty();
}
运行结果如下:
Min buffer size = 33
Buffer: "Hubble's H? ? *42* miles/sec/mpc."
Buffer: "Hubble's H? ? *42* miles/sec/mpc."
这里需要说明一下,std::literals::string_view_literals::operator""sv()是一个重载函数,返回一个std::string_view类型,具体的可参看:
https://en.cppreference.com/w/cpp/string/basic_string_view/operator%22%22sv
format_to_n的例程:
#include <format>
#include <string_view>
#include <iostream>
int main()
{
char buffer[64];
const auto result =
std::format_to_n(buffer, std::size(buffer),
"Hubble's H{0} {1} {2} miles/sec/mpc.",
"\u2080", "\u2245", 42);
std::cout << "Buffer: \"" << std::string_view{buffer, result.size} << "\"\n"
<< "Buffer size = " << std::size(buffer) << '\n'
<< "Untruncated output size = " << result.size << '\n';
}
运行结果:
Buffer: "Hubble's H₀ ≅ 42 miles/sec/mpc."
Buffer size = 64
Untruncated output size = 35
在c++20的文档目前还不太全,包括在vs2022上开发时,指定了c++20标准仍然无法完全编译,还是得指定最新的标准才行,这点还是需要注意。在Linux上编译上最新的gcc,但其它的一些工程和调试啥的都得更新,这样就无法展开工作了,先在Win平台上应用吧。
四、总结
c++标准的进步还是非常明显的,可能对于一些大牛觉得啥也不是,但对于普通的广大程序员还是大有好处的。一个技术的出现,总会有人说好有人说坏,根据自己的实际情况,来决定是不是需要它,应用它,这才是最好的。但是前提是,得有这个判断的能力和水平。不断的提高,不断的拥抱变化,才是根本。