8.1
#include <iostream>
using namespace std;
istream& func(istream &is)
{
std::string buf;
while (is >> buf)
std::cout << buf << std::endl;
is.clear();
return is;
}
int main() {
cout << "请输入一些整数,按 Ctrl+Z 结束" << endl;
func(cin);
return 0;
}
// 运行结果
// 32 4 5
// 3 sffd f
// Ctrl+Z(macOS 用户 commad+D)
// 以上三行是用户输入的测试数据,第三个数据是快捷键。
请输入一些整数,按 Ctrl+Z 结束
32 4 5
32
4
5
3 sffd f
3
sffd
f
^D
Process finished with exit code 0
8.2
上题已实现
8.3
【出题思路】
进一步理解流的状态的检测方式。
【解答】
遇到了文件结束符,或者遇到了 IO 流错误,或者读入了无效数据。
8.4
【出题思路】
本题练习文件输入和流的逐行输入,还练习了使用迭代器遍历容器中的元素。
【解答】
创建文件 data,并向其中写入测试数据。我这里写入的是:
C++ Primer
5th
main.cpp
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
int main() {
// ../data 为文件data的文件名及其相对路径(是相对于可执行程序所在目录的相对路径)
ifstream in("../data"); // 打开文件
if (!in) {
cerr << "无法打开输入文件" << endl;
return -1;
}
string line;
vector<string> words;
while (getline(in, line)) // 从文件中读取一行
words.push_back(line); // 添加到 vector 中
in.close(); // 输入完毕,关闭文件
vector<string>::const_iterator it = words.begin(); // 迭代器
while (it != words.end()) { // 遍历 vector
cout << *it << endl; // 输出 vector 中的元素
++it;
}
return 0;
}
// 运行结果
C++ Primer
5th
Process finished with exit code 0
【其他解题思路】
vector 的遍历还可使用范围 for 循环来实现。程序如下所示:
#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;
int main() {
ifstream in("../data");
if (!in) {
cerr << "无法打开输入文件" << endl;
return -1;
}
string line;
vector<string> words;
while (getline(in, line))
words.push_back(line);
in.close();
for (auto str : words)
cout << str << endl;
return 0;
}
// 运行结果
C++ Primer
5th
Process finished with exit code 0
8.5
【出题思路】
本题练习逐个数据的输入方式。
【解答】
将练习 8.5 中的 while (getline(in, line))
改为 while (in >> line)
即可,其它保持不变。修改后的程序执行结果如下所示:
// 运行结果
C++
Primer
5th
Process finished with exit code 0
8.6
【出题思路】
通过一个较大的例子继续练习文件输入,并练习从命令行获取参数及参数合法性的检测。
【解答】
Sales_data.h 头文件代码与练习 7.7 一样。
main.cpp 代码如下所示:
#include <iostream>
#include <fstream>
#include "Sales_data.h"
using namespace std;
int main(int argc, char *argv[]) {
if (argc != 2) {
cerr << "请给出文件名" << endl;
return -1;
}
ifstream in(argv[1]);
if (!in) {
cerr << "无法打开输入文件" << endl;
return -1;
}
Sales_data total; // 保存当前求和结果的变量
if (total.read(in, total)) { // 读入第一笔交易记录
Sales_data trans; // 保存下一条交易数据的变量
while (trans.read(in, trans)) { // 读入剩余的交易
if (total.isbn() == trans.isbn()) // 检查 isbn
total = total.add(total, trans); // 更新变量 total 当前的值
else {
total.print(cout, total) << endl; // 输出结果
total = trans; // 处理下一本
}
}
total.print(cout, total) << endl; // 输出最后一条交易
}
else { // 没有输入任何信息
cerr << "没有数据" << endl; // 通知用户
return -1;
}
return 0;
}
运行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 为 ../temp
。
注:../temp
即为文件 temp 的文件名及其相对路径(是相对于可执行程序所在目录的相对路径)。
并在文件 temp 中写入如下内容(文件 temp 与 main.cpp 在同一目录下):
0-201-78345-X 3 20.00 19.00
0-201-78345-X 4 20.00 19.00
0-202-78345-X 4 30.00 29.00
0-202-78345-Y 2 200.00 199.00
运行程序,程序执行结果如下所示:
/Users/macOS/CLionProjects/test/cmake-build-debug/test ../temp
0-201-78345-X 7 20 19 0.95
0-202-78345-X 4 30 29 0.966667
0-202-78345-Y 2 200 199 0.995
Process finished with exit code 0
注:可参考练习 6.26
8.7
【出题思路】
本题练习文件输出。
【解答】
Sales_data.h 文件代码同 8.6
main.cpp 代码如下所示:
#include <iostream>
#include <fstream>
#include "Sales_data.h"
using namespace std;
int main(int argc, char *argv[]) {
if (argc != 3) {
cerr << "请给出文件名" << endl;
return -1;
}
ifstream in(argv[1]);
if (!in) {
cerr << "无法打开输入文件" << endl;
return -1;
}
ofstream out(argv[2]);
if (!out) {
cerr << "无法打开输出文件" << endl;
return -1;
}
Sales_data total; // 保存当前求和结果的变量
if (total.read(in, total)) { // 读入第一笔交易记录
Sales_data trans; // 保存下一条交易数据的变量
while (trans.read(in, trans)) { // 读入剩余的交易
if (total.isbn() == trans.isbn()) // 检查 isbn
total = total.add(total, trans); // 更新变量 total 当前的值
else {
total.print(out, total) << endl; // 输出结果
total = trans; // 处理下一本
}
}
total.print(out, total) << endl; // 输出最后一条交易
}
else { // 没有输入任何信息
cerr << "没有数据" << endl; // 通知用户
return -1;
}
return 0;
}
运行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 为 ../temp ../temp2
。
注:../temp ../temp2
即为文件 temp 和 temp2 的文件名及其相对路径(是相对于可执行程序所在目录的相对路径)。
并在文件 temp 中写入如下内容:
-
temp
0-201-78345-X 3 20.00 19.00 0-201-78345-X 4 20.00 19.00 0-202-78345-X 4 30.00 29.00 0-202-78345-Y 2 200.00 199.00
temp2 内容为空
程序执行后两文件内容为:
-
temp
0-201-78345-X 3 20.00 19.00 0-201-78345-X 4 20.00 19.00 0-202-78345-X 4 30.00 29.00 0-202-78345-Y 2 200.00 199.00
-
temp2
0-201-78345-X 7 20 19 0.95 0-202-78345-X 4 30 29 0.966667 0-202-78345-Y 2 200 199 0.995
接着我们运行程序两次,观察 temp 和 temp2 文件内容。如下所示:
-
temp
0-201-78345-X 3 20.00 19.00 0-201-78345-X 4 20.00 19.00 0-202-78345-X 4 30.00 29.00 0-202-78345-Y 2 200.00 199.00
-
temp2
0-201-78345-X 7 20 19 0.95 0-202-78345-X 4 30 29 0.966667 0-202-78345-Y 2 200 199 0.995
注:temp2 文件内容没变
8.8
【出题思路】
本题练习文件的追加模式。
【解答】
将上一题程序中代码 ofstream out(argv[2]);
改为 ofstream out(argv[2], ofstream::app);
。
运行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 为 ../temp ../temp2
。
注:../temp ../temp2
即为文件 temp 和 temp2 的文件名及其相对路径(是相对于可执行程序所在目录的相对路径)。
并在文件 temp 中写入如下内容:
-
temp
0-201-78345-X 3 20.00 19.00 0-201-78345-X 4 20.00 19.00 0-202-78345-X 4 30.00 29.00 0-202-78345-Y 2 200.00 199.00
temp2 内容为空
程序执行后两文件内容为:
-
temp
0-201-78345-X 3 20.00 19.00 0-201-78345-X 4 20.00 19.00 0-202-78345-X 4 30.00 29.00 0-202-78345-Y 2 200.00 199.00
-
temp2
0-201-78345-X 7 20 19 0.95 0-202-78345-X 4 30 29 0.966667 0-202-78345-Y 2 200 199 0.995
接着我们运行程序两次,观察 temp 和 temp2 文件内容。如下所示:
-
temp
0-201-78345-X 3 20.00 19.00 0-201-78345-X 4 20.00 19.00 0-202-78345-X 4 30.00 29.00 0-202-78345-Y 2 200.00 199.00
-
temp2
0-201-78345-X 7 20 19 0.95 0-202-78345-X 4 30 29 0.966667 0-202-78345-Y 2 200 199 0.995 0-201-78345-X 7 20 19 0.95 0-202-78345-X 4 30 29 0.966667 0-202-78345-Y 2 200 199 0.995 0-201-78345-X 7 20 19 0.95 0-202-78345-X 4 30 29 0.966667 0-202-78345-Y 2 200 199 0.995
注:temp2 实现了程序执行结果的追加
8.9
【出题思路】
本题练习字符串流的输入。
【解答】
#include <iostream>
#include <sstream>
#include <string>
#include <stdexcept>
using namespace std;
istream &f(istream &in) {
string v;
while (in >> v, !in.eof()) {
if (in.bad())
throw runtime_error("IO 流错误");
if (in.fail()) {
cerr << "数据错误,请重试" << endl;
in.clear();
in.ignore(100, '\0');
continue;
}
cout << v << endl;
}
in.clear();
return in;
}
int main() {
ostringstream msg;
msg << "C++ Primer 第五版" << endl;
istringstream in(msg.str());
f(in);
return 0;
}
// 运行结果
C++
Primer
第五版
Process finished with exit code 0
8.10
【出题思路】
本题继续练习字符串流的输入。
【解答】
main.cpp
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
int main() {
ifstream in("../data"); // 打开文件
if (!in) {
cerr << "无法打开输入文件" << endl;
return -1;
}
string line;
vector<string> words;
while (getline(in, line)) // 从文件中读取一行
words.push_back(line); // 添加到 vector 中
in.close(); // 输入完毕,关闭文件
vector<string>::const_iterator it = words.begin(); // 迭代器
while (it != words.end()) { // 遍历 vector
istringstream line_str(*it);
string word;
while (line_str >> word) // 通过 istringstream 从 vector 中读取数据
cout << word << '\n';
++it;
}
return 0;
}
// 运行结果
C++
Primer
5th
Process finished with exit code 0
注:文件 data 与 main.cpp 文件在同一目录下。文件 data 内容为:
C++ Primer 5th
8.11
【出题思路】
本题练习字符串流的重复使用,每次通过 str 成员将流绑定到不同的字符串,同时还要调用 clear 来重置流的状态。
【解答】
#include <iostream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
struct PersonInfo {
string name;
vector<string> phones;
};
int main() {
string line, word; // 分别保存来自输入的一行和单词
vector<PersonInfo> people; // 保存来自输入的所有记录
istringstream record;
// 逐行从输入读取数据,直至 cin 遇到文件尾(或其他错误)
while (getline(cin, line) && line != "Q") {
PersonInfo info; // 创建一个保存此记录数据的对象
record.clear(); // 重复使用字符串流时,每次都要调用 clear
record.str(line); // 将记录绑定到刚读入的行(将 line 拷贝到 record 中)
record >> info.name; // 读取名字
while (record >> word) // 读取电话号码
info.phones.push_back(word); // 保持它们
people.push_back(info); // 将此记录追加到 people 末尾
}
for (auto &p : people) {
cout << p.name << " ";
for (auto &s : p.phones)
std::cout << s << " ";
cout << endl;
}
return 0;
}
// zhangsan 23443 34432
// lisi 23423434
// wangwu 234324
// Q
// 上面四行是从控制台输入的测试数据,Q 用于结束输入
// 运行结果
zhangsan 23443 34432
lisi 23423434
wangwu 234324
Q
zhangsan 23443 34432
lisi 23423434
wangwu 234324
Process finished with exit code 0
8.12
【出题思路】
体会根据应用特点决定程序设计策略。
【解答】
由于每个人的电话号码数量不固定,因此更好的方式不是通过类内初始化指定人名和所有电话号码,而是在缺省初始化之后,在程序中设置人名并逐个添加电话号码。
8.13
【出题思路】
本题练习文件流和字符串流输入输出的综合应用。
【解答】
运行程序前,在 CLion -> Run -> Edit Configurations 下配置 Program arguments 为 ../data
。
注:../data
即为输入文件的文件名及其相对路径(是相对于可执行程序所在目录的相对路径)。
并在文件 data 中写入如下内容:
-
data
zhangsan 23443 34432 lisi 23423434 wangwu 234324
main.cpp
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;
struct PersonInfo {
string name;
vector<string> phones;
};
string format(const string &s) {
return s;
}
bool valid(const string &s) {
// 如何验证电话号码将在第 17 章介绍
// 现在简单返回 true
return true;
}
int main(int argc, char *argv[]) {
string line, word; // 分别保存来自输入的一行和单词
vector<PersonInfo> people; // 保存来自输入的所有记录
istringstream record;
if (argc != 2) {
cerr << "请给出文件名" << endl;
return -1;
}
ifstream in(argv[1]);
if (!in) {
cerr << "无法打开输入文件" << endl;
return -1;
}
// 逐行从输入读取数据,直至 cin 遇到文件尾(或其他错误)
while (getline(in, line)) {
PersonInfo info; // 创建一个保存此记录数据的对象
record.clear(); // 重复使用字符串流时,每次都要调用 clear
record.str(line); // 将记录绑定到刚读入的行(将 line 拷贝到 record 中)
record >> info.name; // 读取名字
while (record >> word) // 读取电话号码
info.phones.push_back(word); // 保持它们
people.push_back(info); // 将此记录追加到 people 末尾
}
ostringstream os;
for (const auto &entry : people) { // 对 people 中每一项
ostringstream formatted, badNums; // 每个循环步创建的对象
for (const auto &nums : entry.phones) { // 对每个数
if (!valid(nums))
badNums << " " << nums; // 将数的字符串形式存入 badNums
else
// 将格式化的字符串"写入" formatted
formatted << " " << format(nums);
}
if (badNums.str().empty()) // 没有错误的数
// 打印名字和格式化的数
os << entry.name << " " << formatted.str() << endl;
else
// 否则,打印名字和错误的数
cerr << "input error: " << entry.name
<< " invalid numbers(s) " << badNums.str() << endl;
}
cout << os.str() << endl;
return 0;
}
// 运行结果
zhangsan 23443 34432
lisi 23423434
wangwu 234324
Process finished with exit code 0
8.14
【出题思路】
回顾范围 for 语句的相关内容。
【解答】
这两条语句分别使用范围 for 语句枚举 people 中所有项(人)和每项的 phones 中的所有项(电话号码)。使用 const 表明在循环中不会改变这些项的值:auto 是请求编译器依据 vector 元素类型来推断出 entry 和 nums 的类型,即简化代码又避免出错。
使用引用的原因是,people 和 phones 的元素分别是结构对象和字符串对象,使用引用可避免对对象的拷贝。