目录
一、C语言中的输入与输入
二、流
三、C++中的流
四、C++中的文件IO流
1. 二进制文件
1.1 打开文件
1.2 向文件写入数据
1.3 从文件读取数据
1.4 关闭文件
1.5 综合使用
2. 文本读写
一、C语言中的输入与输入
在C语言中,我们最长使用的输入输出方式就是“scanf()"和“printf()”。scanf()从标准输入设备(键盘)读取数据,并将值存放在变量中。printf()则将制定的文字/字符串输出到标准输出设备(屏幕)。在printf()中,可以控制宽度输出和精度输出。
C语言中的输入输出就是借助了缓冲区来实现的。即通过代码将特定的数据写到缓冲区,OS再将缓冲区内的数据刷新到设备中。反之亦然。
二、流
在C++中,提出了“流”的概念。“流”可以看成是流动的意思,即物质从一处向另一处流动。是对一种有序连续且具有方向性的数据的抽象描述。
C++流是指信息从外部输入设备(如键盘)向计算机内部(如内存)输入和从内存向外部输出设备(显示器)输出的过程。这种输入输出的过程就被叫做“流”。
流的特点是“有序连续”和“具有方向性”。
为了实现流,C++中就定义了I/O标准类库,这些每个类都被称为流/流类。
三、C++中的流
上面就是一张C++中的流的结构图。它是通过继承来实现的。
在以前,大家使用C++中的流时,内置类型可以直接使用。能够直接使用的原因,其实是因为C++库中已经进行了对应的处理。例如istream:
这个类里面包含了大量的接口,其中就有对“>>”的重载:
所以,在平常使用时>>能够对内置类型直接使用的原因,就是C++库中已经对内置类型完成了>>重载。
大家刷题的时候,应该都写过如下代码:
一个while循环里面不断的输入数据。在cin中,是依靠空格来区分字符串的。\n就是换行,将缓冲区内的内容刷新出去。
但是,大家有没有想过,这个循环是如何结束的。实际上,有两种方式结束。
第一种方法就是按下“ctrl c”。这种方法就是向进程发送信号,直接终止进程。
第二种方法就是“ctrl z”。这是让cin返回一个false,终止掉这个流。
注意,ctrl c是终止进程,所以当按下后,会直接终止进程,无论下面还有什么内容,都不会再继续运行。但是ctrl z是让cin返回false,终止掉这个流。所以如果ctrl z终止的流后面还有while循环,它就会进入继续执行。
ctrl c终止:
ctrl z终止:
当然,C++库中的I/O标准库是非常丰富的,这里就不过多介绍,碰到需要的情况下直接去查询即可。
四、C++中的文件IO流
在C++中,根据文件的数据格式分为二进制文件和文本文件。
1. 二进制文件
1.1 打开文件
为了方便演示,先准备以下的类:
在这里,要将ServerInfo的数据通过ConfigManger类写入到文件中。
在C++中,要写入文件,就可以使用ofstream:
这是一个类,因此,要写入数据,首先就需要构造出一个对象。查看它的构造函数:
我们可以构造出一个空的ofstream对象,然后通过open来打开文件:
也可以在构造函数中就直接打开。
C++中打开文件的方式就和C有所不同,它的关键字如下:
其中,in是只读,out是只写,app是追加,trunc是覆盖写,binary是二进制形式写。
C++中传文件打开方式也和C中传“a”,“r+”之类的字符串不同。因为out和in这些可以被看成是在基类中被定义好的静态变量,所以在使用时要加上它所在的域。一般是“ios_base”。因为ios_base是所以输入输出流的基类。
当然,你也可以直接填它所在的域,例如这里就可以填ofstream:
两种方法都是可行的,在使用时记得包头文件<fstream>。
1.2 向文件写入数据
要向文件写入数据,可以使用write函数:
write函数可以写入一个字符串。其中s是要写入的字符串,n是这个字符串的大小。
如果只是想写入一个字符,就可以使用put函数:
总的来讲,使用最多的还是write函数。使用起来也很简单:
1.3 从文件读取数据
要从文件中读取数据,就需要使用ifstream:
它的构造函数和ofstream是非常类似的,都支持无参构造和带参构造:
同样的,无参构造需要使用open函数打开文件;带参构造则是在构造时打开文件。打开方式和ofstream也是一样的:
而ifstream中的读取文件的函数,就是read:
使用起来也很简单:
1.4 关闭文件
在C++,是不用考虑关闭文件的。因为这些流都是用类封装的,一旦出了作用域就会调用析构函数关闭文件,无需用户手动关闭。
1.5 综合使用
有了上面的知识,就可以写出一个简单的读写文件的程序:
运行程序后,可以在目录中找到log.txt文件。打开该文件:
里面是如下内容。出现这些内容的原因是这里是以二进制方式写入的。编译器内部会经过很多处理,例如内存对齐,所导致的。不必过多关注。
总之,要读取的话,是可以读取出正确的数据的:
注意,在这里的程序中,使用的是数组,而不是string。用二进制读写的时候,如果使用string是会出问题的。修改程序:
运行程序:
可以看到,程序此时的返回码为负数。只要这个返回码不为0,就说明程序运行出错了。
这里的原因就是二进制读写。在二进制读写中,因为它要写的是对象中的值。而对象中又使用的是string保存数据,string里面又存在指针,用于指向数据存储的空间。这就导致,二进制读写在写数据时,会将string中的指针写过去,而不是将数据内容写过去:
如果是同一个程序读写还好。如果是不同程序读写,原先写进去的指针就已经是野指针了,造成越界访问。总的来讲,无论是否是同一个程序读写,都会造成程序错误。而使用数组,二进制读写就是把数组的内容原模原样的写过去,所以不会出现错误。因此,在未来如果要使用二进制读写,不要使用string,而是使用数组存储数据。
2. 文本读写
基于上面二进制读写的缺陷。使用在C++中推荐使用文本读写。使用文本读写,那么无论是数组还是string都可以适用。
文本读写的话打开文件的方式和二进制读写是一样的,只是不需要加上ios_base::binary。同时文本读写也可以不传后面打开文件的方式了。因为构造函数中是有缺省参数的。
打开文件也可以直接使用构造函数打开:
文本读写甚至可以不用再使用read和write。因为ifstream中重载了“>>”;ofstream中重载了“<<”。因此,我们甚至可以直接使用这个重载来实现读写文件:
使用起来就和我们以前所使用的标准输入输出流是一样的。
写入如下程序:
运行程序:
结果没有问题。如果想写入自定义类型,例如一个日期类,就可以使用列表初始化和自己重载>>和<<实现。
提供如下一个日期类和对应的>>和<<重载:
修改程序如下:
运行该程序:
程序依然可以正常运行。