题目描述:给定一段文本,按功能查找输入的内容
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)
- 设计程序时要先理清程序设计的思路,一旦思路清晰,那么细微操作会变的简单