《C++ Primer》 chapter 15 TextQuery

  《C++ Primer》中第15章为了讲解面向对象编程,举了一个例子:设计一个小程序,能够处理查询给定word在文件中所在行的任务,并且能够处理“非”查询,“或”查询,“与”查询。例如执行查询 one & of |the ,表示对单词one和of的查询结果取交集,然后对单词the的查询结果取并集。

  书中查询的底层操作实际定义在类TextQuery中,我们在TextQuery的基础上,进一步封装并实现如下图所示的类结构,能够达到上述功能需求。类之间的结构如下图所示:

  

  程序扼要设计如下表所示:

  在此基础之上,可以写出下述代码。代码已经详细注释,具体实现细节不再赘述。

#include <iostream>
#include <set>
#include <vector>
#include <fstream>
#include <sstream>
#include <string>
#include <map>

using namespace std;

class TextQuery {
public:
    //执行查找,返回单词所在行号组成的set
    set<int> run_query(const string &word) const {
        auto it = word_map.find(word);
        if (it == word_map.end())
            return set<int>();
        return it->second;
    }

    //返回line行对应的字符串
    string text_line(int line) const {
        if (line < lines_of_text.size())
            return lines_of_text[line];
        throw out_of_range("line number out of range");
    }

    //返回vector中存储的行的数目
    int size() const {
        return lines_of_text.size();
    }

    //读取文件,建立键值对映射关系
    void read_file(ifstream &is) {
        store_file(is);
        build_map();
    }

private:
    //从文件输入流in中读取行,并将其存储在vector中
    void store_file(ifstream &is) {
        string textline;
        while (getline(is, textline))
            lines_of_text.push_back(textline);
    }

    //建立单词 和行号组成的键值对映射关系
    void build_map() {
        for (int line_no = 0; line_no != lines_of_text.size(); line_no++) {
            string text_line = lines_of_text[line_no];
            istringstream line(text_line);
            string word;
            while (line >> word) {
                word_map[word].insert(line_no);
            }

        }
    }

    //存储所有的行
    vector<string> lines_of_text;
    //存储单词,行号键值对
    map<string, set<int>> word_map;
};

//显示查询结果
void print_results(const set<int> &locs, const TextQuery &file) {
    if (locs.empty()) {
        cout << endl << "Sorry. There are no entries for your query." << endl << "Try again." << endl;
        return;
    }
    //输出行号和对应的行
    for (auto it = locs.begin(); it != locs.end(); it++) {
        cout << "\t" << "(line "
        << (*it) + 1 << ") "
        << file.text_line(*it) << endl;
    }
}

//打开文件,返回文件输入流
ifstream &open_file(ifstream &in, const string &file) {
    in.close();
    in.clear();
    in.open(file.c_str());
    return in;
}

class Query_base {
    friend class Query;

protected:
    virtual ~Query_base() { }

private:
    //定义纯虚函数,对TextQuery对象执行查询
    virtual set<int> eval(const TextQuery &) const = 0;
    //输出查询结果
    virtual ostream &display(ostream & = cout) const = 0;
};
//对单词执行查询
class WordQuery : public Query_base {
    friend class Query;

    WordQuery(const string &s) : query_word(s) { }

    //执行查询,返回查询的结果
    set<int> eval(const TextQuery &t) const {
        return t.run_query(query_word);
    }
    //输出查询结果
    ostream &display(ostream &os) const {
        return os << query_word;
    }
    //要查询的单词
    string query_word;
};

class Query {
    //这些函数能够隐式调用private中的构造函数 Query(Query_base *query),创建Query对象
    friend Query operator~(const Query &);

    friend Query operator|(const Query &, const Query &);

    friend Query operator&(const Query &, const Query &);

public:
    //对要查询的单词初始化Query对象
    Query(const string &s) : q(new WordQuery(s)), use(new int(1)) {

    }
    //复制构造函数
    Query(const Query &c) : q(c.q), use(c.use) {
        ++*use;
    }
    //析构函数
    ~Query() {
        decr_use();
    }
    //重载运算符=
    Query &operator=(const Query &);
    //对TextQuery 执行查询任务
    set<int> eval(const TextQuery &t) const {
        return q->eval(t);
    }

    ostream &display(ostream &os) const {
        return q->display(os);
    }

private:
    //友元重载函数可以访问
    Query(Query_base *query) : q(query), use(new int(1)) { }

    Query_base *q;
    int *use;
    //减少引用计数的值,如果引用计数的值为0,那么删除对象
    void decr_use() {
        if (--*use == 0) {
            delete q;
            delete use;
        }
    }
};
//重载运算符 = ,减少本对象的引用计数,同时增加要复制的对象的引用计数
Query &Query::operator=(const Query &rhs) {
    ++*rhs.use;
    decr_use();
    q = rhs.q;
    use = rhs.use;
    return *this;
}
//重载运算符 <<
ostream &operator<<(ostream &os, const Query &q) {
    return q.display(os);
}

class BinaryQuery : public Query_base {
protected:
    BinaryQuery(Query left, Query right, string op) : lhs(left), rhs(right), oper(op) { }
    //输出 查询 操作
    ostream &display(ostream &os) const {
        return os << "(" << lhs << " " << oper << " " << rhs << ")";
    }
    //左右两个操作数
    const Query lhs, rhs;
    //运算符
    const string oper;
};

class AndQuery : public BinaryQuery {
    //友元重载运算符函数可以访问构造器函数 Query(Query_base *query)
    friend Query operator&(const Query &, const Query &);

    AndQuery(Query left, Query right) : BinaryQuery(left, right, "&") { }
    //查询实际上是对左右操作数的查询结果取交集
    set<int> eval(const TextQuery &file) const {
        set<int> left = lhs.eval(file);
        set<int> right = rhs.eval(file);
        set<int> ret;
        set_intersection(left.begin(), left.end(), right.begin(), right.end(), inserter(ret, ret.begin()));
        return ret;
    }
};

class OrQuery : public BinaryQuery {
    //友元重载运算符函数可以访问构造器函数Query(Query_base *query)
    friend Query operator|(const Query &, const Query &);

    OrQuery(Query left, Query right) : BinaryQuery(left, right, "|") { }
    //查询实际上是对左右操作数的查询结果取并集
    set<int> eval(const TextQuery &file) const {
        set<int> left = lhs.eval(file);
        set<int> right = rhs.eval(file);
        left.insert(right.begin(), right.end());
        return left;
    }
};

class NotQuery : public Query_base {
    //友元重载运算符函数可以访问构造器函数 Query(Query_base *query)
    friend Query operator~(const Query &);

    NotQuery(Query q) : query(q) { };
    //执行的查询实际上是对左右两个操作数的查询结果取差集
    set<int> eval(const TextQuery &file) const {
        auto result = query.eval(file);
        set<int> ret;
        for (int n = 0; n != file.size(); n++) {
            if (result.find(n) == result.end())
                ret.insert(n);
        }
        return ret;
    }

    ostream &display(ostream &os) const {
        return os << "~" << query << ")";
    }

    const Query query;
};

//重载运算符 &
inline Query operator&(const Query &lhs, const Query &rhs) {
    return new AndQuery(lhs, rhs);
}

//重载运算符 |
inline Query operator|(const Query &lhs, const Query &rhs) {
    return new OrQuery(lhs, rhs);
}

//重载运算符 ~
inline Query operator~(const Query &oper) {
    return new NotQuery(oper);
}

//创建TextQuery实例
TextQuery build_textfile(const string &filename) {
    ifstream infile;
    if (!open_file(infile, filename)) {
        cerr << "can‘t open input file!" << endl;
        return TextQuery();
    }
    TextQuery ret;
    ret.read_file(infile);
    return ret;
}

int main() {
    TextQuery file = build_textfile("/Users/zhouyang/Desktop/text.txt");
    string word1, word2, word3;
    while (cin >> word1 >> word2 >> word3) {
        Query q = Query(word1) & Query(word2) | Query(word3);
        cout << "Executing Query for: " << q << endl;
        auto result = q.eval(file);
        print_results(result, file);
    }
    return 0;
}

执行查询: one & of |the , 结果如下:

one of the
Executing Query for: ((one & of) | the)
	(line 1) Many in the US believe the use of the nuclear bomb, though devastating, was right, because it forced Japan to surrender, bringing an end to World War Two.
	(line 2) The daughter of one survivor, who was visiting the memorial on Friday, said the suffering had "carried on over the generations".
	(line 3) "That is what I want President Obama to know," Han Jeong-soon, 58, told the Associated Press news agency. "I want him to understand our sufferings."
	(line 4) Seiki Sato, whose father was orphaned by the bomb, told the New York Times: "We Japanese did terrible, terrible things all over Asia. That is true. And we Japanese should say we are sorry because we are so ashamed, and we have not apologised sincerely to all these Asian countries. But the dropping of the atomic bomb was completely evil."
	(line 6) Media captionFilmmaker Barry Frechette told Shigeaki Mori‘s story in the film Paper Lanterns

	(line 9) China responded to the visit by saying that Japan‘s six-week attack on the Chinese city of Nanjing, which began in December 1937, was more worthy of reflection.
	(line 10) The Chinese say 300,000 people were killed, although other sources say the figure was lower.
	(line 12) ‘Just listen‘ - Japan‘s media on the visit
	(line 13) The Chugoku Shimbun urges Mr Obama to "hear the voices of Hiroshima". "The people of Hiroshima will be watching the president closely, eyeing to what extent he is truly resolved to advance the abolition of nuclear arms," it said.
	(line 14) The Asahi Shimbun carries an article saying Mr Obama‘s "gestures will shape the visit", with the "most powerful gesture" being to "just listen to the bomb victims‘ memories of suffering and activism".
	(line 15) The Japan Times says: "To truly pay homage to those whose lives were lost or irrevocably altered by the Hiroshima and Nagasaki bombings, Obama‘s visit must galvanise the international community to move without delay toward a world free of nuclear weapons. The fact that these weapons have not been used over the past 70 years does not guarantee a risk-free future for our children."
	(line 20) The bomb was nicknamed "Little Boy" and was thought to have the explosive force of 20,000 tonnes of TNT
	(line 21) Paul Tibbets, a 30-year-old colonel from Illinois, led the mission to drop the atomic bomb on Japan
	(line 22) The Enola Gay, the plane which dropped the bomb, was named in tribute to Col Tibbets‘ mother
	(line 23) The final target was decided less than an hour before the bomb was dropped. The good weather conditions over Hiroshima sealed the city‘s fate
	(line 24) On detonation, the temperature at the burst-point of the bomb was several million degrees. Thousands of people on the ground were killed or injured instantly
	(line 25) The hours before the bomb was dropped
时间: 2024-10-10 15:28:10

《C++ Primer》 chapter 15 TextQuery的相关文章

《C++primer》v5 第5章 语句 读书笔记 习题答案

5.1 空语句只有一个";".如果什么也不想做可以使用空语句. 5.2 用花括号{}括起来的叫块,也叫复合语句.有多条语句作用在同一个作用域时,需要用花括号括起来. 5.3 降低了. 5.4 (a)每次迭代时候会初始化iter,但是iter缺少初值,所以这段代码根本不会通过编译.另外这里的括号需要一个bool类型的,而定义迭代器根本不会返回一个bool类型.假如上面那些问题都可以通过,每次迭代都会初始化这个iter,会导致死循环. (b)我试了一下编译未通过是因为没找到适合的find函

《C++primer》v5 第3章 字符串、向量和数组 读书笔记 习题答案

3.1略 3.2 string str; //读行 while(getline(cin,str)) cout<<str<<endl; //读单个词 while(cin>>str) cout<<str<<endl; 3.3 输入运算符读到空白符结束 getline读到换行符结束,并丢弃换行符 3.4 比较大小. 比较大小是比较的第一个不相同的字符的大小. int main() { string a,b; cin>>a>>b;

《C++primer》v5 第1章 开始 读书笔记 习题答案

从今天开始在博客里写C++primer的文字.主要以后面的习题作业为主,会有必要的知识点补充. 本人也是菜鸟,可能有不对之处,还望指出. 前期内容可能会比较水. 1.1略 1.2略 1.3 cin和cout分别是istream和ostream的对象. #include<iostream> using namespace std; int main() { cout<<"Hello,world"<<endl; return 0; } 1.4 #incl

《C++primer》v5 第4章 表达式 读书笔记 习题答案

4.1 105 4.2 *vec.begin()=*(vec.begin())//先调用点运算符,再解引用 *vec.begin()+1=(*vec.begin())+1//先解引用,再加一 4.3略? 4.4 (12/3*4)+(5*15)+(24%4/2)=91 4.5 (a)-86(b)-16 (c)0 (d)0 4.6 n%2 4.7 溢出:计算结果超出该数据类型所能表示的范围 2147483647+1 1U-2 ... 4.8 比较低.. 4.9 首先判断cp是否为空指针,若非空指针则

《C++ Primer》学习 之 编译器推断auto类型

/* <C++ Primer>学习 之 编译器推断auto类型 书P61-P62 重点在于程序中的注释,方便以后复习. */ 1 #include <iostream> 2 #include <cmath> 3 #include <string> 4 using namespace std; 5 6 int main() 7 { 8 int i = 0, &r = i; 9 auto a = r; // a : int 10 11 const int

如何利用《C++ Primer》学习C++?

<C++ Primer>作为久负盛名的C++经典教程,丰富的教学辅助内容.精心组织的编程示范,无论是初学者入门,或是中.高级程序员提升,都是不容置疑的首选. 一本好书只有读过才有价值,然而<C++ Primer>这本厚如砖头的好书,很多人在购买之后,都仅仅只是简单翻阅了前面几个章节,而并未能够认真的完整读完该书. 实验楼为了能够让大家能够更好的利用<C++ Primer>学习C++,推出了深入学习<C++ Primer 第五版>训练营,以任务驱动模式学习经典

(4)风色从零单排《C++ Primer》 变量,引用,指针

从零单排<C++ Primer> --(4)变量,引用,指针   变量的初始化 所谓变量的初始化,指在创建变量的同时给予值. 初始化方法: int units_sold = 0; int units_sold = {0}; int units_sold{0}; int units_sold{0}; long double ld = 3.1415926536: int a{ld}, b = {ld}; //error:narrowing conversion required int c(ld)

《c++primer》学习笔记

花了一个多月时间总算是把这本书看完了,再去看自己家游戏的服务器的代码还是很难懂,里面用到了好多boost库的东西,不过这些东西很多都已经加入了c++11的新标准里了,要到自己能做服务器还得接着学,所以接下来的一个月开始看<C++标准库>. 把看<c++primer>的时候抄的笔记先誊在这里方便以后参考. #include 来自标准库的头文件使用<> 来自非标准库的头文件使用"" ------------------------------------

C++自学笔记_文本查询程序_《C++ Primer》

<C++ Primer> 第10章结束,用一个文本查询程序结束本章 :) 程序将读取用户指定的任意文本文件,然后允许用户从该文件中查找单词.查询的结果是该单词出现的次数,并列出每次出现所在的行.如果某单词在同一行 中多次出现,程序将只显示该行一次.行号按照升序显示. 程序支持以下任务: · 它必须允许用户指明要处理的文件的名字.程序将存储该文件的内容,以便输出每个单词所在的原始行. · 它必须将每一行分解为各个单词,并记录每个单词所在的行.在输出行号时,应保证以升序输出,并且不重复. · 对特