std::unique仅移除相邻重复元素,不重排、不查全局;对未排序字符串如"abacbc"无效,需先排序或改用unordered_set保序去重。

std::unique 不能直接对 string 去重,它只移除相邻重复元素
很多人一看到 std::unique 就以为能像 Python 的 set() 那样把字符串里所有重复字符全删掉,结果发现 "aabbcc" 变成 "abc" 是碰巧——因为原串已排序且重复相邻;而 "abacbc" 用 std::unique 后还是 "abacbc",中间的 a 和 c 没被删。它只扫一遍,仅比较相邻位置,不重排、不查全局。
实操建议:
- 如果目标是「去重并保持原始顺序」(如 "abacbc" → "abc"),得自己遍历 +
std::unordered_set记录见过的字符 - 如果目标是「去重且允许重排」(如统计字符种类),可先
std::sort再用std::unique -
std::unique不真正删除元素,只是把唯一值挪到前面,返回一个迭代器指向新逻辑结尾,必须配合erase才算真删
用 std::unique 处理 std::string 的正确三步写法
对 std::string 调用 std::unique,必须注意它操作的是迭代器范围,且返回值要用于后续擦除。常见错误是忘了 erase,或者传错迭代器范围(比如用了 begin() 到 end() 但没接返回值)。
示例:对已排序字符串去重
立即学习“C++免费学习笔记(深入)”;
std::string s = "aaabbbcccd"; auto last = std::unique(s.begin(), s.end()); s.erase(last, s.end()); // 必须这一步,否则 s 还是 "aaabbbcccd\0\0\0\0"
关键点:
-
std::unique返回的是「新尾迭代器」,不是长度,也不是布尔值 - 它只接受前向迭代器,
std::string::iterator满足,但const_iterator不行(会编译失败) - 若字符串含空字符
'\0',std::unique仍按迭代器走,不会提前停,和 C 风格字符串无关
想保留顺序去重?别硬套 unique,用 set 或 vector + find
这是最常踩坑的地方:强行给未排序字符串用 std::unique,还怪它“不好用”。其实 C++ 标准库没提供一键保序去重的算法,得组合实现。
推荐两种轻量做法:
- 用
std::unordered_set<char>记录已见字符,单次遍历构造新串:std::string dedup; std::unordered_set<char> seen; for (char c : s) { if (seen.insert(c).second) dedup += c; } - 若不能用哈希(比如嵌入式环境),可用
std::vector<char>+std::find,但 O(n²) 时间,小字符串够用 - 注意
std::unordered_set::insert返回pair<iterator, bool>,其中.second表示是否新插入,别只取.first
性能和兼容性:unique 很快,但前提是你已经排好序
std::unique 是 O(n) 时间、O(1) 额外空间,比手写循环还略快一点——但它依赖输入已局部有序。如果先 std::sort 再 std::unique,整体变成 O(n log n),而且会破坏原始顺序。
对比场景:
- 处理日志行或配置项这类天然有序的字符串时,
std::unique是最优解 - 处理用户输入、文件名、URL 片段等无序数据时,
std::unique直接失效,必须换思路 - 在 C++20 中,
std::ranges::unique支持投影(比如忽略大小写去重),但底层逻辑不变:仍只去相邻重复
真正容易被忽略的是:unique 的行为和容器类型无关,只和迭代器范围内的值序列有关。传给它一个 std::vector<char> 或 std::string,只要迭代器合法,效果完全一样。










