0%

C++第三次小项目——文本查询程序

:::info
题目:文本查询程序

实现一个文本查询程序。

需求

1. 使用 C++ 完成代码,确保程序可由 C++ 编译器(如g++)编译。

2. 采用面向对象的思想编写程序,以使程序更为优雅可靠。程序应该尽可能符合正常使用习

惯。

3. 程序实现两个功能,一个是文件格式化功能,另一个是文本查询功能,打开程序时可以选

择功能,类似如下

选择需要的功能:

1. 文件格式化

2. 文本查询

3. 退出

4. 进入文件格式化功能,程序会根据用户后续输入的文件位置(绝对地址或相对地址)和文

件名读取文件,并由用户指定每行最大字符数 n,处理文件,使得文件在行数到达 n 时进

行换行(若为英文,需要切断单词时,提前换行,避免单词被切断,且需使得标点符号不

在行首),并由用户指定保存新文件的位置与新文件名,若位置与新文件名已存在,由用

户确认是否替换(原有文件内容不可改变,除非新文件与原文件在相同位置且有相同文件

名,并被确认替换)。

5. 文本查询允许用户指定一个文件(给定文件位置与文件名),并在其中查询单词,查询结

果是单词在文件中出现的次数及其所在行的列表。如果一个单词在一行中出现多次,此行

只列出一次。行会按照升序输出,即第7行会在第9行之前显示,以此类推。如果查询不

到,则不输出。

6. 文本查询功能可以通过交互式的方式保存用户输入的字符串(与项目二一样,忽略输入时

的各种空格),并可以支持逻辑查询操作(各种逻辑运算)。

10. 尝试支持批量读取文件,并进行针对多文件的查询。

11. 尝试支持将查询结果输出到新文件中,而非打印,如

>>> a | b >> ./result.txt #将结果输出到当前目录的文件result.txt

12. 可尝试多种方式对代码进行优化,包括执行速度、功能等;可以尝试完善错误提示,让错

误提示更加明确;可内嵌帮助文档,通过特定指令能够输出对应帮助文档。

13. 可以使用 STL 等库进行实现,但不能直接采用功能非常类似的他人的程序。

14. 项目报告需对需求进行分析,描述实现方案与项目特色。报告应包含核心代码并进行解

释。如果编译运行复杂,请提供编译运行代码的说明。提供运行结果的展示(如截图),

并尽量给出性能展示与分析。最后给出总结。同时,鼓励写上实现代码时遇到的困难以及

相应的解决思路/方案。

15. 允许适当发挥,使得程序功能更加丰富。

16. 提示:可以参考书籍《C++ Primer 第五版》12.3节与15.9节内容。

:::

实验报告解析

写在前面

得到的一些小细节启发

1.智能指针的初始化

1
2
3
//这样子是在堆上申请内存,同时也能调用set<int>的构造函数
shared_ptr<set<int>> ptr=make_shared<set<int>>()
//如果是new的方法,会导致内存非法访问,大概是因为make_shared控制了它的作用域的问题

2.ofstrem.wirte()方法适用于写二进制数据,而不是文本。

开始解析

:::info
1. 使用 C++ 完成代码,确保程序可由 C++ 编译器(如g++)编译。

2. 采用面向对象的思想编写程序,以使程序更为优雅可靠。程序应该尽可能符合正常使用习

惯。

:::

不需要装多余的库,我们需要用到的基本上STL都有,直接用STL和算法库就行了。因此不需要加多余的编译选项,能够通过编译就行。

本项目是为了实现一个文本查询的功能,需要读写文件操作,需要有文章,打印行号等,为了符合面向对象的思想,同时参考第16点提示去看了一下C++ Primer第五版的对应内容,设计了如下的类,为了适应本程序,所以类里面的对象和方法有一点小小改动。

接下来我们展示我们用到的类

首先是QueryResult和TextQuery类,第一个类设计的目的是为了保存查询后的结果,第二个TextQuery类设计的目的是为了打开文件进行读取操作,然后对文章的内容进行解析。

下面介绍这两个类的功能与作用,以及设计的思想:

基于面向对象的所用类介绍

对于TextQuery

首先对于一篇文章的获取首先要打开文件,程序要求我们查询单词,返回对应的行号和内容。

那么我们首先就要有一个容器存储行号,最好的是set,因为它无序性,不重复。

随后我们输入单词,要返回行号,明显是键值对的结构,这里采用map<string,set>,让它做自己排序

然后要打印其对应行号的内容,这说明我们要存储文章,采用vector存储文章,每个元素就是它的句子。

对于第一个方法,我们在构造函数的时候,就应该把这两个成员处理好,我们读入文章首先要一个文件流,所以传参进来的是一个读入的文件流ifstream。然后一行一行读取进行分割就是。

第二个方法,我们query方法查找要返回一个结果类,结果QueryResult类的代码放在下面一个小标题,我们需要一个string来查询,根据查询的结果(有则返回对应的值,无则返回空集合)采用QueryResult的构造函数返回。

那么问题来了,为了让QueryResult打印出对应的内容,QueryResult也需要传入一个存储文章的容器,这样直接拷贝的话就很麻烦,以及浪费空间,如果直接用TextQuery的file可能还有作用域非法访问的问题,所以我们把TextQuery的成员全部设置成共享指针,让shared_ptr来管理,因此才如代码所示。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class TextQuery{
private:
shared_ptr<vector<string>> file;//存储文章的向量只能得指针
map<string,shared_ptr<set<unsigned>>> wordmap;//用unsigned存储行号
public:
//初始化需要一个文件流。
TextQuery(ifstream& is):file(new vector<string>)//对文件流的单词句子进行解读
{
string text;
while(getline(is,text))//获取每一行
{//file是一个vector对象
file->push_back(text);
int n=file->size()-1;//也就是行号?下标?
istringstream line(text);
string word;
while(line >> word)//用string这个流分解单词
{//匹配每一个单词
shared_ptr<set<unsigned>> lines;//map的值是一个(set对象的)智能指针
if(!wordmap.count(word)){//如果不存在,说明是新单词,开辟一个set空间,并初始化
lines = make_shared<set<unsigned>>();
wordmap[word] = lines;
}
wordmap[word]->insert(n);
}
}
}
QueryResult query(const string& inputword)const//返回查找结果,查找结果是一个queryRE对象
{//如果未找到,就直接返回一个指向空的set的set共享指针
static shared_ptr<set<unsigned>> nodata(new set<unsigned>());//返回智能指针指向空set
auto loc=wordmap.find(inputword);
if(loc == wordmap.end()){
return QueryResult(inputword,nodata,file);
}else{
return QueryResult(inputword,loc->second,file);
}
}

};

对于QueryResult

对于返回的结果类,我们首先肯定保存有指向文章vector的共享指针,然后还要保存选出来的行号set的共享指针,同时保存需要查询的单词。

同理,该让共享指针管理的就让它管理。该类具有一些获取本成员的方法,以及打印的方法。

在以后得利用差集计算非集,所以也弄了个返回指向文章全部行号的集合的共享指针的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class QueryResult{
private:
string inputword;//需要查询的单词
shared_ptr<set<unsigned>> idx;//出现的行号
shared_ptr<vector<string>> file;//查找的文件
public:
QueryResult(string s,shared_ptr<set<unsigned>> i,shared_ptr<vector<string>> f):inputword(s),idx(i),file(f){

}
void print()
{
cout<<"\""<<this->inputword<<"\"出现了"<<this->idx->size()<<"次"<<endl;
//打印出现的每一行
for(auto num:*(this->idx)){
cout<<" (line "<<num+1<<") ";
cout<<*(this->file->begin()+num)<<endl;
}
}
shared_ptr<vector<string>> getfile(){//返回file的共享指针
return file;
}
shared_ptr<set<unsigned>> getset(){//返回行号的共享指针
return idx;
}
shared_ptr<set<unsigned>> getfileset(){//返回整体行号的共享指针
shared_ptr<set<unsigned>> fileset =make_shared<set<unsigned>>();;
for(int i=0;i<file->size();i++){
fileset->insert(i);
}
return fileset;
}
};

到这里,已经可以实现查询一个单词的功能了

关于采用逻辑查询操作的类

我们需要重载逻辑运算,逻辑运算单目是~,双目是|和&,这就是它们的区别。

运算过程中,运算的操作数是什么类型的,运算后的结果又是什么类型?

虽然我们可以运算出一个集合,然后让两个集合进行逻辑运算,这样子可以实现eval计算的基本思路,但是如果我们还需要别方法呢,比如知道当前是在查询什么东西(如知道查询”~(a|b)”)

我们不妨设计一个类,参考书本设计了对应的类

我们按照对应的运算设计对应的类,如NotQueryOrQueryAndQuery,其中双目运算的类继承于BinaryQuery,然后再继承于QueryBase,NotQuery直接继承于QueryBase。然后就是各自对QueryBase的虚函数以及运算符进行重载

我们让运算返回一个结果,这个结果如果是让共享指针去管理,那么就可以省很多问题,因此设计了Query,有一个成员是指向QueryBase类型的一个共享指针。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
class QueryBase{
friend class Query;
protected:
unsigned idx;//没用的成员
virtual ~QueryBase(){};
private:
virtual QueryResult eval(const TextQuery&)const{}
virtual string rep()const{}//生成用于查询的rep文本
};

class WordQuery : public QueryBase{
friend class Query;
public:
~WordQuery() override{

}
private:
//构造函数,接收string
WordQuery(const string &s):query_word(s){}
//重载虚构函数
QueryResult eval(const TextQuery &t)const{
return t.query(query_word);//wordquery返回的结果就是根据单词本身返回的QueryRE
}
string rep() const{
return query_word;
}
string query_word;

};

class Query{
//对三个运算符友元
friend Query operator ~(const Query&);
friend Query operator |(const Query& ,const Query &);
friend Query operator &(const Query& ,const Query &);
private:
//构造函数shared_ptr
//传进来是 指向QueryBase类型的 共享指针
shared_ptr<QueryBase> q;//指向基类的指针
public:
Query(shared_ptr<QueryBase> query):q(query){};
Query& operator =(const Query& que){
q=que.q;
return *this;
}
Query(const string &s){
shared_ptr<WordQuery> ptr(new WordQuery(s));
q=ptr;
}//传进来是string类型的 用来构建WordQuery,来创建Query的q成员
QueryResult eval(const TextQuery &t)const{
return q->eval(t);
}
string rep ()const{
return q->rep();
}
friend ostream & operator<<(ostream &os,const Query& query)
{
return os << query.rep();
}
};

class NotQuery : public QueryBase{
friend Query operator ~(const Query &);
public:
~NotQuery() override{

}
private:
NotQuery(const Query &q):query(q){}
string rep()const{
return "~("+query.rep()+")";
}
QueryResult eval(const TextQuery & text)const {
auto sml=query.eval(text);
auto smlset=sml.getset();
auto allset=sml.getfileset();
set<unsigned> resultset;
//包含关系,差集即是非集,大减小即可。集合本身因为迭代特性所以本身就有序。
set_difference(allset->begin(),allset->end(),smlset->begin(),smlset->end(),inserter(resultset,resultset.begin()));
shared_ptr<set<unsigned>> resultptr=make_shared<set<unsigned>>(resultset);
return QueryResult(rep(),resultptr,sml.getfile());
}
Query query;
};
inline Query operator~ (const Query &operand){
return shared_ptr<QueryBase>(new NotQuery(operand));
}
class BinaryQuery : public QueryBase{
friend Query operator ~(const Query &);
protected:
BinaryQuery(const Query &l,const Query &r,string s):lhs(l),rhs(r),opSym(s){}
string rep()const{
return "~("+lhs.rep()+" "+opSym+" "+rhs.rep()+")";
}
Query lhs,rhs;
string opSym;
};

class OrQuery : public BinaryQuery{
friend Query operator |(const Query &,const Query &);
public:
~OrQuery() override{

}
private:
OrQuery(const Query &l,const Query & r):BinaryQuery(l,r,"|"){}
QueryResult eval(const TextQuery &text)const {
//两个成员分别lhs,rhs。求并集不妨找他们返回的set的共享指针,然后用算法求
//lhs和rhs都是WordQuery
auto r=rhs.eval(text),l=lhs.eval(text);
auto retlines=l.getset();
retlines->insert(r.getset()->begin(),r.getset()->end());//左侧的插入右侧形成并集
return QueryResult(rep(),retlines,l.getfile());//两个file都一样
}

};
inline Query operator| (const Query &lhs,const Query &rhs){
return shared_ptr<QueryBase>(new OrQuery(lhs,rhs));
}
class AndQuery : public BinaryQuery{
friend Query operator &(const Query &,const Query &);
public:
~AndQuery() override{

}
private:
AndQuery(const Query &l,const Query & r):BinaryQuery(l,r,"&"){}
QueryResult eval(const TextQuery &text)const {
//两个成员分别lhs,rhs。求交集不妨找他们返回的set的共享指针,然后用算法求
//lhs和rhs都是WordQuery
auto r=rhs.eval(text),l=lhs.eval(text);//R和L是ResultQE
auto lset=l.getset(),rset=r.getset();
set<unsigned> resultset;
set_intersection(lset->begin(),lset->end(),rset->begin(),rset->end(),inserter(resultset,resultset.begin()));
shared_ptr<set<unsigned>> resultptr=make_shared<set<unsigned>>(resultset);
return QueryResult(rep(),resultptr,l.getfile());
}
};
inline Query operator& (const Query &lhs,const Query &rhs){
return shared_ptr<QueryBase>(new AndQuery(lhs,rhs));
}

关于文件资源管理的类(RAII的思想体现)

对于ofstream和ifstream,设计了对应的类管理对象,这样可以让其在不需要的时候调用析构函数。

在构造函数设计了一些东西,比如如果文件流已打开怎么处理,以及文件是否需要覆写。

以及获取当前管理文件流引用的方法,方便其他类方法的传参。

对于FileFormatter是为了实现需求4而设计,含有IFileStream和OfileStream的对象。

首先读取ifstream里的文章是以单词形式读取,这是为了方便格式化处理,让单词一个一个拼接。拼接好了达到要求就放到一个string,然后放到本类的vector newbuf存储,之后再将句子写到ofstream流里即可。

如果下一个句子有句号开头的情况,就直接拼接到本次string后面

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
class OFileStream{
private:
string FilePath;
ofstream ofile;
public:
OFileStream(const string &name){
ofile.close();
FilePath=name;
if(FileExists(name)){
cout<<"文件存在,是否进行覆写?(Y/N):";
char response;
cin>>response;
cin.ignore();
if(response=='Y'||response=='y'){
ofile.close();
ofile.open(name,ios::out);
if(ofile.is_open()){//判断文件是否已经创建流
cout<<"文件"<<name<<"成功打开,请写入 :)"<<endl;
}else{
cerr<<"文件"<<name<<"打开出现异常 :("<<endl;
}
}else{
cout<<"保留原有文件,不进行操作"<<endl;
}
}else{
//文件不存在的情况
ofile.close();
ofile.open(name,ios::out);
if(ofile.is_open()){//判断文件是否已经创建流
cout<<"文件"<<name<<"成功打开,请写入 :)"<<endl;
}else{
char ch='0';
throw ch;
}
}
}
~OFileStream(){
FilePath="";
if(ofile.is_open()){
ofile.close();
}
}
ofstream& getofsteram(){
return ofile;
}
};
//文件读取操作流
class IFileStream{
private:
string FilePath;
ifstream ifile;
public:
IFileStream(const string &name){
FilePath=name;
if(ifile.is_open()){//判断文件是否已经创建流
cout<<"文件"<<name<<"已被打开过... :|"<<endl;
}
else{
ifile.open(name);
if(ifile.is_open()){//判断文件是否已经创建流
cout<<"文件"<<name<<"可以读取 :)"<<endl;
}else{
string something="文件"+name+"读取出现异常,不存在的路径 :(";
throw something;
}
}
}
~IFileStream(){
FilePath="";
if(ifile.is_open()){
ifile.close();
}
}

ifstream& getifsteram(){
return ifile;
}
};
class FileFormatter{
private:
OFileStream ost;
IFileStream ist;
vector<string> vecbuf;
vector<string> newbuf;
unsigned num;
public:
FileFormatter(const string FilePath,const string newFilePath,unsigned n):ist(FilePath),ost(newFilePath),num(n),vecbuf(),newbuf(){
}
void readFromFile()//读取源文件
{//ist是读取文件的留,我们利用它把文件读取到指定的容器vector中。
//然后再对其进行格式化。所以此函数的作用就是将ist的内容读取出来放到vector中。
//如果文件不存在,初始化的时候会报错
//该函数作用域结束后会调用析构函数,不会造成资源浪费。
stringstream ss;
string temp,t;
char c[num+10];
while (getline(ist.getifsteram(),temp)){
//temp=c;
ss<<temp;
while(ss >> t){
vecbuf.push_back(t);//把每个单词放在容器里。
}
ss.clear();//提取完一行清除字符串流
}
}
void formatText()//格式化字符句子
{//如果单词字符数大于n,则单独独占一行
//外层循环的结束条件是vector是否用完
while(vecbuf.size()){
string str="";
if(vecbuf.front().length()>=num){
newbuf.push_back(vecbuf.front());
vecbuf.erase(vecbuf.begin());//删除第一个元素
}else{
while(str.length()<num){//对于一个句子,当前句子长度+单词长度大于num就不执行了
bool flag=vecbuf.front()=="."||vecbuf.front()==";"||vecbuf.front()=="!"||vecbuf.front()=="\""||vecbuf.front()==","||vecbuf.front()==":"||vecbuf.front()=="?";
if(str.length()==0){
//检测巨首是不是标点
if(flag){//如果是标点符号,进行处理。
cerr<<"句首出现标点符号"<<endl;
}
if(!vecbuf.empty()){
str=vecbuf.front();
vecbuf.erase(vecbuf.begin());
}
}//如果小于,就继续做
else if((str.length()+vecbuf.front().length()+1)<=num&&!flag){//句子加单词长度大于num,而不是加符号
if(!vecbuf.empty()){
str=str+" "+vecbuf.front();
vecbuf.erase(vecbuf.begin());
}
}
else if(flag){
while(flag){
if(!vecbuf.empty()){//如果句尾是标点连着的话,就连续处理
str=str+" "+vecbuf.front();
vecbuf.erase(vecbuf.begin());
flag=vecbuf.front()=="."||vecbuf.front()==";"||vecbuf.front()=="!"||vecbuf.front()=="\""||vecbuf.front()==","||vecbuf.front()==":"||vecbuf.front()=="?";
}else{
flag=false;
}
}
flag=false;
}
else{
break;
}
if(vecbuf.empty()){
break;
}
}
newbuf.push_back(str);
}
}

}
void saveToFile()//将格式化后的文本内容保存到新的位置和文件名。
{
ofstream& outfile=ost.getofsteram();
for(const string &s:newbuf){
outfile<<s<<endl;
}
}
};

解析需求3,4,5

:::info

  1. 程序实现两个功能,一个是文件格式化功能,另一个是文本查询功能,打开程序时可以选择功能,类似如下

选择需要的功能:

1. 文件格式化

2. 文本查询

3. 退出

:::

main函数就是针对需求3来设计,然后该读取的读取,该调用对的调用,以及可能涉及到的一些异常处理。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
int main()
{//对输入的buf进行判别
int cho;
string buf;

string newbuf;
while(1){
menu();
cin>>cho;
cin.ignore();
switch (cho)
{
case 1://让用户输入路径
cout<<"请输入读取的文件路径"<<endl;
cout<<">>> ";
getline(cin,buf);
cout<<"请指定每行最大的字符数n :)"<<endl;
cout<<">>>";
int num;
cin>>num;
cin.ignore();
cout<<"请指定新文件保存的路径 :)"<<endl;
cout<<">>>";
getline(cin,newbuf);
try{
ff=make_shared<FileFormatter>(buf,newbuf,num);
ff->readFromFile();
ff->formatText();
ff->saveToFile();
}catch(string str){
cerr<<"错误的文件路径"<<buf<<"导致打开异常 :("<<endl;
}catch(char ch){
cerr<<"错误的文件路径"<<buf<<"导致写入异常 :("<<endl;
}
break;
case 2:
cout<<"请输入读取的文件路径"<<endl;
cout<<">>> ";
getline(cin,newbuf);//buf就是路径
try{
ifs=make_shared<IFileStream>(newbuf);
cout<<">>> ";
getline(cin,buf);//从输入流中读取一行数据到buf
BlankFliter(buf);//首先排除空格的影响
StringTraslate(buf);
}catch(string str){
cerr<<str;
}
break;
case 3:
exit(0);
break;
default:
cout<<"无效的选项:("<<endl;
break;
}
}
return 0;
}

:::info
4. 进入文件格式化功能,程序会根据用户后续输入的文件位置(绝对地址或相对地址)和文

件名读取文件,并由用户指定每行最大字符数 n,处理文件,使得文件在行数到达 n 时进

行换行(若为英文,需要切断单词时,提前换行,避免单词被切断,且需使得标点符号不

在行首),并由用户指定保存新文件的位置与新文件名,若位置与新文件名已存在,由用

户确认是否替换(原有文件内容不可改变,除非新文件与原文件在相同位置且有相同文件

名,并被确认替换)。

:::

调用在main的代码已经写了,对应FileFormatter类的设计已在需求2展示,这里直接展示结果

:::info
5. 文本查询允许用户指定一个文件(给定文件位置与文件名),并在其中查询单词,查询结

果是单词在文件中出现的次数及其所在行的列表。如果一个单词在一行中出现多次,此行

只列出一次。行会按照升序输出,即第7行会在第9行之前显示,以此类推。如果查询不

到,则不输出。例如,在读取一个文件后,在其中寻找单词element时,输出结果的前几行

应该类似这样:

>>> element

“element” 出现 112 次

(line 36) An element contains only a key;

(line 158) operator creates a new element

(line 160) Regardless of whether the element

(line 168) When we fetch an element from a map, we

(line 214) If the element is not found, find returns

:::

结果如图,相对应的函数,定义了一个StringTraslate和ExpTraslate函数实现对应的效果,以及一些辅助函数如空格过滤,判断输入的字符串是变量还是常量等。

StringTraslate主要是按照操作符分割字符串,已经判断是查询操作还是赋值操作

ExpTraslate先是把StringTraslate处理后的字符串设法转换成后缀表达式,然后利用栈完成运算(利用栈也是为了支持复杂运算)。

对应的栈结构声明为stack<shared_ptr>,这样就不会因为生存周期的问题造成非法内存访问。

最后ExpTraslate调用 qstack.top()->eval(TextQuery(ifs->getifsteram())).print();相关的语句,就可以完成结果效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
shared_ptr<IFileStream> ifs;
shared_ptr<FileFormatter> ff;
void BlankFliter(string &str)//使用C++string类的方法快速删除空格
{
//std::remove_if函数用于将所有空白字符移动到字符串的末尾,
//然后使用erase函数将它们从字符串中删除。
//::isspace函数是一个标准库函数,用于检查一个字符是否为空白字符。
str.erase(std::remove_if(str.begin(), str.end(), ::isspace), str.end());
}
void ExpTraslate(string &buf);
string IsNameReturn(string &buf){//针对一个单词的一次性检查
string result;
auto iter = umap.find(buf);
if(iter!=umap.end()){
result=iter->second;
return result;
}
return buf;
}
void StringTraslate(string &buf)//处理用户输入的字符串
{//首先判断是否执行 查询操作
if(buf[0]=='='){
buf.erase(buf.begin());
}
//然后判断是否执行 赋值 操作
int temp=0;
int times=0;
string name,value;


for(int i=0;i<buf.size()-1;i++){
if(buf[i]=='>'&&buf[i+1]=='>'){
filename=buf.substr(i+2,buf.size()-i-2);
buf=buf.substr(0,i);//前面是查询的单词
filesearch=1;
break;
}
}
for(int i=1;i<buf.length();i++){//跳过第一个字符,检查是否有赋值操作

if(buf[i]=='='){
for(int j=i;j<buf.length();j++){
if(buf[j]=='\"'&&times==0){//找到"说明是常量
temp=j+1;
times++;
}else if(buf[j]=='\"'&&times==1){
value=buf.substr(temp,j-temp);
times++;
break;
}
if(j==buf.length()-1){//找到最后一位的时候,说明这可能是个变量
string qwq=buf.substr(temp,j-temp);
auto iter = umap.find(qwq);
if(iter!=umap.end()){
value=iter->second;
break;
}
else{
cerr<<"输入了一个不存在的变量,如果需要输入常量abcd,请用\"abcd\"包裹常量abcd :("<<endl;
}
}
}
if(times%2){
cerr<<"请确保\"abcd\"包裹常量abcd :("<<endl;
}
name=buf.substr(0,i);
umap[name]=value;
return ;
}
}
//执行表达式处理操作 操作分为分割和计算
ExpTraslate(buf);
}
bool isOperater(string str){
if(str=="|"||str=="&"||str=="~"){
return true;
}
return false;
}
int Precedence(char op) {
if (op == '~') return 3;
if (op == '&') return 2;
if (op == '|') return 1;
else return 0;
}
void ExpTraslate(string &buf){//可能出现错误,可以尝试异常处理
stack<shared_ptr<Query>> qstack;
stack<char> opstack;
char op;
vector<string> backexp;
int word_start=0;
bool last_right=buf[buf.size()-1]==')';//看在最后一个是不是右边括号
bool Had_op=0;
int priority1;
for(int i=0;i<buf.size();i++){//分割字符串并转成后缀表达式
if(buf[i]=='~'||buf[i]=='|'||buf[i]=='&'||buf[i]=='('||buf[i]==')'){
Had_op=1;
op=buf[i];
priority1=Precedence(op);
if (opstack.empty()||op=='('){//或者左括号直接压栈
opstack.push(op);
if(i-word_start>0){
backexp.push_back(buf.substr(word_start,i-word_start));
}
word_start=i+1;
}
else if(op ==')'){//如果当前位置是右括号
if(i-word_start>0){//排除()遇到)的情况
backexp.push_back(buf.substr(word_start,i-word_start));
}
while(!opstack.empty()&&opstack.top()!='('){// 不断将栈中的元素弹出,直到遇到左括号
string tempop(1,opstack.top());
opstack.pop();
backexp.push_back(tempop);
}
if(!opstack.empty()&&opstack.top()=='('){//遇到左括号就将他弹出
opstack.pop();
}
else{
cerr<<"表达式括号不匹配,请检查并重启程序 :("<<endl;
exit(-1);
}
word_start=i+1;
}
else if(op=='~'||op=='|'||op=='&')
{
if(i-word_start>0){//排除(~a)遇到~的情况
backexp.push_back(buf.substr(word_start,i-word_start));
}
while((!opstack.empty())&&(Precedence(opstack.top())>=priority1)){
// 如果栈不空,栈顶为运算符,并且栈顶运算符的优先级大于等于当前运算符的优先级
string tempop(1,opstack.top());
opstack.pop();
backexp.push_back(tempop);
}
//再将操作符压入栈
opstack.push(op);
word_start=i+1;
}
}

}//清空栈,放到后缀表达式
if(!last_right){
backexp.push_back(buf.substr(word_start,buf.size()-word_start));
}
while(!opstack.empty()){//如果op栈不空,弹出所有元素到后缀表达式
string tempop(1,opstack.top());
opstack.pop();
backexp.push_back(tempop);
}
if(!Had_op){//没有运算符,直接走捷径
Query onetime(IsNameReturn(buf));
if(filesearch){
OFileStream ofi(filename);
cout<<onetime.rep()<<endl;

onetime.eval(TextQuery(ifs->getifsteram())).write(ofi.getofsteram());
}else{
cout<<onetime.rep()<<endl;
onetime.eval(TextQuery(ifs->getifsteram())).print();
return;
}

}
//如果有运算符,都在栈里面
while(!backexp.empty()){
if(isOperater(backexp.front()))//判断向量backexp里面的是不是运算符
{
string opstr=backexp.front();
if(opstr=="|"){
if(qstack.size()<2){
cerr<<"表达式所需要的操作数不够... :("<<endl;
return ;
}
shared_ptr<Query> temp1=qstack.top();
//temp1=qstack.top();
qstack.pop();
shared_ptr<Query> temp2=qstack.top();
qstack.pop();
shared_ptr<Query> result=make_shared<Query>(*temp1.get()|*temp2.get());
qstack.push(result);
}else if(opstr=="&"){
if(qstack.size()<2){
cerr<<"表达式所需要的操作数不够... :("<<endl;
return ;
}
shared_ptr<Query> temp1=qstack.top();
//temp1=qstack.top();
qstack.pop();
shared_ptr<Query> temp2=qstack.top();
qstack.pop();
shared_ptr<Query> result=make_shared<Query>(*temp1.get()&*temp2.get());
qstack.push(result);
}else if(opstr=="~"){
if(qstack.size()<1){
cerr<<"表达式所需要的操作数不够... :("<<endl ;
return ;
}
shared_ptr<Query> temp=qstack.top();
qstack.pop();
shared_ptr<Query> result=make_shared<Query>(~(*temp.get()));
qstack.push(result);
}
backexp.erase(backexp.begin());
}//如果不是则执行下面,将Query对象入栈
else{
qstack.push(make_shared<Query>(IsNameReturn(backexp.front())));//栈里面的都是共享指针
backexp.erase(backexp.begin());
}
}
//最后所有运算操作完成
if(qstack.size()==1){
if(filesearch){
OFileStream ofi(filename);
qstack.top()->eval(TextQuery(ifs->getifsteram())).write(ofi.getofsteram());
}else{
qstack.top()->eval(TextQuery(ifs->getifsteram())).print();
}
}else{
cerr<<"表达式可能出现错误,无法运算出唯一结果"<<endl;
exit(-1);
}
}

解析需求6

:::info
文本查询功能可以通过交互式的方式保存用户输入的字符串(与项目二一样,忽略输入时

的各种空格),并可以支持逻辑查询操作(各种逻辑运算)。

:::

还是老样子涉及到unordered_map

1
2
3
4
5
6
7
8
9
10
11
12
unordered_map<string,string>umap{

};
string IsNameReturn(string &buf){//针对一个单词的一次性检查是否是变量
string result;
auto iter = umap.find(buf);
if(iter!=umap.end()){
result=iter->second;
return result;
}
return buf;
}

解析需求7

:::info
7. 要能够打印各种错误信息,比如用户输入不符合规范时。

:::

OFileStream和IFileStream里面都有对应的错误异常处理

输入的路径有问题,就返回主函数重新执行菜单

输入的查询表达式有问题,返回主函数

解析需求9

:::info
9. 尝试支持带括号控制优先级的复杂运算(优先级参照C++运算符优先级),如

>>> ((~a | b & c) | d) & d

:::

利用栈就可以实现复杂表达式的运算,就是优先级的问题。代码在需求5已经贴出

解析需求11

可以看到能够成功写到文件中,其实就是字符串处理操作中,根据>>分割成前后部分,后面打开文件即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class QueryResult{

private:
string inputword;//需要查询的单词
shared_ptr<set<unsigned>> idx;//出现的行号
shared_ptr<vector<string>> file;//查找的文件
public:
void write(ofstream& ofs){
ofs<<"\""<<this->inputword<<"\"出现了"<<this->idx->size()<<"次"<<endl;
for(auto num:*(this->idx)){
ofs<<" (line "<<num+1<<") ";
ofs<<*(this->file->begin()+num)<<endl;
}
..........
...........
.........
}

总结

算是比较正常的完成了一个程序,有了正确的思路和设计方向就是省了很多纠错的时间,对类和对象的印象更加深刻了,以及多态继承等概念,还有共享指针的妙处就是跨越{}作用域给你传输数据。解决了上次没有解决复杂表达式的问题,果然还是直接设计面向对象的栈类型是最佳的,以及重载需要的操作符,让我们更加方便的对对象进行操作。复习了RAII和异常处理类操作,没有用上模板但是没关系,也不强求,看需求。

不足之处感觉就是没有完成所有任务,感觉还是自己不够下功夫。

看了C++prinmer的相关章节,认识到自己还是需要看一遍这本书的,对于内存操作感觉还是有点陌生,以及共享指针的用处,看了才知道怎么用。

源代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
#include <iostream>
#include <sstream>
#include <fstream>
#include <memory>
#include <string>
#include <vector>
#include <map>
#include <unordered_map>
#include <stack>
#include <set>
#include <algorithm>

using namespace std;
#define MAX 1024
unordered_map<string,string>umap{

};
string IsNameReturn(string &buf);
string filename;
bool filesearch=0;
bool FileExists(const string& filename) {
ifstream file(filename);
return file.good();
}
//返回的结果类
class QueryResult{

private:
string inputword;//需要查询的单词
shared_ptr<set<unsigned>> idx;//出现的行号
shared_ptr<vector<string>> file;//查找的文件
public:
QueryResult(string s,shared_ptr<set<unsigned>> i,shared_ptr<vector<string>> f):inputword(s),idx(i),file(f){

}
void print()
{
cout<<"\""<<this->inputword<<"\"出现了"<<this->idx->size()<<"次"<<endl;
//打印出现的每一行
for(auto num:*(this->idx)){
cout<<" (line "<<num+1<<") ";
cout<<*(this->file->begin()+num)<<endl;
}
}
void write(ofstream& ofs){
ofs<<"\""<<this->inputword<<"\"出现了"<<this->idx->size()<<"次"<<endl;
for(auto num:*(this->idx)){
ofs<<" (line "<<num+1<<") ";
ofs<<*(this->file->begin()+num)<<endl;
}
// for(const string &s:newbuf){
// ofs<<s<<endl;
// }
}
shared_ptr<vector<string>> getfile(){//返回file的共享指针
return file;
}
shared_ptr<set<unsigned>> getset(){//返回行号的共享指针
return idx;
}
shared_ptr<set<unsigned>> getfileset(){//返回整体行号的共享指针
shared_ptr<set<unsigned>> fileset =make_shared<set<unsigned>>();;
for(int i=0;i<file->size();i++){
fileset->insert(i);
}
return fileset;
}

};
class TextQuery{
private:
shared_ptr<vector<string>> file;//存储文章的向量只能得指针
map<string,shared_ptr<set<unsigned>>> wordmap;//用unsigned存储行号
public:
//初始化需要一个文件流。
TextQuery(ifstream& is):file(new vector<string>)//对文件流的单词句子进行解读
{
string text;
while(getline(is,text))//获取每一行
{//file是一个vector对象
file->push_back(text);
int n=file->size()-1;//也就是行号?下标?
istringstream line(text);
string word;
while(line >> word)//用string这个流分解单词
{//匹配每一个单词
shared_ptr<set<unsigned>> lines;//map的值是一个(set对象的)智能指针
if(!wordmap.count(word)){//如果不存在,说明是新单词,开辟一个set空间,并初始化
lines = make_shared<set<unsigned>>();
wordmap[word] = lines;
}
wordmap[word]->insert(n);
}
}
}
QueryResult query(const string& inputword)const//返回查找结果,查找结果是一个queryRE对象
{//如果未找到,就直接返回一个指向空的set的set共享指针
static shared_ptr<set<unsigned>> nodata(new set<unsigned>());//返回智能指针指向空set
auto loc=wordmap.find(inputword);
if(loc == wordmap.end()){
return QueryResult(inputword,nodata,file);
}else{
return QueryResult(inputword,loc->second,file);
}
}

};



class QueryBase{
friend class Query;
protected:
unsigned idx;
virtual ~QueryBase(){};
private:
virtual QueryResult eval(const TextQuery&)const{}
virtual string rep()const{}//生成用于查询的rep文本
};

class WordQuery : public QueryBase{
friend class Query;
public:
~WordQuery() override{

}
private:
//构造函数,接收string
WordQuery(const string &s):query_word(s){}
//重载虚构函数
QueryResult eval(const TextQuery &t)const{
return t.query(query_word);//wordquery返回的结果就是根据单词本身返回的QueryRE
}
string rep() const{
return query_word;
}
string query_word;

};

class Query{
//对三个运算符友元
friend Query operator ~(const Query&);
friend Query operator |(const Query& ,const Query &);
friend Query operator &(const Query& ,const Query &);
private:
//构造函数shared_ptr
//传进来是 指向QueryBase类型的 共享指针
shared_ptr<QueryBase> q;//指向基类的指针
public:
Query(shared_ptr<QueryBase> query):q(query){};
Query& operator =(const Query& que){
q=que.q;
return *this;
}
Query(const string &s){
shared_ptr<WordQuery> ptr(new WordQuery(s));
q=ptr;
}//传进来是string类型的 用来构建WordQuery,来创建Query的q成员
QueryResult eval(const TextQuery &t)const{
return q->eval(t);
}
string rep ()const{
return q->rep();
}
friend ostream & operator<<(ostream &os,const Query& query)
{
return os << query.rep();
}
};

class NotQuery : public QueryBase{
friend Query operator ~(const Query &);
public:
~NotQuery() override{

}
private:
NotQuery(const Query &q):query(q){}
string rep()const{
return "~("+query.rep()+")";
}
QueryResult eval(const TextQuery & text)const {
auto sml=query.eval(text);
auto smlset=sml.getset();
auto allset=sml.getfileset();
set<unsigned> resultset;
//包含关系,差集即是非集,大减小即可。集合本身因为迭代特性所以本身就有序。
set_difference(allset->begin(),allset->end(),smlset->begin(),smlset->end(),inserter(resultset,resultset.begin()));
shared_ptr<set<unsigned>> resultptr=make_shared<set<unsigned>>(resultset);
return QueryResult(rep(),resultptr,sml.getfile());
}
Query query;
};
inline Query operator~ (const Query &operand){
return shared_ptr<QueryBase>(new NotQuery(operand));
}
class BinaryQuery : public QueryBase{
friend Query operator ~(const Query &);
protected:
BinaryQuery(const Query &l,const Query &r,string s):lhs(l),rhs(r),opSym(s){}
string rep()const{
return "("+lhs.rep()+" "+opSym+" "+rhs.rep()+")";
}
Query lhs,rhs;
string opSym;
};

class OrQuery : public BinaryQuery{
friend Query operator |(const Query &,const Query &);
public:
~OrQuery() override{

}
private:
OrQuery(const Query &l,const Query & r):BinaryQuery(l,r,"|"){}
QueryResult eval(const TextQuery &text)const {
//两个成员分别lhs,rhs。求并集不妨找他们返回的set的共享指针,然后用算法求
//lhs和rhs都是WordQuery
auto r=rhs.eval(text),l=lhs.eval(text);
auto retlines=l.getset();
retlines->insert(r.getset()->begin(),r.getset()->end());//左侧的插入右侧形成并集
return QueryResult(rep(),retlines,l.getfile());//两个file都一样
}

};
inline Query operator| (const Query &lhs,const Query &rhs){
return shared_ptr<QueryBase>(new OrQuery(lhs,rhs));
}
class AndQuery : public BinaryQuery{
friend Query operator &(const Query &,const Query &);
public:
~AndQuery() override{

}
private:
AndQuery(const Query &l,const Query & r):BinaryQuery(l,r,"&"){}
QueryResult eval(const TextQuery &text)const {
//两个成员分别lhs,rhs。求交集不妨找他们返回的set的共享指针,然后用算法求
//lhs和rhs都是WordQuery
auto r=rhs.eval(text),l=lhs.eval(text);//R和L是ResultQE
auto lset=l.getset(),rset=r.getset();
set<unsigned> resultset;
set_intersection(lset->begin(),lset->end(),rset->begin(),rset->end(),inserter(resultset,resultset.begin()));
shared_ptr<set<unsigned>> resultptr=make_shared<set<unsigned>>(resultset);
return QueryResult(rep(),resultptr,l.getfile());
}
};
inline Query operator& (const Query &lhs,const Query &rhs){
return shared_ptr<QueryBase>(new AndQuery(lhs,rhs));
}
//文件打开写入操作流
class OFileStream{
private:
string FilePath;
ofstream ofile;
public:
OFileStream(const string &name){
ofile.close();
FilePath=name;
if(FileExists(name)){
cout<<"文件存在,是否进行覆写?(Y/N):";
char response;
cin>>response;
cin.ignore();
if(response=='Y'||response=='y'){
ofile.close();
ofile.open(name,ios::out);
if(ofile.is_open()){//判断文件是否已经创建流
cout<<"文件"<<name<<"成功打开,请写入 :)"<<endl;
}else{
cerr<<"文件"<<name<<"打开出现异常 :("<<endl;
}
}else{
cout<<"保留原有文件,不进行操作"<<endl;
}
}else{
//文件不存在的情况
ofile.close();
ofile.open(name,ios::out);
if(ofile.is_open()){//判断文件是否已经创建流
cout<<"文件"<<name<<"成功打开,请写入 :)"<<endl;
}else{
char ch='0';
throw ch;
}
}
}
~OFileStream(){
FilePath="";
if(ofile.is_open()){
ofile.close();
}
}
ofstream& getofsteram(){
return ofile;
}
};
//文件读取操作流
class IFileStream{
private:
string FilePath;
ifstream ifile;
public:
IFileStream(const string &name){
FilePath=name;
if(ifile.is_open()){//判断文件是否已经创建流
cout<<"文件"<<name<<"已被打开过... :|"<<endl;
}
else{
ifile.open(name);
if(ifile.is_open()){//判断文件是否已经创建流
cout<<"文件"<<name<<"可以读取 :)"<<endl;
}else{
string something="文件"+name+"读取出现异常,不存在的路径 :(";
throw something;
}
}
}
~IFileStream(){
FilePath="";
if(ifile.is_open()){
ifile.close();
}
}

ifstream& getifsteram(){
return ifile;
}
};
class FileFormatter{
private:
OFileStream ost;
IFileStream ist;
vector<string> vecbuf;
vector<string> newbuf;
unsigned num;
public:
FileFormatter(const string FilePath,const string newFilePath,unsigned n):ist(FilePath),ost(newFilePath),num(n),vecbuf(),newbuf(){
}
void readFromFile()//读取源文件
{//ist是读取文件的留,我们利用它把文件读取到指定的容器vector中。
//然后再对其进行格式化。所以此函数的作用就是将ist的内容读取出来放到vector中。
//如果文件不存在,初始化的时候会报错
//该函数作用域结束后会调用析构函数,不会造成资源浪费。
stringstream ss;
string temp,t;
char c[num+10];
while (getline(ist.getifsteram(),temp)){
//temp=c;
ss<<temp;
while(ss >> t){
vecbuf.push_back(t);//把每个单词放在容器里。
}
ss.clear();//提取完一行清除字符串流
}
}
void formatText()//格式化字符句子
{//如果单词字符数大于n,则单独独占一行
//外层循环的结束条件是vector是否用完
while(vecbuf.size()){
string str="";
if(vecbuf.front().length()>=num){
newbuf.push_back(vecbuf.front());
vecbuf.erase(vecbuf.begin());//删除第一个元素
}else{
while(str.length()<num){//对于一个句子,当前句子长度+单词长度大于num就不执行了
bool flag=vecbuf.front()=="."||vecbuf.front()==";"||vecbuf.front()=="!"||vecbuf.front()=="\""||vecbuf.front()==","||vecbuf.front()==":"||vecbuf.front()=="?";
if(str.length()==0){
//检测巨首是不是标点
if(flag){//如果是标点符号,进行处理。
cerr<<"句首出现标点符号"<<endl;
}
if(!vecbuf.empty()){
str=vecbuf.front();
vecbuf.erase(vecbuf.begin());
}
}//如果小于,就继续做
else if((str.length()+vecbuf.front().length()+1)<=num&&!flag){//句子加单词长度大于num,而不是加符号
if(!vecbuf.empty()){
str=str+" "+vecbuf.front();
vecbuf.erase(vecbuf.begin());
}
}
else if(flag){
while(flag){
if(!vecbuf.empty()){//如果句尾是标点连着的话,就连续处理
str=str+" "+vecbuf.front();
vecbuf.erase(vecbuf.begin());
flag=vecbuf.front()=="."||vecbuf.front()==";"||vecbuf.front()=="!"||vecbuf.front()=="\""||vecbuf.front()==","||vecbuf.front()==":"||vecbuf.front()=="?";
}else{
flag=false;
}
}
flag=false;
}
else{
break;
}
if(vecbuf.empty()){
break;
}
}
newbuf.push_back(str);
}
}

}
void saveToFile()//将格式化后的文本内容保存到新的位置和文件名。
{
ofstream& outfile=ost.getofsteram();
for(const string &s:newbuf){
outfile<<s<<endl;
}
}
};
shared_ptr<IFileStream> ifs;
shared_ptr<FileFormatter> ff;
void BlankFliter(string &str)//使用C++string类的方法快速删除空格
{
//std::remove_if函数用于将所有空白字符移动到字符串的末尾,
//然后使用erase函数将它们从字符串中删除。
//::isspace函数是一个标准库函数,用于检查一个字符是否为空白字符。
str.erase(std::remove_if(str.begin(), str.end(), ::isspace), str.end());
}
void ExpTraslate(string &buf);
string IsNameReturn(string &buf){//针对一个单词的一次性检查
string result;
auto iter = umap.find(buf);
if(iter!=umap.end()){
result=iter->second;
return result;
}
return buf;
}
void StringTraslate(string &buf)//处理用户输入的字符串
{//首先判断是否执行 查询操作
if(buf[0]=='='){
buf.erase(buf.begin());
}
//然后判断是否执行 赋值 操作
int temp=0;
int times=0;
string name,value;


for(int i=0;i<buf.size()-1;i++){
if(buf[i]=='>'&&buf[i+1]=='>'){
filename=buf.substr(i+2,buf.size()-i-2);
buf=buf.substr(0,i);//前面是查询的单词
filesearch=1;
break;
}
}
for(int i=1;i<buf.length();i++){//跳过第一个字符,检查是否有赋值操作

if(buf[i]=='='){
for(int j=i;j<buf.length();j++){
if(buf[j]=='\"'&&times==0){//找到"说明是常量
temp=j+1;
times++;
}else if(buf[j]=='\"'&&times==1){
value=buf.substr(temp,j-temp);
times++;
break;
}
if(j==buf.length()-1){//找到最后一位的时候,说明这可能是个变量
string qwq=buf.substr(temp,j-temp);
auto iter = umap.find(qwq);
if(iter!=umap.end()){
value=iter->second;
break;
}
else{
cerr<<"输入了一个不存在的变量,如果需要输入常量abcd,请用\"abcd\"包裹常量abcd :("<<endl;
}
}
}
if(times%2){
cerr<<"请确保\"abcd\"包裹常量abcd :("<<endl;
}
name=buf.substr(0,i);
umap[name]=value;
return ;
}
}
//执行表达式处理操作 操作分为分割和计算
ExpTraslate(buf);
}
bool isOperater(string str){
if(str=="|"||str=="&"||str=="~"){
return true;
}
return false;
}
int Precedence(char op) {
if (op == '~') return 3;
if (op == '&') return 2;
if (op == '|') return 1;
else return 0;
}
void ExpTraslate(string &buf){//可能出现错误,可以尝试异常处理
stack<shared_ptr<Query>> qstack;
stack<char> opstack;
char op;
vector<string> backexp;
int word_start=0;
bool last_right=buf[buf.size()-1]==')';//看在最后一个是不是右边括号
bool Had_op=0;
int priority1;
for(int i=0;i<buf.size();i++){//分割字符串并转成后缀表达式
if(buf[i]=='~'||buf[i]=='|'||buf[i]=='&'||buf[i]=='('||buf[i]==')'){
Had_op=1;
op=buf[i];
priority1=Precedence(op);
if (opstack.empty()||op=='('){//或者左括号直接压栈
opstack.push(op);
if(i-word_start>0){
backexp.push_back(buf.substr(word_start,i-word_start));
}
word_start=i+1;
}
else if(op ==')'){//如果当前位置是右括号
if(i-word_start>0){//排除()遇到)的情况
backexp.push_back(buf.substr(word_start,i-word_start));
}
while(!opstack.empty()&&opstack.top()!='('){// 不断将栈中的元素弹出,直到遇到左括号
string tempop(1,opstack.top());
opstack.pop();
backexp.push_back(tempop);
}
if(!opstack.empty()&&opstack.top()=='('){//遇到左括号就将他弹出
opstack.pop();
}
else{
cerr<<"表达式括号不匹配,请检查并重启程序 :("<<endl;
exit(-1);
}
word_start=i+1;
}
else if(op=='~'||op=='|'||op=='&')
{
if(i-word_start>0){//排除(~a)遇到~的情况
backexp.push_back(buf.substr(word_start,i-word_start));
}
while((!opstack.empty())&&(Precedence(opstack.top())>=priority1)){
// 如果栈不空,栈顶为运算符,并且栈顶运算符的优先级大于等于当前运算符的优先级
string tempop(1,opstack.top());
opstack.pop();
backexp.push_back(tempop);
}
//再将操作符压入栈
opstack.push(op);
word_start=i+1;
}
}

}//清空栈,放到后缀表达式
if(!last_right){
backexp.push_back(buf.substr(word_start,buf.size()-word_start));
}
while(!opstack.empty()){//如果op栈不空,弹出所有元素到后缀表达式
string tempop(1,opstack.top());
opstack.pop();
backexp.push_back(tempop);
}
if(!Had_op){//没有运算符,直接走捷径
Query onetime(IsNameReturn(buf));
if(filesearch){
OFileStream ofi(filename);
cout<<onetime.rep()<<endl;

onetime.eval(TextQuery(ifs->getifsteram())).write(ofi.getofsteram());
cout<<"写入成功"<<endl;
}else{
cout<<onetime.rep()<<endl;
onetime.eval(TextQuery(ifs->getifsteram())).print();
return;
}

}
//如果有运算符,都在栈里面
while(!backexp.empty()){
if(isOperater(backexp.front()))//判断向量backexp里面的是不是运算符
{
string opstr=backexp.front();
if(opstr=="|"){
if(qstack.size()<2){
cerr<<"表达式所需要的操作数不够... :("<<endl;
return ;
}
shared_ptr<Query> temp1=qstack.top();
//temp1=qstack.top();
qstack.pop();
shared_ptr<Query> temp2=qstack.top();
qstack.pop();
shared_ptr<Query> result=make_shared<Query>(*temp1.get()|*temp2.get());
qstack.push(result);
}else if(opstr=="&"){
if(qstack.size()<2){
cerr<<"表达式所需要的操作数不够... :("<<endl;
return ;
}
shared_ptr<Query> temp1=qstack.top();
//temp1=qstack.top();
qstack.pop();
shared_ptr<Query> temp2=qstack.top();
qstack.pop();
shared_ptr<Query> result=make_shared<Query>(*temp1.get()&*temp2.get());
qstack.push(result);
}else if(opstr=="~"){
if(qstack.size()<1){
cerr<<"表达式所需要的操作数不够... :("<<endl ;
return ;
}
shared_ptr<Query> temp=qstack.top();
qstack.pop();
shared_ptr<Query> result=make_shared<Query>(~(*temp.get()));
qstack.push(result);
}
backexp.erase(backexp.begin());
}//如果不是则执行下面,将Query对象入栈
else{
qstack.push(make_shared<Query>(IsNameReturn(backexp.front())));//栈里面的都是共享指针
backexp.erase(backexp.begin());
}
}
//最后所有运算操作完成
if(qstack.size()==1){
if(filesearch){
OFileStream ofi(filename);
qstack.top()->eval(TextQuery(ifs->getifsteram())).write(ofi.getofsteram());
cout<<"写入成功"<<endl;
}else{
qstack.top()->eval(TextQuery(ifs->getifsteram())).print();
}
}else{
cerr<<"表达式可能出现错误,无法运算出唯一结果"<<endl;
exit(-1);
}
}
void menu(){
cout<<"选择需要的功能:"<<endl;
cout<<"1.文本格式化"<<endl;
cout<<"2.文本查询"<<endl;
cout<<"3.退出"<<endl;
cout<<"Your Choice: ";
}

int main()
{//对输入的buf进行判别
int cho;
string buf;

string newbuf;
while(1){
menu();
cin>>cho;
cin.ignore();
switch (cho)
{
case 1://让用户输入路径
cout<<"请输入读取的文件路径"<<endl;
cout<<">>> ";
getline(cin,buf);
cout<<"请指定每行最大的字符数n :)"<<endl;
cout<<">>>";
int num;
cin>>num;
cin.ignore();
cout<<"请指定新文件保存的路径 :)"<<endl;
cout<<">>>";
getline(cin,newbuf);
try{
ff=make_shared<FileFormatter>(buf,newbuf,num);
ff->readFromFile();
ff->formatText();
ff->saveToFile();
}catch(string str){
cerr<<"错误的文件路径"<<buf<<"导致打开异常 :("<<endl;
}catch(char ch){
cerr<<"错误的文件路径"<<buf<<"导致写入异常 :("<<endl;
}
break;
case 2:
cout<<"请输入读取的文件路径"<<endl;
cout<<">>> ";
getline(cin,newbuf);//buf就是路径
try{
ifs=make_shared<IFileStream>(newbuf);
cout<<">>> ";
getline(cin,buf);//从输入流中读取一行数据到buf
BlankFliter(buf);//首先排除空格的影响
StringTraslate(buf);
}catch(string str){
cerr<<str;
}
break;
case 3:
exit(0);
break;
default:
cout<<"无效的选项:("<<endl;
break;
}
}
return 0;
}