文本查询程序(cpp primer 5th 第十五章练习)

题目描述:给定一段文本,按功能查找输入的内容
input:word
output:word在文本中出现的次数,行号和所在行的内容

input:~word
output:word出现的次数,没有word的行号和行内容

input:word1 | Word2
output:返回匹配两个条件的任意一个行

input:word1 & word2
output:返回两个条件都匹配的行

input:word1 | word2 & word3
output:返回相应匹配的行

面向对象的解决方案:将不同的功能设计为不同的类,这些类共享一个基类

大致关系如下:在这里插入图片描述
因为And_query和Or_query都操作对象个数都为2,所以为Query_base派生一个Binary_query来接受两个操作对象,然后Binary_query再派生出And_query和Or_query。

我们程序定义接口是用户只希望通过接口来访问整个继承体系,这样整个继承体系就通过接口被隐藏。

在第十二章,我们已定义了Text_query用来查询单词,Query_result用来保存查询的结果,所以我们在此程序用来当做查询的工具函数(tool),因为四种功能类都继承自Query_base,所以我们将Query_base声明为抽象基类,它的成员函数为纯虚函数,要求在派生类中必须被覆盖

实现:Query_base.h和Query.h
Query_base类作为整个继承体系的根基类,它需要为派生类提供虚函数的声明,不希望用户直接访问Query_base类,所以纯虚函数为私有成员

#pragma once
#ifndef QUERY_BASE.H
#define QUERY_BASE.H
#include <iostream>
#include "Text_query.h"
#include "Query_result.h"
using namespace std;
class Query_base {
	friend class Query;   //接口类Query通过Query_base调用其它类,所以声明友元
protected:
	virtual ~Query_base() = default;
private:
	virtual string rep()const = 0;                           //返回需要查找的单词
	virtual Query_result eval(const Text_query&)const = 0;   //查询单词函数
};
#endif // !QUERY_BASE.H

Query类作为接口,用户所做的操作都在接口上操作,接口通过Query_base类完成相应操作,所以Query类为Query_base的友元,因为& | ~运算符在此程序中含义不同,所以Query类中需要对三个运算符进行重载,也需要重载输出运算符来完成输出操作

#pragma once
#ifndef QUERY.H
#define QUERY.H
#include <iostream>
#include "Query_base.h"
#include "Word_query.h"
using namespace std;
class Query {   //接口类,为用户提供接口,实际上是Query_base在执行操作

//因为程序功能需要重载的运算符
	friend Query operator~(const Query&);
	friend Query operator&(const Query&, const Query&);
	friend Query operator|(const Query&, const Query&);
	friend ostream& operator<<(std::ostream&, const Query&);
public:
	Query() = default;
	Query(const string& s):q(new Word_query(s)){}   //接受一个string参数的构造函数
	string rep()const { return q->rep(); }          //返回需要查找的单词
	Query_result eval(const Text_query& t)const { return q->eval(t); }   //查找函数
private:
	Query(std::shared_ptr<Query_base> query):q(query){}
	std::shared_ptr<Query_base> q;              //指向Query_base的指针
};
std::ostream& operator<<(std::ostream& os, const Query& query)
{
	os << query.rep() << endl;
}
#endif // !QUERY.H

Query中以智能指针为参数的构造函数在继承体系中起到很的重要作用,在重载操作符的函数中,返回一个新的Query对象时要调用,不希望用户已指针作为参数来构造Query对象,所以声明为私有成员
eval函数使用Query_base的指针执行虚调用,指针指向的类型在运行时才可知。

Word_query.h

#pragma once
#ifndef WORD_QUERY.H
#define WORD_QUERY.H
#include <iostream>
#include <string>
#include "Query_base.h"
#include "Query.h"
using namespace std;
class Word_query:public Query_base {
	friend class Query;
private:
	Word_query(const string& s) :word(s){}
//在给定Text_query对象上执行查询唯一一个操作
	virtual Query_result eval(const Text_query& t)const { return t.query(word); }
	virtual string rep()const { return word; }
private:
	string word;
};
#endif // !WORD_QUERY.H

Word_query执行单纯的单词查询,Word_query::eval是唯一执行查询操作的函数,拿Not_query举例,当查询~(Query)p时,重载的取反运算符会返回一个Query的对象,该对象的指针指向Not_query,此时调用Not_query的参数为p的构造函数,将p拷贝给Not_query的数据成员(Query)query,因为p使用string来初始化的,所以当执行Not_query的eval时,实际上实在函数体中执行Word_query::eval
Not_query.h
~(Query)p返回一个新的Query,所以我们在Not_query中定义Query重载的取反运算符

#pragma once
#ifndef NOT_QUERY.H
#define NOT_QUERY.H
#include <iostream>
#include "Query_base.h"
#include "Query.h"
#include "Text_query.h"
using namespace std;
class Not_query:public Query_base {
	using line_no = vector<string>::size_type;
	friend Query operator~(const Query&);
private:
	Not_query(const Query& p) :query(p){}
	virtual Query_result eval(const Text_query& t)const;
	virtual string rep()const { return "~("+query.rep()+")"; }
private:
	Query query;   //保存之前的Query
};
inline
Query operator~(const Query& operand)
{
//调用Query参数为指针的构造函数
	return std::shared_ptr<Query_base>(new Not_query(operand));   
}
inline
Query_result Not_query::eval(const Text_query& t)const
{
	auto result = query.eval(t);
	auto ret_lines = make_shared<set<line_no>>();
	auto beg = result.begin();
	auto end = result.end();
	auto sz = result.get_file()->size();
	for (auto n = 0; n != sz; ++n)
	{
		if (beg == end || *beg != n)
		{
			ret_lines->insert(n);
		}
		else if (beg != end)
		{
			++n;
		}
	}
	return Query_result(rep(), ret_lines, result.get_file());
}
#endif // !NOT_QUERY.H

这里重点讲重载函数的意思:~操作返回的是一个新的Query对象,该对象的指针指向一个新构造的Not_query对象,我们通过这个对象来执行相应操作

Binary_query.h
该类是And_query和Or_query基类,是Query_base的派生类,它继承Query_base的两个纯虚函数,并且有三个受保护的数据成员,因为它的两个派生类需要权限访问。

#pragma once
#ifndef BINARY_QUERY.H
#define BINARY_QUERY.H
#include "Query.h"
#include "Query_base.h"
#include <iostream>
using namespace std;
class Binary_query :public Query_base {
protected:
	Binary_query(const Query& query1,const Query query2,const string& op):lhs(query1),rhs(query2),opSym(op){}
	virtual string rep() const { return "(" + lhs.rep() + " " + opSym + " " + rhs.rep + ")"; }
protected:
	Query lhs;
	Query rhs;
	string opSym;  //操作符
};
#endif // !BINARY_QUERY.H

And_query.h

#pragma once
#ifndef AND_QUERY.H
#define AND_QUERY.H
#include <iostream>
#include <set>
#include <iterator>
#include "Query.h"
#include "Text_query.h"
#include "Binary_query.h"
using namespace std;
class And_query:public Binary_query {
	using line_no = vector<string>::size_type;
	friend Query operator&(const Query&,const Query&);
private:
	And_query(const Query& query1, const Query& query2) :Binary_query(query1, query2, "&"){}
	virtual Query_result eval(const Text_query& t) const;
//rep已被继承
};
inline
Query operator&(const Query& lhs, const Query& rhs)
{
	return make_shared<Query_base>(new And_query(lhs, rhs));  //返回一个Query对象,该对象的指针指向新分配得And_query
}
Query_result And_query::eval(const Text_query& t) const
{
	auto left = lhs.eval(t), right = rhs.eval(t);
	auto ret_lines = make_shared<set<line_no>>();
//求两个set的交集
	std::set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(*ret_lines, ret_lines->begin()));
	return Query_result(rep(), ret_lines, left.get_file());
}
#endif // !AND_QUERY.H

该类负责重载Query的&运算符和覆盖继承来的eval函数,重载的&运算符返回一个新的Query对象,该对象的指针指向一个新创建的And_query,该类的rep函数不需要覆盖,直接基类继承来的即可

Or_query.h

#pragma once
#ifndef OR_QUERY.H
#define OR_QUERY.H
#include <iostream>
#include "Binary_query.h"
#include "Query.h"
#include "Text_query.h"
using namespace std;
class Or_query :public Binary_query {
	using line_no = vector<string>::size_type;
	friend Query operator|(const Query&, const Query&);
private:
	Or_query(const Query& query1,const Query& query2):Binary_query(query1,query2,"|"){}
	virtual Query_result eval(const Text_query&)const;
};
inline
Query operator|(const Query& lhs, const Query& rhs)
{
	return make_shared<Query_base>(new Or_query(lhs, rhs));
}
Query_result Or_query::eval(const Text_query& t) const
{
	auto left = lhs.eval(t), right = rhs.eval(t);
	auto ret_lines = make_shared<set<line_no>>(left.begin(), left.end());
	ret_lines->insert(right.begin(), right.end());
	return Query_result(rep(), ret_lines, left.get_file());
}
#endif // !OR_QUERY.H

该类的操作和上述基本相同。

Text_query和Query_result
Text_query执行查找操作,将结果保存到类Query_result中,

#pragma once
#ifndef TEXT_QUERY.H
#define TEXT_QUERY.H
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <map>
#include <set>
#include <vector>
#include "Query_result.h"
using namespace std;
class Text_query {
public:
	using line_no = vector<string>::size_type;              //C++11新标准,可以使用typedef代替*/
	Text_query(ifstream&);                                  //接受输入文件的构造函数
	Query_result query(const string&) const;                //具体的查询函数
private:
	shared_ptr<vector<string>> input;                       //保存输入的vector的智能指针
	map<string, shared_ptr<set<line_no>>> result;           //map保存单词出现的行和列
};
inline
Text_query::Text_query(ifstream& in):input(new vector<string>())
{
	string words;
	while (getline(in, words))
	{
		input->push_back(words);
		istringstream is(words);
		string word;
		int n = input->size();    //当前行号
		while (is>>word)
		{
			auto &lines = result[word];
			if (!lines)
				lines.reset(new set<line_no>);
			lines->insert(n);
		}
	}
}
inline
Query_result Text_query::query(const string& str) const
{
	static shared_ptr<set<line_no>> nodata(new set<line_no>);
	auto loc = result.find(str);
	if (loc == result.end())
		return Query_result(str, nodata, input);
	else
		return Query_result(str, loc->second, input);
}
#endif // !TEXT_QUERY.H
#pragma once
#ifndef QUERY_RESULT.H
#define QUERY_RESULT.H
#include <iostream>
#include "Text_query.h"
using namespace std;
class Query_result {
public:
	typedef vector<string>::size_type line_no;//保存出现的行号,使用类型别名
	friend ostream& operator<<(ostream&, const Query_result&);//输出查询结果

public:
	Query_result(const string& s, shared_ptr<std::set<line_no>> set,
		shared_ptr<vector<string>> v)
		: word(s), nos(set), input(v){}
	set<line_no>::iterator begin() { return nos->begin(); }
	set<line_no>::iterator end() { return nos->end(); }
	shared_ptr<vector<string>> get_file() { return input; }
private:
	string word;//查询的单词
	shared_ptr<std::set<line_no>> nos;//用set保存出现的行号,自动排序
	shared_ptr<vector<string>> input;//输入文件vector的指针
};
#endif // !QUERY_RESULT.H

总结:

  • 继承体系中基类和派生类有两种重要关系,要分清是(is A)还是(has A)
  • 设计程序时要先理清程序设计的思路,一旦思路清晰,那么细微操作会变的简单
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值