【C++重载操作符与转换】文本查询示例
一、文本查询系统的需求分析
一个基础的文本查询系统需要具备以下功能:
- 读取文本文件:将文件内容读取到程序中,作为查询的数据源。
- 单词索引:记录每个单词在文本中出现的位置(行号)。
- 查询功能:用户输入一个单词,系统返回该单词在文本中出现的所有位置及上下文信息。
为了实现这些功能,将借助 C++ 的标准库容器(如vector、map),并通过重载操作符和类型转换,让代码的使用更加自然流畅。
二、核心数据结构设计
2.1 行号类型定义
首先,定义一个类型line_no表示文本中的行号,使用using进行类型重命名:
代码语言:javascript
AI代码解释
using line_no = std::vector<std::string>::size_type;这里将line_no定义为std::vector<std::string>的大小类型,方便后续记录单词在文本中的行号位置。
2.2 单词查询结果类QueryResult
QueryResult类用于存储单个单词的查询结果,包括单词本身、包含该单词的文本以及单词出现的行号集合:
代码语言:javascript
AI代码解释
class QueryResult { friend std::ostream& print(std::ostream&, const QueryResult&); std::string sought; // 要查询的单词 std::shared_ptr<std::vector<std::string>> file; // 指向文本的指针 std::shared_ptr<std::set<line_no>> lines; // 单词出现的行号集合 public: QueryResult(const std::string& s, const std::shared_ptr<std::vector<std::string>>& f, const std::shared_ptr<std::set<line_no>>& l) : sought(s), file(f), lines(l) {} };使用智能指针std::shared_ptr管理file和lines,确保内存的自动释放和共享。
2.3 文本查询类TextQuery
TextQuery类负责读取文本文件,并构建单词索引:
代码语言:javascript
AI代码解释
class TextQuery { public: using line_no = std::vector<std::string>::size_type; TextQuery(std::ifstream&); QueryResult query(const std::string& sought) const; private: std::shared_ptr<std::vector<std::string>> file; // 存储文本内容 std::map<std::string, std::shared_ptr<std::set<line_no>>> wm; // 单词到行号集合的映射 };wm是一个map,键为单词,值为指向该行号集合的智能指针,用于快速查找单词出现的位置。
三、文本查询类的实现
3.1TextQuery构造函数
构造函数读取文本文件,并构建单词索引:
代码语言:javascript
AI代码解释
TextQuery::TextQuery(std::ifstream& in) : file(std::make_shared<std::vector<std::string>>()) { std::string text; while (std::getline(in, text)) { file->push_back(text); // 将每行文本存入 file int n = file->size() - 1; // 当前行号 std::istringstream line(text); // 将行文本拆分为单词 std::string word; while (line >> word) { auto& lines = wm[word]; // 获取单词对应的行号集合 if (!lines) { lines.reset(new std::set<line_no>); // 首次出现,创建新集合 } lines->insert(n); // 记录单词出现的行号 } } }通过std::istringstream将每行文本拆分为单词,并记录每个单词出现的行号。
3.2query函数
query函数根据输入的单词,返回对应的查询结果:
代码语言:javascript
AI代码解释
QueryResult TextQuery::query(const std::string& sought) const { static std::shared_ptr<std::set<line_no>> nodata(new std::set<line_no>); // 未找到单词时的默认结果 auto loc = wm.find(sought); if (loc == wm.end()) { return QueryResult(sought, file, nodata); // 单词未找到 } else { return QueryResult(sought, file, loc->second); // 返回查询结果 } }如果单词不存在,返回一个空的行号集合;否则返回该单词对应的行号集合。
四、重载操作符与类型转换
4.1 重载<<操作符
为了方便输出查询结果,重载<<操作符:
代码语言:javascript
AI代码解释
std::ostream& print(std::ostream& os, const QueryResult& qr) { os << qr.sought << " occurs " << qr.lines->size() << " " << (qr.lines->size() > 1? "times" : "time") << std::endl; for (line_no num : *qr.lines) { // 输出单词出现的每一行 os << "\t(line " << num + 1 << ") " << *(qr.file->begin() + num) << std::endl; } return os; }该操作符将查询结果以友好的格式输出,包括单词出现的次数和具体行内容。
4.2 类型转换辅助(可选扩展)
在某些场景下,可能需要将QueryResult转换为其他类型,比如转换为 JSON 格式用于网络传输。虽然在本示例中未涉及,但可以通过定义类型转换操作符实现:
代码语言:javascript
AI代码解释
// 示例:将 QueryResult 转换为 JSON 字符串(假设存在 JsonString 类型) operator JsonString() const { // 构建 JSON 字符串逻辑 JsonString json; json.add("word", sought); json.add("occurrences", lines->size()); // 添加行号和文本内容 std::vector<JsonString> lineDetails; for (line_no num : *lines) { JsonString line; line.add("line_number", num + 1); line.add("text", *(file->begin() + num)); lineDetails.push_back(line); } json.add("lines", lineDetails); return json; }通过定义operator JsonString(),可以实现QueryResult到JsonString的隐式转换,方便在不同场景下使用查询结果。
五、完整代码示例
代码语言:javascript
AI代码解释
#include <iostream> #include <fstream> #include <sstream> #include <string> #include <vector> #include <memory> #include <set> #include <map> using line_no = std::vector<std::string>::size_type; class QueryResult { friend std::ostream& print(std::ostream&, const QueryResult&); std::string sought; std::shared_ptr<std::vector<std::string>> file; std::shared_ptr<std::set<line_no>> lines; public: QueryResult(const std::string& s, const std::shared_ptr<std::vector<std::string>>& f, const std::shared_ptr<std::set<line_no>>& l) : sought(s), file(f), lines(l) {} }; class TextQuery { public: using line_no = std::vector<std::string>::size_type; TextQuery(std::ifstream&); QueryResult query(const std::string& sought) const; private: std::shared_ptr<std::vector<std::string>> file; std::map<std::string, std::shared_ptr<std::set<line_no>>> wm; }; TextQuery::TextQuery(std::ifstream& in) : file(std::make_shared<std::vector<std::string>>()) { std::string text; while (std::getline(in, text)) { file->push_back(text); int n = file->size() - 1; std::istringstream line(text); std::string word; while (line >> word) { auto& lines = wm[word]; if (!lines) { lines.reset(new std::set<line_no>); } lines->insert(n); } } } QueryResult TextQuery::query(const std::string& sought) const { static std::shared_ptr<std::set<line_no>> nodata(new std::set<line_no>); auto loc = wm.find(sought); if (loc == wm.end()) { return QueryResult(sought, file, nodata); } else { return QueryResult(sought, file, loc->second); } } std::ostream& print(std::ostream& os, const QueryResult& qr) { os << qr.sought << " occurs " << qr.lines->size() << " " << (qr.lines->size() > 1? "times" : "time") << std::endl; for (line_no num : *qr.lines) { os << "\t(line " << num + 1 << ") " << *(qr.file->begin() + num) << std::endl; } return os; } int main() { std::ifstream in("test.txt"); // 假设存在 test.txt 文件 if (!in) { std::cerr << "Could not open file" << std::endl; return -1; } TextQuery tq(in); while (true) { std::cout << "enter word to look for, or q to quit: "; std::string s; if (!(std::cin >> s) || s == "q") break; print(std::cout, tq.query(s)) << std::endl; } return 0; }六、功能扩展与优化
①支持短语查询
通过修改TextQuery的索引构建逻辑,不仅记录单个单词的位置,还记录相邻单词组成的短语位置,从而支持短语查询。
②模糊查询
利用字符串匹配算法(如编辑距离算法),实现模糊查询功能,允许用户输入近似的单词进行查询。
③性能优化
- 优化:使用更高效的数据结构,如
unordered_map替换map,提高单词索引的查询速度。 - 缓存机制:添加查询结果缓存,避免重复查询相同单词时的重复计算。
七、总结
通过这个文本查询示例,我们深入了解了 C++ 中重载操作符与类型转换的实际应用。重载<<操作符让查询结果的输出更加直观,而类型转换则为程序的扩展性提供了可能。同时,文本查询系统的实现过程也展示了如何合理运用标准库容器和智能指针,构建高效、安全的程序。在实际开发中,类似的思路可以应用于更多复杂场景,帮助开发者编写出简洁、强大的代码。