C++ 学习笔记(二)(标准库类型 IO 篇)

前言:主要是自己学习过程的积累笔记,所以跳跃性比较强,建议先自学后拿来作为复习用。

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 开头的类型和函数是宽字符版本。

头文件类型
iostreamistream,wistream 从流读取数据
ostream,wostream 向流写入数据
iostream,wiostream 读写流
头文件类型
fstreamifstream,wifstream 从文件读取数据
ofstream,wofstream 向文件写入数据
fstream,wfstream 读写文件
头文件类型
sstreamistringstream,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::iostatestream 是一种 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 中的字符即可


希望本篇博客能对你有所帮助,也希望看官能动动小手点个赞哟~~。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值