std::unique仅去除相邻重复元素且不改变容器大小,需先排序再配合erase才能实现全量去重;若需保持原始顺序,应使用unordered_set遍历去重。

为什么 std::unique 不能直接去重?
std::unique 并不是“字符串去重”函数,它只对**相邻重复元素**做去重,且不会改变容器大小——它只是把重复元素“挤到末尾”,返回一个指向新逻辑结尾的迭代器。如果你直接对未排序的 std::string 或 std::vector<char></char> 调用 unique,结果往往不符合预期。
常见错误现象:"abac" 经 unique 后仍是 "abac"(因为 'a' 不相邻),而你以为会变成 "abc"。
- 必须先排序(如
std::sort),再unique,才能实现“全量去重” - 但注意:排序会破坏原始字符顺序,如果需要保持首次出现顺序(如
"abac" → "abc"),unique就不适用 -
std::unique只适用于支持随机访问和可比较的序列,对std::string本身可用,但操作的是字符层面
用 unique + erase 做有序去重(保留字典序)
这是最典型的配合用法,适合你明确接受排序后结果的场景,比如生成去重后的字符集合。
std::string s = "hello world"; std::sort(s.begin(), s.end()); auto last = std::unique(s.begin(), s.end()); s.erase(last, s.end()); // → " dehlorw"
关键点:
立即学习“C++免费学习笔记(深入)”;
-
std::sort和std::unique都作用于[begin, end),确保迭代器类型匹配 -
erase的参数必须是unique返回的迭代器(新逻辑结尾)和原end(),缺一不可 - 对
std::string使用时,unique比较的是char,区分大小写;若需忽略大小写,得自己传入二元谓词,例如[](char a, char b) { return std::tolower(a) == std::tolower(b); }
保持原始顺序的真正去重:别硬套 unique
想实现 "abac" → "abc" 这种效果,unique 无能为力。正确做法是遍历 + 查重 + 构建新串。
推荐用 std::unordered_set<char></char> 记录已见字符:
std::string s = "abac";
std::unordered_set<char> seen;
std::string result;
for (char c : s) {
if (seen.insert(c).second) { // insert 返回 pair<iter, bool>,second 为 true 表示新插入
result += c;
}
}
// result == "abc"
注意事项:
- 不要用
std::set(有序,有额外 log n 开销);unordered_set平均 O(1),更合适 - 如果字符串含 Unicode(如 UTF-8 多字节字符),
char级去重会出错——此时应改用std::u8string+ 正确编码切分,unique更是完全失效 - 性能敏感场景下,预分配
result.reserve(s.size())可避免多次内存重分配
误用 unique 导致的崩溃或越界
最常踩的坑是忘记 erase,或 erase 范围错误:
- 只调
std::unique不erase:容器长度不变,末尾残留脏数据(如"hello"变成"helo\0o",但size()还是 5) - 写成
s.erase(std::unique(...), s.end())是对的;写成s.erase(std::unique(...), s.begin() + s.size())看似等价,但若unique返回s.end(),加法可能越界(尤其 debug 模式下迭代器调试检查会报错) - 对空字符串调用
unique是安全的,但若后续代码假设返回迭代器非end(),就可能出问题
复杂点在于:去重目标到底是“字符”还是“子串”、是否区分大小写、是否要保留顺序、输入是否可信——这些都会让 unique 从“技巧”变成“陷阱”。











