目录
一. list 介绍
1. 文章内容介绍
二. list 模拟实现
1. 成员变量
2. 构造函数
3. push_back
4. iterator
4.1 迭代器介绍
4.2 iterator
4.3 const_iterator
4.4 operator->
5. insert
6. size
7. erase
8. push_front
9. pop_back
10. pop_front
11. 拷贝构造
12. 赋值重载
13. swap
14. claer
15. ~list
在C++的STL(Standard Template Library)中,list是一种双向链表容器,可用于存储和操作元素的集合。以下是关于list容器的介绍:
总体上,list是一个灵活、高效、易于使用的容器,用于存储和操作元素集合。在需要频繁插入和删除元素的场景中,list往往是一个很好的选择。
上面只是对 list 的一个基本介绍~
这篇文章的主要内容并不是对 list 的模拟实现,这篇文章的主要内容是对 iterator (迭代器) 的介绍,我们前面见过的迭代器基本都是原生指针,所以我们很简单的就完成了对 iterator 的编写,但是并不是所有的 iterator 都是原生指针,我们更多的 iterator 是我们自己封装的,所有今天我们来学习一下自己封装的 iterator
我们的 list 里面存的是一个一个的节点,这个节点里面需要一个存值的变量,和两个指向相同类型结构的指针指向前一个和后一个节点,所以我们先看一下我们的节点,然后我们这个类里面还需要一个构造函数,方便创建节点的时候初始化
template
struct _list_node
{
T _val;
_list_node* _next;
_list_node* _prev;
_list_node(const T& val = T())
:_val(val)
,_next(nullptr)
,_prev(nullptr)
{}
};
我们现在在看一下我们 list 的成员变量,里面有一个 _list_node 的指针,指向该链表的头节点(哨兵位),还有一个 size 方便我们记录我们的数据个数,下面我们看一下我们的 list 的成员变量
template
class list
{
typedef _list_node Node;
public:
private:
Node* _head;
size_t _size;
};
我们将上面的 _list_node 的节点 typedef 为 Node 节点,模板这里类名并不代表变量名,在模板这里类名+模板才是变量名,这里我们在模板那里就说过,所以我们这里就不多强调了,有需要的可以看一下模板那里
构造函数这里我们要写的是一个默认构造函数,所以就直接看实现吧!
void emptyinit()
{
_head = new Node;
_size = 0;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
emptyinit();
}
我们将让构造函数调用初始化,这里是因为我们后面也会用到这个,所以为例不让代码看起来很冗余,所以我们就这样写。这里在强调一下,我们的 _head 是一个哨兵位,里面什么都不用存储,我们刚开始的 prev 和 next 都指向 _head 自己
我们这里就直接说 push_back 这里是因为我向让代码可以快速的测试起来,让我们来看迭代器的是实现,这里在说明一下,我们今天的 list 的其他函数并不是重点,我们这里的重点是迭代器
我们的尾插就是插入到 end 的前面,我们想一下,我们的 begin是哪一个?我们的 begin就是第一个值,也就是我们的 _head 的下一个位置,而我们的 end 就是最后一个值的后一个位置,也就是 _head 的位置,所以我们这里的尾插就是插入带 end 前面,也就是 _head 的前面。
void push_back(const T& val)
{
Node* tail = _head->_prev;
Node* newnode = new Node(val);
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
++_size;
}
// 其实我们这里是可以复用 insert 的,但是我们的 insert
//是需要用 iterator 做参数的,所以我们就先自己写一个 push_back
//但是这里也会把复用的版本贴出来
void push_back(const T& val)
{
insert(end(), val);
}
我们的 push_back 已经写完了,那么我们看是说我们的 iterator 我们这里就可以尾插一些数据,然后使用迭代器遍历,这个可以下去自己测试一下,这里我就不说了
iterator 是我们 STL里面通用的,这里先简单的介绍一下迭代器,我们的迭代器实际上又可以分为三类
而我们的 list 就是双向迭代器,这里介绍一下着三个迭代器的不同
单项迭代器:就是迭代器只可以++操作
双向迭代器:就是既可以++操作,也可以--操作
随机迭代器:就是可以++,--,+,- ... 等操作
而我们的单项迭代器就是基类其他迭代器就是子类
我们的 list 是和 vector 和 string 是不同的,我们的 vector 和 string 底层是连续的空间,而我们的 list 不是连续的空间,所以我们的 list 的迭代器不可以是原生的指针,我们的 list 的迭代器只能是节点的指针,但是我们又不能只是节点的指针,因为我们的 iterator 需要进行 ++、-- 、*、==、
!=、 甚至是 -> 等操作...所以我们的 iterator 不能只是 节点的指针,我们还需要对他进行各种运算符的重载,所以我们的 list 的迭代器需要是一个类,然后我们对该类进行运算符重载,那么先看一个不怎么完善的迭代器。
template
struct __list_iterator
{
typedef _list_node Node;
Node* _node;
__list_iterator(Node* node)
:_node(node)
{ }
_list_iterator& operator++()
{
_node = _node->_next;
return *this;
}
_list_iterator& operator++(int)
{
Node* prev = _node;
_node = _node->_next;
return *this;
}
_list_iterator& operator--()
{
_node = _node->_prev;
return *this;
}
_list_iterator& operator--(int)
{
Node* prev = _node;
_node = _node->_prev;
return *this;
}
bool operator!=(const _list_iterator& it)
{
return _node != it._node;
}
bool operator==(const _list_iterator& it)
{
return _node == it._node;
}
T* operator*()
{
return _node->_val;
}
};
这就是我们的 iterator 的对象
然后我们的 list 对其进行 typedef __list_iterator
typedef __list_iterator iterator;
所以我们平时就可以向下面这样使用
vector v1;
std::vector::iterator it = v1.begin();
所以实际上 iterator 是我们的类里面的一个对象,我们设计为 struct 是为了更方便访问里面的函数和成员变量,当然你自己也可以设计为 class 然后设置访问限定符都是可以的,但是我的stl 源码刨析里面就是 struct 我们这里是跟着stl里面来的
这里现在有一个问题,如果我们不想让 iterator 可以修改里面的值呢?
所以这是后我们就需要一个 const 的 iterator 那么我们应该怎么做呢?
我们看一下下面这样子可以不可以
typedef const __list_iterator const_iterator;
这里回答我们这里是不可以的,为什么呢?因为我们这样子的话,我们的 iterator的对象是不可以被修改的,而我们是需要对我们的 iterator 进行++ 等操作的,所以我们并不是期望我们不修改 iterator 的对象,我们期望的是我们不会修改 *iterator ,所以我们是要让 *iterator 不会被修改,所以我们可以将 operator* 的返回值变为 const 的,那么我们应该怎么做呢?
解决方案1:
我们可以在写一个 __list_const_iterator 的对象,然后我这个对象和 __list_iterator 的对象的区别就是 operator* 的返回值是不同的,一个是 可以修改的,另一个是 const 的
template
struct __list_const_iterator
{
typedef _list_node Node;
Node* _node;
__list_const_iterator(Node* node)
:_node(node)
{ }
__list_const_iterator& operator++()
{
_node = _node->_next;
return *this;
}
__list_const_iterator& operator++(int)
{
Node* prev = _node;
_node = _node->_next;
return *this;
}
bool operator!=(const __list_const_iterator& it)
{
return _node != it._node;
}
bool operator==(const __list_const_iterator& it)
{
return _node == it._node;
}
const T& operator*()
{
return _node->_val;
}
};
区别只有最下面的 operator* 函数,但是这个解决方案并不是我们所满意的,这样子代码就态冗余了,所以我们还有一个方法
解决方案2:
既然我们的差别就只有 operator* 的返回值是不同的,那么我们可不可以让他模板生成不同的返回值呢?可以的,所以我们这时候我们可以在增加一个类模板参数,我们将普通的 iterator 传入的是 T& 而我们的 const_iterator 的传入 const T& 我们下面可以看一下,但是这时候我们传入两个米板参数,我们要写的类型的名太长了,我们可以对该类型进行 typedef
template // 增加一个模板参数
struct __list_iterator
{
typedef _list_node Node;
typedef __list_iterator self; // 类型名太长,我们可以第其进行 typeddef
Node* _node;
__list_iterator(Node* node)
:_node(node)
{ }
self& operator++() // 我们的 operator++/--
// 返回的还是迭代器,所以我们返回整个类型就可以了
{
_node = _node->_next;
return *this;
}
self& operator++(int)
{
Node* prev = _node;
_node = _node->_next;
return *this;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
self& operator--(int)
{
Node* prev = _node;
_node = _node->_prev;
return *this;
}
bool operator!=(const self& it)
{
return _node != it._node;
}
bool operator==(const self& it)
{
return _node == it._node;
}
Ref operator*() // 我们的 operator* 返回的是一个 T类型的引用/const 引用
// 所以我们将 Ref 传为 T& 就是 普通迭代器,传为 const T&
// 就是 const 的迭代器
{
return _node->_val;
}
};
我们下面看一下 list 里面如何typedef
typedef __list_iterator iterator;
typedef __list_iterator const_iterator;
实际上我们的指针还是需要进行 -> 操作的,假设我们 list 里面存的是一个结构体,那么我们要是光 解引用的话,那么我们拿到的是 结构体类型的对象,如果我们想要访问里面的值,我们还需要解引用后进行点(.)操作 例如:(*it). X
所以我们现在在看一下我们的 operator->
实际上我们的 operator-> 和 operator* 是一样的,假设我们现在不希望被修改,那么我们
operator-> 的返回值是一个不可以被修改的指针,也就是 const T* 的保证我们的值不会被修改,所以我们的解决方案还是前面解决 operator* 的那两个方案,这里我们就不演示第一个方案了,我们直接说第二个。
我们同样是多增加一个类模板,然后普通的 iterator 传入 T* ,const_iterator 传入的是 const T*,下面我们就来看解决方案
template
struct __list_iterator
{
typedef _list_node Node;
typedef __list_iterator self;
Node* _node;
__list_iterator(Node* node)
:_node(node)
{ }
self& operator++()
{
_node = _node->_next;
return *this;
}
self& operator++(int)
{
Node* prev = _node;
_node = _node->_next;
return *this;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
self& operator--(int)
{
Node* prev = _node;
_node = _node->_prev;
return *this;
}
bool operator!=(const self& it)
{
return _node != it._node;
}
bool operator==(const self& it)
{
return _node == it._node;
}
Ref operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &(_node->_val);
}
};
我们在看一下 list 里面如何 typedef
typedef __list_iterator iterator;
typedef __list_iterator const_iterator;
我们的 iterator 对象已经编写完了,但是我们还没又写我们的 list 里面的 begin()等函数
我们下面来看一下,但是这里我们也不多说,很容易明白的!
typedef __list_iterator iterator;
typedef __list_iterator const_iterator;
iterator begin()
{
return _head->_next;
}
iterator end()
{
return _head;
}
const_iterator cbegin() const
{
return _head->_next;
}
const_iterator cend() const
{
return _head;
}
迭代器就讲完了,这里我们还更深入了解了模板。
其实今天的重点就结束了,我们后面的 insert 那些也都是我们之前说过的,但是我们还是继续看一下。
insert 就是给一个 iterator 位置,然后在给一个 val 然后就是插入到 pos 位置之前,下面直接看代码
void insert(iterator pos, const T& val)
{
iterator prev = pos._node->_prev;
Node* newnode = new Node(val);
prev._node->_next = newnode;
newnode->_prev = prev._node;
newnode->_next = pos._node;
pos._node->_prev = newnode;
++_size;
}
为了我们这个模拟实现的完整性,我们还是在说一下 size 这个函数
size_t size()
{
return _size;
}
删除给定位置的元素,但是这里的位置是 iterator
void erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
prev->_next = next;
next->_prev = prev;
delete pos._node;
--_size;
}
其实这里还有一个 erase 是删除一整段连续的节点,但是这里我们就比实现了,我们最简单的就是复用 eraser 上面这个函数,或者就是自己一个一个删除,这个相对麻烦一点。
从这里开始我们下面就都是复用 Insert 和 erase 了
void push_front(const T& val)
{
insert(begin(), val);
}
pop_back 是尾删
void pop_back()
{
erase(--end());
}
pop_front 是头删
void pop_front()
{
erase(begin());
}
拷贝构造,这里也需要和 vetor 是一样的,为了防止 list 里面的值需要深拷贝所以这里我们不能直接memcpy 里面的数据,我们可以一个一个插入
list(const list& l)
{
emptyinit();
const_iterator it = l.cbegin();
while (it != l.cend())
{
push_back(*it);
++it;
}
}
这里我们的复制重载使用现代写法,顺便我们写一个专属于 list 的 swap 函数,swap 函数我们写在赋值重载后面。
list& operator=(list l)
{
swap(l);
return *this;
}
交换函数,这里是和 std::swap 是不一样的,我们的 swap 是创建一个 对象然后进行交换,我们自己实现的是值交换里面的成员变量
void swap(list& l)
{
std::swap(_head, l._head);
std::swap(_size, l._size);
}
clear 函数是将我们的 list 对象里面的值全部删掉,但是要分清楚我们的 clear和 ~list ,我们的 clear 只是删掉里面的值,而我们的 ~list 是不仅删掉里面的值,我们还要对头节点也进行释放,所以他们还是有区别的
void clear()
{
while (_size)
{
pop_back();
}
}
析构函数
~list()
{
clear();
delete _head;
_size = 0;
}
想要源码的可以去我的 戳-> "码云"
那么今天就到这里~