一、需求与设计分析
1.1 核心需求
根据 C++ Primer 11.3.6 练习要求,工具需满足以下功能:
- 规则加载:从map.txt读取替换规则(每行格式:待替换单词 替换后的短语)
- 文本处理:读取file.txt中的待转换文本,将匹配规则的单词替换为对应短语
- 结果输出:将替换后的文本写入output.txt
- 灵活性:支持通过命令行参数自定义规则文件、输入文件和输出文件路径
- 鲁棒性:处理文件打开失败、格式错误等异常情况
1.2 示例输入输出
1 2 3 4 5 6 7 8
| brb be right back k okay? y why r are u you pic picture thk thanks! l8r later
|
- 待转换文本(file.txt):包含缩写词的原始文本
1 2 3
| where r u y dont u send me a pic k thk l8r
|
- 预期输出(output.txt):替换后的标准文本
1 2 3
| where are you why dont you send me a picture okay? thanks! later
|
1.3 技术选型
功能模块 |
技术方案 |
选择理由 |
替换规则存储 |
unordered_map<string, string> |
哈希表结构,查找效率 O (1),适合高频查询 |
文件读取 / 写入 |
ifstream/ofstream |
C++ 标准文件流,支持文本文件操作 |
字符串分割 / 解析 |
istringstream/ostringstream |
方便处理行内单词提取与拼接 |
单词边界判断 |
自定义isDelimiter函数 |
准确识别空格、标点等分隔符 |
二、核心技术解析
2.1 替换规则存储:unordered_map 的优势
unordered_map是 STL 中的哈希表容器,相比map(红黑树实现),它的查找、插入、删除操作平均时间复杂度为 O (1),在单词替换场景中(需要频繁查询 “待替换单词是否存在”)性能更优。
其核心特性:
键(key)唯一:确保每个待替换单词只有一个替换规则
键值对存储:键为 “待替换单词”,值为 “替换后的短语”
支持快速查找:通过find()方法快速定位键,返回迭代器
2.2 文件 IO 处理:安全的文件操作
C++ 文件流(fstream系列)是处理文件的标准方式,使用时需注意:
- 文件打开检查:必须验证is_open()状态,避免文件不存在或权限不足导致崩溃
- 资源自动释放:ifstream/ofstream析构时会自动关闭文件,无需手动调用close()
- 行读取方式:getline()读取整行文本,避免>>运算符自动跳过空格 / 换行的问题
2.3 单词边界识别:准确分割单词
文本中的单词通常被空格、标点(如逗号、句号)分隔,直接使用iss >> word会丢失标点符号(如 “u!” 会被拆分为 “u” 和 “!”)。因此需要自定义isDelimiter函数,判断字符是否为分隔符,确保:
单词部分(如 “u”)被正确提取并替换
分隔符(如 “!”)被保留,不破坏原始文本格式
三、完整代码实现与解析
3.1 错误处理函数
统一处理程序异常,避免崩溃并提示用户错误原因:
1 2 3 4 5
| // 错误处理函数:输出错误信息并终止程序 void error(const string& msg) { cerr << "错误: " << msg << endl; exit(1); // 非0退出码表示程序异常结束 }
|
3.2 规则加载函数(loadMap)
从规则文件读取每行内容,解析为 “键 - 值” 对并存储到unordered_map:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| void loadMap(unordered_map<string, string>& mapping, const string& filepath) { // 打开规则文件 ifstream file(filepath); if (!file.is_open()) { error("无法打开规则文件: " + filepath); // 打开失败则报错 }
string line; // 逐行读取规则文件 while (getline(file, line)) { // 用字符串流解析当前行 istringstream iss(line); string key; // 提取“待替换单词”(键):>>自动跳过前导空格 if (!(iss >> key)) { continue; // 空行或格式错误,跳过当前行 } // 提取“替换后的短语”(值):getline读取剩余所有内容 string value; getline(iss, value); // 移除值前面的空白字符(如键与值之间的空格/制表符) size_t start = value.find_first_not_of(" \t"); if (start != string::npos) { value = value.substr(start); // 截取有效部分 } else { value.clear(); // 若值为空,设为空白字符串 } // 将键值对存入映射表 mapping[key] = value; } }
|
关键细节:
iss >> key提取键后,getline(iss, value)会读取行中剩余所有内容(包括空格),确保替换短语中的空格不丢失
find_first_not_of(" \t")移除值前面的空白字符,避免键与值之间的分隔符被包含在替换短语中
3.3 分隔符判断函数(isDelimiter)
判断字符是否为单词分隔符(空格或标点),确保单词提取准确:
1 2 3 4 5
| bool isDelimiter(char c) { // 转换为unsigned char避免负数ASCII值导致的未定义行为 return isspace(static_cast<unsigned char>(c)) || ispunct(static_cast<unsigned char>(c)); }
|
为什么用static_cast?
isspace和ispunct函数要求输入为unsigned char或EOF,若直接传入char(可能为负数,如中文编码),会导致未定义行为。转换后确保输入符合函数要求。
3.4 文本替换函数(replaceText)
核心逻辑:逐字符遍历文本,提取单词并替换,同时保留原始分隔符:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
| void replaceText(const unordered_map<string, string>& mapping, string& line) { string result; // 存储替换后的结果 string currentWord; // 存储当前正在构建的单词 // 逐字符遍历当前行 for (char c : line) { // 若当前字符是分隔符 if (isDelimiter(c)) { // 处理已构建的单词(若存在) if (!currentWord.empty()) { // 查找单词是否在替换映射中 auto it = mapping.find(currentWord); if (it != mapping.end()) { result += it->second; // 存在则替换 } else { result += currentWord; // 不存在则保留原单词 } currentWord.clear(); // 重置当前单词 } // 保留分隔符(空格/标点) result += c; } else { // 非分隔符,添加到当前单词 currentWord += c; } } // 处理行尾的单词(循环结束后可能仍有未处理的单词) if (!currentWord.empty()) { auto it = mapping.find(currentWord); if (it != mapping.end()) { result += it->second; } else { result += currentWord; } } // 将替换结果赋值给原行(引用传递,直接修改输入) line = result; }
|
核心逻辑拆解:
逐字符遍历文本,区分 “单词字符” 和 “分隔符”
遇到分隔符时,处理已构建的单词(查找并替换),然后保留分隔符
循环结束后,处理行尾可能残留的单词(避免遗漏)
引用传递line参数,直接修改原始文本,避免额外字符串拷贝
3.5主函数(main)
协调程序整体流程:解析命令行参数、加载规则、处理文本、输出结果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| int main(int argc, char* argv[]) { // 默认文件路径:若未指定命令行参数,使用这些路径 string mapFile = "map.txt"; // 规则文件 string inputFile = "file.txt"; // 待转换文本文件 string outputFile = "output.txt"; // 输出文件 // 解析命令行参数:支持自定义文件路径 // 命令行格式:./program 规则文件 输入文件 输出文件 if (argc > 1) mapFile = argv[1]; if (argc > 2) inputFile = argv[2]; if (argc > 3) outputFile = argv[3]; // 1. 加载替换规则到unordered_map unordered_map<string, string> mapping; loadMap(mapping, mapFile); // 2. 打开待转换文本文件 ifstream input(inputFile); if (!input.is_open()) { error("无法打开输入文件: " + inputFile); } // 3. 打开输出文件 ofstream output(outputFile); if (!output.is_open()) { error("无法创建输出文件: " + outputFile); } // 4. 逐行处理文本 string line; while (getline(input, line)) { replaceText(mapping, line); // 替换当前行的单词 output << line << endl; // 将替换后的行写入输出文件 } // 5. 提示转换完成 cout << "转换完成,结果保存在 " << outputFile << endl; return 0; }
|
四、完整实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130
| 根据代码生成博客 #include <iostream> #include <unordered_map> #include <fstream> #include <sstream> #include <string> #include <cctype>
using namespace std;
// 错误处理函数 void error(const string& msg) { cerr << "错误: " << msg << endl; exit(1); }
// 从规则文件加载替换映射 void loadMap(unordered_map<string, string>& mapping, const string& filepath) { ifstream file(filepath); if (!file.is_open()) { error("无法打开规则文件: " + filepath); }
string line; while (getline(file, line)) { istringstream iss(line); string key; // 提取关键字 if (!(iss >> key)) { continue; // 跳过空行或格式错误的行 } // 提取替换值(包含所有剩余内容) string value; getline(iss, value); // 移除值前面的空白字符 size_t start = value.find_first_not_of(" \t"); if (start != string::npos) { value = value.substr(start); } else { value.clear(); // 空值 } mapping[key] = value; } }
// 判断字符是否为单词分隔符 bool isDelimiter(char c) { return isspace(static_cast<unsigned char>(c)) || ispunct(static_cast<unsigned char>(c)); }
// 替换文本中的单词 void replaceText(const unordered_map<string, string>& mapping, string& line) { string result; string currentWord; for (char c : line) { if (isDelimiter(c)) { // 处理当前单词 if (!currentWord.empty()) { auto it = mapping.find(currentWord); if (it != mapping.end()) { result += it->second; } else { result += currentWord; } currentWord.clear(); } // 添加分隔符 result += c; } else { // 构建当前单词 currentWord += c; } } // 处理行尾的单词 if (!currentWord.empty()) { auto it = mapping.find(currentWord); if (it != mapping.end()) { result += it->second; } else { result += currentWord; } } line = result; }
int main(int argc, char* argv[]) { // 设置文件路径,支持命令行参数 string mapFile = "map.txt"; string inputFile = "file.txt"; string outputFile = "output.txt"; // 解析命令行参数 if (argc > 1) mapFile = argv[1]; if (argc > 2) inputFile = argv[2]; if (argc > 3) outputFile = argv[3]; // 加载替换规则 unordered_map<string, string> mapping; loadMap(mapping, mapFile); // 打开输入文件 ifstream input(inputFile); if (!input.is_open()) { error("无法打开输入文件: " + inputFile); } // 打开输出文件 ofstream output(outputFile); if (!output.is_open()) { error("无法创建输出文件: " + outputFile); } // 处理每一行文本 string line; while (getline(input, line)) { replaceText(mapping, line); output << line << endl; } cout << "转换完成,结果保存在 " << outputFile << endl; return 0; }
|