C++ 从入门到精通之五
大纲
- C++ 从入门到精通之一、C++ 从入门到精通之二、C++ 从入门到精通之三
- C++ 从入门到精通之四、C++ 从入门到精通之五、C++ 从入门到精通之六
- C++ 从入门到精通之七、C++ 从入门到精通之八、C++ 从入门到精通之九
- C++ 从入门到精通之十、C++ 从入门到精通之十一、C++ 从入门到精通之十二
- C++ 从入门到精通之十三、C++ 从入门到精通之十四、C++ 从入门到精通之十五
- C++ 从入门到精通之十六、C++ 从入门到精通之十七、C++ 从入门到精通之十八
- C++ 从入门到精通之十九
C++ 容器
迭代器(Iterator)
迭代器的概念
C++ 迭代器(Iterator)是一种用于统一访问和遍历容器元素的抽象工具,可以理解为连接容器与算法之间的桥梁。迭代器通过类似指针的操作方式定位和访问容器中的元素,使 std::vector、std::list、std::map 等不同类型的容器都能够使用统一的遍历方式,同时屏蔽不同容器底层数据结构的差异。STL 算法通常并不直接依赖具体容器,而是通过迭代器对元素进行操作,从而实现数据结构与算法的分离,这也是 STL 泛型编程思想的重要体现。
提示
- C++ 常用的数据结构,包括:栈、队列、链表、树、散列表(哈希表)、图等,推荐阅读书籍 《算法导论》。
迭代器的类型
五种迭代器的介绍

迭代器支持的操作
在 C++ 中,不同类型的迭代器所支持的操作是不一样的,如下图所示:

验证迭代器的类型
- 验证迭代器的类型(一共有五种类型)
1 |
|
在 Windows 平台 VS 2026 中,程序运行输出的结果如下:
1 | ----------begin---------- |
在 Linux 平台 CLion 中,程序运行输出的结果如下:
1 | ----------begin---------- |
迭代器的分类
在 C++ 中,常见的迭代器有以下几类:
iterator:- 可读写正向迭代器;
++向尾部方向移动,*it通常返回可修改引用;- 由
begin()/end()获得。
const_iterator:- 只读正向迭代器;
++向尾部方向移动,但*it返回const引用,不能通过它修改元素;- 由
cbegin()/cend()获得,可由iterator转换而来。
reverse_iterator:- 可读写反向迭代器;通常是对可读写正向迭代器的适配器(如
reverse_iterator<iterator>); ++向头部方向移动,*it通常可修改;- 由
rbegin()/rend()获得。
- 可读写反向迭代器;通常是对可读写正向迭代器的适配器(如
const_reverse_iterator:- 只读反向迭代器;通常是对只读正向迭代器的反向适配(如
reverse_iterator<const_iterator>); ++向头部方向移动,*it为const,不能修改元素;- 由
crbegin()/crend()获得。
- 只读反向迭代器;通常是对只读正向迭代器的反向适配(如
提示
- 在 C++ 中,
iterator迭代器是从const_iterator迭代器继承而来的。 - 值得一提的是,并不是所有 STL 容器都有公开的迭代器接口,比如容器适配器
queue、stack、priority_queue都不提供自己的迭代器,也不提供begin()/end()。
迭代器的使用
iterator
1 |
|
程序运行输出的结果如下:
1 | 2 |
const_iterator
1 |
|
程序运行输出的结果如下:
1 | 1 |
reverse_iterator
1 |
|
程序运行输出的结果如下:
1 | 6 |
const_reverse_iterator
1 |
|
程序运行输出的结果如下:
1 | 3 |
算法(Algorithm)
算法的概述
C++ 标准库算法以迭代器为桥梁,覆盖查找、排序、修改、数值、集合、排列等常见需求,算法主要集中在 <algorithm>、<numeric>、<functional> 等头文件中,通过迭代器与容器解耦,因此同一套算法可以作用于 vector、list、deque、map、set、数组、甚至自定义容器。传统算法灵活,但写法偏底层;C++ 20 ranges 算法更现代、更安全、更易组合。实际开发中应根据容器类型、迭代器类别、性能需求和代码可读性选择合适算法,并注意逻辑删除、排序前提、比较器严格弱序等常见算法使用陷阱。
算法的基本特点
- 算法通过迭代器区间
[first, last)操作元素,不直接依赖具体容器。 - 大多数算法不改变容器大小,只重排、读取或修改已有元素。
- 涉及插入 / 删除的算法(如
remove、unique)通常只做逻辑删除,真正删除需要配合容器的erase。 - 算法可分为非修改序列算法、修改序列算法、排序相关算法、查找算法、数值算法、集合算法等。
- C++ 11 起大量算法增加了
_if、_copy、_n等变体,以及 Lambda 表达式支持。 - C++ 17 起引入并行执行策略(如
std::execution::par)。 - C++ 20 起引入
ranges版本(std::ranges::),可直接作用于容器,更简洁安全。
- 算法通过迭代器区间
算法的注意事项
- 算法不检查区间越界,需保证迭代器有效。
- 逻辑删除算法(
remove、unique)必须配合容器的erase才能真正删除元素。 sort、stable_sort等要求使用随机访问迭代器,list应使用其成员函数sort。lower_bound等二分算法要求区间已按相同规则排序。- 自定义比较函数必须满足严格弱序,否则行为未定义。
- 使用 Lambda 表达式时,需要注意捕获方式,避免悬垂引用。
- 若需并行执行,可使用
std::execution::par等策略,但必须保证并行操作无数据竞争且满足并行算法的要求(如不依赖执行顺序)。
提示
- C++ 算法可以简单理解为
std命名空间中的全局函数或全局函数模板。 - C++ 算法的前两个形参通常都是迭代器类型,用来表示容器中元素的一个区间,通常是左闭右开区间
[first, last)。
算法的实现
在 C++ 算法的内部实现中,算法会根据传入的迭代器分析出它的种类(五种之一);不同种类的迭代器,算法会有不同的处理方式,需要编写不同的代码来应对,而这种针对不同迭代器种类编写不同代码的做法,会直接影响算法的执行效率(效率是一个很重要的指标),这也正是 STL 内部为什么要给迭代器做分类的目的。
算法的分类
非修改序列算法
这类算法不会修改元素,只做查询、计数、遍历。
for_each:对区间内每个元素执行操作。count/count_if:统计等于某值或满足条件的元素个数。find/find_if/find_if_not:查找第一个匹配元素。find_end/find_first_of:查找子序列或任意匹配元素。adjacent_find:查找相邻重复元素。search/search_n:查找子序列或连续n个匹配元素。all_of/any_of/none_of:判断是否全部、任一或没有元素满足条件。equal:比较两个区间是否相等。mismatch:找出两个区间第一个不匹配的位置。lexicographical_compare:按字典序比较两个区间。is_sorted/is_sorted_until:判断区间是否已排序。is_partitioned/partition_point:判断或定位划分点。
修改序列算法
这类算法会修改元素值或重新排列元素。
copy/copy_if/copy_n/copy_backward:复制区间。move/move_backward:移动区间元素。fill/fill_n:填充指定值。transform:对每个元素做变换,可一元或二元。generate/generate_n:用生成器函数填充区间。remove/remove_if:逻辑删除,返回新逻辑结尾。remove_copy/remove_copy_if:删除并复制到另一区间。replace/replace_if:替换满足条件的元素。replace_copy/replace_copy_if:替换并复制。swap/swap_ranges/iter_swap:交换元素或区间。reverse/reverse_copy:反转区间。rotate/rotate_copy:旋转区间。unique/unique_copy:去除相邻重复元素。shuffle:随机打乱区间(C++ 11)。sample:随机抽样(C++ 17)。partition/stable_partition:按条件划分区间。partition_copy:划分并复制到两个区间。
排序相关算法
sort:快速排序的混合实现,平均时间复杂度O(N log N),不稳定。stable_sort:稳定排序,保持相等元素相对顺序。partial_sort/partial_sort_copy:部分排序,取前k个。nth_element:将第n个元素放到最终位置,左边不大于它,右边不小于它。is_sorted/is_sorted_until:判断排序状态。merge/inplace_merge:合并两个有序区间。make_heap/push_heap/pop_heap/sort_heap:堆操作。is_heap/is_heap_until:判断是否为堆。lower_bound/upper_bound/equal_range:有序区间二分查找。binary_search:有序区间判断是否存在某值。
查找与搜索算法
find/find_if/find_if_not:线性查找。find_end/find_first_of:子序列查找。adjacent_find:相邻重复查找。search/search_n:子序列或连续n个匹配。lower_bound/upper_bound/equal_range/binary_search:有序区间二分查找。max_element/min_element/minmax_element:最值查找。clamp:将值限制在区间内(C++ 17)。
数值算法
accumulate:累加或自定义累积。inner_product:内积。adjacent_difference:相邻差分。partial_sum:前缀和。iota:填充递增序列。reduce:并行友好归约(C++ 17)。transform_reduce:变换后归约(C++ 17)。exclusive_scan/inclusive_scan:前缀扫描(C++ 17)。gcd/lcm:最大公约数、最小公倍数(C++ 17)。midpoint:中点计算(C++ 20)。
集合算法
这类算法用于有序区间上的集合操作。
set_union:并集。set_intersection:交集。set_difference:差集。set_symmetric_difference:对称差集。includes:判断是否包含子集。merge:合并两个有序区间。
排列与组合算法
next_permutation:下一个字典序排列。prev_permutation:上一个字典序排列。is_permutation:判断是否为同一排列。
Ranges 算法
C++ 20 新引入了 <ranges> 头文件与 std::ranges:: 命名空间,主要改进如下:
- 可以直接传容器,如
std::ranges::sort(v); - 支持投影(Projection),如按成员排序。
- 支持 views 与惰性求值,如
views::filter、views::transform。 - 算法约束更强,错误信息更清晰。
- 与管道
|组合,代码更简洁。
常见 Ranges 算法与经典算法同名,如 ranges::find、ranges::sort、ranges::transform 等。
算法的使用
特别注意
当同名的成员函数和全局函数(算法)同时存在时,应优先考虑使用容器自带的同名成员函数,只有在容器没有提供同名成员函数的情况下,才考虑使用这些全局算法。这是因为成员函数通常更了解容器自身的内部结构,能够针对特定容器做出更高效的实现,而全局算法受限于通用迭代器接口,往往无法达到同等效率。
for_each
for_each:对区间内每个元素执行指定操作
1 |
|
程序运行输出的结果如下:
1 | 69 23 35 13 56 |
find
find:查找第一个匹配的元素
1 |
|
程序运行输出的结果如下:
1 | found number 35 |
find_if
find_if:根据条件查找第一个匹配的元素
1 |
|
程序运行输出的结果如下:
1 | found number > 15, it is 69 |
sort
sort:快速排序的混合实现,平均时间复杂度O(N log N),不稳定
1 |
|
程序运行输出的结果如下:
1 | 13 23 35 56 69 |
特别注意
std::sort()要求迭代器是随机访问迭代器(Random Access Iterator),并且元素必须支持可比较的排序规则(默认operator <,即从小到大排序)。std::unordered_set的迭代器是前向迭代器(Forward Iterator),不是随机访问迭代器,因此不能用std::sort()排序;如果需要对它的元素排序,应先拷贝到std::vector等支持随机访问的容器中再排序。- C++ 标准库中
std::set/std::map是按红黑树有序存储的,但它们的迭代器是双向迭代器,同样不满足std::sort()的随机访问要求,所以也不能用std::sort()排序;但它们的底层本身已是按operator <有序存储的,不需要再排序。
函数对象(Function Object)
函数对象的概念
C++ 中的函数对象(又称仿函数)是指任何重载了函数调用运算符 operator() 的类或结构体实例,使其能够像普通函数一样以 对象(参数) 的形式被调用,同时还能携带状态(通过成员变量)并拥有自己的类型,从而可以配合模板、标准算法和 std::function 等机制实现灵活的回调、策略配置和延迟执行,相比普通函数指针更具表达力和内联优化潜力。
- C++ 中的函数对象,其作用类似 C 语言中的函数指针。
- 在 C++ 中,将拥有
operator()小括号运算符重载函数的对象称作 “函数对象”,或者称作 “仿函数”。 - 通过函数对象调用
operator(),会产生内联的效果,其执行效率比较高,因为没有函数调用的开销。 - 由于函数对象是使用类生成的,因此函数对象可以拥有相关的成员变量,比如可以通过成员变量来记录函数对象的使用次数。
- 在 C++ 中,常见的函数对象有
std::less、std::greater等。
函数对象的使用
自定义函数对象
1 |
|
程序运行输出的结果如下:
1 | 69 56 35 23 13 |
标准库中的函数对象
C++ 标准库中的函数对象都定义在 <functional> 头文件中,常见的函数对象如下图所示:

1 |
|
程序运行输出的结果如下:
1 | 69 56 35 23 13 |
函数适配器(Function Adapter)
函数适配器的概述
C++ 函数适配器(在部分网络资料中也叫算法适配器)是标准库中一类用于包装、改造或组合可调用对象的工具,它们在不修改原有函数或函数对象的前提下,改变其调用方式或参数形式,从而让同一个算法能适配更多场景。早期的 bind1st、bind2nd、ptr_fun、mem_fun 等已基本被 C++ 11 起的 std::bind、std::function 和 Lambda 表达式取代;如今更常用的适配器包括 std::bind(绑定部分参数、调整参数顺序)、std::not_fn(对谓词结果取反)、std::mem_fn(把成员函数指针变成可调用对象)以及 std::reference_wrapper(配合 std::ref / std::cref 让算法按引用而非拷贝操作元素)。它们共同的核心思想是以可调用对象为输入、以新的可调用对象为输出,从而与 std::sort、std::find_if、std::transform 等算法灵活配合,提升代码的复用性和表达力。
函数适配器的使用
std::bind 使用
1 |
|
程序运行输出的结果如下:
1 | less total: 2 |
迭代器适配器(Iterator Adapter)
迭代器适配器的概述
C++ 中的迭代器适配器是标准库适配器模式在迭代器层面的体现,其核心思想是:在不修改原有迭代器和容器接口的前提下,通过包装一个已有迭代器,产生一个行为不同、但对外仍表现为迭代器的新迭代器,从而让算法能够适配更多场景。按功能和用途,常见的迭代器适配器可以分为以下几类:
反向迭代器:
- 将迭代器的前进方向反过来,递增变成递减、递减变成递增,从而让算法可以反向遍历容器。
std::reverse_iterator,由容器的rbegin()/rend()返回,crbegin()/crend()返回其const版本。
插入迭代器:
- 将对迭代器的 “赋值” 操作转换为 “插入” 操作,使
std::copy等算法能够向容器中追加元素,而不是覆盖已有元素。 std::insert_iterator(由std::inserter创建,在指定位置之前插入)。std::front_insert_iterator(由std::front_inserter创建,向容器头部插入)。std::back_insert_iterator(由std::back_inserter创建,向容器尾部插入)。
- 将对迭代器的 “赋值” 操作转换为 “插入” 操作,使
移动迭代器:
- 解引用时返回右值引用而非左值引用,使算法在搬运元素时触发移动语义,避免不必要的拷贝。
std::move_iterator,由std::make_move_iterator创建,常用于把元素从一个容器 “搬” 到另一个容器。
流迭代器:
- 将输入流或输出流包装成迭代器,使标准算法可以直接从流中读取数据或向流中写入数据。
std::istream_iterator(从输入流读取)。std::ostream_iterator(向输出流写入)。std::wistream_iterator、std::wostream_iterator等以及宽字符版本。
C++ 20 及之后新增的适配器:
std::common_iterator用于把不同类型的迭代器与哨兵统一成同一类型。std::counted_iterator给迭代器附加计数、限定前进步数。std::move_sentinel、std::basic_const_iterator等迭代器由 C++ 23 新引入。
总结
总体来看,迭代器适配器(改造迭代器)与函数适配器(改造可调用对象,如 std::bind、std::not_fn、std::mem_fn)、容器适配器(改造容器接口,如 std::stack、std::queue、std::priority_queue)共同构成了 C++ 标准库中适配器模式的三种典型应用。它们共同的价值在于:以组合和包装的方式复用已有组件,在不改变原有类型的前提下扩展其行为,提升算法的通用性与代码的表达力。
迭代器适配器的使用
std::move_iterator:解引用时返回右值引用而非左值引用,使算法在搬运元素时触发移动语义,避免不必要的拷贝。
1 |
|
程序运行输出的结果如下:
1 | dst: apple banana cherry |
代码分析
std::make_move_iterator(it)把普通迭代器it包装成std::move_iterator。- 解引用这个适配器时,返回的是右值引用(
std::string&&),于是std::vector的区间构造函数会调用移动构造,而不是拷贝构造。 - 常用于:把元素从一个容器搬到另一个容器、把容器元素搬进算法(如
std::copy、std::uninitialized_copy)等场景。 - 特别注意:移动后源容器元素仍可安全析构和重新赋值,但不应再依赖其原值。
