核心库设施是每个c++程序员都应该熟练掌握的。
标准库的核心是很多容器类和一族泛型算法。
算法库为各种经典算法提供了高效的实现。
智能指针是新标准库中最重要的类之一。
c++不直接处理输入输出,而是通过一族定义在标准库中的类型来处理IO。
这些类型支持从设备读取数据、向设备写入数据的IO操作,设备可以是文件、控制台窗口,还有一些类型允许内存IO,即从string读取数据,向string写入数据。
istream 输入流类型 提供输入操作
ostream 输出流类型 提供输出操作
cin,istream 对象,从标准输入读取数据
cout,ostream对象,向标准输出写入数据
cerr,ostream对象,向标准错误写入数据,输出程序错误消息
“>>”,从一个istream对象读取输入数据
“<<”,向一个ostream对象写入输出数据
getline函数,从一个给定的istream读取一行数据,存入一个给定的string对象中。
我们已经使用过的IO类型和对象都是操作char数据的。
默认情况下,IO对象都是关联到用户的控制台窗口的。在实际应用中,无法限制程序仅从控制台窗口进行IO操作,程序常常需要读写命名文件,而且使用IO操作处理string中的字符会很方便。
为了支持不同种类的IO处理操作(控制台、命名文件、string),标准库定义了不同的IO类型
头文件 | 类型 |
---|---|
iostream | istream,wistream 从流读取数据;ostream,wostream 向流写入数据;iostream,wiostream读写文件 |
fstream | ifstream,wifstream 从文件读取数据;ofstream,wofstream 向文件写入数据;fstream,wfstream 读写文件 |
sstream | istringstream,wistringstream 从string读取数据;ostringstream, wostringstream 向string写入数据;stringstream,wstringstream 读写string |
总结:iostream定义了用于读写流的基本类型;
fstream定义了读写命名文件的基本类型;
sstream定义了读写内存string对象的类型。
为了支持使用宽字符的语言,标准库定义了一组类型和对象来操纵wchar_t类型的数据,宽字符版本的类型和函数的名字以一个w开始。宽字符版本的类型和对象与其对应的普通char版本的类型定义在一个同一个头文件中。
设备类型(控制台窗口、命名文件、string)和字符大小(是否是宽字符)都不会影响我们要执行的IO操作。例如:我们可以用>>读取数据,不用管是从控制台窗口、磁盘文件还是一个string获取,也不用管读取的字符能存入一个char对象内,还是需要一个wchar_t对象来存储。
补充:设备类型和字符大小会影响我们选择的IO类的类型。
IO类之间的继承关系:
ifstream和istringstream继承自istream。istring对象如何使用,就如何使用ifstream和istringstream。
ofstream和ostringstream继承自ostream
补充:基类是如何使用的,派生类就如何使用。
由于不能拷贝IO对象,不能将其作为形参或者返回类型,通常以引用的形式传递和返回流。读写一个IO对象会改变其状态,因此其传递和返回的引用不能是const的。
IO错误的例子:
int ival;
cin>>ival;
如果在输入Boo,读操作会失败。代码中的输入运算符期待读取一个int,却得到了一个char,cin会进入错误状态。
一个流一旦发生错误,其上后续的IO操作都会失败。
只有当一个流处于无错状态,才可以从它读取数据,向它写入数据。由于流可能处于错误状态,因此代码通常应该在使用一个流之前检查它是否处于良好状态。
while (cin>>word)
//OK:读操作成功
while循环检查>>表达式返回的流的状态。
当流作为条件使用,只能告诉我们流是否有效,无法告诉我们具体发生了什么。
IO库定义了一个与机器无关的iostate类型,提供了一个表达流状态的完整功能,这个类型作为一个位集合来使用。
badbit表示系统级错误,如不可恢复的读写错误。通常情况下,一旦badbit被置位,流就无法再使用。
在发生可恢复错误后,failbit被置位,如期望读取数组却读出一个字符。这种问题通常是可以修正的, 流还可以继续使用。
如果到达文件结束位置,eofbit和failbit都会被置位。
goodbit的值为0,表示流未发生错误。
如果badbit,failbit和eofbit任一个被置位,则检测流状态的条件会失败。
标准库还定义了一组函数来查询这些标志位的状态:
1 操作good在所有错误均为置位的情况下返回true
2 bad,fail,eof在对应错误位被置位时返回true
3 badbit被置位时,fail也会返回true
使用good或fail是确定流的总体状态的正确方法。将流当做条件使用的代码就等价于!fail()。而bad和eof只能表示特定的错误。
管理条件状态:
流对象的rdstate返回iostate值,对应流的当前状态。
setstate操作给定条件位置位,表示发生了对应的错误。
clear是一个重载函数,有一个不接受参数的版本,也有一个接收iostate类型的参数。不接受参数的版本清除所有错误标志位。
//记住cin的当前状态
auto old_state =cin.rdstate(); //记住cin的当前状态
cin.clear(); //使cin有效
process_input(cin); //使用cin
cin.setstate(old_state); //将cin置为原有状态
带参数的clear版本接受一个iostate的状态值,表示流的新状态。为了复位单一条件的状态为位,首先用rdstate读出当前条件状态,然后用位操作将所需操作复位来生成新的状态。
//复位failbit和badbit,保持其他标志位不变
cin.clear(cin.rdstate() & ~cin.failbit & ~cin.badbit);
每个输出流都管理一个缓冲区,用来保存程序读写的数据。
输入的内容可能立即打印出来,也有可能被操作系统保存在缓冲区中,随后再打印。
有了缓冲机制,操作系统就可以将多个输出操作组合成单一的系统级写操作。由于设备的写操作可能很耗时,允许操作系统将多个输出操作组合成单一的设备写操作可以带来很大的性能提升。
操纵符endl完成换行并刷新缓冲区的工作。IO库类还有两个类似的操纵符:flush和ends。flush刷新缓冲区,但不输出任何额外的字符;ends向缓冲区插入一个空字符,然后刷新缓冲区
cout<<"hi!"<<endl; //输出hi和一个换行,然后刷新缓冲区
cout<<"hi!"<<flush; //输出hi,然后刷新缓冲区,不附加任何额外字符
cout<<"hi!"<<ends; //输出hi和一个空字符,然后刷新缓冲区
如果想在每次输出操作后都刷新缓冲区,可以使用unitbuf操作符,它告诉流接下来的每次写操作之后都进行一次flush操作。而nounitbuf操作符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制
cout<<unitbuf; //所有输出操作后都会立即刷新缓冲区
cout<<nonunitbuf; //回到正常的缓冲方式
如果程序崩溃,输出缓冲区不会被刷新
关联输入和输出流
当一个输入流被关联到一个输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流。
交互式系统通常应该关联输入流和输出流。
tie有两个重载的版本,一个版本不带参数,返回指向输出流的指针。如果本对象当前关联到一个输出流,则返回的就是指向这个流的指针。如果对象未关联到流,则返回空指针。tie的第二个版本接受一个指向ostream的指针,将自己关联到ostream。
既可以将一个istream对象关联到另一个ostream,也可以将一个ostream关联到另一个ostream
cin.tie(&cout); //标准库将cin和cout关联在一起
ostream * old_tie =cin.tie(nullptr); //cin不再与其他流关联
cin.tie(&cerr); //读取cin会刷新cerr而不是cout
cin.tie(old_tie); //重建cin和cout间的正常关联
头文件fstream定义三个类型来支持文件IO:
1 ifstream 从一个给定文件读取数据
2 ofstream 向一个给定文件写入数据
3 fstream 读写给定文件
除了继承自iostream类型的行为之外:用“<<”或者“>>”来读写文件,可以用getline读取数据
fstream中定义的类型还增加了一些新的成员来管理与流关联的文件。
fstream特有的操作:
fstream fstrm; //创建一个未绑定的文件流
fstream fstrm(s); //创建一个fstream ,并打开名为s的文件
fstream fstrm(s,mode); //按指定mode打开文件
fstrm.open(s) //打开名为s的文件,并将文件与fstrm绑定,返回void
fstrm.close(s) //关闭与fstrm绑定的文件,返回void
fstrm.is_open() //返回一个bool值,指出与fstrm关联的文件是否成功打开且尚未关闭
当我们想要读写一个文件时,可以定义一个文件流对象,并将对象与文件关联起来
每个文件流对象都定义了一个名为open的成员函数,它完成一些系统相关的操作,来定位给定的文件,并视情况打开为读或写模式。
创建文件流对象时,我们可以提供文件名(可选)。如果提供了文件名,open会自动被调用
#include
ifstream in(ifile); //提供文件名,open会自动被调用,构造一个ifstream,关联对象与给定文件,打开给定文件
ofstream out; //输出文件流未关联到任何文件
在c++11新标准中,文件名既可以是库类型string对象,也可以是c风格字符数组。
用fstream代替iostream &
在要求使用基类型对象的地方,我们可以继承类型的对象来替代。
接收一个iostream类型引用或指针参数的函数,可以用一个对应的fstream或sstream类型来调用
成员函数open和close
#include
ifstream in(ifile); //构筑一个istream并打开给定文件
ofstream out; //输出文件流未与任何文件相关联
out.open(ifile+".copy"); //打开指定文件
如果调动open失败,failbit会被置位。因为调用open可能失败,进行open是否成功的检测通常是一个好习惯。
一旦一个文件流已经打开,它就保持与对应文件的关联。对一个已经打开的文件流调用open会失败,并会导致failbit被置位。
为了将文件流关联到另外一个文件,必须首先关闭已经关联的文件。
in.close(); //先关闭文件
in.open(ifile+"2"); //打开另一个文件
当一个fstream对象被销毁时,close会自动被调用
每个流都有一个关联的文件模式,用来指出如何使用文件
in | 以读方式打开 |
---|---|
out | 以写方式打开 |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行IO |
无论用哪些方式打开文件,都可以指定文件模式。调用open打开文件时可以,用一个文件名初始化流来隐式打开文件时也可以。
每个文件流类型都定义了一个默认的文件模式,当我们未指定文件模式时,就使用此默认模式:
①与ifstream关联的文件默认以in模式打开
②与ofstream关联的文件默认以out模式打开
③与fstream关联的文件默认以in和out模式打开
以out模式打开文件会丢弃已有数据
默认情况下,我们打开一个ofstream时,文件的内容会被丢弃
阻止一个ofstream清空给定文件内容的方法是同时指定app模式
保留被ofstream打开的文件中已有数据的唯一方法是显式指定app或in模式
每次调用open时都会确定文件模式
每次打开文件时,都要设置文件模式,可能是显式地设置,也可能是隐式地设置。当程序未指定模式时,就使用默认值
sstream头文件定义了三个类型来支持IO内存,这些类型可以向string写入数据,从string读取数据,就像string是一个IO流一样。
stringstream特有的操作
#include
sstream strm; //strm是一个未绑定的stringstream对象。sstream是头文件sstream中定义的一个类型
sstream strm(s); //strm保存string s的一个拷贝。
strm.str(); //返回strm所保存的string的拷贝
strm.str(s); //将string s拷贝到strm中,返回void
当我们的某些工作是对整行文本进行处理,而其他一些工作是处理行内的单个单词,可以考虑使用istringstream
当我们逐步构造输出,希望最后一起打印时,ostringstream是很有用的。
c++使用标准库类来处理面向流的输入输出:
iostream处理控制台IO
fstream处理命名文件
stringstream完成内存string的IO
类fstream和sstream都继承自iostream,可以对基类进行的操作,也可以对派生类操作。
每个IO对象都维护一组条件状态,用来指出此对象上是否可以进行IO操作。如果遇到了错误,对象的状态变为失效,所有后续输入操作都不能执行,直到错误被纠正。标准库提供了一组函数,用来设置和检测这些状态。