前言:主要是自己学习过程的积累笔记,所以跳跃性比较强,建议先自学后拿来作为复习用。
文章目录
1 IO 类
1.1 IO库概述
常见的 IO 库设施:
- i s t r e a m istream istream (输入流)类型,提供输入操作。
- o s t r e a m ostream ostream (输出流)类型,提供输出操作。
- c i n cin cin ,一个 i s t r e a m istream istream 对象,从标准输入读取数据。
- c o u t cout cout ,一个 o s t r e a m ostream ostream 对象,向标准输出写入数据。
- c e r r cerr cerr,一个 o s t r e a m ostream ostream 对象,通常用于输出程序错误消息,写入到标准错误。
C++ 的四个全局流对象:cin、cout、cerr 以及 clog。
- > > >> >> 运算符,用来从一个 i s t r e a m istream istream 对象读取输入数据。
- < < << << 运算符,用来向一个 o s t r e a m ostream ostream 对象写入输出数据。
C++ 使用标准库类来处理面向流的输入和输出:
- iostream 处理控制台 IO。
- fstream 处理命名文件 IO。
- stringstream 完成内存 string 的 IO。
类 fstream 和 stringstream 都是继承自 iostream的,输入类继承自 istream,输出类继承自 ostream。因此可以在 istream(ostream) 对象上执行的操作,在 ifstream(ofstream) 或 istringstream(ostringstream) 对象上也可以执行。
每个 IO 对象都维护一组状态,用来指出此对象上是否可以进行 IO 操作。如果遇到了错误,比如输入流遇到了文件末尾,则对象的状态变为失效,所有后续输入操作都不能执行,直至错误被纠正。标准库提供了一组函数,用来设置和检测这些状态。
1.2 IO 库类型和头文件
w 开头的类型和函数是宽字符版本。
| 头文件 | 类型 |
|---|---|
| iostream | istream,wistream 从流读取数据 |
| ostream,wostream 向流写入数据 | |
| iostream,wiostream 读写流 |
| 头文件 | 类型 |
|---|---|
| fstream | ifstream,wifstream 从文件读取数据 |
| ofstream,wofstream 向文件写入数据 | |
| fstream,wfstream 读写文件 |
| 头文件 | 类型 |
|---|---|
| sstream | istringstream,wistringstream 从 string 读取数据 |
| ostringstream,wostringstream 向 string 写入数据 | |
| stringstream,wstringstream 读写 string |
由于 ifstream 和 istringstream 都继承自 istream,因此如何使用前者,就可以如何使用后两者。比如下列代码,我们如何使用 cin,就可以同样地使用后两个类型的对象:
ifstream ifcin; // 定义后两个类型的对象
istringstream iscin;
cin.getline(); // cin 能调用什么函数,后两个类型的对象就能调用什么函数
ifcin.getline();
iscin.getline();
1.3 IO 对象无拷贝或赋值
不能拷贝或对 IO 对象赋值:
ofstream out1, out2;
out1 = out2; // 错误:不能对流对象赋值
ofstream print(out1); // 错误:不能初始化 ofstream 对象
out2 = print(out2); // 错误:不能拷贝流对象
由于不能拷贝 IO 对象,因此通常以引用的方式传递和返回流,且不能是 const 的(读写 IO 对象会改变其状态)。
1.4 条件状态
IO 类定义了一些函数和标志,可以帮助我们访问和操纵流的条件状态,如下所示:
复位就是将状态位设置成0,置位就是设置成1。
| IO 库条件状态 | |
|---|---|
| stream::iostate | stream 是一种 IO 类型,在1.1中已经列出。iostate 是与机器无关的类型,提供了表达条件状态的完整功能 |
| stream::badbit | 用来指出流已崩溃 |
| stream::failbit | 用来指出一个 IO 操作失败了 |
| stream::eofbit | 用来指出流到达了文件结束 |
| stream::goodbit | 用来指出流未处于错误状态。此值保证为0 |
| s.eof() | 若流 s 的 eofbit 置位,则返回 true |
| s.fail() | 若流 s 的 failbit 或 badbit 置位,则返回 true |
| s.bad() | 若流 s 的 badbit 置位,则返回 true |
| s.good() | 若流 s 处于有效状态,则返回 true |
| s.clear() | 将流 s 中所有条件状态位复位,将流的状态设置为有效。返回 void |
| s.clear(flags) | 根据给定的 flags 标志位,将流 s 中对应条件状态位复位。flags 的类型为 stream::iostate。返回 void |
| s.setstate(flags) | 根据给定的 flags 标志位,将流 s 中对应条件状态位置位。flags 的类型为 stream::iostate。返回 void |
| s.rdstate() | 返回流 s 的当前条件状态,返回值类型为 stream::iostate |
流一旦发生错误,其后面的所有 IO 操作都会失败,只有当流处于无错状态时,我们才可以从它读取数据或者写入数据。确定一个流对象的状态的最简单的办法是将它当作一个条件来使用:
while (cin >> word)
几种标志位的含义:
- badbit 表示系统级错误,如不可恢复的读写错误,一旦其被置位,流就无法使用了。
- failbit 在发生可恢复错误后被置位,如期望读取数值却读出一个字符等错误。
- eofbit 在到达文件结束位置时被置位,failbit 也同时被置位。
- goodbit 的值为0,表示流未发生错误。
- 前三个标志位任何一个被置位,检测流状态的条件都会失败。
对应函数的返回值:
- bad、fail 和 eof 在对应状态位被置位时返回 true。
- badbit 被置位时,fail 也会返回 true。
- good 在所有错误位均未置位的情况下返回 true。
因为 eof 和 bad 操作只能表示特定的错误,所以使用 good 或 fail 是确定流的总体状态的正确方法,实际上经常将流当作条件使用的代码就是 !fail()。
1.5 管理输出缓冲
每个输出流都管理一个缓冲区,用来保存程序读写的数据。数据真正写到输出设备或文件称为缓冲刷新,刷新的原因有很多:
- 程序正常结束。
- 缓冲区满了,刷新后才能写入新的数据。
- 使用操纵符 endl 显式刷新缓冲区。
- 使用操纵符 unitbuf 设置流的内部状态,清空缓冲区。
1.5.1 操纵符 flush 和 ends
这两个操纵符和 endl 一样都能刷新缓冲区,具体的区别见如下代码:
cout << "hi" << endl; // 输出 hi 和一个换行,然后刷新缓冲区
cout << "hi" << flush; // 输出 hi,然后刷新缓冲区,不附加任何额外字符
cout << "hi" << ends; // 输出 hi 和一个空字符,然后刷新缓冲区
1.5.2 操纵符 unitbuf
unitbuf 操纵符会使得流在接下来的每次写操作之后都进行一次 flush 操作,而 nounitbuf 操纵符则重置流,使其恢复正常的缓冲区刷新机制:
cout << unitbuf; // 所有输出操作后都会立即刷新缓冲区
// 任何输出都立即刷新,无缓冲
cout << nounitbuf; // 回到正常的缓冲方式
但是要注意,如果程序异常终止了,输出缓冲区不会被刷新,它所输出的数据会停留在输出缓冲区中等待打印。
1.5.3 关联输入和输出流
当一个输入流被关联到输出流时,任何试图从输入流读取数据的操作都会先刷新关联的输出流,C++ 标准库将 cout 和 cin 关联在一起。因此 cin >> n; 会导致 cout 的缓冲区刷新。
当然,既可以将一个 istream 对象关联到另一个 ostream,也能将一个 ostream 关联到另一个 ostream;每个流同时最多关联到一个流,但多个流可以同时关联到同一个 ostream。
2 文件输入输出
头文件 fstream 定义了三个类型来支持文件的 IO:
- ifstream 从一个给定文件读取数据。
- ofstream 向一个给定文件写入数据。
- fstream 可以读写给定文件。
下表是 fstream、ifstream 和 ofstream 类型对象可调用的函数操作,其他 IO 类型不能调用:
| fstream 特有的操作 | |
|---|---|
| fstream fstrm; | 创建一个未绑定的文件流。头文件 fstream 中定义的一个类型 |
| fstream fstrm(s) | 创建一个 fstream,并打开名为 s 的文件。s 可以是 string 类型,或者是一个指向 C 语言字符串的指针,这些构造函数都是 explicit 的。默认的文件模式 mode 依赖于 fstream 的类型 |
| fstream fstrm(s, mode) | 与前一个构造函数类似,但按指定 mode 打开文件 |
| fstrm.open(s) | 打开名为 s 的文件,并将文件与 fstrm 绑定。s 可以是一个 string 或一个指向 C 语言字符串的指针。默认的文件 mode 依赖于 fstream 的类型。返回 void |
| fstrm.close() | 关闭与 fstrm 绑定的文件。返回 void |
| fstrm.is_open() | 返回一个 bool 值,指出与 fstrm 关联的文件是否成功打开且尚未关闭 |
2.1 使用文件流对象
读写文件需要定义一个文件流对象,将其与文件关联起来。每个文件流类都定义了一个名为 open 的成员函数。创建文件流对象时,可以选择提供文件名,此时 open 会被自动调用。文件名既可以是 string 对象,也可以是 C 语言的字符串,也可以是某一个路径下的文件:
ifstream in(infile); // 创建一个输入流 ifstream 对象并打开给定文件
ofstream out; // 输出文件流并未关联到任何文件
fstream inOut("C:\\file.txt");
不管是输入流还是输出流对象,都需要先打开(open 文件)才能进行读或写。
2.2 合理利用继承
由于继承机制的存在,我们可以将一个派生类(子类)对象当作其基类(父类)对象来使用。换句话说,在要求使用基类型对象的地方,可以用派生类型的对象来替代。比如函数有一个 iostream 类型的引用(或指针)形参,那么可以传递一个对应的 fstream(或 sstream)类型对象。
2.3 成员函数 open 和 close
如果文件流对象调用 open 失败,failbit 会被置位,也因此需要时常检测一下 open 是否成功:
ofstream out; // 输出文件流未与任何文件相关联
out.open(ifile); // 打开指定文件
if (out) // 检查 open 是否成功,成功了才能往文件里写
一旦一个文件流已经打开,它就保持与对应文件的关联。同时,对一个已经打开的文件调用 open 会失败,并导致 failbit 被置位,随后试图使用文件流的操作都会失败。要想将一个文件流关联到另外一个文件,必须先关闭已经关联的文件。如果 open 成功,open 会设置流的状态,使得 good() 为 true。
当文件流对象被销毁时(比如循环内定义的局部对象退出函数时会被销毁),与其关联的文件会被自动关闭(close 会自动被调用)。
练习1
编写函数,以读模式打开一个文件,将其内容读入到一个 string 的 vector 中,将每一行作为一个独立的元素存于 vector 中。
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
void read(const string &infile, vector<string> &vec)
{ // 对读取的文件不做修改,所以定义成常量引用;vector 需要添加元素,所以直接传入引用
ifstream in(infile); // 将输入流关联到文件上,自动调用 open
if (in) // 检查是否打开成功
{
string temp; // temp 存储一行的字符
while (getline(in, temp)) // 依据输入流,逐行读取内容
vec.push_back(temp); // 将新的 string 对象插入到 vec 的末尾
}
}
int main()
{
string file = "myBook.txt";
vector<string> vec;
read(file, vec); // 调用 read 函数
for (auto i : vec) // 使用范围 for 语句输出 vec 中的内容
cout << i << endl;
return 0;
}
myBook.txt 的内容如下:
first line
second line
third line
输出的结果如图:

练习2
重写上面的程序,将每个单词作为一个独立的元素进行存储。此时只需要将 read 函数中的 while 循环的判定做一个修改即可:
void read(const string &infile, vector<string> &vec)
{ // 对读取的文件不做修改,所以定义成常量引用;vector 需要添加元素,所以直接传入引用
ifstream in(infile); // 将输入流关联到文件上,自动调用 open
if (in) // 检查是否打开成功
{
string temp; // temp 存储一行的字符
while (in >> temp) // 依据输入流读取内容,遇到空白就结束本次输入
vec.push_back(temp); // 将新的 string 对象插入到 vec 的末尾
}
}
输出结果如图:

2.4 文件模式
2.4.1 文件模式的使用
每个流都有一个关联的文件模式,用于指出如何使用文件。
| 文件模式 | file mode |
|---|---|
| in | 以读方式打开 |
| out | 以写方式打开 |
| app | 每次写操作前均定位到文件末尾 |
| ate | 打开文件后立即定位到文件末尾 |
| trunc | 截断(清空)文件 |
| binary | 以二进制方式进行 IO |
任何打开文件的方式都能设定文件模式,但有如下限制:
- 只有 ifstream 或 fstream 对象可以设定 in。
- 只有 ofstream 或 fstream 对象可以设定 out。
- 只有当 out 被设定时才可以设定 trunc。
- 在 app 模式下, 即使没有显示设定 out,文件也总是以输出方式被打开。设定 app 的前提是没有设定 trunc。
- 即使没有设定 trunc,以 out 打开的文件也会被截断。要想不截断必须同时设定 app,但这样只能将数据写到文件末尾;或者同时设定 in,即打开文件同时进行读写操作。
- ate 和 binary 可用于任何类型的文件流对象,且可以与其他任何文件模式组合使用。
每个文件流都有默认的文件模式。ifstream 关联的文件默认以 in 打开,ofstream 为 out,而 fstream 则为 in 和 out。
2.4.2 以 out 模式打开文件会丢弃已有数据
如前面所说,打开一个 ofstream 时,文件的内容会被清空(截断)。保留被 ofstream 打开的文件中已有数据的唯一方法是设定 app 或 in 模式(注意 ofstream:: 不能漏):
// 下面三条语句里, file1 都会被截断
ofstream out("file1"); // 隐式地以 out 模式打开并截断文件
ofstream out1("file1", ofstream::out); // 隐式地截断文件
ofstream out2("file1", ofstream::out | ofstream::trunc); // 显式地截断文件
// 为了保留文件内容,必须显式设定 app 或 in 模式
ofstream out3("file1", ofstream::app); // 隐式地以 out 模式打开并追加
ofstream out5("file1", ofstream::out | ofstream::app); // 显式地打开并追加
ofstream out6("file1", ofstream::out | ofstream::in); // 显式地输出并写入
ofstream out3("file1", ofstream::in); // 错误:ofstream 对象不能单独设定 in
对于一个文件流,每次调用 open 时都会确定新的文件模式(open 的前提是文件是关闭的)。
3 string 流
sstream 头文件定义了三个类型来支持内存 IO:
- istringstream 从 string 读取数据。
- ostringstream 向 string 写入数据。
- stringstream 包含上述两个功能。
同理,sstream 继承自 iostream,因此它可以使用 iostream 中的所有操作。除此之外,它也有着特有的操作:
| stringstream 特有的操作 | |
|---|---|
| sstream strm; | strm 是一个未绑定的 stringstream 对象 |
| sstream strm(s) | strm 是一个 sstream 对象,保存 string s 的一个拷贝。此构造函数是 explicit 的 |
| strm.str() | 返回 strm 所保存的 string 的拷贝 |
| strm.str(s) | 将 string s 拷贝到 strm 中。返回 void |
3.1 使用 istringstream
istringstream 的使用场景:对整行文本进行处理,以及处理行内的单个单词等情况。类似于2.3中的练习2,只不过此时的 in 是用 istringstream 来定义的。
练习
编写程序,将来自一个文件中的行保存在一个 vector< string> 中。然后使用一个 istringstream 从 vector 读取数据元素,每次读取一个单词。
由于 stringstream 下的类型都是对 string 类型的对象进行处理,所以文件依然用 ifstream 来进行读取,用 istringstream 来读取 vector 的数据元素(文件中的每一行),具体代码如下:
#include <iostream>
#include <sstream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
void read(const string &infile, vector<string> &vec)
{ // 对读取的文件不做修改,所以定义成常量引用;vector 需要添加元素,所以直接传入引用
ifstream in(infile); // 将输入流关联到文件上,自动调用 open
if (in) // 检查是否打开成功
{
string temp; // temp 保存一行的内容
while (getline(in, temp)) // 依据输入流,逐行读取并存入 temp
vec.push_back(temp); // 将新的 string 对象插入到 vec 的末尾
}
}
int main()
{
string file = "myBook.txt";
vector<string> vec;
read(file, vec);
string word; // 保存单词
for (const auto &it : vec) // 范围 for 语句遍历 vec,it 是 string 类型
{ // 因为不需要改变 vec 中的内容,所以定义成常量引用
istringstream isn(it); // 将记录绑定到当前行
while (isn >> word) // 读取每一个单词
cout << word << " "; // 打印出来
}
return 0;
}
输出结果如下:

3.2 使用 ostringstream
ostringstream 先处理好数据,筛选出有用的信息然后存入 ostringstream 对象,最后再利用 str() 函数集中打印。我们使用标准运算符 << 向对象写入数据,但要注意的是,所有数据实际上是转换为 string 后再向对象添加字符的:
ostringstream out; // 定义一个 ostringstream 对象
while (condition) // 假定在循环中处理数据
{
...
out << " " << data; // 将符合要求的数据写入 out
... // data 不管是什么类型都会先转换成 string 再存入
}
cout << onut.str() << endl; // 最后利用 str() 函数打印保存在 out 中的字符即可
希望本篇博客能对你有所帮助,也希望看官能动动小手点个赞哟~~。
708

被折叠的 条评论
为什么被折叠?



