C++学习笔记----Strings与String View(9)-- 格式化输出

1、字符串格式化输出的发展历程

        C++早期的格式化输出,是没用C语言的格式化输出:printf(),首先是不推荐的,历史车轮滚滚向前,我们不能一直停留在旧石器时期啊。其优缺点也比较明显,缺点是没有类型保护,也不支持自定义类型;优点是易于阅读,因为它将格式与变量分隔的比较清晰,因此也易于转化成其他编程语言,还是举一个例子吧,空口白牙大家印象不深:

	int x = 10, y = 11;
	printf("x has value %d and y has value %d.\n", x, y);

结果为:

x has value 10 and y has value 11.

这些都与C完全一致,随着C++语言的发展,它需要有自己的独特的字符串输出方式,这就诞生了cout方式,这种I/O流的方式解决了C语言的类型保护以及对扩展类型的支持,但其缺点也比较明显,那就是字符串与变量交织在一起,不易迁移转化成其他编程语言。

还是上面的例子,把输出方式修改一下:

	int x = 10, y = 11;
	cout << "x has value " << x << " and y has value " << y << '.' << endl;

输出结果与上面的输出结果完全一致,就不再贴了。

好,C++继续演进,到了C++20,出现了定义在<format>中的std::format,把上面所说的两种进行了合并,取其精华,去其糟粕,结合两者的优点:类型安全,支持扩展,字符串与变量分开,易于迁移转化成其他编程语言,上面的例子就变成了下面的输出方式:

	int x = 10, y = 11;
	cout << format("x has value {} and y has value {}.", x, y) << endl;

输出结果与上面的输出结果也完全一致,就不再贴了。

这个嘛,好像与我们以前写的代码也不完全一样啊,对喽,我的笔记用的是C++23,到了C++23中,发展演进到了出现了std::print()与std::println(),我们以前输出用的都是println(),好了,再把上面代码中的cout与<<修改成println()格式,当然,也不用format了:

	int x = 10, y = 11;
	println("x has value {} and y has value {}.", x, y);

结果自然是一致的,就不多说了吧。

既然已经发展演进到现在了,是不是应该好好夸一夸这种格式化输出的好处了呢,那当然了,当仁不让啊,这种输出方式的优点可多了,且听我娓娓道来:首先是支持UTF-8的编码方式,这就解决了不同编码字符集的问题,省去了许多麻烦事儿;类型安全,支持扩展到用户自定义类型,易于阅读,支持Unicode输出(无国界),支持不同国别语言的本地化,等等等等吧,在这些好处的顶端,就是其效率要比直接使用cout的I/O流要高,当然了,其低层也使用了这些I/O流,毕竟是一步上步发展演化而来的,是站在巨人的肩膀上,前人种树,后人乘凉,怎么能不利用前人好的成果呢,只是要百尺竿头更进一步而已。

2、格式化字符串

        记得在前面的文章中,只有一处提到了格式化字符串输出的格式问题,就是对于字符串转数字时用了一下{:X},文章中也简单解释了一下:X的意义,进行了一点小扩展。现在到了看println()格式化字符串强大力量的时刻了,让我们走起。

        对于format(),print(),println(),其第一个参数就是格式化字符串,字符串中可以包含多个{}对,代表的是要被替换的部分,可以有任意多个,反正我没试过,也没有去查相关资料,到底有多少个,我认为探究这个没有意义;后面的参数就是填充或者说是去替换{}的内容,如果在输出中需要输出{或者},那就用转义字符的方式,用{{或}}达到相关目的。

        好了,我们现在就来看{}的大魔法,其定义为[index] [:specifier],我们知道,这些都是可省略项,index为参数索引,后面系统化的论述时再解释,specifier通过前面的文章,大家也应该知道它是格式化输出相关参数的灵魂了,具体也在后面再详述,我们现在要解决的,是周边的几个小问题,也可能是大家比较关心,或者说是比较容易犯的几个小错误,直接用代码来说明吧,在代码中讲解更直观一些:

	int  n1{ 42 };
	println(n1);         //错误,格式化字符串是必输项,不能省略
	println("{}", n1);  //正确  

	println();//错误,格式化字符串是必输项,不能省略,即使目的只是为了打印一个空行,也不能省略
	println("");        //正确

3、参数索引

        这一小节我们系统化的论述一下格式化字符串中的index参数索引,这个参数可以省略这一点大家都知道了,先解释一下这个参数的意思吧,这个参数是个数字,从0开始,正常的话,也就是省略时,其意义就相当于在一系列的{}中省略了从0开始的系列数字,第一个{}省略了0,第二个省略了1,第三个省略了2,以此类推,等等等等。那如果在{}中加上这个数字的话,就可以灵活输出字符串格式参数后面的输出输出的实际参数的值了,这个灵活是指,可以打乱顺序,可以多次输出同一个参数或者不输出某一个参数等等。还是通过程序示例来说明吧。

import std;
using namespace std;
int main()
{
	int n{ 42 };
	println("Read {} bytes from {}", n, "file1.txt");
	return 0;
}

结果如下:

Read 42 bytes from file1.txt

好,这是一个省略了index的示例,与以前的代码一致,结果也是我们前面说的,第一个{}输出n的值42,第二个{}输出常量或者说文本file1.txt。

        下面我们把index加上,但是要注意,要加都加,不能有的加有的没加,这是错误的,为了节省大家的时间,下面的例子会把刚才所说的几种情况都进行展示,也会在程序中加以说明,请看大屏幕:

import std;
using namespace std;
int main()
{
	int n{ 42 };
	println("Read {} bytes from {}", n, "file1.txt");	//正确,省略index
	println("Read {0} bytes from {1}", n, "file1.txt");	//正确,与省略index时一样
	//println("Read {0} bytes from {}", n, "file1.txt"); //执行时会报错,index要写都写,要不写都不写
	println("从{1}中读取{0}个字节", n, "file1.txt");		//正确,可以任意调整index的位置
	println("Read {0} bytes from {1},Again read {0} bytes.", n, "file1.txt");//正确,可以多次使用同一个index。
	return 0;
}

看结果:

Read 42 bytes from file1.txt
Read 42 bytes from file1.txt
从file1.txt中读取42个字节
Read 42 bytes from file1.txt,Again read 42 bytes.

不难的,是吧。

4、输出到不同的地方

        我们的输出函数中,上来就是格式化字符串,其实前面还有一个参数,就是要输出的地方,省略的话,是指输出到默认的地方,一般是标准控制台,其实也可以输出到其他地方,比如标准错误输出等,我们简单修改一下上面的程序,如下:

import std;
using namespace std;
int main()
{
	int n{ 42 };
	println(cerr,"Read {} bytes from {}", n, "file1.txt");	//正确,省略index
	println(cerr,"Read {0} bytes from {1}", n, "file1.txt");	//正确,与省略index时一样
	//println("Read {0} bytes from {}", n, "file1.txt"); //执行时会报错,index要写都写,要不写都不写
	println("从{1}中读取{0}个字节", n, "file1.txt");		//正确,可以任意调整index的位置
	println("Read {0} bytes from {1},Again read {0} bytes.", n, "file1.txt");//正确,可以多次使用同一个index。
	return 0;
}

结果与上面一样,这是因为我的开发环境的标准输出与标准错误输出是一样的,本示例只是说明,输出目的地也是可以根据实际需要进行指定的。

2、格式化字符串的使用方式

        对于format()、print()、println()函数,其参数格式化字符串如果有错误的话,编译时报错,因为这个参数要求是常量字符串,如果定义一个正常的字符串并进行输出的话,肯定会出错的,示例:

import std;
using namespace std;
int main()
{
	string s{ "Hello World!" };
	println(s);//
	return 0;
}

会报错:error C7595: “std::basic_format_string<char>::basic_format_string”: 对即时函数的调用不是常量表达式。

其实,这个已经不是格式化字符串的错误了,它就是一个正常的字符串的输出,正确的代码应该是这样:

import std;
using namespace std;
int main()
{
	string s{ "Hello World!" };
	//println(s);
	println("{}", s);
	return 0;
}

那么好,回过头来,根据报错信息的提示,它说不是常量,那就改成常量,再看一下结果:

import std;
using namespace std;
int main()
{
	constexpr auto s{ "Hello World!" };
	println(s);
	constexpr auto formatString{ "Value {}" };
	println(formatString, 11);
	return 0;
}

结果:

Hello World!
Value 11

从结果可以看出,第一行其实是打印了一个没有参数的格式化字符串,第二行打印了一个拥有一个参数的格式化字符串,与上一个例子要输出常量字符串的含义是不一样的。当然了,这里面有点绕,大家下点功夫,好好理解一下,我觉得该说的我都说清楚了,祝大家学习愉快。

  • 26
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

王俊山IT

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值