在 C++ 编程中,容器是管理数据的重要工具。不同类型的容器(如vector、map、set等)适用于不同的场景,合理地组合使用它们能够高效解决复杂问题。文本查询程序是一个非常经典的 C++ 容器综合应用案例,通过实现这个程序,我们可以深入理解容器的特性与使用技巧。
// 高效存储文档内容
std::vector<std::string> documents;
documents.reserve(10000); // 预分配内存优化性能insert+erase使用// 建立单词到文档ID的映射
std::unordered_map<std::string, std::vector<size_t>> inverted_index;reserve()预分配桶数量// 维护已索引文档集合
std::set<std::string> processed_files;
// 构建有序词频统计
std::map<std::string, size_t> word_counts;// 实现LRU缓存淘汰机制
std::list<std::string> lru_cache;文本查询程序的核心功能是在给定的文本文件中查询特定单词出现的次数以及所在的行号。例如,当用户输入一个单词时,程序能够返回该单词在文本中出现的总次数,同时列出包含该单词的每一行文本内容及其行号。
在基本功能的基础上,还可以进一步实现单词出现位置的统计可视化(如绘制词频分布图)、支持通配符查询、区分大小写查询等扩展功能。本文先聚焦于基本功能的实现,后续可基于此进行扩展。
用于存储文本文件的每一行内容。vector是一个动态数组,它能够根据需要自动调整大小,方便逐行读取文本文件中的内容并进行存储。例如,可以通过循环将文件中的每一行读入到vector<string>容器中。
这个容器组合用于记录每个单词在哪些行中出现。其中,map的键是单词(string类型),值是一个set<int>,set中存储的是该单词出现的行号。map可以快速地根据单词查找对应的行号集合,而set能够自动对行号进行排序并去除重复项。
除了作为map的值用于存储行号外,set还可以用于其他需要去重和排序的场景。在文本查询程序中,它确保每个行号只被记录一次,并且按照升序排列,便于后续展示。
首先,需要打开并读取文本文件,将每一行内容存储到vector<string>中。以下是实现代码:
#include <iostream>
#include <fstream>
#include <sstream>
#include <vector>
#include <string>
#include <map>
#include <set>
using namespace std;
vector<string> readFile(const string& filename) {
vector<string> lines;
ifstream file(filename);
if (file.is_open()) {
string line;
while (getline(file, line)) {
lines.push_back(line);
}
file.close();
} else {
cerr << "无法打开文件: " << filename << endl;
}
return lines;
}readFile函数接受文件名作为参数,尝试打开文件。如果文件打开成功,通过getline函数逐行读取文件内容,并将每行内容存入lines这个vector<string>容器中,最后关闭文件并返回lines。
读取完文件后,需要对每一行中的单词进行处理,构建单词与行号的对应关系,存入map<string, set<int>>中。代码如下:
map<string, set<int>> buildIndex(const vector<string>& lines) {
map<string, set<int>> wordIndex;
for (size_t i = 0; i < lines.size(); ++i) {
istringstream iss(lines[i]);
string word;
while (iss >> word) {
wordIndex[word].insert(i + 1); // 行号从1开始计数
}
}
return wordIndex;
}buildIndex函数遍历lines中的每一行,使用istringstream将每行拆分成单个单词。对于每个单词,将其作为键在wordIndex这个map中查找,如果键不存在则创建一个新的set<int>,然后将当前行号插入到对应的set中。
有了单词索引后,就可以实现查询功能了。用户输入一个单词,程序根据索引返回查询结果。代码如下:
void queryWord(const string& word, const map<string, set<int>>& wordIndex, const vector<string>& lines) {
auto it = wordIndex.find(word);
if (it != wordIndex.end()) {
const set<int>& lineNumbers = it->second;
cout << "单词 \"" << word << "\" 共出现 " << lineNumbers.size() << " 次。" << endl;
for (int lineNum : lineNumbers) {
cout << "第 " << lineNum << " 行: " << lines[lineNum - 1] << endl;
}
} else {
cout << "单词 \"" << word << "\" 未在文本中出现。" << endl;
}
}queryWord函数接受要查询的单词、单词索引wordIndex以及存储文本行的lines作为参数。通过在wordIndex中查找单词,如果找到则输出单词出现的次数以及包含该单词的每一行内容和行号;如果未找到则提示单词未在文本中出现。
最后,在主函数中调用上述函数,实现完整的文本查询流程。代码如下:
int main() {
string filename = "test.txt"; // 替换为实际文件名
vector<string> lines = readFile(filename);
if (lines.empty()) return 1;
map<string, set<int>> wordIndex = buildIndex(lines);
string query;
cout << "请输入要查询的单词(输入q退出): ";
while (cin >> query && query != "q") {
queryWord(query, wordIndex, lines);
cout << "请输入要查询的单词(输入q退出): ";
}
return 0;
}在main函数中,首先指定要读取的文件名,调用readFile读取文件内容,然后调用buildIndex构建单词索引。接着通过循环让用户输入要查询的单词,调用queryWord进行查询,直到用户输入q退出程序。
假设我们有一个名为test.txt的文本文件,内容如下:

编译并运行上述代码,按照提示输入要查询的单词:
istringstream用于将字符串拆分成单个单词。在buildIndex函数中,通过istringstream将每一行文本拆分成单词,以便构建单词索引。
如果指定的文件名不存在或没有读取权限,ifstream打开文件会失败。可以通过检查is_open的返回值来判断,并在失败时输出错误信息,如readFile函数中所示。
当处理非常大的文本文件时,vector和map可能会占用大量内存。可以考虑使用更节省内存的数据结构,如unordered_map(牺牲有序性换取更快的查找速度和更低的内存占用),或者分块处理文本文件。
当前程序默认是大小写敏感的查询。如果需要实现不区分大小写的查询,可以在构建索引和查询时将单词统一转换为大写或小写,例如使用std::transform函数结合std::tolower或std::toupper来实现。
通过实现文本查询程序,我们综合运用了vector、map、set等 C++ 容器,深入理解了它们的特性和使用场景。从文件读取、单词索引构建到查询功能实现,每个环节都展示了容器在数据管理和处理中的重要作用。