第 8 章

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 的元素分别是结构对象和字符串对象,使用引用可避免对对象的拷贝。

上一篇:KIN 202共鸣的白风 - 分享、流动、传递使我勇敢探索 ∞ 5 / 15 共时讯息预报十三月亮历


下一篇:[题解] 七彩树