C++相关概念和易错语法(34)(C/C++缓冲区同步刷新、IO流)

发布于:2025-02-27 ⋅ 阅读:(16) ⋅ 点赞:(0)

目录

1.C/C++缓冲区同步刷新

(1)产生原因

(2)效率问题即解决方法

(3)cin和cout绑定问题

2.IO流

(1)库的继承体系

(2)IO的四个标志

(3)文件IO

①ofstream、ifstream、fstream

②文本IO和二进制IO

(4)字符串IO

(5)错误信息输出


1.C/C++缓冲区同步刷新

(1)产生原因

C++和C共有两个缓冲区,对应由iostream和cstdio管理,它们各自为各自的缓冲区服务。缓冲区的存在是为了提高效率,在系统的学习中我们能够深刻体会这一点。

以cout和printf为例,在同一个程序里分别使用cout和printf写数据,它们会先放到自己的缓冲区中,我们可以主动调用函数刷新缓冲区,也可以通过触发刷新机制刷新(如\n和endl)。但如果cout代码在printf前,而实际上cout刷新缓冲区的时间比printf晚,这就导致程序打印异常。

因此,要避免打印错乱,C++要兼容C语言,保持同步。当任意一方刷新缓冲区前,都需要先刷新别人的缓冲区,这就是C/C++缓冲区同步刷新。

(2)效率问题即解决方法

每次刷新自己的缓冲区前都要先刷新别人的缓冲区,这就相当于每个输入输出刷新时都需要同时访问两个缓冲区对于高IO需求的程序,这样的效率影响是很大的。

ios_base::sync_with_stdio(false)可以关闭同步刷新,但是这就会导致上述的问题,因此关闭同步刷新后需要我们手动维护代码的IO顺序性了,最好不要C/C++输入输出混用。

(3)cin和cout绑定问题

 cin和cout是标准输入流对象和标准输出流对象,是在引入<iostream>后全局定义的。

细分C++的缓冲区,也分为输入缓冲区和输出缓冲区。cin和cout是绑定的,意思是调用cin就会先刷新输出缓冲区,调用cout就会先刷新输入缓冲区,这和前面我们遇到的效率问题类似。

cin.tie(nullptr)表示调用cin后不会先刷新输出缓冲区了(即和cout取消绑定),cout.tie(nullptr)表示调用cout后不会先刷新输入缓冲区了。

2.IO流

(1)库的继承体系

这一套IO相关的库可以帮助我们实现C++版的输入输出,可以很方便地替代printf、scanf系列函数。

整个继承体系中含有菱形虚拟继承,C++的IO最大的特点就是通过operator<< 和operator>>支持自定义类型。

(2)IO的四个标志

while(cin >> str)可以持续接收输入

重点关注cin >> str的返回值istream&,istream又重载了一个operator bool,底层上是调用了这个函数来进行类型转换的等效的判断语句为:cin.operator>>(string str).operator bool()

如何结束呢?换句话说operator bool()内部是如何判断的呢?这就涉及到IO的四个标志了

先看看下面的代码



#include <iostream>
using namespace std;

void cin_state()
{
	printf("cin的状态:\n");
	printf("%d\n", cin.good());
	printf("%d\n", cin.fail());
	printf("%d\n", cin.bad());
	printf("%d\n\n", cin.eof());
}

void cout_state()
{
	printf("cout的状态:\n");
	printf("%d\n", cout.good());
	printf("%d\n", cout.fail());
	printf("%d\n", cout.bad());
	printf("%d\n\n", cout.eof());
}

int main()
{
	string str;
	while (cin >> str)
	{
		cout << "cin接收的字符串:" << str << endl;
	}

	cin_state();
	cout_state();

	cin.clear();
	printf("在这里恢复状态\n\n");

	cin_state();

	return 0;
}


使用^Z可以退出循环,其本质就是修改cin的状态标志

cin和cout都分别有自己的状态标志,默认只有goodbit为1(表示程序正常),其余标志都为0。

其他错误标志:failbit表示基本所有的错误、badbit表示遇到了很严重的错误(如IO流本身被破坏了,和运行程序无关)、eofbit表示读到了文件末尾。

上述代码中,^Z表示读取到了键盘文件的末尾,触发了错误,因此对应为1。通过clear函数,我们可以复原cin对应的状态,上图也可以验证。

我们因此能够理解,operator bool是通过判断cin是否遇到了错误failbit、badbit、eofbit来判断错误的(有eof错误一定会触发fail)

只要我们及时clear状态,就可以让^Z无效

这种情况就只有kill掉整个进程才行了。

只要是继承自ios_base的成员(几乎所有IO相关的对象),基本都有这四个状态

理解了operator bool()的底层依赖,我们就能更好地在判断语句内使用IO函数了,也能知道如何跳出循环,原理是什么。

更多时候我们都是显式使用其中的一个标志来判断结束,这样更易读,如cin.eof()

(3)文件IO

<fstream>专用于文件IO

①ofstream、ifstream、fstream

下面是常用的函数写的代码

open函数默认分别为ofstream只写、ifstream只读,当然也可以指定打开方式

可以看到ofstream主要适用于向文件里面写数据,ifstream适用于从文件里面读数据,和ostream、istream的功能类似,只不过cin和cout是针对显示器文件打印或读取的。

我们还能使用fstream类型,既可读又可写,需要指定打开方式

②文本IO和二进制IO

在Linux系统里面,我们就已经详细分析了文本文件和二进制文件的底层区别。简单来说就是文本文件的数据都被看做字符,经过了一层ASCII码的转换成对应数字后再转成二进制的,而二进制文件直接将数据转成的二进制。文本文件和二进制文件的核心区别就是编解码方式不一样。

默认打开的文件都是文本文件,要以二进制方式打开需要设置参数,如下代码所示

注意string进行二进制写入的话要使用c_str(),本质上write还是C风格的函数,要传入的是对应字符串的首地址而不是string的首地址,否则会导致写入的数据不是字符串对应的二进制,是乱码。

(4)字符串IO

<sstream>用于字符串相关IO

下面是常用函数实例

和前面的一样,只不过输出的对象变成了字符串而已,o向字符串写,i从字符串读。

重点关注istringstream的提取功能。它可以从字符串中读数据并写到不同的对象中,空格就是分割线,和C语言功能基本一致。但注意这个提取的格式和顺序一定要匹配,并且不能跳过某一部分。例如上图要读取2025就必须先读取前面的"Hello",不能跳步骤。

istringstream的构造可以用char*也可以用string,istringstream和ostringstream虽然不是string类型,但里面存放的有字符串的内容,结合上述代码很好理解这一点。

(5)错误信息输出

cerr、clog可以用于错误的输出,它们都是ostream的对象且和cout用法完全一致。在代码中可以用它来区分普通打印,主要是增加程序可读性。