list容器模拟实现(二)—— 迭代器遍历数据(含++、*、=运算符的重载)
手动模拟实现list容器的迭代器,并使用迭代器遍历数据
list容器模拟实现(一)—— push_back添加数据_abs(ln(1+NaN))的博客-CSDN博客list容器添加数据模拟实现https://blog.csdn.net/challenglistic/article/details/124209673上一部分实现了list容器添加数据的模拟实现,现在我们要设计一个迭代器来遍历这些数据
迭代器可以看作是指针,也就是链表中某个结点的地址
——》我们迭代器中要存的第一个东西就是 结点的地址
目录
一、构建迭代器
迭代器我们使用结构体struct 实现,存放的第一个成员就是 结点地址 ListNode<T>*
namespace xing{
//...
template<class T>
struct __list_iterator
{
typedef ListNode<T> Node;
Node* _node; //代表某个结点的地址
__list_iterator(Node* node) :_node(node)
{
}
};
//...
}
的其中ListNode是上一个部分实现的内容
二、迭代器 ++ 运算符的重载实现
我们通常所看到的迭代器支持 前置++ 或者 后置++
1、前置++ 实现
基本思路:
前置++ 的特点是,先返回自增之前的值,再自增。迭代器的自增指的是 移动到下一个结点
=》用一个临时变量来存自增之前的值,然后再移动到下一个结点
具体实现:
__list_iterator<T>& operator++()
{
_node = _node->_next;
return *this; // this是迭代器的指针,但返回是当前迭代器的对象的引用,需要*来解引用
}
注意:自增以后,返回迭代器对象 *this,*this是一直存在的,所以可以使用 引用&
作用过程:
注意隐藏的this指针,在编译器看来就是:
typedef __list_iterator<T> list_iterator;
list_iterator& operator++(const list_iterator* this)
{
}
实际使用的时候,会默认将迭代器对象 it 的地址传递给隐藏的第一个参数,但是在我们看来,没有传递任何参数。
__list_iterator<T> it;
it++; // 会被转换成 operator++(&it);
2、后置++ 实现
基本思路:
后置++ 的特点是,先自增,再返回自增以后的结果
——》先移动到下一个结点,再返回*this
具体实现:
__List_iterator<T> operator++(int)
{
__list_iterator<T> tmp(_node); //构建一个临时迭代器,记录移动到下一个结点之前的地址
_node = _node->_next;
return tmp;
}
注意:前置++的返回值tmp 出了作用域以后,就不存在了,所以这里不能返回引用
作用过程:
跟前置++一样,在编译器看来就是,只不过这里的int只是用来占位,和前置++区分开来
typedef __list_iterator<T> list_iterator;
list_iterator& operator++(const list_iterator* this, int)
{
}
3、!= 重载实现
基本思路:
比较两边的地址是否相同,左右两边都是迭代器,所以要在迭代器中实现,返回值是一个布尔值
具体实现:
bool operator!=(const __list_iterator<T>& it)
{
return _node != it._node; //实际比较的是两者的地址是否相同
}
作用过程:
重载时无论有多少个形参,编译器编译的时候,this指针永远都在第一个
typedef __list_iterator<T> list_iterator;
// this 是迭代器指针, it 是需要外部传入的迭代器对象
bool operator!=(const list_iterator* this, const list_iterator& it)
{
return _node != it._node; //实际比较的是两者的地址是否相同
}
实际使用时
__list_iterator<T> it1;
__list_iterator<T> it2;
it1 != it2; // it1 是调用方,放在 this指针的位置
// it2 是外部传入的对象,放在 it的位置
// 会转换成 operator(&it1, it2);
4、解引用* 重载实现
返回的是当前地址指向的内容,返回值需要加引用,因为解引用后的返回值可修改,而且this指针是一直存在的,即this指针指向的内容是一直存在的
// 注意隐藏的 this指针
T& operator*()
{
return _node->_data;
}
注意:这里返回值是引用 & 的原因是存在 *it = 2 这种赋值的情况。
5、赋值符 = 重载实现
将右边迭代器的地址赋值给左边迭代器,返回的是地址改变以后的左边的迭代器,即*this
原因是要支持连续赋值 it1 = it2 = it3 ——》 it1 = (it2 = it3)
// 注意隐藏的 this指针
__list_iterator<T>& operator=(const __list_iterator<T>& it)
{
_node = it._node;
return *this;
}
6、指针指向符 -> 重载实现
我们的迭代器是一个结构体,如果存储类型是自定义类型,我们希望能像指针一样使用这个结构体,这就需要 -> 的返回值是对应存储类型的指针
// 注意隐藏的 this指针
T* operator->()
{
return &(_node->_data);
}
注意:这里返回指针的原因是存在 node->next = node 这样的赋值情况。
三、在list类中加入迭代器
构建好迭代器以后,就在list类中引入构建好的迭代器
下面我们就可以在begin函数 和 end函数中使用迭代器了
1、begin函数
list类的begin()函数要返回指向头结点的下一个的迭代器
需要注意的是,返回的iterator不要加 引用符号 & !!!
我们使用 & 的 原则是,出了作用域,变量还在,那就可以使用引用;出了作用域,变量不在,不要使用引用
方式一的匿名对象的作用域仅仅只是当前行,所以这里的返回值不合适返回引用
方式二的临时结构体对象 tmp出了作用域 会被销毁,所以这里的返回值也不合适返回引用
方式一:
iterator begin()
{
return iterator(_head->_next); //匿名对象构建迭代器,调用对应的有参构造函数
}
方式二:
iterator begin()
{
iterator tmp;
tmp._node = _head->_next;
return tmp;
}
2、end函数
end()函数要返回一个指向尾节点的下一个的迭代器(仅列举出方式一的实现)
iterator end()
{
return iterator(_head);
}
四、使用迭代器遍历list元素
1、list元素为内置类型
测试代码如下
2、list元素为自定义类型
测试代码如下
更多推荐
所有评论(0)