C++ Primer习题集之第八章:IO库(Chapter 8)

导读

本章介绍了处理面向流的输入输出的C++标准库类:

         iostream 处理控制台 IO

         fstream 处理命名文件 IO

         stringstream 完成内存

string 的 IO 本章的练习主要帮助读者熟悉基本的控制台输入输出、文件输入输出和字符串输入输出,以及条件状态检测和设置等。

习题 8.1~ 8.14

练习 8.1:编写函数,接受一个istream&参数,返回值类型也是istream&。
此函数须从给定流中读取数据,直至遇到文件结束标识时停止。
它将读取的数据打印在标准输出上。完成这些操作后,在返回
流之前,对流进行复位,使其处于有效状态。

【出题思路】
本题是流的输入输出的基本练习。此外,本节介绍了流的条件状
态,本题还对流的结束状态(本题是遇到文件结束符)、错误状态和
数据错误状态(例如要求输入整数时输入了字符)的检测和处理进行
了练习。

【解答】
示例代码如下:

#include <iostream>
#include <stdexcept>
using namespace std;

istream& f(istream& in)
{
    int v = 0;
    while (in >> v, !in.eof())  // 直到遇到文件结束符才停止读取
    {
        if (in.bad())
        {
            throw runtime_error("IO流错误");
        }

        if (in.fail())
        {
            cerr << "数据错误,请重试:" << endl;
            in.clear();
            in.ignore(100, '\n');
            continue;
        }

        cout << v << endl;
    }

    in.clear();

    return in;
}

int main()
{
    cout << "请输入一些整数,按下 Ctrl + z 结束" << endl;
    f(cin);
    return 0;
}
练习 8.2:测试函数,调用参数为cin。

【出题思路】
通过运行程序来观察流的状态如何产生变化。

【解答】
运行过程如下所示:

请输入一些整数,按下 Ctrl + z 结束
1 2 3
1
2
3
7.6
7
数据错误,请重试:
a
数据错误,请重试:
4
4
^Z

Process returned 0 (0x0) execution time : 20.635 S
Press any key to continue.

#include <iostream>
#include <string>

using namespace std;

istream& func(istream &is)
{
    string buf;
    while (is >> buf)
        cout << buf << endl;
    is.clear();
    return is;
}

int main()
{
    istream& is = func(std::cin);
    cout << is.rdstate() << endl;
    return 0;
}
练习 8.3:什么情况下,下面的while循环会终止?
    while(cin >> i) // ...

【出题思路】
进一步理解流的状态的检测方式。

【解答】
遇到了文件结束符,或者遇到了IO流错误,或者读入了无效数据。
练习 8.4:编写函数,以读模式打开一个文件,将其内容读入到一
个string的vector中,将每一行作为一个独立的元素存于vector中。

【出题思路】
本题练习文件输入和流的逐行输入,还练习了使用迭代器遍历容器中的元素。

【解答】
示例代码如下;

#include <iostream>
#include <fstream>
#include <string>
#include <vector>
using namespace std;

int main()
{
    // 打开文件
    ifstream in("data.txt");
    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();

    // 遍历 vector
    while (it != words.end())
    {
        cout << *it << endl;    // 输出 vector 中的元素
        ++it;
    }

    return 0;
}

【其他解题思路】
vector的遍历还可使用范围for循环来实现。
练习 8.5:重写上面的程序,将每个单词作为一个独立的元素进行存储。

【出题思路】
本题练习逐个数据的输入方式。

【解答】
将循环遍历的while (getline(in, line)) 改为while (in >> line) 即可。
练习 8.6:重写7.1.1节的书店程序(第229页),从一个文件中读取交易记录。
将文件名作为一个参数传递给main(参见6.2.5节,第196页)。

【出题思路】
通过一个较大的例子继续练习文件输入,并练习从命令行获取参
数及参数合法性的检测。

【解答】
示例代码如下:

#include <iostream>
#include <fstream>
#include "Sales_data.h"
using namespace std;

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;
}
练习 8.7:修改上一节的书店程序,将结果保存到一个文件中。将
输出文件名作为第二个参数传递给main函数。

【出题思路】
本题练习文件输出。

【解答】
示例代码如下:

#include <fstream>
#include <iostream>
#include "Sales_data.h"
using namespace std;

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;
}
练习 8.8:修改上一题的程序,将结果追加到给定的文件末尾。对
同一个输出文件,运行程序至少两次,检验数据是否得以保留。

【出题思路】
本题练习文件的追加模式。

【解答】
将上一题程序的 ofstream out l(argv[2]);
改为 ofstream out(argv[2], ofstream::app);。

#include <fstream>
#include <iostream>
#include "Sales_data.h"
using namespace std;

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;
}
练习 8.9:使用你为8.1.2节(第281页)第一个练习所编写的函数
打印一个istringstream对象的内容。

【出题思路】
本题练习字符串流的输入。

【解答】

#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, '\n');
            continue;
        }
        cout << v << endl;
    }
    in.clear();
    return in;
}

int main()
{
    ostringstream msg;
    msg << "C++ Primer 第五版" << endl;
    istringstream in(msg.str());
    f(in);
    return 0;
}
练习 8.10:编写程序,将来自一个文件中的行保存在一个vector<string>中。
然后使用一个 istringstream 从 vector 读取数据元素,每次读取一个单词。

【出题思路】
本题继续练习字符串流的输入。

【解答】
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
using namespace std;

int main()
{
    ifstream in("data.txt"); // 打开文件
    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 << " ";
        cout << endl;
        ++it;
    }

    return 0;
}
练习 8.11:本节的程序在外层 while 循环中定义了 istringstream 对象。如
果 record 对象定义在循环之外,你需要对程序进行怎样的修改?重写程序,将
record 的定义移到 while 循环之外,验证你设想的修改方法是否正确。

【出题思路】
本题练习字符串流的重复使用,每次通过 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;

    while (getline(cin, line)) {
        PersonInfo info; // 创建一个保存此记录数据的对象
        record.clear(); // 重复使用字符串流时,每次都要调用 clear
        record.str(line); // 将记录绑定到刚读入的行
        record >> info.name; // 读取名字

        while (record >> word) // 读取电话号码
            info.phones.push_back(word); // 保持它们
        people.push_back(info); // 将此记录追加到 people 末尾
    }

    return 0;
}

【其他解题思路】
还可通过 rdbuf 成员函数来获得 record 流的缓冲区(一个 stringbuf 对象
的指针),然后利用 stringbuf 的 str 成员函数来直接设置缓冲区(字符串)内容。
注意,此方法仍然需要调用 clear 来将流复位。
练习 8.12:我们为什么没有在 PersonInfo 中使用类内初始化?

【出题思路】
体会根据应用特点决定程序设计策略。

【解答】
由于每个人的电话号码数量不固定,因此更好的方式不是通过类内初始化指定
人名和所有电话号码,而是在缺省初始化之后,在程序中设置人名并逐个添加电话
号码。
练习 8.13:重写本节的电话号码程序,从一个命名文件而非 cin 读取数据。

【出题思路】
本题练习文件流和字符串流输入输出的综合应用。

【解答】
#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;
    }

    while (getline(in, line)) {
        PersonInfo info; // 创建一个保存此记录数据的对象
        record.clear(); // 重复使用字符串流时,每次都要调用 clear
        record.str(line); // 将记录绑定到刚读入的行
        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 number(s) " << badNums.str() << endl;
    }
    cout << os.str() << endl;

    return 0;
}
练习 8.14:我们为什么将entry和nums定义为const auto&?

【出题思路】
回顾范围for语句的相关内容。

【解答】
这两条语句分别使用范围for语句枚举people中所有项(人)和每
项的phones中的所有项(电话号码)。使用const表明在循环中不会改
变这些项的值;auto是请求编译器依据vector元素类型来推断出entry和
nums的类型,既简化代码又避免出错;使用引用的原因是,people和
phones的元素分别是结构对象和字符串对象,使用引用即可避免对象
拷贝。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值