关注“杜明c”,每天进步一点点!
我们常常可以在各个面向对象的语言里看到迭代器这个词,听起来抽象,用起来简单,通过使用迭代器就能简单的提取出一个集合中的元素,而不必关心数据的底层储存方式。这往往却让人对他的本质模糊不清,那么迭代器究竟是什么东西,本文就来探究这个问题。
摘要
-
迭代器的应用
-
猜测
-
探究
-
总结
迭代器的应用
我们先看两个使用迭代器的例子。
python
# python
# nums称为一个可迭代对象
nums = [1,2,3,4]
for i in nums:
print(i)# 历遍nums,输出数据
it = iter(nums)
print(next(it))
print(next(it))
----out----
1
2
3
4
1
2
C++
// C++
list<int> nums = {1,2,3,4}; //创建一个容器装int元素
list<int>::iterator iter; //创建相应的迭代器
for(iter = nums.begin();iter!=nums.end();iter++)
{
cout<<*iter<<endl; //历遍输出元素
}
----out----
1
2
3
4
猜测
看到上面两个例子,你认为迭代器是什么?
-
迭代器是一个容纳元素的容器
-
迭代器是一个指针
在最初接触这个概念的时候,我们往往想到迭代器可能就是一个容器,但是对指针比较熟悉的话,首先会认为迭代器就是一个指针。那么究竟是不是呢?我们可以试验一下。
探究
在的连续的,线性的储存结构里,迭代器是怎么工作的呢?
//我们看一下vector容器,这是一个连续的,线性的容器,如同数组
vector<int> nums = {1,2,43,1}; //创建一个容器装int元素
vector<int>::iterator iter; //创建相应的迭代器
for(iter = nums.begin();iter!=nums.end();iter++)
{
cout<<*iter<<endl; //历遍输出元素
}
他在内存中的储存结构表示如下
我们可以看到,vector容器在内存中的储存方式和数组一样,我们在访问数据的时候可以直接通过下标,通过地址来访问。在这样的储存结构下,迭代器的本质就是一个指针。
我们可以浏览一下,vector容器中迭代器的实现。
//可以自己试着写一个简化的vector容器
template<typename T>
class vector_dm
{
public:
vector_dm(){ //数据初始化
max_number = 10;
head = (T*)malloc(sizeof(T) * max_number);
tail = head;
size = 0;
}
void push_back(T num); //元素入栈
int pop_back(); //元素出栈
void remove(T* iter);
T* begin(); //迭代器返回一个数据类型的指针
T* end(); //迭代器返回一个数据类型的指针
typedef T* iterator; //迭代器直接使用指针实现
private:
int size; //储存元素的数量
T* head;//头部指针
T* tail;//结束指针
int max_number;//当前最大可储存的元素数量
void expansion();//内存扩展函数
};
template<typename T> T* vector_dm<T>::end()
{
return head + size;
}
template<typename T> T* vector_dm<T>::begin()
{
return head;
}
但是,在链式储存结构里,迭代器使用指针来描述,就不是特别的准确。
我们可以看到链式储存结构,元素以一个个结点作为单位,每个结点除了包含数据本身,还包括指向前后结点的指针。
那么迭代器在链式结构的list中具体代表什么呢?
list<int> nums;
list<int>::iterator iter; //创建相应的迭代器
iter = nums.begin();
iter = nums.end();
我们可以看到这时候迭代器还是一个指针,只是从指向数据变成了指向数据结构体,但是他又不同于寻常意义上的指针,因为我们使用解引用运算符,就可以直接提取出数据,而不是提取出一个结构体。
了解了他的原理,我们可以同样尝试使用指针去描述迭代器。
struct node {
T node_data;//特定的数据类型
struct node* next;
struct node* previous;
};
struct node*iterator = nums.begin();//指向一个结点
//提取出结构体中的数据
T NEW_T = *(T*)iterator;//强制类型转换
//这里有一个小技巧,结构体的首地址和首元素的地址相同,所以直接使用指针强制转换可以得到数据本身。
但是这显然不是我们想要的,一个优秀的开发者应该把对象的特性封装好,提取出相关的函数和特性,而不是期望用户了解中间的实现原理。
所以最后一步很简单,我们需要把指针封装成对象,那他就是一个真正意义上的迭代器了,这是一个简单的list容器内部迭代器的实现。重新封装了关于迭代器的方法。
-
解引用*——返回特定类型的数据
-
+——迭代器的加法,指向下一个结点
-
==,!= ——逻辑判断
//c++
struct node {
T node_data;
struct node* next;
struct node* previous;
};
class iterator {
public:
struct node* p_node;
iterator(struct node* new_p_node)
{
p_node = new_p_node;
}
iterator()
{
p_node = NULL;
}
bool operator==(const iterator& x) { return p_node == x.p_node; }
bool operator!=(const iterator& x) { return p_node != x.p_node; }
iterator operator+(int n)
{
iterator iter;
iter.p_node = this->p_node->next;
this->p_node = this->p_node->next;
return iter;
}
iterator operator++(int)
{
iterator iter;
if (this->p_node == NULL) return iter;
iter.p_node = this->p_node->next;
return iter;
}
iterator operator++()
{
iterator iter;
if (this->p_node == NULL) return iter;
iter.p_node = this->p_node->next;
return iter;
}
T operator*()
{
if (!p_node) return NULL;
return p_node->node_data;
}
private:
};
iterator begin()
{
iterator iter = iterator(head);
return iter;
}
iterator end()
{
iterator iter = iterator(NULL);
return iter;
}
总结
那么迭代器是不是指针呢,其实是,也不是,这取决于集合元素的数据存储方式。迭代器更应该描述为一个指针运算集,他是一个对象,不只是简单意义上的指针,通过迭代器内置的运算实现历遍集合,提取元素这一系列的功能,
End
杜明C语言
专注C/C++
每天进步一点点
微信关注