c++:使用标准库的文本查询程序

//实现一个简单的文本查询程序:
//允许用户在给定文件中查询单词。查询结果是单词在文件中出现的次数及其所在行的列表。
//如果一个单词在一行中出现多次,此行只列出一次。行会按照升序输出
#include<iostream>
#include<vector>
#include<string>
#include<fstream>
#include<sstream>
#include<set>
#include<map>
#include<memory>
#include"make_plural.h"
using namespace std;

//QueryResult类的声明
class QueryResult
{
//包含三个数据成员:一个string,保存查询单词;
//一个shared_ptr,指向保存输入文件的vector;
//一个shared_ptr,指向保存单词出现行号的set;


friend ostream& print(ostream&, const QueryResult&);//声明友元函数
                                        //友元函数是能够访问类中的私有成员的非成员函数,它是定义在内外的普通函数,但需要在类的定义中加以声明,声明时只需在友元的名称前加friend;
public:
using line_no = vector<string>::size_type;//创建类型的别名


//成员函数是构造函数,将参数保存在对应的数据成员中,这是在其初始化器列表中完成的
QueryResult(string s, shared_ptr<set<line_no>> p, shared_ptr<vector<string>> f) :sought(s), lines(p), file(f) {};
//与其他函数不同,构造函数除了有名字,参数列表和函数体之外,还可以有初始化列表,初始化列表以冒号开头,后跟一系列以逗号分隔的初始化字段。
                                        //初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。
    //对于内置类型,如int, float等,使用初始化列表和在构造函数体内初始化差别不是很大,但是对于类类型来说,最好使用初始化列表
//初始化列表先于构造函数执行,只能用于构造函数,可同时初始化多个数据成员

/*除了性能问题之外,有些时场合初始化列表是不可或缺的,以下几种情况时必须使用初始化列表:
1.常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面
2.引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面
3.没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化。*/


private:
string sought;//要查询的单词
shared_ptr<set<line_no>> lines;//出现的行号
shared_ptr<vector<string>> file;//输入文件
};

//TextQuery类的声明
class TextQuery
{
public:
using line_no = vector<string>::size_type;//创建类型的别名
TextQuery(ifstream&);//构造函数
QueryResult query(const string&) const;//常成员函数
  /*使用const关键字修饰的函数为常成员函数,声明格式如下:
类型说明符  函数名(参数表) const;
注意:
const是函数类型的一个组成部分,因此在函数的定义部分也要带const关键字。
常成员函数不能更新对象的数据成员,也不能调用该类中没有用const修饰的成员函数(这保证了在常成员函数中绝对不会更改数据成员的值)。
如果将一个对象说明为常对象,则通过该对象只能调用它的常成员函数,而不能调用其他成员函数(这就是C++从语法机制上对常对象的保护,也是常对象唯一的对外接口方式)。
const关键字可以用于对重载函数的区分,例如,如果在类中这样声明:
void print();
void print() const;
这是对print的有效重载。*/

private:
map<string, shared_ptr<set<line_no>>> wm;//每个单词及其对应的行号
shared_ptr<vector<string>> file;//输入文件

};

//TextQuery类的构造函数的定义
TextQuery::TextQuery(ifstream &is) :file(new vector<string>)
{
string text;
while (getline(is, text))//对文件中的每一行
{
file->push_back(text);//保存此行文本
 /*当访问地址(指针或迭代器)的成员或数据时,用“->”
 当访问直接对象的成员或数据时,用“.”*/
int n = file->size() - 1;//当前行号??
istringstream line(text);//将行文本分解为单词
string word;
while (line >> word)
{
//对每行中的每个单词,如果单词不在wm中,以之为下标在wm中添加一项
auto &lines = wm[word];//lines是一个shared_ptr
//map和unordered_map的下标操作:
//c[k]返回关键字为k的元素;如果k不在c中,添加一个关键字为k的元素,对其进行值初始化
if (!lines)//当我们第一次遇到这个单词时,此指针为空
lines.reset(new set<line_no>);//分配一个新的set
lines->insert(n);//将此行插入set中 n应该是value_type类型的对象
}
//定义和改变shared_ptr的一些方法:
//p.reset() 若p是唯一指向其对象的shared_ptr,reset会释放此对象。
//若传递了可选的参数内置指针q(形如p.reset(q)),会令p指向q,否则会将p置空。
//若还传递了参数d(形如p.reset(q,d)),将会调用d而不是delete来释放q
}
}

//TextQuery的成员函数query函数的定义
//接受一个string参数,即查询单词,query用它来在map中定位对应的行号set
QueryResult TextQuery::query(const string &sought)const
{
//如果找到sought,我们将返回一个指针指向此set
static shared_ptr<set<line_no>> nodata(new set<line_no>);
//使用find而不是下标运算符来查找单词,避免将单词添加到wm中
auto loc = wm.find(sought);
if (loc == wm.end())
return QueryResult(sought, nodata, file);
else
return QueryResult(sought, loc->second, file);
}

//print函数的定义
//在给定的流上打印出给定的QueryResult对象
 ostream& print(ostream & os, const QueryResult &qr)
{
//如果找到了单词,打印出现的次数和所出现的位置
os << qr.sought << "occurs" << qr.lines->size() << " " << make_plural(qr.lines->size(), "time", "s") << endl;
    //打印 单词出现的每一行
for(auto num : *qr.lines)//对set中的每一个单词
//避免行号从0开始给用户带来困惑
os << "\t(line" << num + 1 << ")" << *(qr.file->begin() + num) << endl;
return os;
}

int main(int argc, char* argv[])
{

ifstream in(argv[1]);
if(!in)
{
cout<<"打开文件失败"<<endl;
    exit(1);
}
TextQuery tq(in);
while (true)
{
cout << "Please enter a word to look for or 'q' to quit:";
string s;
//若遇到文件尾或用户输入q时循环终止
if (!(cin >> s) || s == "q")
break;
print(cout, tq.query(s)) << endl;
}
system("pause");
return 0;
}


测试结果如下:

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值