17.3 重写12.3节(第430页)中的TextQuery程序,使用tuple代替QueryResult类,你认为哪种设计好,为什么?

TextQuery.h

#pragma once
#ifndef TEXTQUERY_H
#define TEXTQUERY_H
#include<iostream>
#include<vector>
#include<string>
#include<map>
#include<set>
#include<memory>
#include<sstream>
#include<fstream>
#include<tuple>


using std::cin; using std::cout; using std::endl;
using std::string; using std::vector; using std::map; using std::set;
using std::istringstream; using std::ifstream; using std::ofstream; using std::ostream;
using std::shared_ptr; using std::make_shared;
using std::tuple;

using line_no = vector<string>::size_type;
typedef tuple<string, shared_ptr<set<line_no>>, shared_ptr<vector<string>>> QueryResult;

//class QueryResult;

class TextQuery {
public:
	using line_no = vector<string>::size_type;
	TextQuery(ifstream& infile);
	//查询某个单词是否出现过
	QueryResult query(const string& sought) const;
	
private:
	//存储文本的每一行,在动态内存创建一个默认初始化的vector对象
	shared_ptr<vector<string>> file;
	//存储单词以及【出现的行号列表的指针】(有序,不可重复)
	map<string, shared_ptr<set<line_no>>> dic;
};

ostream& print(ostream& os, const QueryResult& q);

#endif

TextQuery.cpp

#include "TextQuery.h"

//这个构造函数初始值列表让智能指针指向一个合法的动态内存
TextQuery::TextQuery(ifstream& infile):file(new vector<string>)
{
	string line, word;
	//读取每一行
	while (getline(infile,line))
	{
		file->push_back(line); //存储每一行的内容,从下标0存起,但对应行号为1
		int n = file->size() - 1;//当前行号,从0开始
		istringstream is(line);
		while (is >> word)
		{
			//如果单词不在dic中,以其为关键字添加一项
			shared_ptr<set<line_no>> &lines = dic[word];
			if (!lines) //第一次遇到单词时,指针lines为空
				lines.reset(new set<line_no>); // 在动态内存分配一个新的set对象,让lines指向该对象
			lines->insert(n);
		}
	}
}

QueryResult TextQuery::query(const string& sought) const
{
	//如果未找到给定单词sought,我们返回一个指向空set的指针
	//局部static对象,不管经过几次,只初始化一次,生命周期直到程序结束
	static shared_ptr<set<line_no>> nodata(new set<line_no>);

	map<string, shared_ptr<set<line_no>>>::const_iterator loc = dic.find(sought);
	if (loc == dic.end()) // 没找到
	{
		return QueryResult(sought, nodata, file);
	}
	else
		return QueryResult(sought, loc->second, file);
}

string make_plural(size_t ctr, const string& word, const string& ending)
{
	return (ctr > 1) ? word + ending : word;
}

//有改动!!!!
ostream& print(ostream& os, const QueryResult &q)
{
	os << std::get<0>(q) << " occurs " << std::get<1>(q)->size() << " "
		<< make_plural(std::get<1>(q)->size(), "time", "s")<<endl;
	for (vector<string>::size_type l : *std::get<1>(q))
		os << "\t" << "(line " << l+1 << " ) " << (*std::get<2>(q))[l] << endl;

	return os;
}

测试文件

void p17_3()
{
	ifstream ifs("query.txt");
	TextQuery tq(ifs);

	while (true)
	{
		string s;
		//遇到文件尾或输入q退出
		if (!(cin >> s) || s == "q")
			break;
		print(cout, tq.query(s)) << endl;
	}
}

我认为用tuple更好,免去了类的管理,使得程序结构更加简洁,但是用tuple不易重构

17.4 测试并编写你自己版本的findBook函数

Sales_data.h

#pragma once
#ifndef SALES_DATA_H
#define SALES_DATA_H
#include<iostream>
#include<string>
using std::ostream;
using std::istream;
using std::string;
using std::cin;
using std::cout;
using std::cerr;
using std::endl;

class Sales_data; //必须要提前声明,告诉编译器,后面有出现Sales_data是合法的。

//重载输入运算符(这是IO运算符)
istream& operator>>(istream&, Sales_data&);
//重载输出运算符,只读Sales_data
ostream& operator<<(ostream&, const Sales_data&);
//重载加法运算符
Sales_data operator+(const Sales_data&, const Sales_data&);
//重载相等运算符
bool operator==(const Sales_data&, const Sales_data&);
bool operator!=(const Sales_data&, const Sales_data&);

bool compareIsbn(const Sales_data&, const Sales_data&);

class Sales_data {
	//声明友元函数,这里声明了之后,外面最好还是要保留一下初始声明
	//友元函数可以访问类中所有级别的成员

	friend bool operator==(const Sales_data&, const Sales_data&);
	friend bool operator!=(const Sales_data&, const Sales_data&);
	friend istream& operator>>(istream&, Sales_data&);//声明为友元让该函数可以访问Sales_data对象的成员
	friend ostream& operator<<(ostream&, const Sales_data&);
	friend Sales_data operator+(const Sales_data&, const Sales_data&);

public:
	
	//Sales_data() = default;
	
	Sales_data(const string& s, unsigned n, double p) :
		bookNo(s), units_sold(n), revenue(p* n) {}
	//定义了一个默认构造函数(因为此时三个成员变量都有了默认初始值),令其与只接受一个string实参的构造函数功能相同
	Sales_data(string s = ""):Sales_data(s,0,0) {}
	//Sales_data(const string& s) :bookNo(s) {}
	
	Sales_data(istream& is):Sales_data()
	{
		//编译器跑到Sales_data.cpp文件中找函数的实现(定义)
		is >> *this; //从键盘上输入的数据输入到调用该函数的对象中
	}
	
	//重载符合赋值运算符
	Sales_data& operator+=(const Sales_data&);
	//重载赋值运算符
	Sales_data& operator=(const string& s);
	
	//定义类型转换运算符,无参,无返回值
	explicit operator string() const { return bookNo; }
	explicit operator double() const { return avg_price(); }
	
	string isbn() const;
	Sales_data& combine(const Sales_data& rhs);
	
private:
	std::string bookNo;
	unsigned units_sold = 0;
	double revenue = 0.0;
	inline
	double avg_price() const;
		
};

#endif

Sales_data.cpp

#include "Sales_data.h" // 通过这个包含就带有一系列的#include<iostream>、using std::....;

string Sales_data::isbn() const
{
	return bookNo;
}
inline //内联函数,可以在调用点处展开,直接将调用替换为结果值
double Sales_data::avg_price() const
{
	return units_sold ? revenue / units_sold : 0;
}

Sales_data& Sales_data::combine(const Sales_data& rhs) 
{
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;
	return *this;
}

istream& operator>>(istream& is, Sales_data& item)
{
	double price = 0;
	is >> item.bookNo >> item.units_sold >> price;
	//item.revenue = item.units_sold * price;
	if (is) // 检查输入是否成功
		item.revenue = item.units_sold * price;
	else
		item = Sales_data();//对象被赋予默认状态
	return is;
}

ostream& operator<<(ostream& os, const Sales_data& item)
{
	os << item.isbn() << " " << item.units_sold << " "
		<< item.revenue << " " << item.avg_price();
	return os;
}

Sales_data operator+(const Sales_data& lhs, const Sales_data& rhs)
{
	Sales_data sum = lhs;
	//法二:sum.combine(rhs);
	//用复合赋值运算符实现算术运算符
	sum += rhs; //调用类重载的+=运算符,推荐
	/*法三:
	sum.units_sold += rhs.units_sold;
	sum.revenue += rhs.revenue;*/
	return sum;
}



Sales_data& Sales_data::operator+=(const Sales_data& rhs)
{
	//复合赋值运算符一般先+,后执行=
	units_sold += rhs.units_sold;
	revenue += rhs.revenue;

	return *this; //返回左侧运算对象的引用
}

bool operator==(const Sales_data& lhs, const Sales_data& rhs)
{
	return lhs.isbn() == rhs.isbn() &&
		lhs.units_sold == rhs.units_sold &&
		lhs.revenue == rhs.revenue;
}

bool operator!=(const Sales_data& lhs, const Sales_data& rhs)
{
	return !(lhs == rhs); //将工作委托给==
}

Sales_data& Sales_data::operator=(const string& s)
{
	bookNo = s;
	return *this;
}

bool compareIsbn(const Sales_data& lhs, const Sales_data& rhs)
{
	return lhs.isbn() < rhs.isbn();
}

findBook函数和reportResults函数

#include<iostream>
#include<vector>
#include<tuple>
#include<algorithm>
#include<numeric>
#include<utility>
#include"Sales_data.h"

using std::accumulate;
using std::tuple;
using std::vector;
using std::equal_range;
using std::pair;
using std::get;

//matches有三个成员,一家书店的索引,和指向这家书店vector中元素的迭代器
typedef tuple<vector<Sales_data>::size_type,
	vector<Sales_data>::const_iterator,
	vector<Sales_data>::const_iterator> matches1;
//files 保存每家书店的销售记录
//findBook 返回一个vector,每家销售了给定书籍的书店在其中都有一项
vector<matches>
findBook1(const vector<vector<Sales_data>>& files, const string& book)
{
	vector<matches> res; // 初始化为空
	//对每家书店,查找与给定书籍匹配的记录范围(若存在)
	for (vector<vector<Sales_data>>::const_iterator it = files.cbegin(); it != files.cend(); ++it)
	{
		//查找具有相同ISBN的Sales_data范围
		pair<vector<Sales_data>::const_iterator, vector<Sales_data>::const_iterator> found = equal_range(it->cbegin(), it->cend(), book, compareIsbn);
	
		//此书店有销售待查找的书籍
		if (found.first != found.second)
		{
			res.push_back(make_tuple(it - files.cbegin(), found.first, found.second));
		}
	}
	return res;
}

void reportResults1(istream& in, ostream& os, const vector<vector<Sales_data>>& files)
{
	string s;//要查找的书
	while (in >> s)
	{
		vector<matches> trans = findBook(files, s);
		if (trans.empty())
		{
			cout << s << " not found in any stores" << endl;
			continue;
		}
		//对每家销售了给定书籍的 书店
		for (const matches& store : trans)
		{
			os << "store " << get<0>(store) << " sales: "
				<< accumulate(get<1>(store), get<2>(store), Sales_data(s)) << endl;
		}
	}
}

测试代码

void p17_4()
{
	vector<vector<Sales_data>> files;
	vector<Sales_data> svec1 = { {"0-201-2283", 4, 20}, {"0-201-3382", 5 ,33}, {"0-203-293891", 8, 41} };
	vector<Sales_data> svec2 = { {"0-201-2281", 4, 10}, {"0-201-3382", 5 ,23}, {"0-203-293191", 8, 41} };
	vector<Sales_data> svec3 = { {"0-201-2283", 4, 21}, {"0-201-33821", 5 ,33}, {"0-203-293811", 8, 41} };
	files.push_back(svec1);
	files.push_back(svec2);
	files.push_back(svec3);
	reportResults(cin, cout, files);
}
17.5 重写 findBook令其返回一个pair,包含一个索引和一个迭代器pair

Sales_data部分代码以及相关头文件包含同上

typedef pair<vector<Sales_data>::size_type, pair<vector<Sales_data>::const_iterator, vector<Sales_data>::const_iterator>> matches2;
vector<matches>
findBook2(const vector<vector<Sales_data>>& files, const string& book)
{
	vector<matches> res; // 初始化为空
	//对每家书店,查找与给定书籍匹配的记录范围(若存在)
	for (vector<vector<Sales_data>>::const_iterator it = files.cbegin(); it != files.cend(); ++it)
	{
		//查找具有相同ISBN的Sales_data范围
		pair<vector<Sales_data>::const_iterator, vector<Sales_data>::const_iterator> found = equal_range(it->cbegin(), it->cend(), book, compareIsbn);

		//此书店有销售待查找的书籍
		if (found.first != found.second)
		{
			res.push_back(make_pair(it - files.cbegin(), make_pair(found.first, found.second)));
		}
	}
	return res;
}

void reportResults2(istream& in, ostream& os, const vector<vector<Sales_data>>& files)
{
	string s;//要查找的书
	while (in >> s)
	{
		vector<matches> trans = findBook(files, s);
		if (trans.empty())
		{
			cout << s << " not found in any stores" << endl;
			continue;
		}
		//对每家销售了给定书籍的 书店
		for (const matches& store : trans)
		{
			os << "store " << store.first << " sales: "
				<< accumulate(store.second.first, store.second.second, Sales_data(s)) << endl;
		}
	}
}
17.6 重写findBook,不使用tuple或pair

思路是使用类

struct matches
{
	vector<Sales_data>::size_type index;
	vector<Sales_data>::const_iterator first;
	vector<Sales_data>::const_iterator last;
	//有参构造
	matches(vector<Sales_data>::size_type index_, vector<Sales_data>::const_iterator first_, vector<Sales_data>::const_iterator last_) :
		index(index_), first(first_), last(last_) {}
};


vector<matches>
findBook(const vector<vector<Sales_data>>& files, const string& book)
{
	vector<matches> res; // 初始化为空
	//对每家书店,查找与给定书籍匹配的记录范围(若存在)
	for (vector<vector<Sales_data>>::const_iterator it = files.cbegin(); it != files.cend(); ++it)
	{
		//查找具有相同ISBN的Sales_data范围
		pair<vector<Sales_data>::const_iterator, vector<Sales_data>::const_iterator> found = equal_range(it->cbegin(), it->cend(), book, compareIsbn);

		//此书店有销售待查找的书籍
		if (found.first != found.second)
		{
			res.push_back(matches(it - files.cbegin(), found.first, found.second));
		}
	}
	return res;
}

void reportResults(istream& in, ostream& os, const vector<vector<Sales_data>>& files)
{
	string s;//要查找的书
	while (in >> s)
	{
		vector<matches> trans =
		findBook(files, s);
		if (trans.empty())
		{
			cout << s << " not found in any stores" << endl;
			continue;
		}
		//对每家销售了给定书籍的 书店
		for (const matches& store : trans)
		{
			os << "store " << store.index << " sales: "
				<< accumulate(store.first, store.last, Sales_data(s)) << endl;
		}
	}
}
17.7 解释你更倾向于哪个版本的findBook,为什么?

我更倾向tuple版本的,因为比较简洁

17.8 在本节最后一段代码中如果我们将Sales_data作为第三个参数传递给accumulate,会发生什么?

所得的结果书的isbn都是空串

17.9 解释下列每个bitset对象包含的位模式
bitset<64> bit(32); //下标为5的位为1,剩余左右31位都是0
bitset<32> bv(1010101);//若1010101二进制位超过32位,则截取低32位给bv;若不超过32位,将该数二进制填充低位,高位补0
string bstr;
cin >> bstr;
bitset<8> bs(bstr); //输入"1001"则得到用该1001来初始化低位,高位用0补
17.10 使用序列1、2、3、5、8、13、21初始化一个bitset,将这些位置置位。对另一个bitset进行默认初始化,并编写一小段程序将其恰当的位置位。
void p17_10()
{
	vector<int> vi = { 1, 2, 3, 5, 8, 21 };
	bitset<32> bset;
	for (const int i: vi)
	{
		bset.set(i);
	}
	cout << bset << endl;
}
17.11 定义一个数据结构包含一个整型对象,记录一个包含10个问题真/假测验的解答。如果测验包含100道题,你需要对数据结构做出什么改变(如果需要的话)?
template<unsigned N>
class Quiz {
public:
	Quiz(string& s) :
		bset(s) {}
private:
	bitset<N> bset;//初始化为全0
};

void p17_11()
{
	string s1("1001001110");//十个题目的解答
	string s2("1001001110", 10);
	Quiz<10> q1(s1);
	Quiz<100> q2(s2);
}
17.12
template<unsigned N>
class Quiz {
	template<unsigned M>
	friend std::ostream& operator<<(std::ostream&, const Quiz<M>&);
public:
	Quiz(string& s) :
		bset(s) {}
	void update(size_t n, bool b)
	{
		bset[n] = b;
	}
private:
	bitset<N> bset;//初始化为全0
};

template <unsigned M>
std::ostream& operator<<(std::ostream& os, const Quiz<M>& q)
{
	os << q.bset;
	return os;
}

void p17_12()
{
	string s1("1001001110");//十个题目的解答
	string s2("1001001110", 10);
	Quiz<10> q1(s1);
	Quiz<100> q2(s2);

	q1.update(0, true);
	cout << q1 << endl;
}
17.13 编写一个整型对象,包含真/假测验的正确答案。使用它来为前两题中的数据结构生成测验成绩
template<unsigned N>
class Quiz {
	//非类型模板参数,输出运算符
	template<unsigned M>
	friend std::ostream& operator<<(std::ostream&, const Quiz<M>&);

	//求分数的友元函数声明 
	template<unsigned M>
	friend size_t grade(const Quiz<M>&, const Quiz<M>&);
public:
	Quiz(string& s) :
		bset(s) {}
	void update(size_t n, bool b)
	{
		bset[n] = b;
	}
private:
	bitset<N> bset;//初始化为全0
};

template <unsigned M>
std::ostream& operator<<(std::ostream& os, const Quiz<M>& q)
{
	os << q.bset;
	return os;
}

template<unsigned M>
size_t grade(const Quiz<M>& lhs, const Quiz<M>& rhs)
{
	//^相同为0不同为1,所以答案正确^结果为0,所以需要反转为1
	return (lhs.bset ^ rhs.bset).flip().count();
}

void p17_13()
{
	string s1("1001001110");
	string s2("1001001101");

	Quiz<10> q1(s1);
	Quiz<10> q2(s2);
	cout << grade<10>(s1, s2) << endl;
}
17.16 如果前一题程序中的regex对象用"[^c]ei"进行初始化会发生什么?用此模式测试你的程序,检查你的答案是否正确

只有三个字母且第一个字母不为c后两个字母为ei才能匹配(如lei,hei,kei)

17.18 修改你的程序,忽略包含"ei"但并非拼写错误的单词,如"albeit"和"neighbor"
//查找不在字符c之后的字符串ei
string pattern("[^c]ei");
//我们需要包含pattern的整个单词
pattern = "[[:alpha:]]*" + pattern + "[[:alpha:]]*";
regex r(pattern); // 构造一个用于查找模式的regex
smatch results;
//定义一个string保存与模式匹配和不匹配的文本
string s;
vector<string> svec{ "albeit", "neighbour", "being" };
while (getline(cin, s))
{
    //反复调用regex_search来寻找文件中的所有匹配
    for (sregex_iterator it(s.begin(), s.end(), r), end_it;
         it != end_it; ++it)
    {
        //忽略某些单词,即便不满足正则表达式
        if (find(svec.begin(), svec.end(), it->str()) != svec.end())
            continue;
        cout << it->str() << endl;

    }
}
17.19 为什么可以不先检查m[4]是否匹配了就直接调用m[4].str()?

如果未匹配,str()会返回空的string,也是可以进行比较的不会出现异常的情况。

17.20 编写你自己版本的验证电话号码的程序
bool valid(const smatch& m)
{
	//如果区号前有一个左括号,则区号后必须有一个有括号,之后紧跟剩余号码或一个空格
	if (m[1].matched)
		return m[3].matched
		&& (m[4].matched == 0 || m[4].str() == " ");
	else
		//否则区号后不能有有括号,另两个部分间分隔符必须同
		return !m[3].matched
		&& (m[4].str() == m[6].str());
}
void p17_20()
{
	string phone = "(\\()?([\\{d}{3}])(\\))?([-. ]?)(\\{d}{3})([-. ]?)(\\{d}{4})";
	regex r(phone); // regex对象,用于匹配我们的输入
	smatch m;
	string s;
	while (getline(cin, s))
	{
		for (sregex_iterator it(s.begin(), s.end(), r), end_it;
			it != end_it; ++it)
		{
			//检查号码格式是否合法
			if (valid(*it))
				cout << "valid: " << it->str() << endl;
			else
				cout << "not valid: " << it->str() << endl;
		}
	}
}
17.23 编写查找邮政编码的正则表达式。一个美国邮政编码可以由五位或九位数字组成。前五位数字和后四位数字可以用一个短横线分隔。
bool valid1(const smatch& m)
{
	//m[0]是完整匹配的结果
	if (m[3].matched)
		return true;
	else //如果没有后四位也不能有-分隔符
		return !m[2].matched;
}

void p17_23()
{
	//用括号分出子表达式
	string postCode = "(\\d{5})([-])?(\\d{4})?";
	regex r(postCode);
	smatch m;
	string s;

	while (getline(cin, s))
	{
		for (sregex_iterator it(s.begin(), s.end(), r), end_it;
			it != end_it; ++it)
		{
			if (valid1(*it))
				cout << "valid:" << it->str() << endl;
			else
				cout << "not valid: " << it->str() << endl;
		}
	}
}
17.25 重写你的电话号码程序,使之只输出每个人的第一个电话号码
void p17_25()
{
	string phone = "(\\()?(\\d{3})(\\))?([-. ])?(\\d{3})([-. ])?(\\d{4})*";
	regex r(phone);
	string s;
	string fmt = "$2.$5.$7";

	while (getline(cin, s))
	{
		smatch result;
		regex_search(s, result, r);
		if (!result.empty())
		{
			cout << result.prefix() << result.format(fmt) << endl;
		}
		else
		{
			cout << "Sorry, No match." << endl;
		}
	}
}
17.31 对于本节程序中的游戏程序,如果在do循环内定义b和e,会发生什么

如果在循环内定义,每次都会生成相同的随机数,总是同一个玩家在走

17.32 如果我们在循环内定义resp会发生什么?

while条件内的resp提示未定义的标识符

17.33 修改11.3.6节(第392页)中的单词转换程序,允许对一个给定单词有多种转换方式,每次随机选择一种进行实际转换。
//构建转换用的map
map<string, string> buildMap(ifstream& map_file)
{
	map<string, string> trans_map;
	string key;	//要转换的单词
	string value; //转换后的内容
	//将一行中的第一个单词读入key,之后获取该行的剩余部分
	while (map_file >> key && getline(map_file, value))
	{
		if (value.size() > 1) //检查是否有转换规则
			trans_map[key] = value.substr(1);
		//trans_map.insert({ key,value.substr(1) });
		else
			throw runtime_error("no rule for " + key);
	}
	return trans_map;
}

//通过转换表m将s转换为指定内容
const string& transform(const string& s, const map<string, string>& m)
{
	map<string, string>::const_iterator it = m.find(s);
	if (it != m.cend())
		return it->second; //使用替换单词
	else
		return s;	//返回原单词
}

void word_transform(ifstream& map_file, ifstream& input)
{
	//生成转换规则
	map<string, string> trans_map = buildMap(map_file);
	string text;
	while (getline(input, text)) //读取每一行
	{
		istringstream stream(text);//用于读取每个单词
		string word;
		bool firstword = true;//判断是否为每行第一个单词,控制是否要打空格
		while (stream >> word)
		{
			if (firstword)
				firstword = false;
			else
				cout << " ";
			//transform将返回第一个参数或转换为对应单词
			cout << transform(word, trans_map);
		}
		cout << endl; // 完成一行转换
	}

}

//测试函数
void p17_33()
{
	static default_random_engine e(time(0));
	static bernoulli_distribution b;
	bool res = b(e);
	ifstream map_file1("rule.txt");
	ifstream map_file2("rule_1.txt");
	ifstream input("input.txt");
	word_transform(res ? map_file1 : map_file2, input);;

}

rule.txt

brb be right back
k okay?
y why
r are
u you
pic picture
thk thanks!
18r later

rule_1.txt

brb Be right back
k Okay?
y Why
r Are
u You
pic Picture
thk Thanks!
18r Later

input.txt

where r u
y dont u send me a pic
k thk 18r
Logo

华为开发者空间,是为全球开发者打造的专属开发空间,汇聚了华为优质开发资源及工具,致力于让每一位开发者拥有一台云主机,基于华为根生态开发、创新。

更多推荐