目录
一、认识list底层结构
二、list的构造类函数
三、迭代器
四、数据的访问
五、容量相关的函数
六、关于数据的增删查改操作
前言
要模拟实现list,必须要熟悉list的底层结构以及其接口的含义,在上一篇我们仔细讲解了list的常见接口的使用及其含义,这篇我们就直接进入主题
一、list底层结构
list底层实现的是带头双向循环链表,list底层实现需要三个类,分别是链表的结点类,链表的迭代器类,链表本身
// List的节点类
template<class T>
struct ListNode
{
ListNode<T>* _prev;
ListNode<T>* _next;
T _data;
ListNode(const T& data=T())
:_prev(nullptr)
,_next(nullptr)
,_data(data)
{}
};
//List的迭代器类
template<class T,class Ref,class Ptr>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T,Ref,Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node)
{}
Ref operator*();
Ptr operator->();
Self& operator++();
Self operator++(int);
Self& operator--();
Self operator--(int);
bool operator!=(const Self& l);
bool operator==(const Self& l);
};
//list类
template<class T>
class list
{
typedef ListNode<T> Node;
Node* _head;//哨兵位的头节点
size_t _size;//链表数据个数
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T&> const_iterator;
}
给大家讲一下这三个类的关联,链表类是主类,链表的结点用一个类封装起来,构造起来更方便,放在主类里面不方便且冗余,由于链表的物理结构不连续,所以不能像vector和string那样单纯的用原生指针来实现,我们可以把结点类再次进行封装,封装成迭代器,让它能很好的指向链表的结点,利用它的结构优势来重载运算符遍历这个链表
二、初始化list的构造函数
1、默认构造
list();
void empty_init()
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
_size = 0;
}
list()
{
empty_init();
//这里没有直接写默认构造,而是通过empty_init来实现,因为后面我们要多次用到这个函数来初始化哨兵位的头节点,写在默认构造里面就不方便其他地方的调用了
}
2、用n个val来构造链表
list(size_t n, const T& val = T());
list(size_t n, const T& val = T())
{
empty_init();//要记得初始化哨兵位的头节点
for (size_t i = 0; i < n; i++)
{
push_back(val);
}
_size = n;
}
3、迭代器区间构造
template <class InputIterator> list(InputIteratorfirst, InputIterator last);
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
first++;
}
}
特别注意:
如果你写了迭代器区间构造,就一定要重载上面的list(size_t n, const T& val = T())
,如果你不重载的话,他会报一个错误 非法间接寻址,博主我深受其害🤡,再重载一个这个函数list(int n, const T& val = T());
就可以 其实就是改一个类型的事
为什么要重载一下?
list<int> li(10,1);
你传的参数都是int类型,两个类型是一样的,而迭代器区间构造的类型是一样的,最符合,这就导致你没走你
想走的那个构造函数,你想走的那个函数list(size_t n, const T& val = T())在这个案例中是size_t和int类型,虽然
也可以走这个函数,但是编译器觉得迭代器区间构造更好,所以你得重载一个int,int类型的,这样编译器就会
走你重载的函数了
4、拷贝构造
(用来初始化一个正在创建的对象)
list(const list<T>& li);
list(const list<T>& li)
{
empty_init();
for (auto& e : li)
{
push_back(e);
}
_size = li._size;
}
5、赋值构造
(两个已经存在的对象,一个赋值给另一个)
list<T>& operator=(const list<T> li)
void swap(list<T>& li)
{
std::swap(_head, li._head);
std::swap(_size, li._size);
}
list<T>& operator=(list<T> li)
{
swap(li);
return *this;
}
6、析构函数
~list();
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
_size = 0;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
三、迭代器
讲到list迭代器这里,我们先把迭代器类完善一下
template<class T,class Ref,class Ptr>//Ref表示引用,Ptr表示指针
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T,Ref,Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node)
{}
//*it
Ref operator*()
{
return _node->_data;
}
//it->
Ptr operator->()
{
return &_node->_data;
}
//++i
Self& operator++()
{
_node = _node->_next;
return *this;//返回的变量出了作用域不会被销毁用引用返回更合适
}
//i++
Self operator++(int)
{
Self temp(*this);
_node = _node->_next;
return temp;//返回的变量出了作用域会被销毁用传值返回更合适
}
Self& operator--()
{
_node = _node->_prev;
return *this;
}
Self operator--(int)
{
Self temp(*this);
_node = _node->_prev;
return temp;
}
Ref operator*()
{
return _node->_data;
}
bool operator!=(const Self& s)
{
return _node != s._node;//这里是用迭代器的地址去比较
}
bool operator==(const Self& s)
{
return _node == s._node;
}
};
这个类里面的接口,我们重点讲解一下这个Ptr operator->();
如下图:
iterator begin();
iterator end();
const_iterator begin();
const_iterator end();
template<class T>
class list
{
typedef ListNode<T> Node;
Node* _head;//哨兵位的头节点
size_t _size;//链表数据个数
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T&> const_iterator;
iterator begin()
{
//return iterator(_head->_next);匿名对象
return _head->_next;//单参数的构造函数隐式类型转换
}
iterator end()
{
return _head;
}
const_iterator begin()
{
return _head->_next;
}
const_iterator end()
{
return _head;
}
}
四、数据的访问
1、front 访问头结点
T& front();
T& front()
{
return _head->_next->_data;
}
const T& front()const;
const T& front() const
{
return _head->_next->_data;
}
2、back 访问尾节点
T& back();
T& back()
{
return _head->_prev->_data;
}
const T& back()const;
const T& back() const
{
return _head->_prev->_data;
}
五、容量相关的函数
size 有效数据个数
size_t size()const;
size_t size() const
{
return _size;
}
empty 判断是否为空
bool empty()const;
bool empty()
{
return _size == 0;
}
六、关于数据的增删查改操作
push_back 尾插数据
void push_back(const T& val) ;
void push_back(const T& val)
{
Node* newnode = new Node(val);
Node* tail = _head->_prev;
tail->_next = newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
_size++;
}
pop_back 尾删数据
void pop_back() ;
void pop_back()
{
Node* tail = _head->_prev;
Node* prev = tail->_prev;
delete tail;
prev->_next = _head;
_head->_prev = prev;
_size--;
}
push_front 头插数据
void push_front(const T& val);
void push_front(const T& val)
{
Node* newnode = new Node(val);
Node* next = _head->_next;
//_head newnode next
newnode->_next = next;
next->_prev = newnode;
newnode->_prev = _head;
_head->_next = newnode;
_size++;
}
pop_front 头删数据
pop_front();
void pop_front()
{
Node* del = _head->_next;
Node* next = del->_next;
delete del;
_head->_next = next;
next->_prev = _head;
_size--;
}
insert 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val);
void insert(iterator pos, const T& val)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(val);
//prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
_size++;
}
erase 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos);
iterator erase(iterator pos)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
delete cur;
prev->_next = next;
next->_prev = prev;
_size--;
return next;
}
clear 清除结点
void clear();
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
_size = 0;
}
swap 交换两个链表
void swap(list<T>& l);
void swap(list<T>& li)
{
std::swap(_head, li._head);
std::swap(_size, li._size);
}
list篇到这里就结束了🎉,欢迎大家来指教我的下一篇stack和queue