目录
一、list 的结构剖析
list 作为 C++ STL 中的一员,其底层结构是带头双向循环链表。这种结构的设计,赋予了 list 独特的性能优势。与普通的双向链表不同,带头双向循环链表有一个头节点,它不存储实际的数据,主要作用是简化链表的操作,使得链表在进行插入、删除等操作时,无需对空链表等特殊情况进行额外的处理。同时,链表的双向特性,使得我们可以从任意一个节点出发,向前或向后遍历链表;而循环特性,则保证了链表的最后一个节点的后继指针指向头节点,头节点的前驱指针指向最后一个节点,形成一个完整的环。
在带头双向循环链表中,每个节点由数据域和指针域组成。数据域用于存储实际的数据,它可以是任意类型,这使得 list 具有很强的通用性;指针域则包含两个指针,一个指向前驱节点(prev),另一个指向后继节点(next)。通过这两个指针,节点之间形成了双向的链接关系,使得链表的遍历和操作变得更加灵活。下面是节点实现代码:
template <class T>
class ListNode {
ListNode* _prev;// 前驱指针,指向上一个节点
ListNode* _next;// 后继指针,指向下一个节点
T _val;// 数据域,存储数据
ListNode(const T& val=T())
: _prev(nullptr)
, _next(nullptr)
, _val(val)
{ }
};
二、迭代器的实现
1.迭代器类的设计与实现
1.1模板参数:
在设计 list 迭代器类的时候,通常会用到三个模板参数,分别是数据类型 T、引用类型 Ref 和指针类型 Ptr ,这么做是为了区分普通迭代器和 const 迭代器。在普通迭代器里,Ref 是 T &,Ptr 是 T* ,可以对元素进行读写操作;在 const 迭代器里,Ref 是 const T &,Ptr 是 const T *,只能读取元素。通过这种方式,就能在同一个迭代器类模板里实现两种迭代器的功能,提高代码的复用性,让代码能被更高效地使用。
1.2成员变量与函数:
迭代器类最关键的成员变量是指向 list 节点的指针,凭借它迭代器可在链表节点中自由访问和操作节点。构造函数则根据传入的节点指针初始化迭代器,使其能准确指向正确节点。
1.3 运算符重载:
为了使迭代器能够像指针一样使用,我们需要对一些常用的运算符进行重载,包括解引用运算*、箭头运算符->、自增运算符++、自减运算符--、比较运算符!=和==等。
template<class T,class Ref,class Ptr>
struct List_iterator {
typedef ListNode<T> Node;
typedef List_iterator<T, Ref, Ptr> self;
Node* _node;
List_iterator(Node* node)
:_node(node)
{ }
Ref operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &(_node->val);
}
// 前置自增
self operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
tmp._node =tmp._node->_next;
return tmp;
}
self operator--()
{
_node = _node->_prev;
return *this;
}
self operator++(int)
{
self tmp(*this);
tmp._node = tmp._node->_prev;
return tmp;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
};
2.const 迭代器的实现
const 迭代器和普通迭代器最大的不同就是,const 迭代器只能读取容器里的元素,没办法修改元素的值。在实现 const 迭代器的时候,我们可以用模板参数来区分普通迭代器和 const 迭代器。当使用 const 迭代器时,把模板参数 Ref 和 Ptr 分别设为 const T & 和 const T* ,这样在解引用和访问成员的时候,返回的就是 const 类型的引用和指针,也就保证了元素是常量,不能被修改。
在 list 类中,通常会提供两个版本的begin和end函数,一个返回普通迭代器,另一个返回 const 迭代器。这样,当我们使用 const 对象时,就会调用返回 const 迭代器的函数,确保在遍历过程中不会意外修改容器中的元素。代码如下:
template<class T>
class list {
public:
typedef ListNode<T> Node;
typedef List_iterator<T, T&, T*> iterator;
typedef List_iterator<T, cosnt T&, const T*> const_iterator;
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head->_prev);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head->_prev);
}
private:
Node* _head;
};
三、list 类的实现
1.基本框架与成员变量
#pragma once
#include<iostream>
using namespace std;
namespace my_list{
template <class T>
struct ListNode {
ListNode* _prev;
ListNode* _next;
T _val;
ListNode(const T& val=T())
: _prev(nullptr)
, _next(nullptr)
, _val(val)
{ }
};
template<class T,class Ref,class Ptr>
struct List_iterator {
typedef ListNode<T> Node;
typedef List_iterator<T, Ref, Ptr> self;
Node* _node;
List_iterator(Node* node)
:_node(node)
{ }
Ref operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &(_node->_val);
}
// 前置自增
self operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
tmp._node =tmp._node->_next;
return tmp;
}
self operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
tmp._node = tmp._node->_prev;
return tmp;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
};
template<class T>
struct list {
typedef ListNode<T> Node;
typedef List_iterator<T, T&, T*> iterator;
typedef List_iterator<T, const T&, const T*> const_iterator;
private:
Node* _head;
public:
};
}
我们将双向链表的实现放在一个自定义命名空间my_list中,以避免命名冲突。整个代码主要包含两个模板结构体:ListNode和List_iterator,以及一个模板类list。ListNode和List_iterator在上面已经实现好了,接下来我们将实现各个成员函数。
2.构造函数
1.无参构造
list()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
2.拷贝构造
list(const list<T>& l)
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
for (const_iterator it = l.begin(); it != l.end(); ++it)
{
push_back(*it);
}
}
拷贝构造函数,用于创建一个新对象,该对象是另一个同类型对象的副本。拷贝构造函数的参数通常是一个对同类对象的常量引用。里面用到的push_back()函数后面我们会实现。
3.析构函数和clear函数
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
Node* cur = _head->_next;
while (cur != _head)
{
Node* tmp = cur->_next;
delete cur;
cur = tmp;
}
cur = nullptr;
_head->_next = _head;
_head->_prev = _head;
}
这里我们通过调用 clear() 函数清空链表中的所有节点,然后释放头节点的内存,并将头指针置为 nullptr,实现析构函数。而clear()主要用于清空链表中的所有节点,只保留头节点。
4.插入与删除
1.插入函数
iterator insert(iterator pos, const T& val)
{
Node* cur = new Node(val);
Node* temp = pos._node;
Node* next = pos._node->_next;
temp->_next = cur;
cur->_prev = temp;
cur->_next = next;
next->_prev = cur;
return iterator(cur);
}
我们在这里用temp存取要插入位置的节点和用next存取它下一个节点,用cur动态分配一个空间存val,让temp后驱指针指向cur,cur的前驱指针指向temp,cur的后驱指针指向next,next的前驱节点指向cur,这样我们就完成了在任意位置的插入。
2.头插与尾插
void push_back(const T& val)
{
iterator it = --end();
insert(it, val);
}
void push_front(const T& val)
{
iterator it = begin();
insert(it, val);
}
头插函数,我们先获取指向链表第一个节点的迭代器,然后调用 insert() 函数在该位置插入新节点。尾插也一样,我们先获取最最后一个节点的指针,然后插入。
3.删除函数
iterator erase(iterator pos)
{
Node* cur = pos._node->_prev;
Node* next = pos._node->_next;
cur->_next = next;
next->_prev = cur;
delete pos._node;
return iterator(next);
}
这里,我们用cur存取要删位置的前一个节点,next存取要删的节点的下一个节点。然后让cur的后驱指针指向next,next的前驱指针指向cur,最后释放要删节点的空间,这样就完成了删除。
4.头删和尾删
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
这里我们直接调用删除函数,分别传入最后一个节点的位置,和第一个节点的位置即可完成尾删和头删。
5.访问函数
T& front()
{
return _head->_next->_val;
}
T& back()
{
return _head->_prev->_val;
}
我们直接通过头节点便可访问最后一个元素和第一个元素。
6.其他函数
1.empty():判断链表是否为空。
bool empty()
{
return (_head->_next == _head);
}
2.size():返回链表的元素个数。
size_t size()
{
size_t count = 0;
for (const_iterator it = begin(); it != end(); ++it)
{
count++;
}
return count;
}
四、完整代码
#pragma once
#include<iostream>
using namespace std;
namespace my_list {
template <class T>
struct ListNode {
ListNode* _prev;
ListNode* _next;
T _val;
ListNode(const T& val = T())
: _prev(nullptr)
, _next(nullptr)
, _val(val)
{
}
};
template<class T, class Ref, class Ptr>
struct List_iterator {
typedef ListNode<T> Node;
typedef List_iterator<T, Ref, Ptr> self;
Node* _node;
List_iterator(Node* node)
:_node(node)
{
}
Ref operator*()
{
return _node->_val;
}
Ptr operator->()
{
return &(_node->_val);
}
// 前置自增
self operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
tmp._node = tmp._node->_next;
return tmp;
}
self operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
tmp._node = tmp._node->_prev;
return tmp;
}
bool operator!=(const self& s)
{
return _node != s._node;
}
bool operator==(const self& s)
{
return _node == s._node;
}
};
template<class T>
struct list {
typedef ListNode<T> Node;
typedef List_iterator<T, T&, T*> iterator;
typedef List_iterator<T, const T&, const T*> const_iterator;
private:
Node* _head;
public:
iterator begin()
{
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator begin()const
{
return const_iterator(_head->_next);
}
const_iterator end()const
{
return const_iterator(_head);
}
list()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
}
list(const list<T>& l)
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
for (const_iterator it = l.begin(); it != l.end(); ++it)
{
push_back(*it);
}
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
Node* cur = _head->_next;
while (cur != _head)
{
Node* tmp = cur->_next;
delete cur;
cur = tmp;
}
cur = nullptr;
_head->_next = _head;
_head->_prev = _head;
}
iterator insert(iterator pos, const T& val)
{
Node* cur = new Node(val);
Node* temp = pos._node;
Node* next = pos._node->_next;
temp->_next = cur;
cur->_prev = temp;
cur->_next = next;
next->_prev = cur;
return iterator(cur);
}
void push_back(const T& val)
{
iterator it = --end();
insert(it, val);
}
void push_front(const T& val)
{
iterator it = begin();
insert(it, val);
}
iterator erase(iterator pos)
{
Node* cur = pos._node->_prev;
Node* next = pos._node->_next;
cur->_next = next;
next->_prev = cur;
delete pos._node;
return iterator(next);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
T& front()
{
return _head->_next->_val;
}
T& back()
{
return _head->_prev->_val;
}
bool empty()
{
return (_head->_next == _head);
}
size_t size()
{
size_t count = 0;
for (const_iterator it = begin(); it != end(); ++it)
{
count++;
}
return count;
}
};
}
好了,今天的分享就到这里了,我们下次再见。