接下来一个地方就是我们的操纵符
为了控制这个流的输出的过程
不仅仅我们以前面那个模式来去写
它要提供一系列的操纵符来控制
整个流的输入输出的格式
你看到我们前面设了
把它设成16进制的设精度
那种东西都叫它的操纵符
你要设定这个流嘛
这个流 它在设计实现中
有一个非常精巧的地方 就是什么呢
就是数据对象实际上是连续输出的
os<<a<<b<<c 你就按照这个格式
它实际上是一个连续输出的一个过程
如果你把控制这个流的操作的过程
插在这个连续输出的中间
你不就打断了这个连续输出的逻辑了吗
你比如我os<<a 然后我想“<<b”
但是我又想把“<<b”的精度给它调一下
好吧 你就写os<<a; os.precision(4)
然后os<<b 然后os.precision(3)
然后os<<c
多罗嗦 多麻烦 对吧
你如果按照这个模式来写
流的连续操控就被打断了
那流还有意义吗
我们实现流的最主要的目的
就是想实现一个连续操作嘛
你自来水管一打开
那水就“哗哗哗”往下流啊
要不怎么叫流呢 对不对
它是个连续动作啊
所以流式操作必须保证这个连续性
那么所有对流的格式
进行控制的那些操作
必须是以输出对象
或输入对象一样的方式进行处理
也就是说 控制流格式的那个东西
本身应该可以作为流的那个参数
必须做到这一条 那么怎么做它呢
那就manipulator操纵符
它做的就是这个事情
实现操纵符的目的就在这里
操纵符有两种
一种是没有参数的操纵符
那么它在实现上
使用的其实就是一个函数指针
还有一种 就是带着一个单参数的操纵符
在实现上 所有的这种单参数的操纵符
它都是一个函子 什么叫函子呢
函子就是带有函数指针功能的
操纵符类的一个对象
它在实现上 实际上是重载了
函数调用操作符的一个操纵符类
然后你构造这个操纵符类的一个对象
然后在那个对象上面
调用它重载的函数调用操作符
然后它就表现行为就像一个函数一样了
这就叫一个函子
有的时候我们称它为函数对象
叫function object
有的时候你就直接称叫functor 就叫函子
我就把它翻译成“函子” 就这个意思
函子怎么实现 怎么用
我们下一讲讲泛型编程的时候
也会专门去讨论它
现在同学们只需要知道
所有的流式的单参数的操纵符
在实现上都是函子
所以相当精巧地
实现了我们流式的连续操控
你比如讲
std::cout << "Hello World!" << std::endl
这就是一个操纵符
表示一个换行的动作 无参数
我定义一个整型量n 初始化成1024
std::cout << std::dec << n << '\n' << std::hex
<< n << std::endl（dec：decimal表示它是个十进制；
hex：十六进制）
你看 我就把这些操纵符：
dec操纵符、hex操纵符
（一个是十进制操纵符一个十六进制操作符）、
一个endl操纵符（换行符）
这三个操纵符
我就直接地插在了流式操作的中间
这样的话我就没有打断
整个流的连续书写特性
数据流动的关系也没有被临时地中断
你看到 这样的书写
是不是看上去非常自然、灵巧
这是操纵符的最重要的一个地方
就按照这个模式去工作的
在标准的流类库里面
它提供了很多个操纵符
从boolalpha、dec、endl 一直往后
1个、2个、3个、4个、5个、6个、
7个、8个、9个、10个、11个
我们这里11个 又来11个 还有呢
提供了一堆操纵符 你都可以直接用
接下来就是我们的文件流
文件它和其它的流
有一点点细微的特殊性
就是什么呢 文件一般是保存在我们的
外部存储介质上面的
文件的生命周期本身
可能远远大于我们程序的生命周期
我一个程序 我运行 我得到一个结果
我把这个结果写到这个文件里
我程序就结束了 那个文件还在
我下一次运行这个程序
就从那个文件中把那个数据给读出来
这个动作叫数据的持久化
我能够完成这个动作
就意味着我这个程序要和这个文件
形成一个对应的关系
那么这个数据
怎么从这个文件中流入流出呢
那么我们就需要架构文件流
从数据流动角度来讲
除了源和目的发生了变化
文件流本身的设计和我们标准流的设计
它的基本原理实际上是一致的
但是文件的操作呢 它又很特殊
因为它是外部一个资源
我们怎么去操控它呢
需要和操作系统进行打交道
所以它又有一些自己独有的特性
从文件操作角度来讲
我们最重要的是要完成文件的读写操作
我从文件中读数据
把这个数据写到文件里 对吧
我们主要的是使用文件指针的模式
来操纵我们这个文件读写的位置
我要决定我这个数据写在文件什么地方呀
对吧 如果我们需要文件进行读写操作
那么我们就需要文件指针
这样的一个指针
它就代表了文件的当前访问位置
你的数据读取或写入
将从文件的什么地方开始 就这个意思
老式的C语言 有的时候呢
使用文件句柄或者文件描述符
来描述打开的文件数据对象
它打开某个数据对象 那怎么描述它呢
有的系统使用文件句柄
有的系统使用文件描述符
不是使用我们标准C++里面提到的文件流
你注意这一点就行了
文件流 你要想使用的话
必须包含我们这个头文件“fstream”
它的内容都实现在“fstream”这个头文件里
要按照特定的格式重载我们的流操作符
这样的话才能够把这个指定的对象
按照一个特定的方式去流出去
否则的话 你就得一个成员、一个成员地
自己手工去写操作 那就麻烦了
因为好多成员都是私有的
在这种情况下你必须提供
对应的Get/Set函数
你才能够操作 对吧
而且那个格式就很费劲了
你要一个一个地去写
对于这个类的使用者来讲
输出一次类的信息确实很麻烦
如果对象很多
那代码写出来很长的 会崩溃的
那么对于库的设计者来讲呢
你为了让它不崩溃
你就得重载类的流操作符
你必须要做到这个事情
使用者来讲呢 那就简单了
一旦我们重载了流操作符
那么只需要创建我们的文件流对象
然后进行输入输出 完事了
那个使用就简单了
文件流有几个主要的打开方式
第一个 有一种追加模式打开
一种是二进制格式的打开
或者输入模式打开 输出模式打开 等等
所有这些文件的打开模式
在我们标准流类库里
都有一些专门的介绍
每一个流打开以后
它都有一个特定的流状态
流状态就表示我们在流进行操纵过程中
它成功或失败的像这样的一个状态信息
你比如讲 有一个goodbit
这样一个状态 表示好位状态
就是什么呢 当这个操作做完了以后
流是完好无损的
操作成功了 就这个意思
badbit 坏位 就表示操作失败
像eofbit 表示流结束位
像failbit 流操作失败的时候
我们就会设定它 它就失败了
这个failbit 实际上有可能恢复的错误
你比如我打开一个文件
我想写入 但是那个文件不能写
它被别人锁定了
那么就会返回一个failbit 一个坏位
不是说那个文件真地没有
不 那个文件有
它只是被锁定了 临时被锁定了
你等一会儿它就能写了
有可能吧 这是当然有可能的
那么在这种情况下面
它不是说它不能写 然后重试
没准retry一下 重试一遍 它就能写了
这种情况太正常了
所以这返回的可能就是一个failbit
如果那个文件本身就损坏了 磁盘坏了
那个地方的数据压根就找不着了
那肯定返回的就是一个badbit
那是一个坏位
这个情况下一般是没有办法恢复的
是这个意思
流操作本身都可能——任何一个流操作
都可能——影响我们的流状态
而流状态当然对流操作行为
其实也能够造成影响
一旦你流状态存在这样的错误
那么所有的I/O操作 它都会失效
不做了 有问题了
这个操作肯定都有问题了
出现了一个failbit 还做什么呢
对吧 不行了
现在操作都失效了 不做了
是这个意思 注意这一点
可以通过一系列的成员函数
来测试我们的流处于什么样的状态
你比如说 good测试 eof测试
fail测试 bad测试 等等
你还可以使用它重载的“！”操作符
和哑型指针操作符
来进行一些特定的操作
比如讲“！”操作符
它等价于什么呢 等价于我们的fail
当操作这个流的时候
一会儿向这个流中写入一部分数据
一会儿又从流中再读取一部分数据
那个流读取的位置
它有一个文件指针指向它
就会时刻发生变化
我想知道这个位置指针到底指向了哪里
或者我想把它设到一个特定的地方
那么我就需要对这个流进行定位
这就是流的位置指针
位置指针 它指向我们下一次读写操作
将要发生的那个地方
正常情况下是这个模式
它跟着我们的输入输出的这个动作
会不断发生变化的
对于一个单向流 一个位置指针就够了
因为它是一个单向流动
如果是一个双向流动
又有输入又有输出 是个双向流动
那么这个时候我就需要两个文件指针
对吧 一个方向是一个
这是两个 双文件指针
流位置指针 想知道它在什么地方
那么你就可以调用成员函数tellp/tellg
可以获得位置指针的位置信息
写指针用tellp 读指针使用tellg
你获得了以后
想把这个文件指针做一些改变
那么你就可以用seekp/seekg
然后去定位
这样一个位置指针到指定的地方
就按照这个模式就行
seekp处理的就是输出目的的 写的
seekg呢 就是读取的 就输入目的
当你用seekp和seekg进行流指针定位的时候
要注意可以提供一个单参数的版本
就是可以使用获取的位置指针来去做它
如果需要传递一个额外参数
那就是需要传递一个偏移量
传递一个定位的基准
从流的开始位置开始计算偏移量的话
那就用beg这个定位基准
从当前位置开始计算偏移 就用cur
从流的最结尾 文件的结尾开始定位
那就用end这个定位基准