目录
一. 初步了解
1.构造、析构、赋值
2.容量
3.元素访问
4.增删
二. 模拟实现
框架
push_back
迭代器
带参构造、析构、赋值
增删
反向迭代器
所有代码
说白了,就是一个双向循环带头链表,由于我们在数据结构中已经学习过链表的知识,所以在接口的使用上还是很简单的,我们就放一些cplusplus中相关结构的图片和一些代码来作简单的使用,重点依旧是放在模拟实现上
一. 初步了解
1.构造、析构、赋值
void test1()
{
list<int> l1;
list<int> l2(2, 4);
list<int> l3(l2.begin(), l2.end());
list<int> l4(l3);
list<int> l5;
l5=l2;
}
2.容量
由于链表没有容量的概念,因此也就没有reserve,但resize还是有的,归到增 里面去了
void test2()
{
list<int> l1;
list<int> l2(2, 4);
cout << l1.empty() << endl;
cout << l2.size() << endl;
}
3.元素访问
由于链表无法做到随机访问,因此与string和vector不同,没有元素访问(Element access) (硬要说也有个front和back,没啥用)
4.增删
与string、vector不同,由于链表的插入删除很便利,不需要进行元素的大量移动,所以多了几个相关的接口
void test3()
{
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.pop_back();
l1.resize(2, 4);
l1.push_front(3);
list<int>::iterator it = l1.begin();
it++;
l1.insert(it, 2, 5);
l1.pop_front();
l1.erase(l1.begin(), it);
l1.clear();
}
1 -> 1,2 -> 1 -> 1,4 -> 3,1,4 -> 3,5,5,1,4 -> 5,5,1,4 -> 1,4 ->
有点抽象,凑活看吧,不行就自己扔编译器里调试去
大概就这么些东西,当然接口远远不止这些,这里挑了点模拟实现可能用得到的说了说,反正接口就那么些东西,功能好多都不变,实在不懂看cplusplus去吧,主要还是专注模拟实现
二. 模拟实现
框架
与string与vector不同的是,在定义主体前,list还需要定义一下节点,当然,为了避免冲突,别忘了放在命名空间里
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
, _prev(nullptr)
, _data(data)
{}
};
之后便是主体的定义,由于是一个带头链表,类成员变量只需要一个头结点的指针
而为了使用起来方便一些,我们可以typedef一下节点的类型
template<class T>
class list
{
public:
typedef ListNode<T> Node;
private:
Node* _head;
};
然后,就是无参的构造函数(有参的、拷贝构造、析构、赋值这些涉及到了迭代器,后面再说)
由于是双向循环的,因此只需要让prev和next都指向自己就好了
list()
{
_head = new Node;
_head->prev = _head;
_head->next = _head;
}
push_back
其他的增删大多都涉及到迭代器,但链表里没有节点也不好讲迭代器,就先把push_back讲讲
尾插节点嘛,由于是双向循环的,尾节点也很好找
push_back(const T& x)
{
Node* newnode(x);
_head->prev->next = newnode;
newnode->prev = _head->prev;
newnode->next = _head;
_head->_prev = newnode;
}
迭代器
与模拟string与vector不同,它们的迭代器实质上就是指针,只需要typedef一下就能正常的实现解引用和++等操作,而list不同,list的迭代器若是直接使用指针,解引用得到的是节点,而不是节点中的数据,而由于链表各个节点在内存中都不是连续的,所以++操作也无法使指针移向下一个节点,一次,我们还需要定义一个迭代器的类模板,通过操作符重载来达到想要的效果
template<class T>
struct __list_iterator
{
typedef ListNode<T> Node;
Node* _node;
__list_iterator(Node* x)
:_node(x)
{}
T& operator*()
{
return _node->_data;
}
__list_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
__list_iterator<T> operator++(int)
{
__list_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
__list_iterator<T>& operator--()
{
_node = _node->_prev;
return *this;
}
__list_iterator<T> operator--(int)
{
__list_iterator<T> tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const __list_iterator<T>& it) const
{
return _node != it._node;
}
bool operator==(const __list_iterator<T>& it) const
{
return _node != it._node;
}
};
在定义好迭代器后,就可以随手把begin和end写出来了(rbegin、rend后面讲)
typedef __list_iterator<T> iterator;
iterator begin()
{
return iterator(_head->next);//第一个节点,就是头结点的下一个
}
iterator end()
{
return iterator(_head);//最后一个节点的下一个,由于是循环的,也就是头结点
}
然后我们可以测试一下
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
for (list<int>::iterator it = lt.begin();it != lt.end();it++)
{
cout << *it << " ";
}
cout << endl;
for (list<int>::iterator it = lt.begin(); it != lt.end(); it++)
{
*it *= 2;
cout << *it << " ";
}
cout << endl;
}
而在使用的过程中,涉及到了迭代器的拷贝、析构等,而迭代器中的成员变量依旧是链表中节点的地址,不需要进行深拷贝,也不需要随着迭代器的销毁而将节点也销毁掉,因此只需要使用默认构造的即可
而当出现其他情况,例如容器被const修饰了,那迭代器应该怎么办呢
例如我们定义一个print函数
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
若是想要使用const修饰的迭代器,首先我们不能只是将*重载添加一个consr版本,因为被const修饰的是容器而不是迭代器,当然,我们可以重新写一个类定义const迭代器
template<class T>
struct __const_list_iterator
{
typedef ListNode<T> Node;
Node* _node;
__const_list_iterator(Node* x)
:_node(x)
{}
const T& operator*()
{
return _node->_data;
}
__const_list_iterator<T>& operator++()
{
_node = _node->_next;
return *this;
}
__const_list_iterator<T> operator++(int)
{
__const_list_iterator<T> tmp(*this);
_node = _node->_next;
return tmp;
}
__const_list_iterator<T>& operator--()
{
_node = _node->_prev;
return *this;
}
__const_list_iterator<T> operator--(int)
{
__list_iterator<T> tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const __const_list_iterator<T>& it) const
{
return _node != it._node;
}
bool operator==(const __const_list_iterator<T>& it) const
{
return _node != it._node;
}
};
但,这样做代码会过于冗杂,因为除了名称不同以及*重载时的返回类型不同,绝大多数代码都是一样的
为了精简,我们可以利用一下模板参数
我们可以通过一个模板参数来控制解引用操作符重载的返回类型
template<class T, class Ref>
struct __list_iterator
{
typedef ListNode<T> Node;
Node* _node;
__list_iterator(Node* x)
:_node(x)
{}
Ref operator*()
{
return _node->_data;
}
__list_iterator<T, Ref>& operator++()
{
_node = _node->_next;
return *this;
}
__list_iterator<T, Ref> operator++(int)
{
__list_iterator<T, Ref> tmp(*this);
_node = _node->_next;
return tmp;
}
__list_iterator<T, Ref>& operator--()
{
_node = _node->_prev;
return *this;
}
__list_iterator<T, Ref> operator--(int)
{
__list_iterator<T, Ref> tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const __list_iterator<T, Ref>& it) const
{
return _node != it._node;
}
bool operator==(const __list_iterator<T, Ref>& it) const
{
return _node != it._node;
}
};
而在list中,就需要根据情况来判断模板参数是否应该被const修饰
typedef __list_iterator<T, T&> iterator;
typedef __list_iterator<T, const T&> const_iterator;
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
这样,就能完成const迭代器了
但上面所用到的都是基于list类型为内置类型的情况下,而若是为自定义类型呢?
再次搬出我们的日期类
struct Date
{
int _year;
int _month;
int _day;
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
};
void test_list2()
{
list<Date> lt;
lt.push_back(Date(2022, 3, 12));
lt.push_back(Date(2022, 3, 13));
lt.push_back(Date(2022, 3, 14));
list<Date>::iterator it = lt.begin();
while (it != lt.end())
{
cout << (*it)._year << "/" << (*it)._month << "/" << (*it)._day << endl;
++it;
}
cout << endl;
}
在打印日期类的年月日时,使用的是(*it)._year这种类型,而我们其实更习惯于it->_year,因此,我们可以对->操作符也进行重载
T* operator->()
{
return &_node->_data;
}
重载的确是这样的,但原式就变成了_node->_data _year这样,而我们其实想要
_node->_data-> _year这样,这也就需要写作it->->_year,为了方便统一,直接将它优化为了
it->_year这样。
while (it != lt.end())
{
cout << it->_year << "/" << it->_month << "/" << it->_day << endl;
++it;
}
而同样,->运算符重载的返回类型也有const之分,因此也可以向上面那样改变一下模板。同时,随着模板参数越来越多,我们也可以在迭代器内部将迭代器名称typedef一下
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* x)
:_node(x)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const self& it) const
{
return _node != it._node;
}
bool operator==(const self& it) const
{
return _node != it._node;
}
};
template<class T>
class list
{
public:
typedef ListNode<T> Node;
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
//。。。。。。
}
带参构造、析构、赋值
首先就是传n和val以及传迭代器的
list(int n, const T& val = T())
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
template<class InputIterator>
list(InputIterator first, InputIterator last)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
while (first != last)
{
push_back(*first);
++first;
}
}
这两种方式看起来简单,其实也是有些问题需要注意
void test_list3()
{
list<int> lt(5, 1);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
若是我们这样使用,会出现非法的间接寻址这样的报错,这是因为,在构造lt时,传入的参数是5和1,都是int类型,而size_t与int并不很匹配,但由于都是int类型,因此更加匹配传迭代器的构造,因此就导致非法间接寻址,我们无法做到将n的类型改变为size_t,只能是将n的类型改变为int。因此在这里我们也就不能向往常一样因为n不能为负数就将其类型写作size_t
之后,就是拷贝构造,依旧是涉及到传统写法和现代写法
list(const list<T>& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
for (auto e : it)
{
push_back(e);
}
}
list(const list<T>& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
list<T> tmp(lt.begin(), lt.end());
std::swap(_head, tmp._head);
}
而我们可以看到,与往常不同,在交换之前,我们先是初始化头结点,这是因为头结点是肯定存在的,若是不进行初始化,会在交换给tmp后作为随机值被销毁,发生问题。
然后就是赋值
list<T>& operator=(list<T> lt)
{
std::swap(_head, lt._head);
return *this;
}
list<T>& operator=(const list<T>& lt)
{
if (this != <)
{
clear();
for (auto e : lt)
{
push_back(e);
}
}
}
同时,在传统写法中还用到了clear,而clear中涉及的erase,先用着,后面马上讲了
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
而由于赋值是在对象定义之后,已经进行了初始化,因此就没有拷贝构造当中的问题
最后就是析构,也没有什么需要注意的
~list()
{
clear();
delete _head;
_head = nullptr;
}
增删
最主要的就两个,insert和erase
挺简单的,就是链表的插入删除,只是要注意erase删除后pos会失效
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
delete pos._node;
prev->_next = next;
next->_prev = prev;
return iterator(next);
}
而头插,尾删之类的复用就行了
void push_front(const T& x)
{
insert(begin(), x);
}
void push_back(const T& x)
{
insert(end(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
反向迭代器
首先,我们通过原码可以得知,反向迭代器其实是正向迭代器的封装,rbegin取的是end,即最后一个节点的下一个位置,而rend取得是begin,即第一个节点的位置
而这并不是我们原本理解的反向迭代器
为啥呢?我也不知道,设计的大佬就这样写的
而若是这样写,那么在取地址时取得应该就是该迭代器下一个位置的数据了
而++与--实际就是正向迭代器的--与++
而与迭代器不同的是,迭代器的第一个模板参数是List存储的数据类型,而反向迭代器需要通过正向迭代器来运行,所以第一个模板参数采用的便是正向迭代器
同时,最好是把这块命名空间单独存放在一个头文件中,至于为什么后面说
namespace szt
{
template <class Iterator, class Ref, class Ptr>
class reverse_iterator
{
public:
typedef reverse_iterator<Iterator, Ref, Ptr> self;
reverse_iterator(Iterator it)
:_it(it)
{}
Ref operator*()
{
Iterator prev = _it;
return *--prev;
}
Ptr operator->()
{
return &operator*();
}
self& operator++()
{
--_it;
return *this;
}
self& operator--()
{
++_it;
return *this;
}
bool operator!= (const self& rit) const
{
return _it != rit._it;
}
private:
Iterator _it;
};
}
而在list类中也别忘了typedef一下
typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
而我们完全可以按照正向迭代器的方式来写一个新的反向迭代器,那么我们为什么要这样写呢?
因为若是利用正向迭代器来完成反向迭代器,那么我们可以直接使用这段代码来完成其他容器的反向迭代器,只需要对原本的类做出一点小小的改动,这也就是为什么我们将它单独存储在一个头文件中的原因。
而我们通过原码可以得知,原码中的反向迭代器只使用了迭代器这一个模板参数,这是因为可以通过正向迭代器中的内嵌类型来得到反向迭代器的Ref与Ptr
具体是怎么实现的呢?
首先需要typedef一下正向迭代器中的Ref与Ptr从而获取内嵌类型
typedef Ref reference;
typedef Ptr pionter;
之后typedef一下正向迭代器中的reference和pionter
但由于reference和pionter是模板参数,在实例化之前得不到具体的类型,所以我们需要在它们之前加上typename使得在实例化之后再进行typedef。
typedef typename Iterator::reference Ref;
typedef typename Iterator::pionter Ptr;
当然我们也可以直接使用typename Iterator::reference和typename Iterator::pionter
所有代码
反向迭代器
#include<iostream>
using namespace std;
namespace bit
{
template <class Iterator>
class reverse_iterator
{
typedef reverse_iterator<Iterator> self;
typedef typename Iterator::reference Ref;
typedef typename Iterator::pointer Ptr;
public:
reverse_iterator(Iterator it)
:_it(it)
{}
Ref operator*()
{
Iterator prev = _it;
return *--prev;
}
Ptr operator->()
{
return &operator*();
}
self& operator++()
{
--_it;
return *this;
}
self& operator--()
{
++_it;
return *this;
}
bool operator!= (const self& rit) const
{
return _it != rit._it;
}
private:
Iterator _it;
};
}
list
#include"reverse_iterator.h"
namespace szt
{
struct Date
{
int _year;
int _month;
int _day;
Date(int year = 1, int month = 1, int day = 1)
:_year(year)
, _month(month)
, _day(day)
{}
};
template<class T>
struct ListNode
{
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())
:_next(nullptr)
, _prev(nullptr)
, _data(data)
{}
};
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef ListNode<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
typedef Ref reference;
typedef Ptr pionter;
Node* _node;
__list_iterator(Node* x)
:_node(x)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const self& it) const
{
return _node != it._node;
}
bool operator==(const self& it) const
{
return _node != it._node;
}
};
template<class T>
class list
{
public:
typedef ListNode<T> Node;
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;
typedef reverse_iterator<const_iterator, const T&, const T*> const_reverse_iterator;
typedef reverse_iterator<iterator, T&, T*> reverse_iterator;
list()
{
_head = new Node;
_head->_prev = _head;
_head->_next = _head;
}
list(int n, const T& val = T())
{
_head = new Node;
_head->_next = _head;
_head->_prev = _head;
for (size_t i = 0; i < n; ++i)
{
push_back(val);
}
}
template<class InputIterator>
list(InputIterator first, InputIterator last)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
while (first != last)
{
push_back(*first);
++first;
}
}
list(const list<T>& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
list<T> tmp(lt.begin(), lt.end());
std::swap(_head, tmp._head);
}
list<T>& operator=(list<T> lt)
{
std::swap(_head, lt._head);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
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);
}
void clear()
{
iterator it = begin();
while (it != end())
{
erase(it++);
}
}
void push_front(const T& x)
{
insert(begin(), x);
}
void push_back(const T& x)
{
insert(end(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* prev = pos._node->_prev;
Node* next = pos._node->_next;
delete pos._node;
prev->_next = next;
next->_prev = prev;
return iterator(next);
}
private:
Node* _head;
};
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
//*it = 10;
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
for (list<int>::iterator it = lt.begin();it != lt.end();it++)
{
cout << *it << " ";
}
cout << endl;
for (list<int>::iterator it = lt.begin(); it != lt.end(); it++)
{
*it *= 2;
cout << *it << " ";
}
cout << endl;
}
void test_list2()
{
list<Date> lt;
lt.push_back(Date(2022, 3, 12));
lt.push_back(Date(2022, 3, 13));
lt.push_back(Date(2022, 3, 14));
list<Date>::iterator it = lt.begin();
while (it != lt.end())
{
cout << it->_year << "/" << it->_month << "/" << it->_day << endl;
++it;
}
cout << endl;
}
void test_list3()
{
list<int> lt2(5, 1);
for (auto e : lt2)
{
cout << e << " ";
}
cout << endl;
}
}