Chapter 8. The IO Library

Exercise 8.1

Write a function that takes and returns an istream&. The function should read the stream until it hits end-of-file. The function should print what it reads to the standard output. Reset the stream so that it is valid before returning the stream.

1
2
3
4
5
6
7
8
istream& func(istream &is)
{
std::string buf;
while (is >> buf)
std::cout << buf << std::endl;
is.clear();
return is;
}

Exercise 8.2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
//
// ex8_02.cpp
// Exercise 8.2
//
// Created by pezy on 11/27/14.
// Copyright (c) 2014 pezy. All rights reserved.
//
// @Brief Test your function by calling it, passing cin as an argument

#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;
}

rdstate()std::basic_ios 类的成员函数,用于获取流的状态标志。在这个特定的上下文中,is.rdstate() 被用来获取输入流 is 的状态。std::basic_ios 是 C++ 标准库中用于处理输入输出的基类,它提供了一系列函数来管理和检查流的状态。

rdstate() 函数返回的是一个位掩码,其中每个位表示了一个特定的状态标志,如 eofbit(文件结束标志)、failbit(失败标志)和 badbit(严重故障标志)等。这些标志的具体含义如下:

  • eofbit:表示已经到达输入流的末尾(End Of File)。
  • failbit:表示输入操作失败,可能由于输入值与期望类型不匹配等原因。
  • badbit:表示流的状态已经不可用,可能由于输入输出设备的错误等原因。
  • goodbit:表示流的状态正常,无任何错误发生。

在代码中,通过 is.rdstate() 来获取输入流 is 的状态,并将其输出到标准输出流。然后,通过检查各个位来判断流是否处于预期的状态。

Exercise 8.3

What causes the following while to terminate?

1
while (cin >> i) /*  ...    */

putting cin in an error state cause to terminate. such as eofbit, failbit and badbit.

Exercise 8.4

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//
// ex8_04.cpp
// Exercise 8.4
//
// Created by pezy on 11/9/14.
// Copyright (c) 2014 pezy. All rights reserved.
//
// @Brief Write a function to open a file for input and read its contents into
// a vector of strings,
// storing each line as a separate element in the vector.

#include <fstream>
#include <string>
#include <vector>
#include <iostream>

using std::vector;
using std::string;
using std::ifstream;
using std::cout;
using std::endl;

void ReadFileToVec(const string& fileName, vector<string>& vec)
{
ifstream ifs(fileName);
if (ifs) {
string buf;
while (std::getline(ifs, buf)) vec.push_back(buf);
}
}

int main()
{
vector<string> vec;
ReadFileToVec("../data/book.txt", vec);
for (const auto& str : vec) cout << str << endl;
return 0;
}

Exercise 8.5

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
//
// ex8_04.cpp
// Exercise 8.4
//
// Created by pezy on 11/9/14.
// Copyright (c) 2014 pezy. All rights reserved.
//
// @Brief Write a function to open a file for input and read its contents into
// a vector of strings,
// storing each line as a separate element in the vector.

#include <fstream>
#include <string>
#include <vector>
#include <iostream>

using std::vector;
using std::string;
using std::ifstream;
using std::cout;
using std::endl;

void ReadFileToVec(const string& fileName, vector<string>& vec)
{
ifstream ifs(fileName);
if (ifs) {
string buf;
while (std::getline(ifs, buf)) vec.push_back(buf);
}
}

int main()
{
vector<string> vec;
ReadFileToVec("../data/book.txt", vec);
for (const auto& str : vec) cout << str << endl;
return 0;
}
  1. ReadFileToVec 函数接受两个参数:fileName(文件名)和 vec(字符串向量的引用)。该函数的目的是打开文件,逐行读取文件的内容,并将每一行作为一个字符串存储到字符串向量 vec 中。

  2. ReadFileToVec 函数中,首先尝试打开指定的文件 fileName,并将其关联到一个输入文件流 ifs。如果文件成功打开(即 if (ifs) 返回 true),则开始读取文件内容。

  3. 在一个 while 循环中,使用 std::getline 函数从输入文件流 ifs 中读取一行内容,并将其存储到字符串 buf 中。然后,将 buf 添加到字符串向量 vec 的末尾。

  4. main 函数首先创建了一个空的字符串向量 vec,然后调用 ReadFileToVec 函数,将文件 "../data/book.txt" 中的内容读取到 vec 中。

  5. 最后,在 main 函数中使用一个范围循环遍历字符串向量 vec 中的每个字符串,并将它们输出到标准输出流中

Exercise 8.9

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//
// ex8_09.cpp
// Exercise 8.9
//
// Created by pezy on 11/29/14.
// Copyright (c) 2014 pezy. All rights reserved.
//
// @Brief Use the function you wrote for the first exercise in § 8.1.2 (p.314)
// to print the contents of an istringstream object.
// @See Exercise 8.1

#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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//
// ex8_10.cpp
// Exercise 8.10
//
// Created by pezy on 11/29/14.
// Copyright (c) 2014 pezy. All rights reserved.
//
// @Brief Write a program to store each line from a file in a vector<string>.
// Now use an istringstream to read each element from the vector a word
// at a time.

#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;
}
  1. main 函数中,首先尝试打开文件 "../data/book.txt" 并将其关联到输入文件流 ifs。如果文件打开失败,则输出错误信息到标准错误流 cerr,并返回 -1。

  2. 创建一个字符串向量 vecLine,用于存储文件中的每一行。

  3. 使用 while 循环和 getline 函数逐行读取文件中的内容,并将每一行存储到字符串向量 vecLine 中。

  4. 对于字符串向量 vecLine 中的每个字符串 s,创建一个 istringstream 对象 iss,用于从字符串中逐个读取单词。

  5. 在一个嵌套的 while 循环中,使用 iss >> word 逐个读取字符串流中的单词,并将其输出到标准输出流中。

  6. main 函数返回 0,表示成功执行。

总之,这段代码实现了从文件中逐行读取内容,并将每一行存储到一个字符串向量中。然后,使用 istringstream 从每个字符串中逐个读取单词,并将它们输出到标准输出流中。

Exercise 8.11

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
//
// ex8_11.cpp
// Exercise 8.11
//
// Created by pezy on 11/29/14.
// Copyright (c) 2014 pezy. All rights reserved.
//
// @Brief The program in this section defined its istringstream object inside
// the outer while loop.
// What changes would you need to make if record were defined outside
// that loop?
// Rewrite the program, moving the definition of record outside the
// while, and see whether you thought of all the changes that are
// needed.

#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;
}
  1. PersonInfo info;:创建一个 PersonInfo 结构体对象 info,用于存储当前行的信息。

  2. record.clear();:清空 record 流的状态标志,以确保流处于可用状态。

  3. record.str(line);:将当前行 line 设置为 record 流的字符串内容,这样 record 流将以当前行的内容作为输入源。

  4. record >> info.name;:从 record 流中读取一个字符串,存储到 info.name 中,这是当前行中的第一个字符串,即姓名。

  5. while (record >> word) info.phones.push_back(word);:使用一个 while 循环从 record 流中连续读取字符串,直到流的末尾。每次读取的字符串都存储到 info.phones 向量中,这样就可以将当前行中的所有电话号码都存储到 info.phones 中。

  6. people.push_back(info);:将存储了当前行信息的 info 对象添加到 people 向量中,这样就完成了当前行的信息存储。

Exercise 8.12

Why didn’t we use in-class initializers in PersonInfo?

Cause we need a aggregate class here. so it should have no in-class initializers.

Exercise 8.13

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
//
// ex8_13.cpp
// Exercise 8.13
//
// Created by pezy on 11/29/14.
// Copyright (c) 2014 pezy. All rights reserved.
//
// @Brief Rewrite the phone number program from this section to read from
// a named file rather than from cin.
// @See ex8_11.cpp

#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;
}
  1. 首先,程序尝试打开文件 "../data/phonenumbers.txt" 并将其关联到输入文件流 ifs。如果文件打开失败,则输出错误信息到标准错误流 cerr,并返回 -1。

  2. 创建一个字符串向量 people,用于存储从文件中读取的每个人的信息。

  3. 使用 while 循环和 getline 函数逐行读取文件中的内容。在每次迭代中,创建一个 PersonInfo 结构体对象 info,用于存储当前行的信息。

  4. 使用 istringstream 对象 record 将当前行的内容作为输入流,并将 info.name 初始化为第一个字符串,即姓名。然后,使用 while 循环从输入流中读取电话号码,并将它们存储到 info.phones 向量中。

  5. 将存储了当前行信息的 info 对象添加到 people 向量中,以便后续处理和输出。

  6. 对于每个 entry(即 people 向量中的每个 PersonInfo 对象),使用 ostringstream 对象 formattedbadNums 分别存储格式化后的电话号码和非法的电话号码。

  7. 遍历每个 entry 的电话号码,并根据是否合法来进行格式化。如果电话号码合法,则使用 format 函数对其进行格式化,并将其存储到 formatted 对象中;否则,将其存储到 badNums 对象中。

  8. 最后,如果不存在非法的电话号码,则将姓名和格式化后的电话号码输出到标准输出流;否则,输出错误信息,指出哪些电话号码是非法的。

通过以上步骤,程序能够读取名为 "phonenumbers.txt" 的文件中的电话号码信息,并对其进行格式化输出,同时检测并报告非法的电话号码。

Exercise 8.14

Why did we declare entry and nums as const auto &?

  • cause they are all class type, not the built-in type. so reference more effective.
  • output shouldn't change their values. so we added the const.

我们为什么将 entrynums 声明为 const auto &

因为它们都是类类型,而不是内置类型。使用引用会更有效率,因为它们的拷贝可能比较昂贵。

另外,由于输出操作不会改变它们的值,我们添加了 const 修饰符来确保在输出时不会修改它们的值。