当前位置: 首页 > news >正文

STL 算法与迭代器终极指南:从基础到高级应用

       STL(Standard Template Library,标准模板库)是 C++ 中最强大的工具之一,而算法与迭代器作为 STL 的核心组件,为开发者提供了高效、灵活的数据处理能力。本文将全面解析 STL 算法与迭代器的工作原理、常用操作及高级技术,帮助你充分利用 STL 提升代码质量与开发效率。

一、迭代器:STL 的 "胶水"

         迭代器(Iterator)是连接容器与算法的桥梁,它提供了一种统一致一的方式来访问容器中的元素,而无需暴露容器的内部实现细节。

1.1 迭代器的本质与分类

        迭代器本质上是一种行为类似指针的对象,它重载了*->++--等运算符,使开发者可以像操作指针一样操作容器元素。

STL 将迭代器分为五大类别,按功能从弱到强排列:

  • 输入迭代器(Input Iterator):只能读取元素,且只能单向移动(++),如istream_iterator
  • 输出迭代器(Output Iterator):只能写入元素,且只能单向单向单向移动(++),如ostream_iterator
  • 前向迭代器(Forward Iterator):可读写元素,单向移动,支持多次赋值,如forward_list的迭代器
  • 双向迭代器(Bidirectional Iterator):可读写元素,双向移动(++、--),如listsetmap的迭代器
  • 随机访问迭代器(Random Access Iterator):支持所有迭代器操作,还支持随机访问(+=-=[]等),如vectordequearray的迭代器

1.2 迭代器的基本操作

所有迭代器都支持的基本操作:

// 假设it是一个迭代器
*it;          // 解引用,获取当前元素
++it;         // 移动到下一个元素
it++;         
it1 == it2;   // 判断两个两个迭代器是否指向同一位置
it1 != it2;

随机访问迭代器额外支持:

it + n;       // 移动n个位置
it - n;
it += n;
it -= n;
it1 - it2;    // 计算两个迭代器之间的距离
it[n];        // 访问第n个元素
it1 < it2;    // 比较位置
it1 > it2;
it1 <= it2;
it1 >= it2;

1.3 容器与迭代器的对应关系

不同容器提供的迭代器类型不同:

容器迭代器类型
vector随机访问
deque随机访问
list双向
set/multiset双向
map/multimap双向
unordered_set/unordered_map前向
forward_list前向
array随机访问

1.4 迭代器适配器

STL 提供了几种特殊的迭代器适配器:

  1. 反向迭代器(reverse_iterator)

    vector<int> v = {1, 2, 3, 4};
    for (auto it = v.rbegin(); it != v.rend(); ++it) {cout << *it << " ";  // 输出:4 3 2 1
    }
    
  2. 插入迭代器(insert iterator)

    • back_inserter:在容器末尾插入元素
    • front_inserter:在容器开头插入元素
    • inserter:在指定位置插入元素
    vector<int> v1 = {1, 2, 3};
    vector<int> v2;// 将v1的元素复制到v2
    copy(v1.begin(), v1.end(), back_inserter(v2));
    
  3. 流迭代器(stream iterator)

    // 从标准输入读取整数
    istream_iterator<int> in(cin), eof;
    vector<int> v(in, eof);// 输出到标准输出
    ostream_iterator<int> out(cout, " ");
    copy(v.begin(), v.end(), out);
    
  4. 移动迭代器(move_iterator):C++11 引入,用于实现元素的移动而非复制

二、STL 算法概览

         STL 算法是一系列模板函数,用于处理容器中的元素,实现了各种常用操作。这些算法定义在<algorithm>头文件中,部分数值算法在<numeric>中。

2.1 算法的分类

STL 算法可分为以下几大类:

  1. 非修改性序列操作:不改变容器中的元素,如for_eachfindcount
  2. 修改性序列操作:改变容器中的元素,如copytransformreplace
  3. 排序与相关操作:如sortmergebinary_search
  4. 数值算法:如accumulateinner_product
  5. 集合算法:如set_unionset_intersection
  6. 关系算法:如equallexicographical_compare

2.2 算法的参数模式

大多数 STL 算法遵循一致的参数模式:

  • 前两个参数是迭代器,指定操作的范围[first, last)
  • 后续参数根据算法功能而定

例如:

// 查找范围内等于value的元素
find(first, last, value);// 对范围内的每个元素应用func
for_each(first, last, func);

三、常用 STL 算法详解

3.1 非修改性算法

3.1.1 遍历元素:for_each

for_each对范围内的每个元素应用函数:

vector<int> v = {1, 2, 3, 4, 5};// 使用函数
void print(int x) {cout << x << " ";
}
for_each(v.begin(), v.end(), print);// 使用lambda表达式(C++11)
for_each(v.begin(), v.end(), [](int x) {cout << x << " ";
});
3.1.2 查找元素:find, find_if, find_if_not
vector<int> v = {1, 2, 3, 4, 5};// 查找值为3的元素
auto it = find(v.begin(), v.end(), 3);// 查找第一个偶数
it = find_if(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});// 查找第一个非偶数(C++11)
it = find_if_not(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});
3.1.3 计数元素:count, count_if
vector<int> v = {1, 2, 3, 4, 5, 3};// 计数等于3的元素
int cnt = count(v.begin(), v.end(), 3);// 计数偶数的个数
cnt = count_if(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});
3.1.4 检查条件:all_of, any_of, none_of (C++11)
vector<int> v = {1, 3, 5, 7, 9};// 所有元素都是奇数?
bool all_odd = all_of(v.begin(), v.end(), [](int x) {return x % 2 != 0;
});// 有偶数吗?
bool has_even = any_of(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});// 没有负数?
bool no_negative = none_of(v.begin(), v.end(), [](int x) {return x < 0;
});

3.2 修改性算法

3.2.1 复制元素:copy, copy_if, copy_n
vector<int> v = {1, 2, 3, 4, 5};
vector<int> dest(5);// 复制整个范围
copy(v.begin(), v.end(), dest.begin());// 复制偶数(C++11)
vector<int> evens;
copy_if(v.begin(), v.end(), back_inserter(evens), [](int x) {return x % 2 == 0;
});// 复制前3个元素(C++11)
vector<int> first_three;
copy_n(v.begin(), 3, back_inserter(first_three));
3.2.2 转换元素:transform

transform可以将一个或两个范围的元素经过函数转换后写入目标范围:

vector<int> v = {1, 2, 3, 4, 5};
vector<int> res;// 单参数版本:将每个元素乘以2
transform(v.begin(), v.end(), back_inserter(res), [](int x) {return x * 2;
});// 双参数版本:两个范围元素相加
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};
vector<int> sum(3);
transform(a.begin(), a.end(), b.begin(), sum.begin(), [](int x, int y) {return x + y;
});
3.2.3 替换元素:replace, replace_if, replace_copy
vector<int> v = {1, 2, 3, 4, 5};// 将3替换为30
replace(v.begin(), v.end(), 3, 30);// 将偶数替换为0
replace_if(v.begin(), v.end(), [](int x) {return x % 2 == 0;
}, 0);// 复制的同时替换,不改变原容器
vector<int> replaced;
replace_copy(v.begin(), v.end(), back_inserter(replaced), 0, 100);
3.2.4 移除元素:remove, remove_if, remove_copy
vector<int> v = {1, 2, 3, 4, 5, 3};// 移除所有3(注意:实际是将元素前移,返回新的结束迭代器)
auto new_end = remove(v.begin(), v.end(), 3);
v.erase(new_end, v.end());  // 真正删除元素// 移除所有偶数
new_end = remove_if(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});
v.erase(new_end, v.end());// 复制的同时移除
vector<int> odds;
remove_copy_if(v.begin(), v.end(), back_inserter(odds), [](int x) {return x % 2 == 0;
});

3.3 排序与相关算法

3.3.1 排序:sort, stable_sort
vector<int> v = {3, 1, 4, 1, 5, 9};// 默认升序排序
sort(v.begin(), v.end());// 降序排序
sort(v.begin(), v.end(), greater<int>());// 自定义排序:按绝对值
sort(v.begin(), v.end(), [](int a, int b) {return abs(a) < abs(b);
});// stable_sort保持相等元素的相对顺序
vector<pair<int, int>> vp = {{2, 1}, {1, 2}, {2, 3}};
stable_sort(vp.begin(), vp.end(), [](const auto& a, const auto& b) {return a.first < b.first;
});
// 结果: {{1,2}, {2,1}, {2,3}} 保持了first=2的元素的相对顺序
3.3.2 二分查找:binary_search, lower_bound, upper_bound

二分查找要求容器已排序:

vector<int> v = {1, 3, 5, 7, 9};// 检查元素是否存在
bool has_5 = binary_search(v.begin(), v.end(), 5);// 查找第一个 >= 5的元素
auto it_low = lower_bound(v.begin(), v.end(), 5);// 查找第一个 > 5的元素
auto it_high = upper_bound(v.begin(), v.end(), 5);// 在自定义排序的容器中使用
vector<int> v_abs = {1, -2, 3, -4};
sort(v_abs.begin(), v_abs.end(), [](int a, int b) {return abs(a) < abs(b);
});
// 查找时必须使用相同的比较函数
auto it = lower_bound(v_abs.begin(), v_abs.end(), 3, [](int a, int b) {return abs(a) < abs(b);
});
3.3.3 打乱与分区:shuffle, partition
vector<int> v = {1, 2, 3, 4, 5, 6, 7, 8, 9};// 随机打乱元素(C++11)
random_device rd;
mt19937 g(rd());
shuffle(v.begin(), v.end(), g);// 分区:将偶数放前面,奇数放后面
auto partition_it = partition(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});// stable_partition保持同组元素的相对顺序
auto stable_partition_it = stable_partition(v.begin(), v.end(), [](int x) {return x % 2 == 0;
});

3.4 数值算法

数值算法定义在<numeric>头文件中:

#include <numeric>
3.4.1 累加:accumulate
vector<int> v = {1, 2, 3, 4, 5};// 求和,初始值为0
int sum = accumulate(v.begin(), v.end(), 0);// 求乘积,初始值为1
int product = accumulate(v.begin(), v.end(), 1, [](int a, int b) {return a * b;
});// 拼接字符串
vector<string> strs = {"Hello", " ", "World", "!"};
string result = accumulate(strs.begin(), strs.end(), string(""));
3.4.2 内积:inner_product
vector<int> a = {1, 2, 3};
vector<int> b = {4, 5, 6};// 计算 1*4 + 2*5 + 3*6 = 32
int ip = inner_product(a.begin(), a.end(), b.begin(), 0);// 自定义运算:(1+4) * (2+5) * (3+6) = 5*7*9=315
int custom_ip = inner_product(a.begin(), a.end(), b.begin(), 1,[](int a, int b) { return a * b; },    // 外运算[](int a, int b) { return a + b; });   // 内运算
3.4.3 前缀和:partial_sum
vector<int> v = {1, 2, 3, 4, 5};
vector<int> prefix_sums(v.size());// 计算前缀和:1, 1+2, 1+2+3, ...
partial_sum(v.begin(), v.end(), prefix_sums.begin());// 自定义前缀运算:计算前缀乘积
partial_sum(v.begin(), v.end(), prefix_sums.begin(), [](int a, int b) { return a * b; });

3.5 集合算法

集合算法用于处理有序范围:

3.5.1 集合交、并、差:set_intersection, set_union, set_difference
vector<int> a = {1, 2, 3, 4, 5};
vector<int> b = {3, 4, 5, 6, 7};
vector<int> result;// 交集:3,4,5
result.resize(min(a.size(), b.size()));
auto it = set_intersection(a.begin(), a.end(), b.begin(), b.end(), result.begin());
result.resize(it - result.begin());// 并集:1,2,3,4,5,6,7
result.resize(a.size() + b.size());
it = set_union(a.begin(), a.end(), b.begin(), b.end(), result.begin());
result.resize(it - result.begin());// 差集:a - b = 1,2
result.resize(a.size());
it = set_difference(a.begin(), a.end(), b.begin(), b.end(), result.begin());
result.resize(it - result.begin());

四、迭代器与算法的高级技术

4.1 迭代器的算术运算与距离

对于随机访问迭代器,可以进行算术运算:

vector<int> v = {1, 2, 3, 4, 5};// 计算迭代器之间的距离
ptrdiff_t n = v.end() - v.begin();  // 结果为5// 移动迭代器
auto it = v.begin() + 2;  // 指向3// 使用distance函数(适用于所有迭代器)
n = distance(v.begin(), v.end());  // 结果为5

4.2 算法的复杂度考量

STL 算法都有明确定义的时间复杂度,了解这些有助于选择合适的算法:

  • O (1):如advance(随机访问迭代器)
  • O (n):如for_eachfindcount
  • O (n log n):如sortstable_sort
  • O (n + m):如set_union(n 和 m 是两个范围的大小)
  • O (log n):如binary_search

4.3 自定义迭代器

虽然 STL 提供了丰富的迭代器,但有时我们需要创建自定义迭代器。实现自定义迭代器需要定义以下类型和运算符:

template <typename T>
class MyIterator {
public:// 必须定义的五种迭代器关联类型using value_type = T;using reference = T&;using pointer = T*;using difference_type = ptrdiff_t;using iterator_category = std::random_access_iterator_tag;// 构造函数MyIterator(pointer ptr) : m_ptr(ptr) {}// 解引用运算符reference operator*() const { return *m_ptr; }pointer operator->() const { return m_ptr; }// 递增运算符MyIterator& operator++() {  // 前置++m_ptr++;return *this;}MyIterator operator++(int) {  // 后置++MyIterator temp = *this;m_ptr++;return temp;}// 递减运算符(双向迭代器及以上需要)MyIterator& operator--() {  // 前置--m_ptr--;return *this;}MyIterator operator--(int) {  // 后置--MyIterator temp = *this;m_ptr--;return temp;}// 算术运算符(随机访问迭代器需要)MyIterator operator+(difference_type n) const {return MyIterator(m_ptr + n);}MyIterator operator-(difference_type n) const {return MyIterator(m_ptr - n);}// 复合赋值运算符(随机访问迭代器需要)MyIterator& operator+=(difference_type n) {m_ptr += n;return *this;}MyIterator& operator-=(difference_type n) {m_ptr -= n;return *this;}// 下标运算符(随机访问迭代器需要)reference operator[](difference_type n) const {return m_ptr[n];}// 比较运算符bool operator==(const MyIterator& other) const {return m_ptr == other.m_ptr;}bool operator!=(const MyIterator& other) const {return !(*this == other);}// 关系运算符(随机访问迭代器需要)bool operator<(const MyIterator& other) const {return m_ptr < other.m_ptr;}bool operator>(const MyIterator& other) const {return other < *this;}bool operator<=(const MyIterator& other) const {return !(*this > other);}bool operator>=(const MyIterator& other) const {return !(*this < other);}private:pointer m_ptr;
};

4.4 算法的并行化(C++17)

C++17 引入了算法的并行版本,通过执行策略参数来指定并行方式:

#include <execution>  // 包含并行执行策略vector<int> v(1000000);// 串行执行(默认)
sort(v.begin(), v.end());// 并行执行(C++17)
sort(execution::par, v.begin(), v.end());// 并行+向量执行(C++17)
sort(execution::par_unseq, v.begin(), v.end());

并行算法可以显著提高大数据量处理的效率,但需要注意:

  • 并行算法可能会有额外的开销,对于小数据量可能不如串行算法
  • 传递给并行算法的函数必须是线程安全的
  • 并非所有算法都有并行版本

五、实战经验与最佳实践

5.1 选择合适的迭代器类型

  • 当需要高效随机访问时,优先选择vectordeque
  • 当需要频繁插入 / 删除元素时,优先选择list并使用双向迭代器
  • 对于只读操作,使用const_iterator可以提供更好的安全性和潜在的优化

5.2 避免常见陷阱

  1. 迭代器失效:当容器发生修改(插入 / 删除元素)时,迭代器可能失效

    vector<int> v = {1, 2, 3, 4};
    auto it = v.begin() + 2;  // 指向3
    v.erase(it);  // 此时it失效,不能再使用
    
  2. 算法与容器的匹配:确保算法要求的迭代器类型与容器提供的一致

    list<int> l = {3, 1, 4, 1, 5};
    // 错误:sort要求随机访问迭代器,而list提供双向迭代器
    // sort(l.begin(), l.end()); // 正确:使用list自带的sort方法
    l.sort();
    
  3. 正确处理remove的结果remove并不真正删除元素,只是将它们移到容器末尾

    vector<int> v = {1, 2, 3, 2, 4};
    // 错误:v的大小仍然是5
    // remove(v.begin(), v.end(), 2);// 正确:使用erase删除尾部的"垃圾"元素
    auto new_end = remove(v.begin(), v.end(), 2);
    v.erase(new_end, v.end());  // 现在v的大小是3
    

5.3 提高性能的技巧

  1. 预留空间:使用reserve避免多次内存分配

    vector<int> v;
    v.reserve(1000);  // 预留空间
    for (int i = 0; i < 1000; ++i) {v.push_back(i);
    }
    
  2. 使用emplace系列函数:直接在容器中构造元素,避免复制

    vector<pair<int, string>> v;
    // 避免:创建临时对象再复制
    // v.push_back({1, "one"});// 更好:直接在容器中构造
    v.emplace_back(1, "one");
    

  3. 选择合适的算法:例如,find对于无序容器是 O (n),而unordered_set::find是 O (1) 平均复杂度

六、总结

          STL 的算法与迭代器是 C++ 中强大的工具,它们提供了一致的接口来处理各种数据结构,大大提高了代码的复用性和效率。掌握迭代器的分类和特性,熟悉常用算法的功能和复杂度,能够帮助你写出更简洁、高效、可维护的 C++ 代码。

         通过本文的学习,你应该已经对 STL 算法与迭代器有了全面的了解。但 STL 的学习是一个持续的过程,建议在实际开发中多使用、多实践,同时查阅官方文档和源码,深入理解其内部实现原理,这样才能真正发挥 STL 的强大威力。

        记住,最好的学习方式是实践。尝试用 STL 算法重写你现有代码中的循环和处理逻辑,你会惊讶于 STL 带来的便捷和高效!

http://www.dtcms.com/a/311431.html

相关文章:

  • 函数指针——回调函数
  • 文件同步神器-rsync命令讲解
  • ESP32- 项目应用1 智能手表之功能补全 #5
  • UDP通信中BIND端口号的作用解析,LOCALPORT的关系解析
  • 代码随想录刷题Day23
  • verilog的学习
  • 高效游戏状态管理:使用双模式位运算与数学运算
  • 从基础功能到自主决策, Agent 开发进阶路怎么走?
  • 技巧|SwanLab记录ROC曲线攻略
  • VueX进阶Pinia
  • go idea goland debug 报错 no debug info found
  • 从递归到动态规划-解码方法
  • Json Jsoncpp
  • 深入 Go 底层原理(十四):timer 的实现与高性能定时器
  • python JSONPath 表达式生成器
  • 淘宝获取商品SKU详情API接口操作指南
  • 交互 Codeforces Round 1040 Interactive RBS
  • 开发指南128-基础类-BaseDAO
  • 力扣面试150题--回文数
  • ABP VNext + NATS JetStream:高性能事件流处理
  • FPGA kernel 仿真器调试环境搭建
  • 分类任务当中常见指标 F1分数、recall、准确率分别是什么含义
  • 「iOS」————SideTable
  • 基于Dockerfile 部署一个 Flask 应用
  • WAIC引爆AI,智元机器人收购上纬新材,Geek+上市,157起融资撑起热度|2025年7月人工智能投融资观察 · 极新月报
  • 【传奇开心果系列】Flet框架流式输出和实时滚动页面的智能聊天机器人自定义模板
  • github在界面创建tag
  • 性能测试-性能测试中的经典面试题二
  • 超级人工智能+无人机操控系统,振兴乡村经济的加速器,(申请专利应用),严禁抄袭!
  • spring-ai-alibaba 学习(十九)——graph之条件边、并行节点、子图节点