🪐🪐🪐欢迎来到程序员餐厅💫💫💫
今日主菜:vector类
主厨:邪王真眼
所属专栏:c++专栏
主厨的主页:Chef‘s blog
总用光环在陨落,总有新星在闪烁
【本节目标】
1. 节点
2.迭代器
3.list
4. list与vector的对比
前言:
这次的list类较vector和string会更复杂,因此我们将他拆分为节点的类,迭代器的类,以及list类主体三点进行讲解。
一. list的介绍
- 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- 2. list的底层是双向带头循环链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
- 3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
- 4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。
- 5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素
二.节点
要点须知:
- 使用struct,标明公有属性(这样从外部调用比较方便)
- list是带头双向循环链表
template<class T>
struct ListNode//驼峰式命名
{
ListNode<T>* _prev;
ListNode<T>* _last;
T _val;
ListNode(const T &val=T())//缺省参数
:_prev(nullptr)
,_last(nullptr)
,_val(val)
{}
};
二、迭代器
由于list的每个结点物理空间不连续,导致迭代器不能像之前string、vector那样简单的设计为指针,而是设计为一个类(进行封装),以此完成*、->、++、–-等一系列操作。
2.1 成员变量与默认构造函数
注意事项:
仍然使用struct,标明公有属性成员变量是一个结点的指针
template <class T,class Ref,class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;//为了方便使用节点类,我们给它重命名一下
typedef ListIterator<T, Ref, Ptr>self;//原理同上
ListIterator(Node* ptr)
:Node(ptr)
{}
Node* _Node;
};
我知道你现在很疑惑为什么模版要用到三个参数,别急,等会讲解。
这里的迭代器的实际不可谓不妙,后面你就会称赞它的
2.2 operator*
Ref operator*()
{
return _Node->_val;
}
解释:对比一下以前的代码
2.3 operator->
Ptr operator->()
{
return &_Node->val;
}
同样的的道理,这就是为什么迭代器的模板参数有三个。
这里的返回值是数据val类型的指针,分为const和非const
2.4 operator++
注意事项:
- 为了区分前置和后置,后置参数加上int(无实际意义,以示区分)
- 前置传引用返回,后置传值返回
self& operator++()
{
_Node = _Node->_last;
return _Node;
}
self operator++(int)
{
Node* tmp = _Node;
_Node = _Node->last;
return tmp;
}
2.5 operator- -
与++同理
self operator--(int)
{
Node* tmp = _Node;
_Node = _Node->_prev;
return tmp;
}
self operator--()
{
_Node = _Node->_prev;
return _Node;
}
2.6operator==
bool operator==(self& s)
{
return _Node == s._Node;
}
2.6operator!=
bool operator!=(self& s)
{
return _Node != s._Node;
}
三、list
3.1 成员变量
注意事项:
head是哨兵位
template<class T>
class List
{
public:
typedef ListNode Node;
private:
Node* _head;
};
3.2对迭代器的处理
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&,const T*> const_iterator;
- 1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
- 2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动我们现在只实现begin和end
3.2.1begin()
注意事项:
- 1.返回值类型要强制类型转换
- 2.返回的是_head的下一个,不是_head,_head是哨兵位
iterator begin()
{
return iterator(_head->_last);
}
const_iterator begin()const
{
return const_iterator(_head->_last);
}
3.2.2 end
细节:
1.返回值类型要强制类型转换
2.返回的是_head
iterator end()
{
return iterator(_head);
}
const_iterator end()const
{
return const_iterator(_head);
}
3.3 默认成员函数
3.3.1构造函数
(1)创建哨兵位,缺省参数
List(T val=T())
:_head(new Node)
{
_head->_last = _head;
_head->_prev = _head;
_head->_val = val;
}
(2)迭代器区间构造
template <class InputIterator>
List(InputIterator first, InputIterator last)
{
_head=new Node;
_head->_last = _head;
_head->_prev = _head;
_head->_val = T();
while (first != last)
{
push_back(*first);
++first;
}
}
push_back是尾插,这个我们之后再写,先挖个坑
3.3.2 析构函数
~List()
{
clear();
delete _head;
_head = nullptr;
}
clear的作用是消除除了哨兵位之外的节点。咱们后面实现
3.3.3拷贝构造函数
摩登写法:
List(List<T>& l)
{
_head(new Node);
_head->_last = _head;
_head->_prev = _head;
_head->_val = T();
List<T>tmp(l.begin(), l.end());
swap(tmp);
}
3.3.4 operator=
现代写法
注意事项:
- 传参变成传值,这样就会拷贝构造出一个临时对象
- 再使用list中的swap,交换*this和tmp的值,完成赋值重载
List<T>& operator=(List<T>l)
{
swap(l);
return*this;
}
3.4 修改
3.4.1 insert
注意事项:
迭代器不会失效
void intsert(iterator pos,T val)//原本在这个位置的节点向后移
{
Node* cur = pos._Node;
Node* prev = cur->_prev;
Node* NewNode = new Node(val);
NewNode->_prev = prev;
prev->_last = NewNode;
NewNode->_last = cur;
cur->_prev = NewNode;
}
3.4.2 push_front
头插
void push_front(T val)
{
Node* NewNode = new Node(val);
Node* last = _head->_last;
NewNode->_prev = _head;
NewNode->_last = last;
last->_prev = NewNode;
_head->_last = NewNode;
}
3.4.3 push_back
尾插
void push_back(T val)
{
Node* NewNode = new Node(val);
Node* tail = _head->_prev;
tail->_last = NewNode;
NewNode->_prev = tail;
NewNode->_last = _head;
_head->_prev = NewNode;
}
3.4.4 erase
指定位置删除
注意事项:
- assert断言,防止删除哨兵位
- 返回删除节点的下一位,防止迭代器失效
iterator erase(iterator pos)
{
assert(pos!=end());
Node* cur = pos._Node;
Node* prev = cur->_prev;
Node* last = cur->_last;
prev->_last =last;
last->_prev = prev;
delete cur;
return iterator(last);
}
3.4.5 pop_front
void pop_front()
{
erase(begin());
}
3.4.6 pop_back
void pop_back()
{
erase(--end());
}
3.4.7 clear
清除所有结点(除哨兵位以外)
void clear()
{
Node* New = _head->_last;
while (New != _head)
{
Node* News = New->_last;
delete New;
New = News;
}
}
3.4.8 swap
交换两个list类的值
注意事项:
使用std库中的swap函数,要带有std::,不然会被认成你所写的swap,进而导致无限递归。
void swap(List<T>&l)
{
std::swap(_head,l._head);
}
3.4.9list的迭代器失效
4. list与vector的对比
我的模拟源码仅供参考,第一次写,有错误欢迎在评论区指出
#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<assert.h>
using namespace std;
template<class T>
struct ListNode//驼峰式命名
{
ListNode<T>* _prev;
ListNode<T>* _last;
T _val;
ListNode(const T &val=T())//缺省参数
:_prev(nullptr)
,_last(nullptr)
,_val(val)
{}
};
template <class T,class Ref,class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;//为了方便使用节点类,我们给它重命名一下
typedef ListIterator<T, Ref, Ptr>self;//原理同上
ListIterator(Node* ptr)
:_Node(ptr)
{}
Ref operator*()
{
return _Node->_val;
}
Ptr operator->()
{
return &_Node->val;
}
self& operator++()
{
_Node = _Node->_last;
return _Node;
}
self operator++(int)
{
Node* tmp = _Node;
_Node = _Node->last;
return tmp;
}
self operator--(int)
{
Node* tmp = _Node;
_Node = _Node->_prev;
return tmp;
}
self operator--()
{
_Node = _Node->_prev;
return _Node;
}
bool operator==(self& s)
{
return _Node == s._Node;
}
bool operator!=(self& s)
{
return _Node != s._Node;
}
Node* _Node;
};
template<class T>
class List
{
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&,const T*> const_iterator;
typedef ListNode<T> Node;
iterator begin()
{
return iterator(_head->_last);
}
const_iterator begin()const
{
return const_iterator(_head->_last);
}
iterator end()
{
return iterator(_head);
}
const_iterator end()const
{
return const_iterator(_head);
}
List(T val=T())
:_head(new Node)
{
_head->_last = _head;
_head->_prev = _head;
_head->_val = val;
}
template <class InputIterator>
List(InputIterator first, InputIterator last)
{
_head=new Node;
_head->_last = _head;
_head->_prev = _head;
_head->_val = T();
while (first != last)
{
push_back(*first);
++first;
}
}
~List()
{
clear();
delete _head;
_head = nullptr;
}
List(List<T>& l)
{
_head(new Node);
_head->_last = _head;
_head->_prev = _head;
_head->_val = T();
List<T>tmp(l.begin(), l.end());
swap(tmp);
}
List<T>& operator=(List<T>l)
{
swap(l);
return*this;
}
void intsert(iterator pos,T val)//原本在这个位置的节点向后移
{
Node* cur = pos._Node;
Node* prev = cur->_prev;
Node* NewNode = new Node(val);
NewNode->_prev = prev;
prev->_last = NewNode;
NewNode->_last = cur;
cur->_prev = NewNode;
}
void push_front(T val)
{
Node* NewNode = new Node(val);
Node* last = _head->_last;
NewNode->_prev = _head;
NewNode->_last = last;
last->_prev = NewNode;
_head->_last = NewNode;
}
void push_back(T val)
{
Node* NewNode = new Node(val);
Node* tail = _head->_prev;
tail->_last = NewNode;
NewNode->_prev = tail;
NewNode->_last = _head;
_head->_prev = NewNode;
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._Node;
Node* prev = cur->_prev;
Node* last = cur->_last;
prev->_last = last;
last->_prev = prev;
delete cur;
return iterator(last);
}
void pop_front()
{
erase(begin());
}
void pop_back()
{
erase(--end());
}
void clear()
{
Node* New = _head->_last;
while (New != _head)
{
Node* News = New->_last;
delete New;
New = News;
}
}
void swap(List<T>&l)
{
std::swap(_head,l._head);
}
private:
Node* _head;
};
总结
学习完list类,与之前相比,最大的收获就是迭代器的设计,同时也对多参数模板有了更深一步的了解,虽然过程艰辛,但是,List,over!
相信下一个难题也会被攻克
觉得有用的话,就点个赞支持一下吧😘😘😘