什么是IO?
C语言和C++,我们其实已经接触到了两个IO的概念
#include<stdio.h>
#include<iostream>
iostream,便是IO流,其中I表示in,O表示out,代表着用户的输入和终端的输出。在之前的C++语法中,我们都很少去考虑输入输出,而是直接用固定的测试用例来验证结果。但是对于大多数的程序都是传递给用户进行互动,IO流在实际中才是最应该考虑到的问题。
什么是流?
和我们常了解的水流电流等一样,流是一段连续且有方向的概念。其中IO是流的两个方向,In是用户不断向内存内输入数据,Out是终端向外输出数据。但是流是连续的,数据捕获却并非连续,用户可以不断向内存中输入,但是程序只会捕获其需要的片段,剩下没有被捕获到的数据留在缓冲区待命,一直到程序再次捕获缓冲区的数据。
C++中的IO流
C语言里,最常用的IO流是熟知的printf和scanf,其中scanf和printf便分别表示了I和O。C++中也可以使用这两个来实现IO,但是为什么还要再另外写出cout和cin来代替C语言的IO?
C语言的IO最难用的地方,便是需要用特定的占位符来表示数据。但是有了泛型,我们很多时候并不知道他到底是个什么类型,于是占位符常常会出现一些奇奇怪怪的bug。
于是,C++为了解决这个问题,便利用了面向对象来重新实现了IO流,其中最大的改变便是可以自动识别出IO的类型,不需要用占位符来规定数据的类型。虽然这样有好有坏,但是printf又不是被禁用了,不好用的时候我们不用不就好了。
标准IO流
C++标准库中提供了4个全局的IO流对象
- cin,标准输入,将数据从键盘输入到内存
- cout,标准输出,数据从内存输出到控制台
- cerr,标准错误的输出
- clog,日志输出
其中,cin的输入并非直接提取当时键盘的输入,而是键盘会输入到缓冲区,而cin从缓冲区中提取数据,剩下未被提取到的数据会留在缓冲区等待被下一次的提取。
并且,如果我们输入的对象是字符或者字符串,则空格和回车无法通过cin被输入,因为空格和回车会被作为分隔符,表示一串数据的输入完毕。
此时,便会有我们在做IO时经常会面临的一串代码:
while(cin>>a)
{//...
}
做IO时我们也许并不会考虑到这个问题:循环是如何判断中止的?
我们都知道,对于一个流插入,其返回值是一个istream对象;同样,对于一个流提取,其返回值是一个ostream对象。对象转换为需要被判断的bool值,编译器是没有办法完成这一操作的。于是,STL中为其又加入了一个新语法——类型转换的重载。
operator bool( )
operator bool,其作用在需要被转换的时候,编译器会自动去调用这个重载函数,将其转换为合适的类型。这一重载函数没有返回值类型,或者说其返回值强制是被重载的类型,所以在其前不需要加上返回值的类型。
class A
{
public:A(int a):_a(a){}//打破了往常的0为false,其他为true,重新定义了true和false的判断方法operator bool(){if (_a > 10)return false;elsereturn true;}
private:int _a;
};int main()
{A a1(20);A a2(1);//调用了operator bool函数来进行类型的转换cout << (bool)a1 << endl;//falsecout << (bool)a2 << endl;//true
}
其最大的意义便是修改了固定空为false其余为true的判断方式,从而让bool判断更加便利
并且,流插入和流提取并不是当空即为终止,其必须要通过特定的特征按键才会终止(比如Windows下的ctrl+Z),通过这种方式修改了true和false的判断方式,从而可以人为去规定流插入和流提取的终止。
同样,不仅只有bool可以进行运算符重载,其他类型也可以进行运算符重载,在需要的时候大大提高程序的灵活性。
文件IO流
文件IO流同样也存在着三个对象:
- ifstream,只输入用
- ofstream,只输出用
- fstream,输入和输出
但是文件的读写有着两种方式:二进制文件和文本文件。用最简单的方式来区别两种文件:
二进制文件类似于浅拷贝,直接将所有数据原封不动拷贝到文件中;
而文本文件则是深拷贝,只将有效数据拷贝到文件里。
举个例子,对于一个单链表,我们将其存入文件中
如果采用二进制文件,则是直接暴力复制其里面所有的数据,包括其头结点所存的值、头结点的next指针等值的二进制机器码,读取的时候也是读取二进制数据码。
但是,程序每次运行时地址是不同的,也就是复制了指针,再去访问那个指针则是一个野指针,读取到的数据是无效的。
如果采用文本文件,则需手动编写读写的方式,手动遍历每个结点,然后以字符串的形式将每个结点所存的值写入文本中,读取的时候也需要手动编写读取的方式,将文本中的字符串依次转换为合适的类型,重新装入链表的每一个结点里。
而文本操作的函数,因为在不同的库中可能会产生差异,并且大多数可能会自己去实现,所以需要用的时候查库便可以了。