在 C++ 标准库中,std::unique_copy 定义于 <algorithm> 头文件,用于将输入序列中的连续重复元素压缩后拷贝到输出序列。它和 std::unique 同样遵循去除相邻重复项的原则,但不会原地修改容器,而是把去重后的结果写入新的目标范围。调用前需要包含 <algorithm> 和 <iterator>,后者提供 std::back_inserter 等输出迭代器适配器。函数的基本签名如下:template<class InputIt, class OutputIt> OutputIt unique_copy(InputIt first, InputIt last, OutputIt d_first); 此外还有一个重载版本接受二元谓词,用于自定义相等的判断条件。

一、std::unique_copy 的基本用法
先从最简单的整型容器开始。假设有一个 std::vector<int>,其中包含连续重复的元素,我们希望把相邻重复项去掉后放入新的容器。由于输出容器在调用前可能为空,适配器 std::back_inserter 会在每次写入时自动调用 push_back,避免手动预分配容量。下面是一段完整的示例代码。
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> data{1, 2, 2, 3, 3, 3, 4, 2, 2};
std::vector<int> result;
std::unique_copy(data.begin(), data.end(), std::back_inserter(result));
for (int v : result) {
std::cout << v << ' ';
}
return 0;
}
这段程序会输出 1 2 3 4 2。可以看到,开头的连续两个 2 被压缩为一个,连续三个 3 被压缩为一个,但最后的两个 2 因为在 4 之后重新出现,且彼此相邻,所以被保留了一个。这说明 std::unique_copy 不会检查整个序列中是否存在相同值,它只比较当前元素与上一个已经写入输出的元素。如果两者不同,就写入;如果相同,就跳过。
如果不使用 std::back_inserter,而是传入了普通输出迭代器,就必须保证目标范围有足够的空间,否则写入会越界。例如可以先把目标容器 resize 到输入容器的大小,再调用算法。算法执行完毕后会返回一个输出迭代器,指向最后一个写入元素的下一个位置,可以用这个返回值计算出最终有效长度,并对容器进行 erase 收缩。
从实际效果看,std::unique_copy 非常适合那些不能原地修改的数据源,比如只读的输入区间、共享数据或者需要保留原始顺序副本的场景。相比手动写循环去重,它更加简洁,也能更好地表达意图。
二、和排序配合实现真正意义上的去重
如果业务需求是删除所有重复值,而不是只删除连续重复值,那么就必须先让相同元素相邻。最直接的做法是调用 std::sort 对输入序列排序,再使用 std::unique_copy 进行拷贝去重。下面示例展示了如何把一组乱序整数变成不重复的有序结果。
#include <iostream>
#include <vector>
#include <algorithm>
#include <iterator>
int main() {
std::vector<int> data{5, 1, 3, 1, 4, 3, 5, 2};
std::vector<int> result;
std::sort(data.begin(), data.end());
std::unique_copy(data.begin(), data.end(), std::back_inserter(result));
for (int v : result) {
std::cout << v << ' ';
}
return 0;
}
运行后会得到 1 2 3 4 5,所有重复项都被清除。这种组合的时间复杂度主要来自排序,为 O(n log n),std::unique_copy 本身只需线性时间 O(n)。如果原始顺序不重要,并且后续处理依赖有序性,这是非常高效的方案。
与之相比,std::unique 会返回一个新的逻辑末尾迭代器,之后还需要调用容器的 erase 才能真正删除多余元素。而 std::unique_copy 不修改原容器,天然适合把去重结果写入新容器或者输出流。比如读取一个已经排好序的日志文件时,可以直接将 std::istream_iterator 作为输入,将 std::ostream_iterator 作为输出,中间经过 std::unique_copy 完成连续重复行的过滤,代码会非常紧凑。
当然,如果需要保留元素首次出现的原始顺序,同时又要求全局去重,那么排序加 std::unique_copy 并不合适。此时可以借助 std::unordered_set 记录已经出现过的元素,但要注意无序容器的哈希开销和内存占用。选择哪种方案,需要根据数据规模、元素类型和顺序要求综合判断。
三、自定义去重规则
默认情况下,std::unique_copy 使用 operator== 判断两个元素是否相等。对于字符串去重,如果希望忽略大小写,就可以传入一个自定义二元谓词。谓词应当返回 true 表示两个元素被视为相等。需要注意的是,输入序列必须已经按照相同规则分组,否则非相邻的等价元素仍然无法被合并。下面代码先按忽略大小写排序,再按相同规则去重。
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
#include <iterator>
#include <cctype>
int main() {
std::vector<std::string> words{"Apple", "apple", "banana", "Banana", "cherry"};
auto equal_ignore_case = [](const std::string& a, const std::string& b) {
return std::equal(a.begin(), a.end(), b.begin(), b.end(),
[](unsigned char x, unsigned char y) {
return std::tolower(x) == std::tolower(y);
});
};
auto less_ignore_case = [](const std::string& a, const std::string& b) {
return std::lexicographical_compare(
a.begin(), a.end(), b.begin(), b.end(),
[](unsigned char x, unsigned char y) {
return std::tolower(x) < std::tolower(y);
});
};
std::sort(words.begin(), words.end(), less_ignore_case);
std::vector<std::string> result;
std::unique_copy(words.begin(), words.end(), std::back_inserter(result), equal_ignore_case);
for (const auto& w : result) {
std::cout << w << '\n';
}
return 0;
}
上述代码执行后,Apple 和 apple 会被视为同一字符串,Banana 和 banana 也会被合并,最终输出只包含 Apple、banana、cherry。谓词内部使用了 std::equal 和 std::tolower 逐个字符比较,这样可以避免直接修改原字符串的大小写形式。
自定义谓词并不局限于字符串。任何一个结构体或类,只要能够提取出一个用于比较的键,都可以按照该键去重。比如有一个订单列表,希望按照订单号去重,就可以在排序和去重时同时比较 order_id。但必须注意,排序使用的比较规则和 std::unique_copy 使用的相等规则要保持一致。若排序按订单号升序,而去重按客户名判断相等,相邻元素可能并不等价,最终结果会不符合预期。
另外,谓词如果过于复杂,比如需要访问外部状态或做大量计算,可能会降低算法性能。对于轻量级比较,lambda 表达式或函数对象通常足够;对于重负载场景,可以考虑提前预处理出标准化键,再基于键值进行排序和去重,这样能避免在每次比较中重复执行昂贵操作。
四、复杂度、注意事项与常见误区
std::unique_copy 的性能非常稳定:它最多进行 last - first - 1 次比较,时间复杂度为 O(n)。在已知序列已经按分组规则排列好的前提下,这是最直接的去重拷贝方式。它不像基于哈希的方式需要额外构建桶结构,也不像红黑树那样需要维护节点平衡。
最常见的误区是以为它能够全局去重。比如把 {1, 2, 1} 直接交给 std::unique_copy,会得到 {1, 2, 1},因为第二个 1 虽然和第一个 1 相等,但中间隔着 2,它们并不是连续重复项。要解决这个问题,要么先排序,要么根据需求改用基于哈希集合的辅助过滤。
另一个常见问题与输出迭代器有关。如果目标容器没有预先分配空间,又使用了 std::back_inserter,不会出现问题;但如果使用 result.begin() 作为输出迭代器,而 result 是空的,程序就会产生未定义行为。建议优先使用 std::back_inserter 或者提前调整目标容器大小。
此外,复制元素时可能会产生较多拷贝开销。对于存储大对象的容器,可以考虑在去重前先将对象移动到一个新容器,或者让输出容器直接保存指针。如果使用 C++11 及之后的移动语义,可以配合 std::move_iterator 减少不必要的拷贝,但需要注意 std::unique_copy 的输入范围如果是普通迭代器,输出时会调用赋值操作,具体是否移动取决于元素类型的赋值运算符实现。
总体而言,std::unique_copy 是 C++ 标准算法库中一个灵活且高效的工具。只要理解它只处理相邻重复项这一核心语义,并在需要全局去重时先排序或选择替代方案,就能在日志处理、数据清洗、流式输出等场景中写出更加可靠的代码。
C++算法std::unique_copy去重拷贝修改时间:2026-09-20 11:07:03