一、前言
虽然大家对迭代器的认识可能都是从C++开始的,但事实上它并不是C++独有的概念。
它出现的目的是为了解决这样一个问题:遍历元素。
千万不要小看遍历元素这一看似简单的操作,比如程序中最基本的两个数据结构:数组、链表。
这两个结构的底层实现原理就决定了它们两个的遍历方式是不一样的:
- 数组:可以通过下标随即访问任何一个元素
- 链表:只能从头、或从尾部挨个读取
除此之外还有树、队列、栈等数据结构,想要遍历它们内部的元素的方式各不相同。
但对于使用者来说,大部分时候是不关心其底层实现结构的,我们只想要完成遍历这个功能而已。
所以就出现了迭代器这一概念,它的作用就在于将不同的数据结构遍历方式全部统一起来了。
二、基本使用
这里以C++、rust语言为例对照观察它们的通用性,其它语言如果有这一概念的实现,使用方式也都是差不多的。
比如C++中的vector本质是一个数组、list本质是一个链表,其遍历方式就是不一样的,甚至由于list本身的特性,根本就不支持直接遍历:
#include<iostream>
#include<vector>
#include<list>
using namespace std;
int main() {
vector<int> vec = { 1,2,3,4,5 };
list<int> ls = { 1,2,3,4,5 };
for (int i = 0; i < vec.size(); i++) {
cout<<vec[i]<<endl;
}
for (int i : ls) {
cout << i << endl;
}
}
这里为了遍历C++中的链表,只能使用这种新型的for
遍历方式来挨个取出其中的元素,但事实上这种遍历本质上依旧是在调用迭代器的功能,这里不过是将这一过程给封装了。
所以可以说,C++中的list
标准库根本就没有提供基本的遍历方案,只提供了迭代器的方案。
从这里就能明显的看出不同数据结构遍历方式的不一致性,而迭代器就能将这一过程统一起来。
首先是vector结构:
vector<int> vec = { 1,2,3,4,5 };
vector<int>::iterator beg = vec.begin();
vector<int>::iterator end = vec.end();
for (auto i = beg; i !=end ; i++) {
cout << *i << endl;
}
虽然看起来代码变得更加复杂了,但熟悉之后其实也还好,迭代器也是一种类型,并且属于具体的某个数据结构。
比如这里是vector<int>
中的迭代器类型。
甚至大多数时候,你都可以直接将迭代器当作指针看待,所以循环体内部是通过*
来取出迭代器中的值的。
并且你还能用++
这种符号来对其进行自增操作,而事实上这一操作本质就是在将迭代器移动到下一个元素的位置,直到它等于末尾了,那么循环也就结束了。
而这也正是begin
、end
这两个函数的目的,返回该数据结构中的开始迭代器与结束迭代器,所有C++中的容器都有这两个函数。
了解了上面一个的使用,那么其它容器的迭代器使用你就基本都会了:
list<int> ls = { 1,2,3,4,5 };
list<int>::iterator beg = ls.begin();
list<int>::iterator end = ls.end();
for (auto i = beg; i != end; i++) {
cout << *i << endl;
}
可以看到,list迭代器的使用方式基本就是一样的。
看完了C++,我们再来看看rust中的迭代器,看看两种语言之间的迭代器有什么通用的地方:
fn main() {
let mut v=Vec::new();
v.push(1);
v.push(2);
v.push(3);
for i in v.iter(){
println!("{i}");
}
}
首先同样是vector
,只不过rust中将其名字改为了Vec
,它可以通过iter
函数直接返回其内部的迭代器,然后放在for循环中就可以用了。
然后是链表:
use std::collections::LinkedList;
fn main() {
let mut ls=LinkedList::new();
ls.push_back(1);
ls.push_back(2);
ls.push_back(3);
for i in ls.iter(){
println!("{i}");
}
}
可以看到,依旧是如此,通过其上的iter
函数返回内部的迭代器,然后就可以直接用for循环遍历输出其中的内容了。
即使它们底层的实现原理并不一致,但对于我们使用者来说,却是完全不需要关心这些细节,直接用就行了。
其它语言,比如python中也是有这一概念的,只不过可能它封装的更加彻底,只要你对某个数据结构使用循环,那么其就默认调用了迭代器,而无需你亲自来启用这一功能。