c++23中的新功能之十二格式化输出

222 篇文章 90 订阅

一、介绍

说起格式化输出那真是一言难尽,从汇编到C,到c++,到c#、Java、Python、Go…哪个不是各出手段,尽显风骚。搞c++开发的,好多人其实用的不外乎是printf。可是这个函数有个致命的问题,它只能打印他自己能玩儿的东西,想换个他不知道的,好,做梦吧。
而c++在后来也觉得它用着不爽,毕竟我是带类的C,你不能打印类,那不等于大多数的工作没法完成。没办法,只好自己搞了个流式输入输出,重载一下operator<<,小日子和美美的过下去了。然而,肯定会有然而。
然而就是,STL中有大量的容器,可你不支持,举个简单例子,有一个std::vector,直接<<vec不可以,换句话说不支持Ranges,可这个是c++20的重大特征啊。所以出现了std::format,可由于各种原因,这个东西好用是好用,可功能面太窄了,竟然不支持流时代支持的文件操作,甚至原来流支持的它也不支持(std::complex,而且仍然对Ranges是未来支持的态度)。
这就出现了两个情况:一个是继续完善std::format,一个是推出了std::print(或者是println啥,看最后标准定义)。那么流(std::cout及其它)呢?默认可能会被淘汰吧。

二、c++20的std::format

printf和流的就不用再写了,大家都用得非常熟稔。先从c++20的std::format开始。
先看一个cppreference.com上的例子:

#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 += "{} "; // constructs the formatting string
        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

在std::formatter中默认已经对基本的数据类型进行了处理,但要是处理类似于自定义类的方式有两种方式来解决,一种是继承std::formatter来实现;另外一种就是自己实现parse和format两个函数。
看一下前者的例子:
先看cppreference提供的:

#include <format>
#include <iostream>

// 类型 T 的包装
template<class T>
struct Box
{
    T value;
};

// 能用被包装值的格式说明格式化包装 Box<T>
template<class T, class CharT>
struct std::formatter<Box<T>, CharT> : std::formatter<T, CharT>
{
    // 从基类继承 parse()

    // 通过以被包装值调用基类实现定义 format()
    template<class FormatContext>
    auto format(Box<T> t, FormatContext& fc)
    {
        return std::formatter<T, CharT>::format(t.value, fc);
    }
};

int main()
{
    Box<int> v = {42};
    std::cout << std::format("{:#x}", v);
}

再照着来一个:

template <typename _T1, typename _T2>
struct ABC {
	_T1 v1;
	_T2 v2;
};

template <typename _T1, typename _T2, typename _CharT>
struct std::formatter<ABC<_T1, _T2>, _CharT> : std::formatter<_T1, _CharT>
{	 
	template <typename _FormatContext>
	auto format(const ABC<_T1, _Ty2>& v, _FormatContext& format_context )
	{
		auto it = std::formatter<_T1, _CharT>::format(v.v1, format_context);
		it = '\n';
		it = std::formatter<_T2, _CharT>().format(v.v2, format_context);
		return it;
	}
};


#include <iostream>
void Test()
{
	ABC<int, double> abc{
	.v1 = 1,
		.v2 = 2.1
	};
	std::cout << std::format("abc = {}", abc);
}
int main()
{
	Test();
	return 1;
}

第二种大家有兴趣可以自己搞搞,没觉得有如operator<<简单。
更多请参看:
https://zh.cppreference.com/w/cpp/utility/format/formatter

三、c++23中的std::print

而在新标准中,对关联容器和非关联容器都有不同的处理方式,一般来说关联容器比较简单,直接输了就可以了,但对于非叛逆容器,如Map和Set系列等,可能对Ranges的输了不感冒,那么就需要象上面一样自己来定义一些输出格式。另外对于一些特殊形式,比如char数组到string,一些无法打印的类型这些都需要进行处理。最后,大家都知道,在STL还有一种容器适配器的存在,它们怎么处理?
所以在目前提出的解决方式中提出了规范:需要输入Range,元素类型可格式化并且元素类型不能是Range自己。当然还对视图以及一些引用形式及容器适配器都做了规范。比如默认就是格式化成[item0,item1…],使用方括号。而std::pair,std::tuple则是使用小括号,其它的可以参看最新的标准出来。
除此之外,std::println还提供了一些类似于正则内部的一些参数,可以去除一些内容,比如?可以忽视的调试符,n,去除外包装(就是外面那对括号),诸如种种,以最终标准及编译器厂商为准。
看一个例子:

std::println("{:?}", std::pair{3, 3}); // (3, 3)
std::println("{:n}", std::pair{3, 3}); // 3, 3
std::println("{:m}", std::pair{3, 3}); // 3: 3

同样,这些都是对于STL自带Ranges的容器,如果是自定义的呢?还是需要std::range_formatter来自定义元素进行格式化。My God!

四、总结

反正在这个方面上,可能看上去简单了,但目前还没法体现出简单来。估计大佬们也在挠头。让他们去慢慢折腾吧,等着最后的结果就行。能用就用,不能用就暂时绕着呗。反正c++23到工程上用,肯定还有一大段时间。目前工程上能用到c++17的就已经是很潮的公司了。

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
要实现 PL/0 编译器的功能扩展,使其支持字符串类型和类似 C++ 的输出格式,我们可以按照以下步骤进行操作: 1. 修改词法分析器 我们需要修改 PL/0 编译器的词法分析器,以支持字符串类型的识别和分析。我们可以定义一个的 token 类型,例如 STRING,表示一个字符串类型的值。对于字符串类型的常量,我们可以使用双引号(")将其括起来,例如 "Hello, world!"。 2. 修改语法分析器 我们需要修改 PL/0 编译器的语法分析器,以支持字符串类型的语法规则。我们可以定义一个的非终结符,例如 string,表示一个字符串类型的值。对于字符串类型的常量,我们可以使用 string 规则来表示,例如: ``` string ::= '"' chars '"' chars ::= char | char chars char ::= any character except '"' and '\' ``` 其,chars 表示一个或多个字符,char 表示一个字符,它可以是除双引号和反斜杠之外的任何字符。 3. 修改符号表和代码生成器 我们需要修改符号表和代码生成器,以支持字符串类型的变量和操作。我们可以在符号表添加一个的变量类型,例如 TYPE_STRING,表示一个字符串类型的变量。对于字符串类型的操作,例如字符串的连接和比较,我们需要在代码生成器添加相应的指令来实现。 4. 添加输出语句 最后,我们需要添加一个的输出语句,例如 print,用于输出字符串类型的值。我们可以定义一个的关键字,例如 PRINT,表示一个输出语句。对于输出字符串类型的值,我们可以在输出语句使用格式化字符串的语法,例如: ``` print("Hello, %s!", str); ``` 其,%s 表示一个字符串类型的值,str 表示一个字符串类型的变量。 通过以上步骤,我们可以扩展 PL/0 编译器的功能,使其支持字符串类型和类似 C++ 的输出格式。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值