# Chapter 8 The IO Library
# 前面章节已经在用的 IO 库设施
- istream:输入流类型,提供输入操作。
- ostream:输出流类型,提供输出操作
- cin:一个
istream
对象,从标准输入读取数据。 - cout:一个
ostream
对象,向标准输出写入数据。 - cerr:一个
ostream
对象,向标准错误写入消息。 - >> 运算符:用来从一个
istream
对象中读取输入数据。 - << 运算符:用来向一个
ostream
对象中写入输出数据。 - getline 函数:从一个给定的
istream
对象中读取一行数据,存入到一个给定的string
对象中。
# The IO Classes
# IO 类
# 标准库定义的 IO 类型
iostream
头文件:从标准流中读写数据,istream
、ostream
等。fstream
头文件:从文件中读写数据,ifstream
、ofstream
等。sstream
头文件:从字符串中读写数据,istringstream
、ostringstream
# IO 对象不可复制或赋值
- 1.IO 对象不能存在容器里.
- 2. 形参和返回类型也不能是流类型。
- 3. 形参和返回类型一般是流的引用。
- 4. 读写一个 IO 对象会改变其状态,因此传递和返回的引用不能是
const
的。
# 条件状态
状态 | 解释 |
---|---|
strm:iostate | 是一种机器无关的类型,提供了表达条件状态的完整功能 |
strm:badbit | 用来指出流已经崩溃 |
strm:failbit | 用来指出一个 IO 操作失败了 |
strm:eofbit | 用来指出流到达了文件结束 |
strm:goodbit | 用来指出流未处于错误状态,此值保证为零 |
s.eof() | 若流 s 的 eofbit 置位,则返回 true |
s.fail() | 若流 s 的 failbit 置位,则返回 true |
s.bad() | 若流 s 的 badbit 置位,则返回 true |
s.good() | 若流 s 处于有效状态,则返回 true |
s.clear() | 将流 s 中所有条件状态位复位,将流的状态设置成有效,返回 void |
s.clear(flags) | 将流 s 中指定的条件状态位复位,返回 void |
s.setstate(flags) | 根据给定的标志位,将流 s 中对应的条件状态位置位,返回 void |
s.rdstate() | 返回流 s 的当前条件状态,返回值类型为 strm::iostate |
上表中, strm
是一种 IO 类型,(如 istream
), s
是一个流对象。
# 管理输出缓冲
- 每个输出流都管理一个缓冲区,执行输出的代码,文本串可能立即打印出来,也可能被操作系统保存在缓冲区内,随后再打印。
- 刷新缓冲区,可以使用如下 IO 操纵符:
endl
:输出一个换行符并刷新缓冲区。flush
:刷新流,单不添加任何字符。ends
:在缓冲区插入空字符null
,然后刷新。unitbuf
:告诉流接下来每次操作之后都要进行一次flush
操作。nounitbuf
:回到正常的缓冲方式。
# Exercise 8.1
编写函数,接受一个
istream&
参数,返回值类型也是istream&
。此函数须从给定流中读取数据,直至遇到文件结束标识时停止。它将读取的数据打印在标准输出上。完成这些操作后,在返回流之前,对流进行复位,使其处于有效状态。
解:
std::istream& func(std::istream &is) | |
{ | |
std::string buf; | |
while (is >> buf) | |
std::cout << buf << std::endl; | |
is.clear(); | |
return is; | |
} |
# Exercise 8.2
测试函数,调用参数为
cin
。
解:
#include <iostream> | |
using std::istream; | |
istream& func(istream &is) | |
{ | |
std::string buf; | |
while (is >> buf) | |
std::cout << buf << std::endl; | |
is.clear(); | |
return is; | |
} | |
int main() | |
{ | |
istream& is = func(std::cin); | |
std::cout << is.rdstate() << std::endl; | |
return 0; | |
} |
# Exercise 8.3
什么情况下,下面的
while
循环会终止?
while (cin >> i) /* ... */ |
解:
如 badbit
、 failbit
、 eofbit
的任一个被置位,那么检测流状态的条件会失败。
# File Input and Output
# 文件输入输出
- 头文件
fstream
定义了三个类型来支持文件 IO:ifstream
从一个给定文件读取数据。ofstream
向一个给定文件写入数据。fstream
可以读写给定文件。
- 文件流:需要读写文件时,必须定义自己的文件流对象,并绑定在需要的文件上。
# fstream 特有的操作
操作 | 解释 |
---|---|
fstream fstrm; | 创建一个未绑定的文件流。 |
fstream fstrm(s); | 创建一个文件流,并打开名为 s 的文件, s 可以是 string 也可以是 char 指针 |
fstream fstrm(s, mode); | 与前一个构造函数类似,但按指定 mode 打开文件 |
fstrm.open(s) | 打开名为 s 的文件,并和 fstrm 绑定 |
fstrm.close() | 关闭和 fstrm 绑定的文件 |
fstrm.is_open() | 返回一个 bool 值,指出与 fstrm 关联的文件是否成功打开且尚未关闭 |
上表中, fstream
是头文件 fstream
中定义的一个类型, fstrm
是一个文件流对象。
# 文件模式
文件模式 | 解释 |
---|---|
in | 以读的方式打开 |
out | 以写的方式打开 |
app | 每次写操作前均定位到文件末尾 |
ate | 打开文件后立即定位到文件末尾 |
trunc | 截断文件 |
binary | 以二进制方式进行 IO 操作。 |
# Exercise 8.4
编写函数,以读模式打开一个文件,将其内容读入到一个
string
的vector
中,将每一行作为一个独立的元素存于vector
中。
解:
void ReadFileToVec(const string& fileName, vector<string>& vec) | |
{ | |
ifstream ifs(fileName); | |
if (ifs) | |
{ | |
string buf; | |
while (getline(ifs, buf)) | |
vec.push_back(buf); | |
} | |
} |
# Exercise 8.5
重写上面的程序,将每个单词作为一个独立的元素进行存储。
解:
void ReadFileToVec(const string& fileName, vector<string>& vec) | |
{ | |
ifstream ifs(fileName); | |
if (ifs) | |
{ | |
string buf; | |
while (ifs >> buf) | |
vec.push_back(buf); | |
} | |
} |
# Exercise 8.6
重写 7.1.1 节的书店程序,从一个文件中读取交易记录。将文件名作为一个参数传递给
main
。
解:
#include <fstream> | |
#include <iostream> | |
#include "../ch07/ex7_26.h" | |
using std::ifstream; using std::cout; using std::endl; using std::cerr; | |
int main(int argc, char **argv) | |
{ | |
ifstream input(argv[1]); | |
Sales_data total; | |
if (read(input, total)) | |
{ | |
Sales_data trans; | |
while (read(input, trans)) | |
{ | |
if (total.isbn() == trans.isbn()) | |
total.combine(trans); | |
else | |
{ | |
print(cout, total) << endl; | |
total = trans; | |
} | |
} | |
print(cout, total) << endl; | |
} | |
else | |
{ | |
cerr << "No data?!" << endl; | |
} | |
return 0; | |
} |
# Exercise 8.7
修改上一节的书店程序,将结果保存到一个文件中。将输出文件名作为第二个参数传递给
main
函数。
解:
#include <fstream> | |
#include <iostream> | |
#include "../ch07/ex7_26.h" | |
using std::ifstream; using std::ofstream; using std::endl; using std::cerr; | |
int main(int argc, char **argv) | |
{ | |
ifstream input(argv[1]); | |
ofstream output(argv[2]); | |
Sales_data total; | |
if (read(input, total)) | |
{ | |
Sales_data trans; | |
while (read(input, trans)) | |
{ | |
if (total.isbn() == trans.isbn()) | |
total.combine(trans); | |
else | |
{ | |
print(output, total) << endl; | |
total = trans; | |
} | |
} | |
print(output, total) << endl; | |
} | |
else | |
{ | |
cerr << "No data?!" << endl; | |
} | |
return 0; | |
} |
# Exercise 8.8
修改上一题的程序,将结果追加到给定的文件末尾。对同一个输出文件,运行程序至少两次,检验数据是否得以保留。
解:
#include <fstream> | |
#include <iostream> | |
#include "../ch07/ex7_26.h" | |
using std::ifstream; using std::ofstream; using std::endl; using std::cerr; | |
int main(int argc, char **argv) | |
{ | |
ifstream input(argv[1]); | |
ofstream output(argv[2], ofstream::app); | |
Sales_data total; | |
if (read(input, total)) | |
{ | |
Sales_data trans; | |
while (read(input, trans)) | |
{ | |
if (total.isbn() == trans.isbn()) | |
total.combine(trans); | |
else | |
{ | |
print(output, total) << endl; | |
total = trans; | |
} | |
} | |
print(output, total) << endl; | |
} | |
else | |
{ | |
cerr << "No data?!" << endl; | |
} | |
return 0; | |
} |
# string Streams
# string 流
- 头文件
sstream
定义了三个类型来支持内存 IO:istringstream
从string
读取数据。ostringstream
向string
写入数据。stringstream
可以读写给定string
。
# stringstream 特有的操作
操作 | 解释 |
---|---|
sstream strm | 定义一个未绑定的 stringstream 对象 |
sstream strm(s) | 用 s 初始化对象 |
strm.str() | 返回 strm 所保存的 string 的拷贝 |
strm.str(s) | 将 s 拷贝到 strm 中,返回 void |
上表中 sstream
是头文件 sstream
中任意一个类型。 s
是一个 string
。
# Exercise 8.9
使用你为 8.1.2 节第一个练习所编写的函数打印一个
istringstream
对象的内容。
解:
#include <iostream> | |
#include <sstream> | |
using std::istream; | |
istream& func(istream &is) | |
{ | |
std::string buf; | |
while (is >> buf) | |
std::cout << buf << std::endl; | |
is.clear(); | |
return is; | |
} | |
int main() | |
{ | |
std::istringstream iss("hello"); | |
func(iss); | |
return 0; | |
} |
# Exercise 8.10
编写程序,将来自一个文件中的行保存在一个
vector
中。然后使用一个istringstream
从vector
读取数据元素,每次读取一个单词。
解:
#include <iostream> | |
#include <fstream> | |
#include <sstream> | |
#include <vector> | |
#include <string> | |
using std::vector; using std::string; using std::ifstream; using std::istringstream; using std::cout; using std::endl; using std::cerr; | |
int main() | |
{ | |
ifstream ifs("../data/book.txt"); | |
if (!ifs) | |
{ | |
cerr << "No data?" << endl; | |
return -1; | |
} | |
vector<string> vecLine; | |
string line; | |
while (getline(ifs, line)) | |
vecLine.push_back(line); | |
for (auto &s : vecLine) | |
{ | |
istringstream iss(s); | |
string word; | |
while (iss >> word) | |
cout << word << endl; | |
} | |
return 0; | |
} |
# Exercise 8.11
本节的程序在外层
while
循环中定义了istringstream
对象。如果record
对象定义在循环之外,你需要对程序进行怎样的修改?重写程序,将record
的定义移到while
循环之外,验证你设想的修改方法是否正确。
解:
#include <iostream> | |
#include <sstream> | |
#include <string> | |
#include <vector> | |
using std::vector; using std::string; using std::cin; using std::istringstream; | |
struct PersonInfo { | |
string name; | |
vector<string> phones; | |
}; | |
int main() | |
{ | |
string line, word; | |
vector<PersonInfo> people; | |
istringstream record; | |
while (getline(cin, line)) | |
{ | |
PersonInfo info; | |
record.clear(); | |
record.str(line); | |
record >> info.name; | |
while (record >> word) | |
info.phones.push_back(word); | |
people.push_back(info); | |
} | |
for (auto &p : people) | |
{ | |
std::cout << p.name << " "; | |
for (auto &s : p.phones) | |
std::cout << s << " "; | |
std::cout << std::endl; | |
} | |
return 0; | |
} |
# Exercise 8.12
我们为什么没有在
PersonInfo
中使用类内初始化?
解:
因为这里只需要聚合类就够了,所以没有必要在 PersionInfo
中使用类内初始化。
# Exercise 8.13
重写本节的电话号码程序,从一个命名文件而非
cin
读取数据。
解:
#include <iostream> | |
#include <sstream> | |
#include <fstream> | |
#include <string> | |
#include <vector> | |
using std::vector; using std::string; using std::cin; using std::istringstream; | |
using std::ostringstream; using std::ifstream; using std::cerr; using std::cout; using std::endl; | |
using std::isdigit; | |
struct PersonInfo { | |
string name; | |
vector<string> phones; | |
}; | |
bool valid(const string& str) | |
{ | |
return isdigit(str[0]); | |
} | |
string format(const string& str) | |
{ | |
return str.substr(0,3) + "-" + str.substr(3,3) + "-" + str.substr(6); | |
} | |
int main() | |
{ | |
ifstream ifs("../data/phonenumbers.txt"); | |
if (!ifs) | |
{ | |
cerr << "no phone numbers?" << endl; | |
return -1; | |
} | |
string line, word; | |
vector<PersonInfo> people; | |
istringstream record; | |
while (getline(ifs, line)) | |
{ | |
PersonInfo info; | |
record.clear(); | |
record.str(line); | |
record >> info.name; | |
while (record >> word) | |
info.phones.push_back(word); | |
people.push_back(info); | |
} | |
for (const auto &entry : people) | |
{ | |
ostringstream formatted, badNums; | |
for (const auto &nums : entry.phones) | |
if (!valid(nums)) badNums << " " << nums; | |
else formatted << " " << format(nums); | |
if (badNums.str().empty()) | |
cout << entry.name << " " << formatted.str() << endl; | |
else | |
cerr << "input error: " << entry.name | |
<< " invalid number(s) " << badNums.str() << endl; | |
} | |
return 0; | |
} |
# Exercise 8.14
我们为什么将
entry
和nums
定义为const auto&
?
解:
它们都是类类型,因此使用引用避免拷贝。
在循环当中不会改变它们的值,因此用 const
。
# Chapter Summary
🍓:)