迭代器
概念介绍
迭代器是一种抽象的设计理念,它提供了一种方法,使我们能够依序访问某个容器所含的各个元素,而又无需暴露该容器的内部表示方式。这句话应该如何理解呢?请看下面的例子:
// 使用迭代器访问元素
#include <iostream>
#include <vector>
int main() {
std::vector<int> myVector = {1, 2, 3, 4, 5};
// 使用迭代器访问元素
for (std::vector<int>::iterator it = myVector.begin(); it != myVector.end(); ++it) {
std::cout << *it << " ";
}
std::cout << std::endl;
return 0;
}
//不使用迭代器访问元素
#include <iostream>
#include <vector>
int main() {
std::vector<int> myVector = {1, 2, 3, 4, 5};
// 直接访问元素
for (size_t i = 0; i < myVector.size(); ++i) {
std::cout << myVector[i] << " ";
}
std::cout << std::endl;
return 0;
}
通过上面的例子可以看到,使用迭代器访问元素仅需要使用容器的成员函数begin()和end()就可以了,不需要知道容器内部是如何存储和组织数据的;但是如果不使用迭代器访问元素,那么就需要访问容器的大小(size())和通过索引访问元素,这两个操作直接依赖于容器的具体实现。
基本特征
迭代器的基本特征如下所示:
- 迭代器像指针一样工作
- 提供了统一的访问容器元素的接口
- 是容器和算法之间的桥梁
迭代器的基本操作
// 1. 获取迭代器
vector<int> vec = {1, 2, 3, 4, 5};
vector<int>::iterator it = vec.begin(); // 起始迭代器
vector<int>::iterator end = vec.end(); // 末尾迭代器
// 2. 迭代器的移动
++it; // 向前移动一位
--it; // 向后移动一位(双向/随机访问迭代器支持)
it += 2; // 向前移动2位(随机访问迭代器支持)
// 3. 通过迭代器访问元素
int value = *it; // 解引用获取元素
*it = 10; // 修改元素(如果不是const迭代器)
// 4. 迭代器的遍历
for(auto it = vec.begin(); it != vec.end(); ++it) {
cout << *it << " ";
}
常见用法示例
// 1. 范围for循环(C++11)
vector<int> nums = {1, 2, 3, 4, 5};
for(const auto& num : nums) {
cout << num << " ";
}
// 2. 使用迭代器进行插入
vector<int> vec = {1, 2, 3};
auto it = vec.begin() + 1;
vec.insert(it, 5); // 在第二个位置插入5
// 3. 使用迭代器进行删除
list<int> lst = {1, 2, 3, 4, 5};
auto it = lst.begin();
++it; // 移动到第二个元素
it = lst.erase(it); // 删除并返回下一个位置的迭代器
// 4. 反向迭代器
vector<int> vec = {1, 2, 3, 4, 5};
for(auto rit = vec.rbegin(); rit != vec.rend(); ++rit) {
cout << *rit << " "; // 从后向前遍历
}
注意事项
- 迭代器失效
vector<int> vec = {1, 2, 3};
auto it = vec.begin();
vec.push_back(4); // 可能导致迭代器失效
// *it; // 危险!迭代器可能已经失效
这种情况下有vector可能由于容量不够,重新分配内存,以便存在新插入的数值,这种情况下已有的迭代器还是指向原有地址。
- 使用auto
// 使用auto简化迭代器声明
auto it = container.begin();
// 代替
// container<T>::iterator it = container.begin();
- 检查有效性
vector<int> vec = {1, 2, 3};
auto it = vec.begin();
if(it != vec.end()) {
// 确保迭代器有效再使用
*it = 10;
}
std::begin/std::end
概念介绍
std::begin和std::end是c++11引入的全局函数,它们提供了一种统一的方式来访问容器或者数组的起始和结束迭代器。
基本定义
// 函数原型(简化版)
template <class C>
auto begin(C& c) -> decltype(c.begin());
template <class C>
auto end(C& c) -> decltype(c.end());
// 数组特化版本
template <class T, size_t N>
T* begin(T (&array)[N]);
template <class T, size_t N>
T* end(T (&array)[N]);
使用场景
容器使用
vector<int> vec = {1, 2, 3, 4, 5};
auto first = std::begin(vec); // 等价于 vec.begin()
auto last = std::end(vec); // 等价于 vec.end()
// 遍历示例
for(auto it = std::begin(vec); it != std::end(vec); ++it) {
cout << *it << " ";
}
数组使用
int arr[] = {1, 2, 3, 4, 5};
auto first = std::begin(arr); // 返回指向数组首元素的指针
auto last = std::end(arr); // 返回指向数组尾后位置的指针
// 遍历示例
for(auto it = std::begin(arr); it != std::end(arr); ++it) {
cout << *it << " ";
}
std::begin/end 与成员函数 begin()/end() 的区别
主要区别
// 1. 成员函数方式
vector<int> vec = {1, 2, 3};
auto it1 = vec.begin(); // 只能用于容器
// 2. 全局函数方式
int arr[] = {1, 2, 3};
auto it2 = std::begin(arr); // 可用于容器和数组
auto it3 = std::begin(vec); // 也可用于容器
通用性
// 编写通用代码,同时支持容器和数组
template<typename Container>
void printFirst(Container& c) {
// 使用std::begin可以同时处理容器和数组
auto first = std::begin(c);
if(first != std::end(c)) {
cout << *first << endl;
}
}