首选用std::istringstream分词+std::unordered_set判重保留首次出现顺序,注意大小写和标点影响判重,需预处理清洗键但保留原格式输出,连续/首尾空格被自动压缩。

用 std::unordered_set 去重并保持顺序
直接遍历单词、用哈希表记录是否见过,是兼顾效率和可读性的首选。C++ 没有内置“按空格切分 + 去重保留首次出现”的单行函数,必须手动拆分再过滤。
常见错误是误用 std::set(自动排序)或反复调用 std::string::find 导致 O(n²) 时间——尤其长文本下明显卡顿。
- 用
std::istringstream按空白符安全分词,它自动跳过多余空格、制表符、换行符 - 用
std::unordered_set<:string></:string>判重,平均 O(1) 插入和查找 - 只把**第一次出现**的单词加入结果 vector,后续重复跳过
- 最后用空格拼接,注意末尾不加多余空格
std::string removeDuplicateWords(const std::string& s) {
std::istringstream iss(s);
std::string word;
std::unordered_set<:string> seen;
std::vector<:string> unique;
while (iss >> word) {
if (seen.find(word) == seen.end()) {
seen.insert(word);
unique.push_back(word);
}
}
std::string result;
for (size_t i = 0; i 0) result += " ";
result += unique[i];
}
return result;
}</:string></:string>
注意大小写和标点是否算“相同单词”
上面代码中 "Hello" 和 "hello" 被视为不同单词,"word," 和 "word" 也被视为不同——因为 std::istringstream 不剥离标点,逗号成了单词的一部分。
如果需求是“忽略大小写+剥离常见标点”,不能只靠 operator>>。得在插入 seen 前预处理:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 转小写:用
std::tolower遍历每个字符 - 去标点:用
std::isalnum过滤,或用std::regex_replace(但性能差,慎用) - 但注意:原始输出仍应保留原格式(比如用户输入是
"Hello,",输出还是"Hello,"),只是判重时用清洗后的键
遇到连续空格或首尾空格怎么办
std::istringstream 默认跳过所有空白符,所以输入 " a b a " 会正确产出 {"a", "b", "a"},最终输出 "a b",首尾/中间多余空格天然被压缩。
但如果业务要求**严格保留下列格式**(比如日志解析需保留原始缩进),就不能用 std::istringstream,得手写分割逻辑:
- 用
std::string::find_first_not_of(" \t\n")定位单词起点 - 再用
find_first_of(" \t\n")找终点 - 每次
substr提取,同时记录原始空白位置(若需还原) - 这种场景下,去重逻辑不变,但分词成本显著上升
性能敏感时避免频繁字符串拼接
上面示例里最后用循环拼接 result += ...,在 C++11 及以后通常有 SSO(短字符串优化)和移动语义兜底,问题不大。但若单词数上万,建议预先估算容量:
- 用
result.reserve(unique.size() * avg_word_len + unique.size())避免多次内存重分配 - 更激进的做法:先存所有单词指针或视图(
std::string_view,C++17),最后统一拼接 - 不过绝大多数实际场景(如配置解析、用户输入清洗),没必要提前优化到这一步
真正容易被忽略的是标点处理边界——比如英文缩写 "don't" 或带连字符的 "state-of-the-art",是否该整体视为一个单词?这取决于你的语义定义,代码不会自动猜,得在分词前明确规则。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










