终于学完了C++,前十二章基础知识(一些零散的知识点啊啊啊啊啊,不停的写题写题...),接下来就该学习C++程序设计核心部分,类设计者的工具,在此之前,我们可以用一个简单的程序来简单的总结一下。话不多说,开始正题。
文本查询程序
用户功能简述:给定一个文件,输入关键字,然后输出该关键字在文件中出现的个数、行数及该行字符串
<1>功能分析
需要三个个功能:
1.当程序读取文件是,它需要记住单词出现的每一行,因此逐行读取文件。
2.它把必须能提取每个单词所关联的行号,及该行的字符串,且行号升序且无重复
3.将找出来行数、字符串、以及次数输出到控制台。
<2>功能实现方案
1 2部分.实现:读取后需要一个数据结构去储存,考虑到需要逐行读取,且后期需要提取行号。因此这个结构最好是vector
接下来找出单词所在行,这个实现有两种方案
(1)提前扫描文本所有的单词,并储存其行数,需要关联string和set
(2)在用户输入单词时,再在我们一二部分实现部分定义的vector中提取信息。
方案一,需求更大的空间,而方案二在运行时需求更多的时间。
我们先看看方案一:
功能实现对于本方案而言,无疑包含两部分工作
一是读取文件,并作准备工作包括:读取文件位置,读取文件内容到vector,将文件内所有单词关联到行储存到map中。
二是读取用户输入单词,并作处理工作包括:在map中提取行号,打印出行号和对应的行。
那么,考虑到这些工作都是围绕数据进行,按照C的编程方式,我们会使用分文件的编写方案。但在数据共享和数据安全性方面,显然有更高效和理想的方式。我们将编写两个类来协助工作。
一个类是TextQuery,负责读取。另一个类是TextResult,负责打印结果。
在设计两个类的时候,大多数初学朋友都会,先行设计这两个类。往往更耗时间,我们不如,先看看这样一个文本查询程序需要怎样的操作,然后根据这样的操作,去一一填补。下面我们先进行进行设计程序流程图。
这么,简单???对啊!就是这么简单,接下来,看我们初步建立的类和文件。
那么我们该先写那一部分勒,我这里打算先写TextResult,并行、相关性不大的类设计时,哪一个开始都好。而本例明显有因果关系。程序设计,我个人比较喜欢执果求因。
首先我需要这样的结果
给程序输入文本的地址(本例是C:/Users/me_wa/Desktop/TextTest.txt)
然后出现文字enter word to look for, or q to quit:
用户输入单词后,出现如上结果。
然后考虑怎样出现这样的结果。我们先编写TextResult,前面说了它是负责出结果的。对应如上结果,我需要的数据就有了。分别是输入的单词、前面功能实现方案提到保存文件的 vector或它的指针、map中单词对应的set
因为TextQuery负责读取,它已经为vector和set分配了相应的内存,我们为了节省内存,就使用指针,指向TextQuery给vector和set分配的内存。再者拷贝实体也会花费时间,如果文件比较大,TextQuery读取文件分配了内存,TextResult为了获取三个数据就再分配内存然后拷贝,显然耗费空间时间。
当然为了防止TextQuery将内存意外释放,而我们TextResult访问空地址的情况出现。我们这里使用智能指针。
最后考虑一点,我们的结果TextResult数据结构已经搭建完成,那么我们如何从哪获取呢?
这里我们考虑通过TextResult的构造函数传进去,也就是说,当TextQuery读取完数据后,构造时TextResult把这三个参数传进来。
当然print函数可以作为TextResult的成员出现的,也可以定义为TextResult的友元函数也可以打印出相应结果。但是print函数和TextResult的功能性契合度不高,它并不承担着改变TextResult数据的任务,也不是TextResult的功能性必须函数。将TextResult对象当作参数传入print函数函数,打印出其数据成员即可,这样的灵活性也也更高。
因此我们的TextResult和print这样写
"TextResult.h"
#ifndef _TEXTRESULT_H__
#define _TEXTRESULT_H__
#include
#include
#include
#include
#include
class TextResult
{
using line_no = std::vector::size_type;
friend std::ostream& print(std::ostream& , TextResult&);
public:
TextResult(std::string w,
std::shared_ptr> f,
std::shared_ptr> l) :
word(w), files(f), lines(l){};
~TextResult() {};
private:
std::string word;//查找的单词
std::shared_ptr> files; //指向vector的智能指针
std::shared_ptr> lines; //指向map中对应pair第二个元素:set的智能指针
};
std::ostream& print(std::ostream& os, TextResult&qr);
#endif _TEXTRESULT_H__
TextResult.c
#include "TextQuery.h"
std::ostream& print(std::ostream& os, TextResult& qr)
{
//输出内容xx单词出现x次
os << qr.word << " occurs " << qr.lines->size()
<< ((qr.lines->size()) > 1 ? " times." : " time.") << std::endl;
//利用set村存的行号从vector中提取这一行的字符串
for (auto& num : *(qr.lines))
{
os << "\t(line " << num + 1 << ") "
<< (*(qr.files))[num] << std::endl;
}
return os;
}
好了,我们的数据已经能输出了,现在就等TextQuery读入数据然后,构造TextResult输出了。工作已经完成了一半了,你看朕的半壁江山已经为你打下!!!哈!哈!哈!
TextQuery前面功能实现方案说了,我们需要两个数据结构一个是vector,另一个是map。当然为了数据内存共享,使用new分配内存。那么现在考虑如何获取文件数据到vector和map。既然最初给我们的是一个文件位置,那么我们就使用ifstream好了。当然最方便的方法就是在TextQuery构造之时,将绑定该文件的ifstream作为参数传进来,然后再构造函数中将文件数据读到vector和map。我们没必要另起一个成员函数去初始化数据,再者构造函数的意义就是初始化数据。
好了,最后一个问题,数据如何传给TextResult,当然我们可以在用户代码中构造TextResult,然后将TextQuery的成员作为参数传进去,当然在这之前需要声明友元。
但是,这样很麻烦,而TextResult和TextQuery关联性比较大,我们完全可以将这个过程定义为TextQuery的成员函数。然后return TextResult对象给print函数。一气呵成。
好了我们这样写
TextQuery.h
#ifndef _TEXTQUERY_H__
#define _TEXTQUERY_H__
#include "TextResult.h"
#include
#include
#include
TextQuery.c
#include "TextQuery.h"
#include
using namespace std;
TextQuery::TextQuery(std::ifstream& is):
files(new std::vector)
{
string s;
while (getline(is,s))
{
//读取行到vector
files->push_back(s);
//记录当前行数
auto n = files->size() - 1;
//读取行信息到map
istringstream line(s);
string word;
while (line >> word)
{
//添加word关键字的pair到map
auto& linesSet = wm[word];
//只有当前单词为新单词,才会new出set
if (!linesSet)
linesSet.reset(new set);
//给set插入行号
linesSet->insert(n);
}
}
}
TextResult TextQuery::query(const std::string& sought) const // 这里const是防止有人修改类成员,隐含传入的this指针式是const的
{
//如果找不到单词,就返回这个静态的空set。
static shared_ptr> nodate(new set);
auto loc = wm.find(sought);
if(loc == wm.end())
return TextResult(sought, files, nodate);
return TextResult(sought, files, loc->second);
}
以上,我们完成了一套服务,最后就是完成用户代码,来使用这两个类,还记得我们的用户代码流程嘛,不记得向上翻翻。
#include
#include "TextResult.h"
#include "TextQuery.h"
using namespace std;
class TextQuery;
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));
}
}
int main(int argc, char**argv)
{
if (argv[1] == NULL)
{
return 1;
}
ifstream file(argv[1]);//"C:/Users/me_wa/Desktop/TextTest.txt"
runQueries(file);
system("pause");
return 0;
}
以上结束。。。
哦,对了,这个参数需要从main输入,vs是无法完成的了。。。
步骤是
1、打开cmd
2、在你工程里找到exe文件然后拖到cmd界面(你会发现是一串地址)
3、在2步骤产生的字符串之后,输入你所要查询文件的位置。
4、回车
接下来就可以运行啦哈哈哈。
以上我可能忽略了一些东西,如果有问题,评论区提问我啊哈哈哈