【C++】list用法&&简单模拟实现_list&& c+

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

list<int>L1;
L1.push\_back(10);
L1.push\_back(20);
L1.push\_back(30);
L1.push\_back(40);

list<int>L2;
L2.assign(10, 100);

cout << "交换前: " << endl;
PrintList(L1);
PrintList(L2);

cout << endl;

L1.swap(L2);

cout << "交换后: " << endl;
PrintList(L1);
PrintList(L2);

}


![image-20230106102612931](https://iubaopicbed.oss-cn-shenzhen.aliyuncs.com/img2/picbed202301061026002.png)
![image-20230106102629648](https://iubaopicbed.oss-cn-shenzhen.aliyuncs.com/img2/picbed202301061026685.png)


---


### 1.5 list 插入和删除


**功能描述:**


* 对list容器进行数据的插入和删除




| 函数声明 | 接口说明 |
| --- | --- |
| push\_front | 在list首元素前插入值为val的元素 |
| pop\_front | 删除list中第一个元素 |
| push\_back | 在list尾部插入值为val的元素 |
| pop\_back | 删除list中最后一个元素 |
| insert | 在list position 位置中插入值为val的元素 |
| erase | 删除list position位置的元素 |
| swap | 交换两个list中的元素 |
| clear | 清空list中的有效元素 |


**示例**



// list插入和删除
// push_back/pop_back/push_front/pop_front
void TestList5()
{
int array[] = { 1, 2, 3 };
list L(array, array + sizeof(array) / sizeof(array[0]));

// 在list的尾部插入4,头部插入0
L.push\_back(4);
L.push\_front(0);
PrintList(L);

// 删除list尾部节点和头部节点
L.pop\_back();
L.pop\_front();
PrintList(L);

}


![image-20230106103518520](https://iubaopicbed.oss-cn-shenzhen.aliyuncs.com/img2/picbed202301061035550.png)

// insert /erase
void TestList6()
{
int array1[] = { 1, 2, 3 };
list L(array1, array1 + sizeof(array1) / sizeof(array1[0]));

// 获取链表中第二个节点
auto pos = ++L.begin();
cout << \*pos << endl;

// 在pos前插入值为4的元素
L.insert(pos, 4);
PrintList(L);

// 在pos前插入5个值为5的元素
L.insert(pos, 5, 5);
PrintList(L);

// 在pos前插入[v.begin(), v.end)区间中的元素
vector<int> v{ 7, 8, 9 };
L.insert(pos, v.begin(), v.end());
PrintList(L);

// 删除pos位置上的元素
L.erase(pos);
PrintList(L);

// 删除list中[begin, end)区间中的元素,即删除list中的所有元素
L.erase(L.begin(), L.end());
PrintList(L);

}


![image-20230106103538833](https://iubaopicbed.oss-cn-shenzhen.aliyuncs.com/img2/picbed202301061035863.png)

// resize/swap/clear
void TestList7()
{
// 用数组来构造list
int array1[] = { 1, 2, 3 };
list l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));
PrintList(l1);

// 交换l1和l2中的元素
list<int> l2;
l1.swap(l2);
PrintList(l1);
PrintList(l2);

// 将l2中的元素清空
l2.clear();
cout << l2.size() << endl;

}


![image-20230106103556897](https://iubaopicbed.oss-cn-shenzhen.aliyuncs.com/img2/picbed202301061035925.png)


---


### 1.6 list容量大小操作


**功能描述:**


* 对list容器的大小进行操作




| 函数声明 | 接口说明 |
| --- | --- |
| empty | 检测list是否为空,是返回true,否则返回false |
| size | 返回list中有效节点的个数 |
| resize(num,elem) | 重新指定容器的长度为num,若容器变长,则以elem值填充新位置,如果容器变短,则末尾超出容器长度的元素被删除 |


**示例**



//大小操作
void TestList8()
{
listL1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);

if (L1.empty())
{
	cout << "L1为空" << endl;
}
else
{
	cout << "L1不为空" << endl;
	cout << "L1的大小为: " << L1.size() << endl;
}

//重新指定大小
L1.resize(10);
PrintList(L1);

L1.resize(2);
PrintList(L1);

}


![image-20230106104259886](https://iubaopicbed.oss-cn-shenzhen.aliyuncs.com/img2/picbed202301061042926.png)


---


### 1.7 list 数据存取


**功能描述:**


* 对list容器中数据进行存取




| 函数声明 | 接口说明 |
| --- | --- |
| front | 返回list的第一个节点中值的引用 |
| back | 返回list的最后一个节点中值的引用 |


**示例**



//数据存取
void TestList9()
{
listL1;
L1.push_back(10);
L1.push_back(20);
L1.push_back(30);
L1.push_back(40);

//cout << L1.at(0) << endl;//错误 不支持at访问数据
//cout << L1[0] << endl; //错误 不支持[]方式访问数据
cout << "第一个元素为: " << L1.front() << endl;
cout << "最后一个元素为: " << L1.back() << endl;

//list容器的迭代器是双向迭代器,不支持随机访问
list<int>::iterator it = L1.begin();
//it = it + 1;//错误,不可以跳跃访问,即使是+1

}


![image-20230106104439866](https://iubaopicbed.oss-cn-shenzhen.aliyuncs.com/img2/picbed202301061044902.png)
总结:


* list容器中不可以通过[]或者at方式访问数据
* 返回第一个元素 — front
* 返回最后一个元素 — back




---


## 2. list的模拟实现


### 这次要模拟实现的类及其成员函数接口总览



namespace hdm
{
//模拟实现list当中的结点类
template
struct ListNode
{
//成员函数
ListNode(const T& val = T()); //构造函数

	//成员变量
	T _val;                 //数据域
	ListNode<T>\* _next;   //后继指针
	ListNode<T>\* _prev;   //前驱指针
};

//模拟实现list迭代器
template<class T, class Ref, class Ptr>
struct ListIterator
{
	typedef ListIterator<T> Node;
	typedef ListIterator<T, Ref, Ptr> Self;

	ListIterator(node\* pnode);  //构造函数

	//各种运算符重载函数
	Self operator++();
	Self operator--();
	Self operator++(int);
	Self operator--(int);
	bool operator==(const Self& s) const;
	bool operator!=(const Self& s) const;
	Ref operator\*();
	Ptr operator->();

	//成员变量
	Node\* _node; //一个指向结点的指针
};

//模拟实现list
template<class T>
class list
{
public:
	typedef ListNode<T> Node;
	// 正向迭代器
	typedef ListIterator<T, T&, T\*> iterator;
	typedef ListIterator<T, const T&, const T\*> const_iterator;

	// 反向迭代器
	typedef ReverseIterator<iterator> reverse_iterator;
	typedef ReverseIterator<const_iterator> const_reverse_iterator;

	//默认成员函数
	list();
	list(const list<T>& lt);
	list<T>& operator=(const list<T>& lt);
	~list();

	//迭代器相关函数
	iterator begin();
	iterator end();
	const_iterator begin() const;
	const_iterator end() const;
    //反向迭代器
    reserve_iterator rbegin();
    reserve_iterator rend();
    const_reserve_iterator rbegin()const;
    const_reserve_iterator rend();const;

	//访问容器相关函数
	T& front();
	T& back();
	const T& front() const;
	const T& back() const;

	//插入、删除函数
	void insert(iterator pos, const T& x);
	iterator erase(iterator pos);
	void push\_back(const T& x);
	void pop\_back();
	void push\_front(const T& x);
	void pop\_front();

	//其他函数
	size_t size() const;
	void resize(size_t n, const T& val = T());
	void clear();
	bool empty() const;
	void swap(list<T>& lt);

private:
	Node\* _head; //指向链表头结点的指针
};

}


### 2.1 结点类的实现


我们在之前的章节中曾经讲述过并且也模拟实现过纯C语言版本,C++中STL标准库中的list用的是双向循环链表,它的结构看似复杂,功能却是最好,最容易实现的


![image-20230109151213943](https://img-blog.csdnimg.cn/img_convert/e568029831264254c01ea69387392b41.png)


我们若要实现list,则首先需要实现一个结点类。而一个结点需要存储的信息有:数据、前一个结点的地址、后一个结点的地址(跟我们之前实现的C语言版本差不多,只不过在C语言叫结构体,而且它没有自动初始化的功能,而C++有构造函数可以解决这一问题)


而对于该结点类的成员函数来说,我们只需实现一个构造函数即可,它的节点释放可用list这个大类来实现


list的节点类



// List的节点类
template
struct ListNode
{
ListNode(const T& val=T())
:_val(val)
, _next(nullptr)
, _prev(nullptr)
{}

	T _val;
	ListNode\* _next;
	ListNode\* _prev;
};



---


### 2.2 迭代器的模拟实现


迭代器就是把不同的数据结构 "相同功能 "的函数装到一个名字相同的函数里,这样的话你在写算法的时候就可以不管你要操作的数据结构的逻辑结构了。  
 比如不管是链表,数组还是别的什么,统一都用迭代器进行访问的话可能都是 Next()表示下一个元素 Pre()表示上一个元素等等


至于迭代器怎么能够做到这样的功能呢,首先就是我们要实现这样的功能,必须要对对应的数据结构有一定的理解,比如vector,


我们要实现++就是下一个元素,–就是上一个元素,对于vector来说这是非常简单的,因为vector的定义是什么呀,它就是在一段


连续的空间中存储数据,因为地址的连续,原生的指针就天然的就形成了所谓的迭代器了。


![image-20230109153522791](https://img-blog.csdnimg.cn/img_convert/e268a63cd59854ca91dc441b018ccd97.png)


那么对于list呢?


很不幸,list的结构导致它的迭代器并没有vector的迭代器这么简单,因为list的空间并不连续,它的节点都是按需申请的,是用一个指针把它串联起来,这就导致了我们仅仅对它的原生指针进行++,–这样后操作后,得到的结果并不一定是我们想象的下一个节点。


所以我们需要对它的迭代器进行重新封装,并不能认为它的原生指针就可以当迭代器!!!


![image-20230109153652762](https://img-blog.csdnimg.cn/img_convert/be1023e8554698e9e1d3738cd944e7b4.png)


​ 那么我们怎么实现这个迭代器呢?


其实我们熟悉这个结构,就可以很容易知道,我们要实现++找到下一个元素,其实对于list的操作就是它节点的next指针对应的地址就是它下一个元素,–找到上一个元素,就是prev指针对应的地址,所以我们只需要对++,–等操作符进行操作符重载即可!



/*
List 的迭代器
迭代器有两种实现方式,具体应根据容器底层数据结构实现:

  1. 原生态指针,比如:vector

  2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:

  3. 指针可以解引用,迭代器的类中必须重载operator*()

  4. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()

  5. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
    至于operator–()/operator–(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载

  6. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
    */
    template <class T,class Ref, class Ptr>
    class ListIterator
    {
    public:
    typedef ListNode Node;
    typedef ListIterator<T, Ref, Ptr> Self;//Self是当前迭代器对象的类型:
    public:
    // Ref 和 Ptr 类型需要重定义下,实现反向迭代器时需要用到
    typedef Ref Ref;
    typedef Ptr Ptr;
    ListIterator(Node* node=nullptr)
    :_node(node)
    {}
    //
    // 具有指针类似行为
    //返回当前结点指针所指结点的数据
    Ref operator*()
    {
    return _node->_val;
    }
    //对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可
    Ptr operator->()
    {
    return &(_node->_val);
    }
    //
    // 迭代器支持移动
    //前置++:先让结点指针指向后一个结点,然后再返回“自增”后的结点指针
    Self operator++()
    {
    _node = _node->_next;
    return *this;
    }
    //后置++:先记录当前结点指针的指向,然后让结点指针指向后一个结点,最后返回“自增”前的结点指针
    Self operator++(int)
    {
    Self tem(*this);
    _node = _node->_next;
    return tem;
    }
    Self operator–()
    {
    _node = _node->_prev;
    return *this;
    }
    Self operator–(int)
    {
    Self tem(*this);
    _node = _node->_prev;
    return tem;
    }
    //
    // 迭代器支持比较
    //判断这两个迭代器当中的结点指针的指向是否不同
    bool operator!=(const Self& lt)const
    {
    return _node != lt._node;
    }
    //判断这两个迭代器当中的结点指针的指向是否相同
    bool operator==(const Self& lt)const
    {
    return _node == lt._node;
    }

     Node\* _node;
    

    };


迭代器类的模板参数说明  
 这里我们所实现的迭代器类的模板参数列表当中为什么有三个模板参数?



template<class T, class Ref, class Ptr>


在list的模拟实现当中,我们typedef了两个迭代器类型,普通迭代器和const迭代器。



// 正向迭代器
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;


这里我们就可以看出,迭代器类的模板参数列表当中的Ref和Ptr分别代表的是引用类型和指针类型。


当我们使用普通迭代器时,编译器就会实例化出一个普通迭代器对象;当我们使用const迭代器时,编译器就会实例化出一个const迭代器对象。


若该迭代器类不设计三个模板参数,那么就不能很好的区分普通迭代器和const迭代器。


->运算符的重载说明



//对于->运算符的重载,我们直接返回结点当中所存储数据的地址即可
Ptr operator->()
{
return &(_node->_val);
}


对于->运算符的重载,我们直接返回结点当中所存储数据的地址


可能你会觉得不对,按照这种重载方式的话,这里使用迭代器访问日期类当中的成员变量时不是应该用两个->吗?


![image-20230109161749009](https://img-blog.csdnimg.cn/img_convert/650a240eb87d197e28368e1468d4be82.png)




---


### 2.3 反向迭代器模拟实现


实现反向迭代器前首先要知道一个名词“适配器”


所谓的适配器就是:通过限制模型的功能以让它满足另一个模型的功能,相当于改变了接口,但实现不变。


我们要实现反向迭代器其实很简单,就在原来的迭代器基础上改接口即可


![image-20230109155345060](https://img-blog.csdnimg.cn/img_convert/83728c647b92cea71338b5e7a657380e.png)


但是对于operator\*()我们要特殊处理一下,因为反向迭代器的rbegin对应的是原迭代器的end()


也就是说它指向了最后一个元素的后一个位置,所以要取这个rbegin的元素的时候,我们要对end()–后再取元素



Ref operator*()
{
iterator tmp = _it;
return *(–tmp);
}


反向迭代器的适配器实现



//适配器
// 给我不同容器的正向迭代器,适配出对应的这个容器需要的反向迭代器
template
class ReverseIterator
{
// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的一个类型,而不是静态成员变量
// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量
// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
public:
typedef typename iterator::Ref Ref;
typedef typename iterator::Ptr Ptr;
typedef ReverseIterator Self;

    ReverseIterator(iterator it)
        :\_it(it)
        {}
    //前置++
    Self operator++()
    {
        --_it;
        return \*this;
    }
    //后置++
    Self operator++(int)
    {
        Self tmp(\*this);
        --_it;
        return tmp;
    }
    //前置--
    Self operator--()
    {
        ++_it;
        return \*this;
    }
    //后置--
    Self operator--(int)
    {
        Self tmp(\*this);
        ++_it;
        return tmp;
    }

    Ref operator\*()
    {
        iterator tmp = _it;
        return \*(--tmp);
    }

    Ptr operator->()
    {
        return &(operator\*());
    }

    bool operator!=(const Self & l) const
    {
        return _it != l._it;
    }
    private:
    iterator _it;
};



---


### 2.4 list的模拟实现


#### 构造函数


list是一个带头双向循环链表,在构造一个list对象时,直接申请一个头结点,并让其前驱指针和后继指针都指向自己即可


![image-20230109162251228](https://img-blog.csdnimg.cn/img_convert/5970bd265135acf51159aa91a0044ff1.png)


无参的简单构造



list()
{
CreatNode();
}
void CreatNode()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}


迭代器构造


这里的迭代器不一定是list的迭代器,也可能是用的其他容器来构造list,所以需要模板实现



//迭代器构造
template
list(InputIterator first, InputIterator last)
{
CreatNode();
while (first != last)
{
push_back(*first);
++first;
}
}




---


#### 拷贝构造


拷贝构造函数就是根据所给list容器,拷贝构造出一个对象


对于拷贝构造,这里提供**两种写法:**


1. 传统写法

 **先申请一个头结点,并让其前驱指针和后继指针都指向自己,然后将所给容器当中的数据,通过遍历的方式一个个尾插到新构造的容器后面**



//拷贝构造函数
list(const list& lt)
{
_head = new node; //申请一个头结点
_head->_next = _head; //头结点的后继指针指向自己
_head->_prev = _head; //头结点的前驱指针指向自己
for (const auto& e : lt)
{
push_back(e); //将容器lt当中的数据一个个尾插到新构造的容器后面
}
}


2. 现代写法

 **利用迭代器构造函数,构造一个一模一样的tmp,然后再跟它交换**



//拷贝构造
list(const list& l)
{
CreatNode();
list tmp(l.begin(), l.end());
swap(tmp);
}




---


#### 赋值运算符重载函数


对于赋值运算符的重载,这里也提供**两种写法:**


1. 传统写法  
 **这是比较容易理解的一种写法,先调用clear函数将原容器清空,然后将容器lt当中的数据,通过遍历的方式一个个尾插到清空后的容器当中**。



//传统写法
list& operator=(const list& lt)
{
if (this != &lt) //避免自己给自己赋值
{
clear(); //清空容器
for (const auto& e : lt)
{
push_back(e); //将容器lt当中的数据一个个尾插到链表后面
}
}
return *this; //支持连续赋值
}


2. 现代写法

 **首先利用编译器机制,故意不使用引用接收参数,通过编译器自动调用list的拷贝构造函数构造出来一个list对象,然后调用swap函数将原容器与该list对象进行交换**



//现代写法
list& operator=(list tmp)
{
swap(tmp);
return *this;
}




---


#### 析构函数


先调用clear函数清理容器当中的数据,然后将头结点释放,最后将头指针置空



~list()
{
clear();
delete _head;
_head = nullptr;
}




---


#### 迭代器相关函数


begin:返回的是第一个有效数据的迭代器


end:最后一个有效数据的下一个位置的迭代器


对于list这个带头双向循环链表来说,第一个有效数据的迭代器就是头结点后的第一个结点的地址构造出来的迭代器,而其最后一个有效数据的下一个位置的迭代器就是头结点的地址构造出来的迭代器。



// List的迭代器
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}

//const迭代器也必不可少
const_iterator begin() const
{
return const_iterator(_head->_next);
}

const_iterator end()const
{
return const_iterator(_head);
}


对于反向迭代器


rbegin:就是正向迭代器的end()


rend: 就是正向迭代器的begin()



reverse_iterator rbegin()
{
return reverse_iterator(end());
}

reverse_iterator rend()
{
return reverse_iterator(begin());
}

//const迭代器
const_reverse_iterator rbegin() const
{
return reverse_iterator(end());
}

const_reverse_iterator rend() const
{
return reverse_iterator(begin());
}




---


#### 插入、删除函数


插入函数算是老生常谈了,跟以往不同的就是这次要返回新节点位置的迭代器


![在这里插入图片描述](https://img-blog.csdnimg.cn/93b74fb662444685a75b3de6d060dd63.gif#pic_center)



// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& x)
{
Node* newNode = new Node(x);
Node* cur = pos._node;
//将新节点插入
cur->_prev->_next = newNode;
newNode->_prev = cur->_prev;
cur->_prev = newNode;
newNode->_next = cur;

return	iterator(newNode);

}


还有一些头插,尾插的接口,其实都可以复用上面的插入函数



void push_back(const T& val)
{
insert(iterator(_head), val);
}

void push_front(const T&val)
{
insert(iterator(_head->_next), val);
}


erase函数可以删除所给迭代器位置的结点。同时还要返回该位置下一个节点的迭代器,可以有效防止迭代器失效的问题。



// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
Node* del = pos._node;
Node* Ret = del->_next;

//删除节点
del->_prev->_next = Ret;
Ret->_prev = del->_prev;
delete del;
return iterator(Ret);

}


当然还有一些头删,尾删,也是可以复用上面的接口



void pop_back()
{
erase(_head->_prev);
}

void pop_front()
{
erase(_head->_next);
}




---


#### resize


**resize函数的规则:**


1. 若当前容器的size小于所给n,则尾插结点,直到size等于n为止。
2. 若当前容器的size大于所给n,则只保留前n个有效数据。



void resize(size_t newsize, const T& val = T())
{
size_t oldsize = size();
//newsize<oldsize,将元素减少到newsize
if (newsize <= oldsize)
{
while (newsize < oldsize)
{
pop_back();
–oldsize;
}
}
else //newsize>oldsize,用val扩充元素到newsize
{
while (newsize > oldsize)
{
push_back(val);
oldsize++;
}
}
}


#### clear


clear函数用于清空容器,我们通过遍历的方式,逐个删除结点,只保留头结点



void clear()
{
Node*cur = _head->_next;
while (cur != _head)
{
Node* del = cur;
cur = cur->_next;
delete del;
}
}


#### swap


swap函数用于交换两个容器,list容器当中存储的实际上就只有链表的头指针,我们将这两个容器当中的头指针交换



void swap(list& l)
{
std::swap(_head, l._head);
}


这里我们复用标准库里面的交换函数swap即可,不过要主要的是要加上访问限定符,没加std::就有可以导致无限递归




---


### 完整的实现代码



#pragma once
#include
using namespace std;

namespace hdm
{
// List的节点类
template
struct ListNode
{
ListNode(const T& val=T())
:_val(val)
, _next(nullptr)
, _prev(nullptr)
{}

	T _val;
	ListNode\* _next;
	ListNode\* _prev;
};
 /\*

List 的迭代器
迭代器有两种实现方式,具体应根据容器底层数据结构实现:

  1. 原生态指针,比如:vector

  2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:

  3. 指针可以解引用,迭代器的类中必须重载operator*()

  4. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()

  5. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
    至于operator–()/operator–(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载–

  6. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
    */

    template <class T,class Ref, class Ptr>
    class ListIterator
    {
    public:
    typedef ListNode Node;
    typedef ListIterator<T, Ref, Ptr> Self;
    public:
    // Ref 和 Ptr 类型需要重定义下,实现反向迭代器时需要用到
    typedef Ref Ref;
    typedef Ptr Ptr;
    ListIterator(Node* node=nullptr)
    :_node(node)
    {}
    //
    // 具有指针类似行为
    Ref operator*()
    {
    return _node->_val;
    }

     Ptr operator->()
     {
    

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
img

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

l)
, _next(nullptr)
, _prev(nullptr)
{}

	T _val;
	ListNode\* _next;
	ListNode\* _prev;
};
 /\*

List 的迭代器
迭代器有两种实现方式,具体应根据容器底层数据结构实现:

  1. 原生态指针,比如:vector

  2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:

  3. 指针可以解引用,迭代器的类中必须重载operator*()

  4. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()

  5. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
    至于operator–()/operator–(int)释放需要重载,根据具体的结构来抉择,双向链表可以向前移动,所以需要重载,如果是forward_list就不需要重载–

  6. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
    */

    template <class T,class Ref, class Ptr>
    class ListIterator
    {
    public:
    typedef ListNode Node;
    typedef ListIterator<T, Ref, Ptr> Self;
    public:
    // Ref 和 Ptr 类型需要重定义下,实现反向迭代器时需要用到
    typedef Ref Ref;
    typedef Ptr Ptr;
    ListIterator(Node* node=nullptr)
    :_node(node)
    {}
    //
    // 具有指针类似行为
    Ref operator*()
    {
    return _node->_val;
    }

     Ptr operator->()
     {
    

网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。

需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)
[外链图片转存中…(img-ga7Kv8Br-1713338189894)]

一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!

  • 22
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值