“字符串减法”指按语义单位(如行或句号分隔的子句)删除重复项、仅保留首次出现者;需先明确定界规则,再用std::getline配合std::unordered_set逐段去重并注意trim和空行处理。

什么是“字符串减法”:先明确问题边界
“字符串减法”不是 C++ 标准库里的概念,用户实际想表达的是:给定一个长字符串(比如由多个子句拼接而成,用句号、换行或特定分隔符隔开),删除其中所有重复出现的子句,只保留第一次出现的那份。注意,这不是字符级去重(如 std::unique),而是**按语义单位(子句)做去重**。
关键判断点:子句如何界定?常见场景是按行(
)、按句号+空格(". ")、或自定义分隔符。不明确分隔规则,直接写“删重复子句”必然出错。
用 std::getline + std::unordered_set 拆分并去重
这是最常用也最可控的做法:把输入按行切分,逐行检查是否已见过,没见过的才保留。适合以换行为单位的子句(如日志、配置块、多行文本)。
- 用
std::istringstream或std::ifstream配合std::getline逐行读取 - 用
std::unordered_set<:string></:string>记录已出现的子句(注意:默认区分大小写和首尾空格) - 每读一行,先
trim(手动实现或用std::regex_replace去首尾空白)再查表;未命中则插入集合并追加到结果 - 错误现象:
std::getline读到空行时返回空字符串,若不跳过会导致“空子句”被当作有效项重复拦截
示例片段:
std::string input = "Hello.
World.
Hello.
C++.
";
std::istringstream iss(input);
std::string line;
std::unordered_set<:string> seen;
std::vector<:string> unique_lines;
while (std::getline(iss, line)) {
// 简单 trim(实际项目建议用更健壮的 trim 函数)
line.erase(0, line.find_first_not_of("
"));
line.erase(line.find_last_not_of("
") + 1);
if (line.empty()) continue;
if (seen.find(line) == seen.end()) {
seen.insert(line);
unique_lines.push_back(line);
}
}</:string></:string>
按句号分割时,别忽略标点和空格的干扰
若子句以句号结尾(如自然语言段落),直接用 std::string::find(".") 切分会出问题:英文中句号可能出现在缩写(Dr.、Mr.)或小数(3.14)里,不能无脑切。
- 推荐用正则:
std::regex("(.*?\.)(?=\s+[A-Z]|$)")尝试匹配“句号+后续大写字母或结尾”,但需权衡性能 - 更稳妥的做法是:先按空格/换行粗切,再对每个候选子串检查是否以
"."结尾且长度合理(如 > 2 字符),再人工校验 - 常见坑:
std::string::substr起始位置错位、越界导致std::out_of_range;用std::string_view可避免拷贝但需确保源字符串生命周期足够长
性能与内存:大文本下避免反复拼接字符串
当输入是几 MB 的文本,且子句数量上千时,频繁调用 result += line + "
" 会触发多次内存重分配,拖慢速度。
- 改用
std::vector<:string></:string>缓存唯一子句,最后一次性用std::ostringstream拼接 - 若最终要输出到文件,直接用
std::ofstream边判重边写入,不缓存全部结果 -
std::unordered_set插入平均 O(1),但哈希冲突多时退化;若子句极长(如每行上万字符),考虑用std::set<:string></:string>(O(log n) 但稳定)或计算 SHA-256 后存哈希值(空间换时间)
真正难处理的是子句边界模糊、嵌套分隔符、或需要语义理解(比如“他说‘你好。’然后笑了。”里的句号)。这种时候,正则也不够用,得上 parser —— 但那就超出“字符串减法”的范畴了。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











