文本查询程序

要求:程序允许用户在一个给定文件中查询单词。查询结果是单词在文件中出现的次数及所在行的列表。如果一个单词在一行中出现多次,此行只列出一次。

对要求的分析:

1.读入文件,必须记住单词出现在每一行。因此,程序需要逐行读取文件,并将每一行分解成独立的单词;

2. 程序生成输出时,它必须能提取每个单词所关联的行号;行号必须按升序出现且无重复;必须能打印给定行号的文本。

数据结构分析:

使用vector<string>来保存整个文件的拷贝;使用set来保存每个单词出现的行号;使用map将单词与行号关联起来。

我们在此基础上,再抽象一层:

定义一个保存文本输入的类,叫TextQuery。有两个操作:一是读取文件构造对象;二是执行查询操作。

查询操作:返回单词所在行及其文本。此结果定义一个类:QueryResult。

TextQuery与QueryResult的关系

由于QueryResult所需要的数据都保存在一个QTextQuery对象中,我们必须确定如何访问它们。我们可以拷贝行号的set,但这样很耗时。而且我们不希望拷贝vector。

所以两个类共享了数据

 class QueryResult;

 class TextQuery
{
public:
using line_no = vector<string>::size_type;
TextQuery(ifstream&);
QueryResult query(const string&) const;//不完全类型可以声明(但不能定义)为参数类型和返回类型
private:
shared_ptr<vector<string>> file;
map<string, shared_ptr<set<line_no>>> wm;
}; TextQuery::TextQuery(ifstream& is)
:file(new vector<string>)
{
string text;
while (getline(is, text))
{
file->push_back(text);
int n = file->size() - ;
istringstream line(text);
string word;
while (line >> word)
{
auto &lines = wm[word];
if (!lines)
lines.reset(new set<line_no>);
lines->insert(n);
}
}
} class QueryResult
{
friend ostream& print(ostream&, const QueryResult&);
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) {}
private:
string sought;
shared_ptr<set<line_no>> lines;
shared_ptr<vector<string>> file;
}; //如果没有找到string,应该返回什么?
//我们定义一个局部static对象,它指向一个空行号set的shared_ptr,未找到单词,则返回此对象的一个拷贝
QueryResult TextQuery::query(const string &sought) const
{
static shared_ptr<set<line_no>> nodata(new set<line_no>);
//不使用下标运算符来查找,避免将单词添加到wm中
auto loc = wm.find(sought);
if (loc == wm.end())
return { sought, nodata, file };
else
return { sought, loc->second, file };
} ostream& print(ostream &os, const QueryResult &qr)
{
os << qr.sought << " occurs " << qr.lines->size() << " time(s)" << endl;
for (auto num : *qr.lines)
os << "\t(line " << num + << ") " << *(qr.file->begin() + num) << endl;
return os;
} void runQueries(ifstream& infile)
{
TextQuery tq(infile);
while (true)
{
cout << "enter word to look for, or q to quit: ";
string s;
if (!(cin >> s) || s == "q")
break;
print(cout, tq.query(s)) << endl;
}
}

邮件与目录

资源管理并不是类需要定义自己的拷贝控制成员的唯一原因。一些类也需要拷贝控制成员的帮助来进行簿记工作或其他操作。

要求:有两个类Message和Folder,分别表示电子邮件消息和目录。每个Message可以出现在多个Folder中,Message内容只有一个副本——>如果一条Message的内容被改变,则我们从它所在的任何Folder来浏览此Message。

设计思路如下:

Message保存一个它所在Folder的指针的set;Folder保存一个它包含Message的指针的set。

当我们拷贝一个Message时,副本和原对象是不同的Message对象。因此,拷贝Message的操作包括消息内容和Folder指针set的拷贝;而且,我们必须在每个包含此消息的Folder中都添加一个指向新创建的Message的指针;

当我们销毁一个Message时,它将不复存在。因此,我们必须从包含此消息的所有Folder中删除指向此Message的指针;

当我们将一个Message对象赋予另一个Message对象时,左侧Message的内容会被右侧Message的内容所替代。同时,还必须更新Folder集合。

我们可以看到:

析构函数和拷贝赋值运算符都必须从包含一条Message的所有Folder中删除它。

拷贝构造函数和拷贝赋值运算符都要将一个Message添加到给定的一组Folder中。

我们定义两个private工具函数来完成这些工作。

 class Message
{
friend class Folder;
friend void swap(Message&, Message&);
public:
explicit Message(const string &str = "")
:contents(str) {}
Message(const Message&);
Message& operator=(const Message&);
Message(Message &&m);
Message& Message::operator=(Message &&rhs);
~Message();
void save(Folder&);
void remove(Folder&);
private:
string contents;
set<Folder*> folders;
void addToFolders(const Message&);
void removeFromFolders();
void moveFolders(Message *m);
}; void Message::save(Folder &f)
{
folders.insert(&f);
f.addMsg(this);
} void Message::remove(Folder &f)
{
folders.erase(&f);
f.remMsg(this);
}
//拷贝控制:
void Message::addToFolders(const Message &m)
{
for (auto f : m.folders)
f->addMsg(this);
}
void Message::removeFromFolders()
{
for (auto f : folders)
f->remMsg(this);
} Message::Message(const Message &m)
:contents(m.contents),folders(m.folders)
{
addToFolders(m);
}
Message::~Message()
{
removeFromFolders();
}
Message& Message::operator=(const Message &rhs)
{
//先从左侧对象的folders中删除此Message指针,然后再添加到右侧运算对象的folders中,从而实现自赋值的正确处理
removeFromFolders();
contents = rhs.contents;
folders = rhs.folders;
addToFolders(rhs);
return *this;
}
//定义自己的swap版本
void swap(Message &lhs, Message &rhs)
{
using std::swap;
for (auto f : lhs.folders)
f->remMsg(&lhs);
for (auto f : rhs.folders)
f->remMsg(&rhs); swap(lhs.folders, rhs.folders);
swap(lhs.contents, rhs.contents);
for (auto f : lhs.folders)
f->addMsg(&lhs);
for (auto f : rhs.folders)
f->addMsg(&rhs);
} //移动操作
void Message::moveFolders(Message *m)
{
folders = std::move(m->folders);//使用set的移动赋值运算符
for (auto f : folders)
{
f->remMsg(m);
f->addMsg(this);
}
m->folders.clear();
} Message::Message(Message &&m)
:contents(std::move(m.contents))
{
moveFolders(&m);
} Message& Message::operator=(Message &&rhs)
{
if (this != &rhs)
{
removeFromFolders();
contents = std::move(rhs.contents);
moveFolders(&rhs);
}
return *this;
}

上述代码以Message类为例。

Chapter12&Chapter13:程序实例的更多相关文章

  1. (转)实战Memcached缓存系统(5)Memcached的CAS程序实例

    1. 非CAS 首先看一个不是CAS的Memcached程序实例.实例的问题原型,见上一篇博文. 程序实例: package com.sinosuperman.memcached; import ja ...

  2. 如何让Windows程序只运行一个程序实例?

    要实现VC++或者MFC只运行一个程序实例,一般采用互斥量来实现,即首先用互斥量封装一个只运行一个程序实例的函数接口: HANDLE hMutex = NULL; void MainDlg::RunS ...

  3. Linux Epoll介绍和程序实例

    Linux Epoll介绍和程序实例 1. Epoll是何方神圣? Epoll但是当前在Linux下开发大规模并发网络程序的热门人选,Epoll 在Linux2.6内核中正式引入,和select类似, ...

  4. 微信小程序实例源码大全

    微信小程序实例源码大全下载 微信小应用示例代码(phodal/weapp-quick)源码链接:https://github.com/phodal/weapp-quick 微信小应用地图定位demo( ...

  5. MFC 只启动一个程序实例

    问题描述: 我们开发过程中可能会经常遇到,只启动一个程序实例.即一个程序启动之后,如果再次执行该程序,将会恢复之前打开的程序,而不是打开一个新的程序. 实现原理:利用FindWindow/FindWi ...

  6. 一个完整的Installshield安装程序实例—艾泽拉斯之海洋女神出品(四) --高级设置二

    原文:一个完整的Installshield安装程序实例-艾泽拉斯之海洋女神出品(四) --高级设置二 上一篇:一个完整的安装程序实例—艾泽拉斯之海洋女神出品(三) --高级设置一4. 根据用户选择的组 ...

  7. 微信小程序实例教程(一)

    序言 开始开发应用号之前,先看看官方公布的「小程序」教程吧!(以下内容来自微信官方公布的「小程序」开发指南) 本文档将带你一步步创建完成一个微信小程序,并可以在手机上体验该小程序的实际效果.这个小程序 ...

  8. JDBC 程序实例小练习

    JDBC 程序实例问题 编程实现如下功能:在数据库中建立一个表,表名为student,其结构为学号.姓名.性别.年龄.英语.JavaSE程序设计.初级日语.总分,在表中输入多条记录. 学生的总分信息, ...

  9. C++程序实例唯一方案,窗口只打开一次,程序只打开一次

    首先是方法: // IsAlreadyRunning - 是否已经运行 BOOL IsAlreadyRunning() { BOOL bRet = FALSE; HANDLE hMutex = ::C ...

  10. 开发一个微信小程序实例教程

    一.注册小程序账号 1.进入微信公众平台(https://mp.weixin.qq.com/),注册小程序账号,根据提示填写对应的信息即可.2.注册成功后进入首页,在 小程序发布流程->小程序开 ...

随机推荐

  1. SQL SERVER ->> BCP导出数据到平面文件

    --开启xp_cmdshell sp_configure ‘show advanced options’, ; GO RECONFIGURE; GO sp_configure ‘xp_cmdshell ...

  2. C#AutoResetEvent和ManualResetEvent的区别

    一:终止状态和非终止状态 首先说说线程的终止状态和非终止状态.AutoResetEvent和ManualResetEvent的构造函数中,都有bool变量来指明线程的终止状态和非终止状态.true表示 ...

  3. leetcode:Search for a Range(数组,二分查找)

    Given a sorted array of integers, find the starting and ending position of a given target value. You ...

  4. Hopfield模型

    1982年,J.Hopfield提出了可用作联想存储器的互连网络,这个网络称为Hopfield网络模型,也称Hopfield模型.Hopfield神经网络模型是一种循环神经网络,从输出到输入有反馈连接 ...

  5. sgu 495. Kids and Prizes (简单概率dp 正推求期望)

    题目链接 495. Kids and Prizes Time limit per test: 0.25 second(s)Memory limit: 262144 kilobytes input: s ...

  6. C# Access DBHelp

    /* * By :落落 * URL: Www.MyLuoLuo.Com */ using System; using System.Collections.Generic; using System. ...

  7. 【转】PWM占空比和分辨率

    占空比是接通时间与周期之比冲量相等而形状不同的窄脉冲加在具有惯性的环节上时,其效果基本相同占空比:就是输出的PWM中,高电平保持的时间与该PWM的时钟周期的时间之比,如一个PWM的频率是1000Hz, ...

  8. echarts地图点定位的问题

    1,生成地图 2,如果需要产生地图上的点位,需要在配置中传入geoCoord具体数据为一下: { "海门": [121.15, 31.89], "鄂尔多斯": ...

  9. 简单使用Junit

    不需要配置,导入相应jar,然后在测试的方法上面加入注解@Test 执行的时候选择junit即可.

  10. mysql,多表的内外连接+子查询

    表: student house course 关系:student_course 多对多 student house 多对一 需求:查询房间1 的学生 都学习了什么课程 select s.s_nam ...