C++文件输入/输出

最近特别查阅了一下关于C++文件的输入/输出的资料,所以就整理了一下写了这篇笔记。

一、什么是流

当前的计算机具有很多种设备,但是无论是哪种设备都要与数据和信息进行打交道,所以这就牵扯到设备与数据之间的I/O操作。而每种设备又有着不同的特性和操作协议,由于过于复杂,所以我们一般是不会和这些通信细节打交道的,这些东西都交给了操作系统,并且操作系统也向我们提供了更为简单和统一的I/O接口。
说了这么多,这和流又有什么关系?那么接下来就需要说一下I/O操作的方式了,这也是为更好的理解“流”。假设数据就是一堆沙土,现在我们需要把它挪动到另外一处,那么现实生活中我们一般会有两种做法,第一种做法是使用一辆大一点的车一次性将所有沙土搬运到目标地点;而第二种做法则是使用相对小的车,分多次将所有沙土搬运到目标地点。这两种方式我们不能说谁就一定优于谁,因为这毕竟要根据实际的情况才能做出判断,就C++语言程序而言,所有的I/O操作都只是简单的从程序中移入移除字节而已,所以此时采用第二种策略则更为适合这种处理数据的方式,而像这种一点一点的搬运“沙土”的整个过程便被称为“流”。以我现在的理解,“流”更像是一种高度抽象化的行为方式,但在计算机的世界里偏偏存在着将抽象变为具体的法术“类/结构体”,所以也就有了stream这一数据结构(感觉自己的语言表达能力有限,只能讲到这个程度了)。如果非要找个现实中和“流”相似的一样东西,那应该就是河道/水道,就像下面这张图所示:

在这里插入图片描述
在图中,文件就像是水源,“流”就像是河道,程序则类似是水沟/湖泊之类的东西,但“流”的行为方式就是采用了上述的“化整为零”的策略。

二、什么是缓冲区

既然有了“流”,那为啥还需要缓冲区呢?这也是我一直以来的困惑,既然有了“流”,我们只需要一点一点将数据读取到我们的程序中去不就行了吗,为啥又出来一个缓冲区啊。要搞清楚这个问题我们就需要看一下没有缓冲区会怎么样,如果程序直接读取硬盘中的文件内容并不是不可以,只是程序每次只能处理少量数据(也就是使用系统IO,如linux中的read),而且从硬盘文件中重复直接读取数据会耗费大量的计算机硬件的资源(频繁的内核态与用户态进行切换),所以为了解决这两个问题也就出现了缓冲区这一概念(系统IO这种方式自有其优势之处:如它的响应速度是很快的)。缓冲区本质上是一块较大的内存空间(通常是512字节或4096字节),它起到了一种中介的作用,也就是它从文件中读取大量的数据存储在自己的内存空间中(我推测这里的内存可能是指我们常说的RAM),然后我们的程序只需要问缓冲区要数据就可以了。这有什么好处呢?好处有两点:1、增大了数据的吞吐量,因为每次读取较多的数据之后它才会传递给我们的程序(标准io),减少了内核态与用户态的交换频率;2、像磁盘驱动器的设备是以512字节块为传输单位,而我们的程序每次却只能处理一个字节,所以缓冲区的第二个作用就是匹配这两种信息的传输速率,也就是我一边接受512字节块,一边一个一个字节传出。类似于下图:

在这里插入图片描述

而程序输出数据时,首先会填满缓冲区,然后才会把整块数据传给硬盘,这被称为刷新缓冲区,反之亦然。其实只要对缓冲区做过一些了解就清楚,缓冲区的刷新行为主要有以下几种方式:(1)行缓冲(stdout):缓冲区中在遇到“\n”、缓冲区满了会刷新缓冲区;(2)全缓冲(大多数文件流):只有缓冲区满了才会刷新缓冲区;(3)无缓冲(stderr):来一个立即处理一个,也就没有缓冲区的这一说了。但是就像现实生活一样,突然来了一些紧急需要处理的数据怎么办呢,那就只能特事特办提前刷新缓冲区,也就是使用fflush函数即可。

缓冲区就像上述例子中的搬运沙土的车,它是沙土从起点到终点的运输工具。由此我们可以推断出,在整个数据传输的过程中,一方面像磁盘驱动器之类的设备只需要把数据放到“车”上就行,而另一方面“车”具体会驶向哪里只有“车”自己知道,换句话说我们就可以在“起点”和“终点”这两处地方“做手脚”。这样我们就可以通过改变流缓冲区的两端的对象,以使得数据按照我们的心意从文件到程序,再从程序到文件。

三、代码实现文件IO

3.1 使用文件流对象读取数据

ifstream类:
在这里插入图片描述在这里插入图片描述在这里插入图片描述
ofstream类:
在这里插入图片描述在这里插入图片描述在这里插入图片描述

#include<iostream>
#include<string>
#include<fstream>

using namespace std;

int main()
{
	string path = R"(C:\Users\23547\Desktop\data.txt)";
	string outpath = R"(C:\Users\23547\Desktop\data1.txt)";
	ifstream input(path);	//创建带有缓冲区的输入流并将该流缓冲区关联输入文件
	ofstream output(outpath);	//创建输出流并关联输出文件
	
	int a, b, c;
	if (input.bad())
	{
		return 0;
	}
	
	while (input >> a >> b >> c)	//ifstream类重载了>>运算符
	{
		output << a << b << c << "\n";	//ofstream类重载了<<运算符
	}
	
	input.close();	//刷新缓冲区并关闭关联的文件
	output.close();		
	system("pause");
	return 0;
}

题外话:我记得我用C#语言进行文件I/O时,如果输出流对象最后不执行关闭操作,会发现数据没有写入输出文件中去,现在才理解了当时应该是没有刷新缓冲区才会造成了数据没有写入的错误。因为可能是写入数据没有达到512字节,所以缓冲区没有自己刷新,而如果自己再没有进行手动刷新的话(close操作),数据就只会被存在内存之中而不会写入文件中。不过我也尝试了一下C++的流对象,即使不执行close操作,数据仍会写入到输出文件中去。

运行效果:
data.txt文件:
在这里插入图片描述

data1.txt
在这里插入图片描述

3.2重定向(使用rdbuf函数)

ifstream(或ofstream)::rdbuf函数:其会得到一个流缓冲区,也就是会返回一个内部filebuf对象指针(指向是原始文件的流缓冲区对象指针),如图所示:

在这里插入图片描述在这里插入图片描述

ios::rdbuf函数:其有两种形式,第一种是返回一个与当前流关联的流缓冲区对象指针,第二种形式则是将一个streambuf对象指针指向的对象设置为某个与流关联的流缓冲区,并清除错误状态标志(在这里也就是设置cin的流缓冲区)。这两种形式返回的streambuf指针都是指向他们各自的原始文件流缓冲区对象(控制台)。

在这里插入图片描述

#include<iostream>
#include<string>
#include<fstream>

using namespace std;

int main()
{
	string inpath = R"(C:\Users\23547\Desktop\data.txt)";
	string outpath = R"(C:\Users\23547\Desktop\data1.txt)";
	ifstream input(inpath);		//创建带有缓冲区的输入流并将该流缓冲区关联输入文件
	ofstream output(outpath);

	int a, b, c;
	auto cn = cin.rdbuf(input.rdbuf());		//将input的流缓冲区传给cin对象,改变cin的流缓冲区,那么此时cin等同于input,都指向同一个缓冲区
	auto cou = cout.rdbuf(output.rdbuf());	//将output的流缓冲区传给cout

	while (cin >> a >> b >> c)
	{
		cout << a << b << c;
	}

	cin.rdbuf(cn);		//将原来的cin的缓冲区传回给cin,重置控制台
	cout.rdbuf(cou);

	cout << "执行成功!!!" << endl;
	input.close();
	output.close();
	system("pause");
	return 0;
}

运行效果同3.1中的效果,而以我现在的理解只能到达这个程度了,可能中间理解的与实际情况有偏差,所以如果有错误还请批评指正。

参考书籍:《C++ primer plus》和《C和指针》

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

大鱼BIGFISH

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

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

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

打赏作者

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

抵扣说明:

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

余额充值