用 std::istringstream 拆词 + std::vector 去重是最稳的起点,因其自动跳过连续空白、逻辑清晰可控,避免手动切分导致的边界和标点问题。

用 std::istringstream 拆词 + std::vector 去重是最稳的起点
直接在原字符串上做替换容易漏边界、错空格,尤其当重复单词跨多空格或含标点时。先按词拆解,再逐个比对前一个有效词,逻辑清晰且可控。
关键点是:只比较“语义词”,忽略前后空格和多余分隔符。所以得用 std::istringstream 自动跳过连续空白,而不是用 std::string::find 手动切。
- 每次读出一个
word,若非空且与上一个存下的词不同,就推入result容器 - 注意:大小写是否敏感需提前约定,
std::equal配std::tolower可实现忽略大小写比较 - 标点没剥离?那它会成为词的一部分(如
"hello,"和"hello"被视为不同),真要处理标点得额外清洗,不建议混在去重逻辑里
手写循环时别用 == 直接比 std::string 连续段
有人想用 string::find 找 "word word" 然后替换,这在实际中几乎必出错——空格数量不确定、单词可能被标点包围、边界重叠(如 "a a a" 中第二次替换会影响第三次匹配)。
更糟的是,用 std::regex_replace 写 R"((\b\w+\b)\s+)" 看似简洁,但 C++11/14 的 std::regex 实现常有性能问题或不支持某些语法,GCC libstdc++ 甚至长期禁用部分功能。
立即学习“C++免费学习笔记(深入)”;
- 正则适合一次性简单模式,连续重复单词本质是上下文相关,需状态记忆,正则硬套反而难维护
- 如果坚持用正则,务必测试
"the the"、"the the"(两个空格)、"The THE"三种情况 -
std::regex构造开销大,高频调用时不如手动拆词快
保留原始空格格式?基本做不到,也通常不该做
用户常隐含期待“只删重复词,其余空格、换行、制表符全留原样”。但这是反模式:语义去重和格式保留是两件事。比如 "cat\t\tcat\nfish",删掉第二个 cat 后,中间的 \t\t\n 留着毫无意义,还导致后续处理(如再分词)出错。
- 标准做法是输出“规范分隔”结果:词间单空格,首尾无空格
- 真要保格式,得用带位置信息的词法分析(如记录每个词的起始索引、前后分隔符),复杂度飙升,99% 场景没必要
- 若必须保缩进/换行结构,说明这不是“删重复单词”,而是“编辑某类标记文本”,应换用 AST 或专用 parser
完整可跑示例(C++11 起兼容)
std::string removeConsecutiveDuplicates(const std::string& s) {
std::istringstream iss(s);
std::vector<std::string> words;
std::string word;
std::string last;
while (iss >> word) {
if (word != last) {
words.push_back(word);
last = word;
}
}
std::string result;
for (size_t i = 0; i < words.size(); ++i) {
if (i > 0) result += " ";
result += words[i];
}
return result;
}
这段代码处理 "hello hello world world" 得到 "hello world",干净、无依赖、不崩空串或纯空格输入。唯一要注意的是:它默认区分大小写。若需忽略,把 word != last 替成自定义比较函数即可。
真正麻烦的从来不是“怎么删”,而是“什么算重复”——是全字匹配?词干?还是语义等价?这些得由业务定,库函数不会替你决定。


















