C++ Primer第八章①
C++ Primer
第二部分 C++标准库
随着C++版本的一次次修订,标准库也越来越强大,这部分我们要介绍标准库中一些大家都应该知道的比较常用的东西。
到底什么叫标准库呢?其实标准库的核心就是很多容器类和一族泛型算法(数据结构加算法),这些东西能帮助我们编写简洁高效的程序,我们只要把精力放在求解的问题上就好了。
IO库
C++是通过标准库中的IO库来处理输入输出的,本章就是来介绍这个IO的,之前也学了一些,就是cin之类的。接下来就系统地学一下,彻底搞定这个IO。
IO就是Input和Output。
不要认为这一章很简单,它里面有很多东西要学的,我开始看的时候觉得这一章没啥,现在看来还是要好好理解下的:
我们先来看看我们已经学到过的一些IO库设施:
- istream输入流类,提供输入操作
- cin, istream对象,从标准输入读取数据
- 运算符>>,用来从istream对象中读取输入数据
- getline函数,从一个给定的istream读取一行数据,存入一个给定的string对象中
- ostream输出流类型,提供输出操作
- cout,ostream对象,向标准输出写入数据
- cerr,ostream对象,用于输出程序错误信息,写入到标准错误
- 运算符<<,用来向一个ostream对象写入数据
IO类
为了支持不同种类的IO处理操作,除了iostream之外,C++还定义了两个类,我们索性三个一起说了:
- iostream定义了用于读写流的基本类型
- fstream定义了读写命名文件的类型
- sstream定义了读写内存string对象的类型
graph LR iostream-->istream-ostream
graph LR fstream-->ifstream-ofstream
ifstream和istringstream都继承自istream,先不用管继承什么意思,只要记住我们怎么用istream,就怎么用其他两个。graph LR sstream-->istringstream-ostringstream
IO对象不能拷贝或赋值
这是规定,所以我们一般都用引用,来几个错误例子示范:
ofstream o1, o2;
o1 = o2; //错误:不能赋值
ofstream print(ofstream); //错误:这里是一个print声明,
//后一个ofstream会被初始化,就是拷贝,不行
//前一个是返回类型,返回的时候也会拷贝,不行
记住两个不能:
- 不能拷贝IO对象,所以不能把它们用作形参或返回类型,一般我们就用引用
- 读写IO对象会改变它的状态,所以它的引用不能是const的 总之,只能是通过普通引用的方式来使用它。
条件状态
IO操作一个与生俱来的问题就是错误,因为你的用户不会那么听话。IO类定义了一些函数和标志来帮我们处理这个问题。我们慢慢介绍(原书在P279-P280)
来个错误示范:
int a;
cin >> a;
这个代码是没问题的,但是,用户没有输入数字,而是输入了字母。这样cin就会进入错误状态,一旦一个流发生错误,后续的所有Io操作都会失败,所以啊,代码通常应该在使用一个流之前检查它是否处于良好状态,比较简单的做法是把它当作条件:
while(cin >> a)
{
...//读操作成功后再执行
}
查询流的状态
我们前面讲流作为条件使用,只能告诉我们一个结果,无法告诉我们具体发生了什么。
IO库定义了一个与机器无关的iostate类型,它提供了表达流状态的完整功能。具体的用到再查把,只要记住它有一个iostate来表示遇到的问题。
管理条件状态
来一段代码看看如何管理条件状态
auto old _state = cin.rdstate(); //调用rdstate记住cin的当前状态
cin.clear(); //调用clear函数使cin有效
process_input(cin); //使用cin
cin.setstate(old_state); //将cin置为原有状态
clear()会清除所有错误标志位(复位)。
clear还有一个接受一个参数的版本,下面的代码只复位failbit和badbit:
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit); //括号内为位运算
管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据,例如:
os << "sss";
文本串可能立即打印,也可能***作系统保存在缓冲区中随后再打印。有了这么一个缓冲机制,操作系统就可以将程序的多个输出操作组合成单一的系统级写操作。
导致缓冲刷新(就是数据真正写到输出设备或文件中)的原因有很多:
- 程序正常结束,作为main函数的return操作的一部分,缓冲刷新被执行
- 缓冲区满了,所以要刷新缓冲,以便后来的数据能继续写入缓冲
- 使用例如endl的操作符来显式刷新缓冲区
- 在每个输出操作后,我们可以用unitbuf设置流的内部状态来清空缓冲区。对于cerr来说,unitbuf是默认设置的,因此写到cerr的内容都是立即刷新的
- 一个输出流关联到另一个流时,当读写被关联的流时,关联到的流的缓冲区自动刷新。例如,默认情况下,cin和cerr都关联到cout,因此读cin或者写cerr都会导致cout的缓冲区被刷新
刷新输出缓冲区
有三个显式刷新的符:
cout << "你大爷" << endl; //输出你大爷和换行,然后刷新缓冲区
cout << "你大妈" << flush; //输出你大妈,然后刷新缓冲区
cout << "你大伯" << ends; //输出你大伯和一个空字符,然后刷新缓冲区
神器一直刷新缓冲区-unitbuf操作符
看代码就懂系列:
cout << unitbuf;
//接下来的所有输出都立即刷新,无缓冲
cout << nounitbuf //恢复正常的缓冲方式
关联输入和输出流
cin >> a;
这句话会刷新缓冲区,为啥呢,前面说过,标准库将cout和cin关联起来,所以从cin中读取数据会刷新cout缓冲区。
这是系统设置的关联流,下面介绍一下我们如何自己设置关联流,要借助一个函数tie。
ostream *old_tie = cin.tie(nullptr);
//原来cin与cout是默认关联,现在cin不再与其他流关联
cin.tie(&cerr); //cin现在与cerr关联,读取cin会刷新cerr而不是cout了
cin.tie(old_tie); //恢复常态
每个流同时最多关联到一个流,但一个ostream可以同时被多个流关联。
#C++工程师#