登录  | 立即注册

游客您好!登录后享受更多精彩

查看: 23|回复: 0

C++Primer学习笔记15.标准库

[复制链接]

44

主题

-24

回帖

30

积分

新手上路

积分
30
发表于 3 天前 | 显示全部楼层 |阅读模式

使用标准库

文本查询程序要实现的功能:允许用户在一个给定文件中查询单词。查询结果是单词在文件中出现的次数及其所在行的列表(包括行号及对应文本),如果一个单词在一行中出现多次,此行只列出一次。

文本查询程序设计

<b>需求分析</b>

要实现以上功能,此文本查询程序要做的工作:

  1. 程序读取输入文件时,需要记住单词出现的每一行,并将每一行分解为独立的单词。
  2. 程序生成输出时,需要能提取每个单词关联的行号,行号按升序出现且无重复,且能打印给定行号中的文本。

<b>数据结构设计</b>

使用以下成员存储所需数据:

  1. 一个 vector:按行存储从文件中读取的文本,每一个 string 元素代表一行。
  2. 使用 istringstraem 来将行文本分解为单词。
  3. 一个 map>:存储文件中的每个单词及其对应的行号列表,键 string 为单词,键值 set 为对应的行号列表,set 保证每行只出现一次且行号按升序保存。

<b>类的设计</b>

定义两个类:一个类 TextQuery 负责保存从输入文件读取到的内容,提供 “读取输入文件” 和 “查询内容” 两个接口;

此外,因为我们要的返回结果包含多种内容(单词出现次数、所有关联行号及对应文本),为了一次性地将查询结果返回(而不是一个又一个的变量),还要定义一个类 QueryResult 用来保存所有查询结果。这个类有一个 print 函数,完成结果打印工作。

<b>在类之间共享数据</b>

进一步地设计:

  1. QueryResult 要包含的内容包括单词出现次数、所有关联行号及对应文本,如果这些数据都拷贝得话会浪费时间,因此最好通过获取指针来避免拷贝操作。
  2. 使用了指针后 QueryResult 就和 TextQuery 共享了数据,这时如果 TextQuery 先销毁了就会导致 QueryResult 引用不存在地数据。因此应该使用 shared_ptr 来共享数据。

<b>使用TextQuery类</b>

设计一个类时,在真正实现类的成员前先编写使用这个类的程序,是一种非常有用的方法。这可以看到类是否具有我们所需要的操作。

void runQueries(ifstream& infile) {
    TextQuery tq(infile);  // 用指向文件的输入流初始化类,初始化时完成保存文件、建立查询 map 的工作。
    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; // 查询并打印结果
    }
}       

文本查询程序类的定义

TextQuery 的定义

class TextQuery{
    friend class QueryResult;
    using line_no = std::vector<std::string>::size_type;

public:
    TextQuery(std::ifstream &in_file);
    ~TextQuery() = default;

    QueryResult query(const std::string &word) const;

private:
    std::shared_ptr<std::vector<std::string>> text_lines_;
    std::map<std::string, std::shared_ptr<std::set<line_no>>> word_indexs_;
};

TextQuery 构造函数

TextQuery::TextQuery(std::ifstream &in_file) : 
    text_lines_(std::make_shared<std::vector<std::string>>()){
    std::string line_temp;
    while (std::getline(in_file, line_temp)){
        text_lines_->push_back(line_temp);
        int line_num = text_lines_->size() - 1; // 当前行号

        std::istringstream in_line(line_temp);
        std::string word;
        // 读取空格分隔的单词直接用 >> 运算符即可
        while (in_line >> word) {
            auto &lines = word_indexs_[word];         // lines 是一个 shared_ptr 的引用
            if (!lines)                               // 如果 word_indexs_ 中不存在 word,那么 lines 会是一个空指针
                lines.reset(new std::set<line_no>()); // 分配一个新的 set
            lines->insert(line_num);                  // 插入当前行号
        }
    }
}

QueryResult 的定义

class QueryResult{
    friend std::ostream &print(std::ostream &os, const QueryResult &qr);
    using line_no = std::vector<std::string>::size_type;

public:
    QueryResult(std::string word,
                std::shared_ptr<std::set<line_no>> word_index,
                std::shared_ptr<std::vector<std::string>> text_lines) : word_(word), word_index_(word_index), text_lines_(text_lines) {}

private:
    std::string word_;
    std::shared_ptr<std::set<line_no>> word_index_;
    std::shared_ptr<std::vector<std::string>> text_lines_;
};

query 函数

直接返回 QueryResult 即可,不必将其作为参数传入其中。

// 注意:函数定义时也要加 const
QueryResult TextQuery::query(const std::string &word) const {
    if (word_indexs_.find(word) == word_indexs_.end())
        return QueryResult(word, std::make_shared<std::set<line_no>>(), text_lines_);
    else
        return QueryResult(word, word_indexs_.at(word), text_lines_);
}

打印结果

print 函数的定义遵循了标准库中 print 函数的惯例,第一个参数和返回值都是流。

std::ostream &print(std::ostream &os, const QueryResult &qr){
    os << qr.word_ << " occurs " << qr.word_index_->size() << " times " << std::endl;
    for (auto num : *qr.word_index_)
        os << "\t(line " << num + 1 << ") " << *(qr.text_lines_->begin() + num) << std::endl;
    return os;
}         
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|断点社区 |网站地图

GMT+8, 2025-1-18 09:46 , Processed in 0.068503 second(s), 27 queries .

Powered by XiunoBBS

Copyright © 2001-2025, 断点社区.

快速回复 返回顶部 返回列表