目录
引入
输入输出缓冲区的作用
流
c++io流
介绍
为什么要把流进行面向对象的设计呢?
原理
使用的注意点
istream类型对象转换为逻辑条件判断值
引入
转换运算符
文件io
介绍
示例
注意点
说明
利用字节流特性
字符串io
介绍
istringstream
ostringstream
示例
引入
在原先c语言中,我们一般使用scanf和printf来进行输入输出,并且有输入输出缓冲区的存在
(代码实际上应该是底层中的内存)
输入输出缓冲区的作用
- 它可以让原本没有"行"概念的计算机在解析缓冲区内容后,返回一个"行"
- 不同的操作系统和环境可能有不同的底层 I/O 实现方式,但通过使用标准库函数和输入输出缓冲区,程序员可以将底层的实现细节隐藏起来,使程序在不同平台上运行更加可靠和可移植
- 通过在内存中累积一定量的数据,程序可以减少频繁的 I/O 操作,从而提高效率
而这种数据运输的过程,被比喻为"流"
流
- (来源于 -- 如何理解编程语言中「流」(stream)的概念? - 知乎 (zhihu.com))
- 流是一种抽象概念,它表示数据的有序序列,这些数据可以被读取、写入或处理,数据可以是各种类型的,这些序列形成一个数据传输通道(可以理解为是一个管道),程序可以逐个字节或块地读取和写入数据,而不必关心数据的来源或去向
- 它可以描述 数据在 程序 和 外部源/目标 之间的传输
- 流分为两种,输入流用于从[外部源]读取数据存入[计算机内部],而输出流用于将数据写入[外部设备]
- 其中,C语言的标准库提供的用于处理输入输出的函数,可以被认为是操作流的接口
- 而在C++中,也引入了面向对象的流库,其中流对象成为了数据输入和输出的关键接口
c++io流
介绍
- 其中,流被抽象成类的对象
- c++的io流库形成了一个庞大的继承体系,最基本的基类是ios_base,在这之上派生出各种类
- 一般我们是使用iostream中的cin,cout全局流对象进行输入输出操作(其中c表示console,意思是控制台)
- c中有fscanf/fprintf等f系列函数,用于处理文件;有sscanf/sprintf等s系列函数,用于处理字符串
- 所以c++也沿用了这个设计,fstream包括了对文件进行io的函数,sstream包括了对字符串进行io的函数
为什么要把流进行面向对象的设计呢?
- 因为在c中,无法直接向自定义类型输入输出,它提供的接口都是针对内置类型
- 所以在c++中增加了自定义类型直接和控制台交互的操作(也就是面向对象的设计)
- 这样把流对象化,可以让"流"这个概念更加形象(cin>>a ,把输入流的内容流入到a中;cout<<a,把a流入到输出流中)
原理
- 实际上,自定义类型的io操作是通过重载运算符<<和>>实现的
- 并且,c++中的cin,cout不是可以自动识别类型吗
- 实际上是通过函数重载实现的
使用的注意点
输入的数据类型必须与要提取的数据类型一致,否则出错 出错只是在流的状态字state中对应位置位(置1),程序继续- 空格和回车都可以作为数据之间的分格符,所以多个数据可以在一行输入,也可以分行输入,如果需要读取整个行,包括空格,可以使用getline函数
istream类型对象转换为逻辑条件判断值
引入
- 多行输入可以用while(cin>>n)来实现,为什么呢?
- 是因为通过重载的>>,返回一个cin对象的引用
- 而这个引用可以作为while的条件,也就意味着cin可以被转换为bool值
- 这个转换依靠了这个函数:
- 而这个函数就是c++中的转换运算符
转换运算符
转换运算符是用于定义 类类型 到 另一种数据类型 的自定义转换的特殊成员函数
格式 : operator+类型名
class MyClass { public: operator int() { return someValue; } private: int someValue; };
class MyClass { public: operator AnotherClassType() { AnotherClassType result; // Define the logic to create and initialize result return result; } };
所以一个简单的while(cin>>n)实际的调用过程:
- cin调用operator>>,去流中提取数据,然后返回一个istream&
- 这个对象调用operator bool,如果接收流失败,或者有结束标志,则返回false,其他返回true
- 然后这个bool值作为while的判断条件
文件io
介绍
c++根据文件内容的数据格式分为二进制文件和文本文件,基本上和c一样c++ 标准库中有许多不同的标志,用于指定流对象的行为,可以组合在一起以设置不同的模式:采用文件流对象操作文件的一般步骤:
- 定义一个文件流对象
- ifstream ifile(只输入用)
- ofstream ofile(只输出用)
- fstream iofile(既输入又输出用)
- 使用文件流对象的成员函数打开一个磁盘文件,使得文件流对象和磁盘文件之间建立联系
- 使用提取和插入运算符对文件进行读写操作,或使用成员函数进行读写
- 关闭文件
示例
#include<iostream> #include<fstream> #include<sstream> #include<string> using namespace std; struct info_data { char _data[50]; //不能使用string/vector int _port; }; class information { public: //ifstream& operator<<() information(const string& arr) //我们的文件操作首先需要一个文件路径 :_filename(arr){} void b_read(info_data& info) { ifstream i(_filename, ios_base::in | ios_base::binary);//用二进制读方式打开 i.read((char*)&info, sizeof(info));//将流中数据读入info中 i.close(); } void b_write(const info_data& info) { ofstream i(_filename, ios_base::out | ios_base::binary| ios_base::app);//用二进制追加写方式打开 i.write((const char*)&info, sizeof(info));//将info写入流中 i.write("\n", sizeof("\n")); i.close(); } void t_read(info_data& info) { ifstream i(_filename); i >> info._data >> info._port;//将流中数据读入info中 i.close(); } void t_write(const info_data& info) { ofstream i(_filename, ios_base::out | ios_base::app);//追加方式 i << info._data << " " << info._port<<endl; //将info写入流中(可以在中间加入空格和换行符,这样存入文件中的数据更易懂) i.close(); } private: string _filename; }; int main() { information t("test.txt"); //文本方式读写 info_data data; t.t_write({ "hello",80 }); t.t_read(data); //二进制方式读写 info_data tmp; t.b_write({ "hello",80 }); t.b_read(tmp); cout << tmp._data << " " << tmp._port << endl; return 0; }
文件内容:
代码执行结果:
tmp实际存放数值:
注意点
- 如果用string/vector存储数据(也就是字符串),一旦字符串长度有点长,就会在堆上开辟空间存放数据,只会存储一个地址指向那块空间,那存入文件的也是那个地址
- 但下次程序执行,读取文件时,会读取到上次运行时生成的地址,在这次就是无效地址,越界访问了
- 注意,打开文件之后,如果不需要它了, 最好显式关闭文件
说明
- 文本读取时,是按行读取;而二进制读取是读整个文件,所以tmp的_data成员是写入的所有字符,_port被默认初始化为0
- 二进制存入的内容大概率我们都是看不懂的,所以如果需要文件能读懂,最好是文本读写
利用字节流特性
void test3() { ifstream i("code.cpp"); char c; while (i.get(c)) { cout << c; } }
利用文件中存储的数据是字节流形式,可以通过get函数获取字符,然后把某个文件全部读出
字符串io
介绍
istringstream
- 可以从一个字符串中提取数据
- 也是一种反序列化的过程,通常包括从字节流中读取数据,解码数据并将其还原为原始数据结构的字段
ostringstream
- 将数据格式化为字符串,可以像输出到标准输出流一样来构建字符串
- 属于简单序列化的过程,序列化过程通常包括将数据的字段按照一定的协议编码为字节流,并将其写入文件或通过网络发送
- 最后通过str()成员函数来获取格式化后的字符串
示例
void test2() { int a = 1; double b = 1.1; ostringstream o;//格式化写入 o << a << " " << b; string str = o.str(); cout << str << endl; ofstream i("test.txt", ios_base::out | ios_base::app);//将字符串写入文件 i << str << endl; i.close(); }
文件内容:
代码结果: