第二部分(C++标准库)
1).了解C++
标准库设施很重要。
2).除了使用标准库读写与控制台相关联的流之外,学习其他的库类型,帮助我们读写命名文件以及完成string
对象的内存IO操作。
3).标准库的核心是很多容器类和一族泛型算法,这些设施可以帮助我们编写简洁高效的程序。
4).标准库会去关注那些簿记操作的细节,特别是内存管理,这样我们的程序就可以将全部的注意力投入到需要求解的问题上。
5).vector
的更多相关内容,以及顺序容器;string
支持的操作,可以将string
看成是只包含字符元素的特殊容器(它其实不是容器,只是很像);string
支持很多容器操作,但不是全部。
6).泛型算法,这类算法通常在顺序容器一定范围内的元素上或者其他类型的序列上进行操作。例如,标准库提供了copy
算法,完成一个序列到另一个序列的元素的拷贝;find
算法,实现给定元素的查找……
7).泛型算法的通用性体现在两个方面,
- 可以用于不同类型的序列。
- 对序列中元素的类型限制小,大多数类型都是允许的。
8).关联容器,关联容器的元素是通过关键字来访问的。关联容器支持很多的顺序容器的操作,也定义了自己的一些操作。
9).动态内存管理相关的语言特性,和库设施。智能指针的一个标准版本,它是新标准库中最重要的类之一。通过使用智能指针,可以大幅度地提高使用动态内存代码的鲁棒性。
10).最后会给出一个较大例子,使用第二部分介绍的所有标准库设施。
第八章(IO库)
1).C++语言不直接处理输入和输出,而是通过定义一族定义在标准库中的类型来处理IO。这些类支持从设备中读取数据,向设备中写入数据的IO操作。设备可以是文件或者控制台窗口等,还有一些类允许内存IO,即从string
中读取数据,向string
写入数据。
2).IO库定义了读写内置类型值操作,此外,一些类,如string
,通常也会定义类似的IO操作来读写自己的操作。(内置类型由标准库类型处理IO)
3).如何编写自己的输入输出运算符?如何控制输出格式以及如何对文件进行随机访问。
4).IO库设施,
istream
类型,提供输入操作,cin
是一个istream
对象,从标准输入中读取数据,ostream
对象,提供写入操作,cout
是它的一个对象,向标准输出中写入对象;cerr
也是一个ostream
对象,通常用于程序的错误消息,写入到标准错误中。>>
,用来从一个istream
对象读取数据,<<
,用来向一个ostream
对象中写入数据,getline
函数,从给定的istream
对象中读取一行数据,存入到给定的string
对象。
/1.IO类
1).目前为止,我们使用过的IO类型和对象都是操纵char数据的。默认情况下这些对象都是关联到用户的控制台窗口的。
2).IO操作处理string
中的字符会很方便,此外应用程序还可能读写需要宽字符支持的语言。
3).为了支持以上的操作,标准库除了定义istream
和ostream
外还有一些其他的IO类型。(一个类类型就在一个头文件中),分别定义在三个独立的头文件中
iostream
定义了用于读写流的基本类型,fstream
定义了向读写命名文件的类型,ifstream,ofstream,ftream
,sstream
定义了向内存string
读写的操作。istringstream,ostringstream,stringstream
。
4).为了支持宽字符语言,标准库还特别定义了一组类型和对象来操纵wchat_t
类型的数据。
- 宽字符版本的类型和函数的名字以一个W开始。例如,
wcin,wcout,wcerr
分别是cin,cout,cerr
的宽字符版对象。 - 宽字符版本的类型和对象和其对应的普通
char
版本的类型定义在同一个头文件中。
5).注:流分为两类,char
字符集,wchar_t
字符集。
6).IO类型间的关系,概念上,设备和字符大小都不会影响我们的IO操作。
- 我们可以用>读取数据>,而不管是控制台窗口或者磁盘文件或者
string
中读取数据。 - 同样的,我们也不用管读取的字符能存入一个
char
对象内,还是需要一个wchar_t
对象来存储。
7).标准库是通过继承机制,来做到这一点的。利用模板我们可以使用具有继承关系的类,而不必了解继承机制如何工作。
8).继承机制,可以使得我们可以声明一个特定的类型继承另一个类。我们通常可以将一个派生类(继承类)对象当作其基类(所继承的类)对象来使用。例如,ifstream
和istringstream
都继承自istream
,所以我们怎么使用cin
,就可以怎么使用继承类的对象。getline
,>>
等。同理cout
,如何使用,ofstream
和ostringstream
的对象就如何使用。(cin->char in;cout->char out
);
9).以上说明,本节以下介绍的都可以无差别的应用于普通流,文件流,string
流,以及char
和wchar_t
版本。
//1.IO对象无拷贝或赋值
1).注意:
{
ofstream out1,out2;
out1 = out2;//不可以对流对象进行拷贝
oftream print (ofstream);
//以上定义一个函数,但是,我们没有办法对一个流形参
//进行赋值,也没有办法对一个流返回值进行赋值。
out2 = print (out2);//错误,因为我们没有办法
//对流形参赋值,对流返回值进行返回,更不能对流对象
//赋值
}
2).进行IO操作的函数通常以引用的方式传递或者返回流。并且注意到,读写一个IO对象会改变其状态,因此传递和返回的引用不能是const。
//2.条件状态
1).IO操作与生俱来的一个问题就是,可能发生错误。
- 可恢复的错误;
- 发生在系统深处的错误,超出了应用程序可以修正的范围;
2).IO类定义了一些函数和标志,帮助我们访问和和操纵流的条件状态。
IO库的条件状态(s表示流对象) | 简要介绍 |
---|---|
str::iostate | str是一种IO类型,(istream,ostream……);iostate是一种机器相关的类型,提供了条件状态的所有功能 |
str::badbit | 流已经崩溃 |
str::failbit | 一个IO操作失败 |
str::eofbit | 流到达了文件的末尾 |
str::goodbit | 流没有出现错误,为零 |
s.eof() | 如果eofbit置位(为1),那么返回true |
s.good() | 流有效,返回true |
s.bad() | |
s.fail() | 如果failbit或者badbit置为,返回1 |
s.clear() | 将流的状态全部复位(0),流的状态为有效,返回void |
s.clear(str::iostate) | 将对应的(failbit和badbit两个条件为置0)条件为复位,`iostate & ~cin.badbit & ~cin.failbit |
s.setstate(str::state) | 置为指定的状态,其实与上面一个没有什么区别 |
s.rdstate() | 读取流当前的状态 |
3).一个简单的错误的例子就是,int i;cin >> i;//如果我们输入字符或者文件结束标志,cin就会进入错误状态
。
4).一个流一旦发生错误,其后序的IO操作都会失败,只有一个流处于无错状态时,我们才可以从它读取数据,向它写入数据。
5).由于一个流可能处于错误状态,所以代码应该先检查流是否处于良好的状态再对它进行使用。一个简单的检查方法就是,将它作为条件,while (cin >> word)
。
6).但是将流作为条件,只能知道流是否有效,而不能告诉我们流到底发生了什么。对于不同的问题,解决的办法是不一样的。
7).IO类型中的与机器无关的iostate应该作为位的集合来使用,就是进行一系列的位的操作。
8).注意,
- 四个iostate是constexpr值,用来配合位运算。
- badbit表示的是系统级别的错误,例如不可恢复的读写错误,它一旦被置位,流就不可以在使用了
- failbit表示的是可以修正的错误,例如上述的期望一个int类型的但输入的是一个字符串的错误.流可以继续使用
- eofbit和failbit都会被置位,当到达文件结束时。
- goobit的值为0,表示流是有效的。
- badbit,failbit,eofbit中的任何一个被置位(为1),检测流都是条件都是失败的。就是说不知道具体的原因。
9).auto state = s.rdstate();//注意auto的使用。
//3.管理输出缓冲
1).每一个输出流都管理一个缓冲区,用来保存程序的读写的数据,如果执行os << "hello world!";
,文本可能会立即地打印出来,也可能被操作系统保存在缓冲区中,随后在进行打印。
2).缓冲区机制的优点,有了缓冲区机制,操作系统可以将程序的多个输出操作组合成单一的系统级别写操作。由于设备的写操作可能很耗时间,允许系统将多个输出操作组合在一起进行单一的设备写操作可以带来很大的性能提升。
3).导致缓冲刷新**(数据真正地写到输出设备或文件)**的原因有很多,
- 程序的正常结束,**作为main函数中return操作的一部分,缓冲刷新被执行。
- 缓冲满时,需要刷新缓冲,而后的数据才能继续写入缓冲区中。
- 可以用操纵符
endl
显式地指出进行缓冲去的刷新。 - 在每一个输出操作之后,我们是可以用操纵符
unitbuf
设置刷新流的内部状态,来清空缓冲区。默认情况下,对于cerr
时设置unitbuf
的,因此,写到cerr
的内容时都是立即刷新的。 - 一个输出流可能被关联到另一个流。在这种情况下,当读写被关联的流时,关联到的流的缓冲区会被刷新,例如,默认情况下,
cin
和cerr
都关联到cout
,因此进行读cin
或者写cerr
都会导致cout
的缓冲区被刷新。
4).endl
操纵符进行刷新缓冲区并进行换行的工作。
ends
操纵符,向缓冲区插入一个空的字符,然后刷新缓冲区。flush
操纵符,刷新缓冲区,但是没有输出额外的符号。uintbuf
操纵符,想要每一次输出操作之后都刷新缓冲区,可以使用unitbuf
,它告诉流在接下来的每一个写的操作之后都进行一次``flush操作。而
nounitbuf`操纵符则重置流,使其恢复使用正常的系统管理的缓冲区刷新机制。
{
cout << unitbuf;//以下所有的输出操作都立即进行刷新缓冲区操作;
cout << nounitbuf;//回到正常的缓冲方式
}
5).如果程序是异常终止的,那么输出缓冲区是不会被刷新的。当一个程序崩溃时,它所输出的数据很可能停留在输出缓冲区中等待打印。所以,当调试一个已经崩溃的程序时,需要确认那些你认为已经输出的数据确实已经刷新了。否则,可能将大量的时间浪费在追踪代码为什么没有执行上(即,输出语句为什么一直没有执行呢?),而实际上,代码已经执行了,只是程序崩溃之后缓冲区没有被刷新,数据数据被挂起没有打印而已。
6).关联输入和输出流,当一个输入流被关联到输出流时,去输入流中读取数据的操作,都会先刷新你关联的输出流。因为标准库将cout
和cin
关联在一起的。所以一旦cin
进行写的操作,cout
的缓冲区就会被刷新。(谁先谁后呢?)
7).答案是,先刷新再读取数据。所有的交互式系统通常应该关联输入和输出。这样的好处就是,所有的输出,包括用户的提示信息,都会再读操作之前就被打印出来。
8).tie()
IO类的成员函数,用来关联流,注意目前是只有关联到ostream的流。
- 第一个版本是不接受参数,返回指向输出流的指针。如果该对象 当前关联到一个输出流,则返回的就是指向这个输出流的指针,如果没有关联到流,返回空指针。
- 第二个版本,接受一个指向ostream的指针。将自己关联到这个ostream。返回同上(注意是未关联之前的,否则课本有误)。
9).注意,
- 我们既可以将istream关联到一个ostream,也可以将一个ostream关联到一个ostream。
- 解开一个流的指针,我们可以传递一个空指针。
ostream *old_tie = cin.tie(nullptr);//返回的是cin之前关联的ostream
- 每一个最多关联到一个流,但是同一个流可以有多个流同时关联。
/2.文件输入输出
1).三个IO类型。
ifstream
,从一个给定的文件读取数据。ofstream
,向一个给定的文件写入数据。fstream
,可以读写给定的文件。
2).由于继承性质,直接当成cin
和cout
使用就可以。
3).fstrwam
,ofstream
,ifstream
,特有的操作。
名称 | 作用 |
---|---|
fstream fstrm | 创建一个未绑定的文件流。fstream 是头文件fstream 中定义的一个类型 |
fstream fstrm(s) | 创建一个fstream ,并且打开一个名字为s的文件。s可以是string 类型或者是指向c风格的字符串。这下构造函数都是explicit 。默认的文件模式依赖于fstream 的类型 |
ftream fstrm(s,mode) | 同上,但是指定了模式打开文件 |
fstrm.open() | 打开名字为s的文件,并且将文件与fstrm 绑定。s同上。返回void |
fstrm.close() | 关闭与fstrm 绑定的文件,返回void |
fstrm.is_open() | 返回一个bool 值,指出与fstrm 关关联的文件是否成功打开并且尚未关闭 |
//1.使用文件流对象
1).两个模式,读或者写。
2).对象的创建。
{
ifstream in(infile);//open自动被调用,构造一个对象,并且打开指定的文件(关联)
ostream out;//只是创建一个对象。
}
3).在一个使用基类类型的地方,我们可以使用继承类型对象来代替。**意味着我们可以,用fstream
或者sstream
类型来调用接受的是iostream
类型的函数。**例如,在之前的read()``print()
函数中,我们函数的形参是iostream&
但是我们可以给他们传递fstream
,并且我们注意到,fstream
已经和文件进行了绑定。
4).注意,
- 由于
open
会失败,且失败时,failbit
会置位所以我们可以用简单的if
来判断是否成功的打开文件了。 - 对于一个已经打开文件的流调用
open
会导致fialbit
被置位。随后试图使用文件流的操作都会失败。 - 为了将文件流(已经关联一个文件了)关联到另一个文件中,我们必须先关闭已经关联的文件,
in.close();in.open(ifile+"2");//打开第二个输入文件
- 如果
open
成功,那么open会设置流的状态,使得good()
变为true
。 - 一个
fstream
对象离开其作用域时,与之关联的文件会自动关闭。也就是说,当一个fstream
对象被销毁时,close
会自动被调用。(自动析构)
练习8.4,
getline(fstream,string)
;- 迭代器*it;不修改的迭代器,
const_iterator
; - 文件名,要包括后缀。
练习8.7,
- 输出到指定文件,它会自己创建文件。
- 用法和cout没有什么区别。
练习8.8
- 默认情况下是,每一次都进行覆盖。
- 在后面加上参数,进行追加模式。
ofstream::app
;
//2.文件模式
1).每一个流都有一个关联的文件模式,来指出如何使用文件。
方式 | 含义 |
---|---|
in | 以读的方式打开 |
out | 以写的方式打开 |
app | 每一次操作前都定位到文件末尾,也就是说,不会将前面的内容进行覆盖 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行IO,这个需要特别指定。 |
2).指定模式的时机。
- 调用
open
打开文件时,第二个参数是mode。 - 用文件名字进行初始化时,也是第二个参数。
3).注意,
- 只可以对
ofstream
和fstream
设定out
模式。 - 只可以对
ifstteam
和fstream
设定in
模式。 - 只可以在
out
模式下才可以设定trunc
。 - 只要
trunc
没有被设定,就可以设定app
模式。在app
模式下。即使没有显式地指出位out
模式,文件也默认是out
。 - 默认情况下,就是
trunc
,(即文件会被覆盖),就是说以out打开的文件会被截断。为了保留以out
模式打开的文件的聂荣,必须同时指定app
或者,同时指定in
,即打开文件同时进行读写操作。 ate
和binary
可以用于任何类型的文件流对象中,可以和其他任何文件模式一起使用。
4).每一个文件流类型都默认了一个文件模式。没有指定就是默认的形式。
ifstream
,就是in
。ofstream
,就是out
。fstream
,就是in
和out
。
5).以下情况,文件都会被截断。
{
ofstream out1 ("data.out");
ofstream out2 ("data.out",ofstream::out);
ofstream out3 ("data,out",ofstream::out | ofstream::trunc);
}
6).以下情况,文件内容会被保留。
{
ofstream out1 ("data.out",ofstream::app);
ofstream out2 ("data.out",ofstream::out|ofstream::app);
//我们可以猜测,ofstream设置了两个的默认实参
}
7).open()
也是一样的用法。第二个参数指明模式,没有则默认是out
和trunc
(对于ofstream)。
/3.string流
1).sstream
头文件中定义了三个类型来支持内存IO。string
就一个IO流一样。(类比文件的绑定。)
istringstream
从string
中读取数据。ostringstream
向string
写入数据。stringstream
既可以向string
写也可以读数据。
2).除了继承而来的操作,sstream还定义了一些特殊操作。
名称 | 作用 |
---|---|
sstream strm; | strm 是一个未绑定的sstream 对象,sstream 是头文件sstrem 定义的一个类型。 |
sstream strm(s); | strm 是一个stream 对象,保存一个string s 的拷贝,此构造函数时explicit 的,不支持隐式转换。 |
strm.str() | 返回的是strm所保存的拷贝 |
strm.str(s) | 将string s 拷贝到strm中,返回void 。(也就是说相当于将它重新构造) |
//1.使用istringstream
1).对于整行文本中的单个的单词进行处理。
{
//个人信息类
struct PeopleInfo{
string name;
vector<string> phone;
};
ifstream in ("data.in");
if(!in)//判断文件是否成功打开
{
cerr……
}
vector<PeopleInfo> people;
string line,word;//存放行和每一个电话号码,以便进行循环。
//每一次从文件中读取一行。
while (getline(in,line))
{
PersonInfo person;//方便进行操作
istringstream record (line);//绑定string流
record >> person.name;
//存放每一个电话号码
while(record >> word)
{
person.phone.push_back(word);
}
//一个人的信息记录完毕。
people.push_back(person);
}
in.close();//关闭文件。
}
2).注意。
- 对于输入流,它会依次读取下去。
- 对一行
string
进行绑定就是为了实现,一行中单个单词的提取工作。 - 与
cin
一样的,record
(string流,一样的到达文件末尾一样的是failbit
置位)
//2.使用ostringsrtream
1).验证每一个电话号号码都有效,我们才输出这个人。通过内存IO,达到简化目的。
{
for (const auto &entry : people)
{
ostringstream formatted,badNums;
for (const auto &nums : entry.phone)
{
if (!valid (nums))
badNums << " " << nums;
else
{
formatted << " " << format(nums);
}
}
if (badNums.str().empty) //没有错误数
os << entry.name << " "
<< formatted.str() << endl;
else
cerr << "input error:" << entry.name
<< "invalid number(s) " << badNums.str() << endl;
}
}
2).分析。
- 通过一种暂存机制,来达到检验的目的。
- 注意到,
str()
返回的是一个string
的拷贝,可以调用empty()
, string
流的写入操作实际上就是向string
对象添加字符。不仅如此,配合str()
还可以输出。
/4.总结
1).C++使用标准库类型,处理面向流的输入和输出。
iostream
处理控制台IOfstream
处理文件IOstringstream
处理内存IO
2).文件流,ofstream,ifstream,fstream的对象。
3).字符串流,istringstream,ostringstream,sstream的对象。