list容器模拟实现(一)—— push_back添加数据_abs(ln(1+NaN))的博客-CSDN博客list容器添加数据模拟实现https://blog.csdn.net/challenglistic/article/details/124209673上一部分实现了list容器添加数据的模拟实现,现在我们要设计一个迭代器来遍历这些数据

迭代器可以看作是指针,也就是链表中某个结点的地址

——》我们迭代器中要存的第一个东西就是 结点的地址


目录

一、构建迭代器

二、迭代器 ++ 运算符的重载实现

1、前置++ 实现

2、后置++ 实现

3、!= 重载实现

4、解引用* 重载实现

5、赋值符 = 重载实现

6、指针指向符 -> 重载实现

三、在list类中加入迭代器

1、begin函数

2、end函数

四、使用迭代器遍历list元素

1、list元素为内置类型

2、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元素为自定义类型

测试代码如下

Logo

权威|前沿|技术|干货|国内首个API全生命周期开发者社区

更多推荐