看到list肯定有朋友要问,这东西数据结构不也有吗,你C++还能把它搞出花来?
不过C++还真把它修成了花。那到底是怎么修的呢?
目录
前言:
一、list介绍
list:
二、list使用
list的构造
begin和end
size和empty
list基本函数
三、list实现
四、迭代器和空间配置器
迭代器iterator
空间配置器allocator
迭代器失效问题
list是可以在常数范围内任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
list的底层是双向循环链表结构
list和forward_lsit非常相似,最主要的区别是后者是单链表,只能向前迭代,已让其更简单高效。
❤️与其他的序列式容器相比(array,vector,deque),list通常在任一位置进行插入、移除的效率更高。(毕竟是链表)
虽然但是,list和forward_lsit不支持随机访问(链表通病),不能像数组那样可以按照下标跳转。
看完这些,肯定有哥们小声嘟囔,能不能别说废话,说说它跟双向循环链表有什么区别。
好,先来说说list大概都有什么,大体是什么。
list是一个容器,我们前面学过模板和类了,这里的list正是我们通过模板类实现的一个通用容器。
我们数据结构中的链表常常是取决于你定义的数据类型,没次要新建一个链表都要重新实现一下它的构造和方法实现,没错这里的list是一个通用模板,就是说我们把它的实现方法和成员变量都都封装起来的,我们并不需要去关注它是怎么实现的,我们要做的就是学会它提供的接口如何去使用。
那它会不会出现不能满足我需求的情况?
目前来看不太会,它的底层代码相当之美丽,真的很美,感兴趣的可以自己去看看它的源码。
虽然本文把list使用放在了实现之前,但还是建议在使用接口之前先去了解一下它的底层实现,盲目的看可能会看不懂,建议是先去看看私有成员的定义,再去顶部看一下类型萃取,然后是list的六大函数(这里面使用频率最高的大概是重载),在源码中几乎没个函数每个成员都能看到封装的身影。
什么?你问我先看源码还是先看我的文章?
如果你也能先看完我的文章,那真的是
废话不多说,我们先来看一下list的使用
构造函数( (constructor) )
|
接口说明
|
list()
|
构造空的 list
|
list (size_type n, const value_type& val = value_type())
|
构造的 list 中包含 n 个值为 val 的元素
|
list (const list& x)
|
拷贝构造函数
|
list (InputIterator first, InputIterator last)
|
用 [first, last) 区间中的元素构造 list
|
示例://使用list前,记得加头文件
1.
list lt;
2.
list lt1(10, 2);
3.
listlt2(lt1);
//auto lt2(lt1);
//这里可以用auto类型推断来解放双手
4.
int arr[] = { 1,2,3,4,5,6,7,8,9 };
listlt5(arr,arr+sizeof(arr)/sizeof(int));
list::iterator it2 = find(lt5.begin(), lt5.end(), 3);
list::iterator it3 = find(lt5.begin(), lt5.end(), 7);
list l6(it2, it3);
//这里的it2、it3、是迭代器,后文会去介绍
这里可以和front、back做一个区别:
front返回list的第一个节点中值的引用,back返回list的最后一个节点中值的引用。
函数声明
|
接口说明
|
empty
|
检测 list 是否为空,是返回 true ,否则返回 false
|
size
|
返回 list 中有效节点的个数
|
int arr[] = { 1,2,3,4,5,6,7,8,9 };
listlt(arr, arr + sizeof(arr) / sizeof(int));
cout << "size" << lt.size() << endl;
cout << "empty" << lt.empty();
cout << endl;
函数声明
|
接口说明
|
push_front
|
在 list 首元素前插入值为 val 的元素
|
pop_front
|
删除 list 中第一个元素
|
push_back
|
在 list 尾部插入值为 val 的元素
|
pop_back
|
删除 list 中最后一个元素
|
insert
|
在 list position 位置中插入值为 val 的元素
|
erase
|
删除 list position 位置的元素
|
swap
|
交换两个 list 中的元素
|
clear
|
清空 list 中的有效元素
|
注意:
erase的参数是迭代器,而remove是一个值,也就是erase可以删除一个区间,而remove只能删除一个值。假如一个数组有多个相同的数值,例如12344444,使用remove时会删除所有的4,而erase则会删除迭代器所指的数值。
添加和删除(Insert erase)都会有返回值: 返回添加位置的迭代器、删除位置后往后顺延的迭代器。
swap对于要交换的双方没有大小要求,并不是不一样大就不能交换。
void main() {
int arr[] = { 1,2,3,4,5,6,7,8,9 };
listlt(arr, arr + sizeof(arr) / sizeof(int));
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;
int br[] = { 100,200,300 };
listlt2(br, br + sizeof(br) / sizeof(int));
cout << "br = ";
for (const auto& e : lt2)
cout << e << " ";
cout << endl;
lt.swap(lt2);
for (const auto& e : lt)
cout << e << " ";
cout << endl;
for (const auto& e : lt2)
cout << e << " ";
cout << endl;
lt.clear();
}
除此之外还有下面这就几个函数也比较常用:
sort排序可以按照从前到后也可从后到前,
resize可以调大也可以调小,重新设置容器尺寸,如果想要的长度比原长度小,那么截处后面的元素,如果要比原尺寸大,那就后面补默认值。
unique去重必须是所重复的数据是连续的,遇到1 2 3 2 3 2 4这种就会失效。
void main() {
int arr[] = { 2,2,4,7,2,6,8,1,3,9,5 };
listlt(arr, arr + sizeof(arr) / sizeof(int));
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;
lt.sort();
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;
/*lt.remove(2);
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;*/
lt.unique();
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;
lt.resize(10);
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;
lt.resize(2);
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;
}
merge合并两个链表之前,必须保证两个链表有序, 有序(只要两个链表有顺序就行如 3 6 9 和 1 4 8 9)
splice拼接没有顺序要求
void main() {
int arr[] = { 1,2,3,7,8,9,4,5,6 };
listlt(arr, arr + sizeof(arr) / sizeof(int));
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;
int br[] = { 100,200,300 };
listlt2(br, br + sizeof(br) / sizeof(int));
cout << "br = ";
for (const auto& e : lt2)
cout << e << " ";
cout << endl;
/*lt.merge(lt2);
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;*/
list::iterator pos = find(lt.begin(), lt.end(), 9);
list::iterator start = find(lt2.begin(), lt2.end(), 100);
lt.splice(pos, lt2, start);//从lt2中删除 插入lt中
cout << "arr = ";
for (const auto& e : lt)
cout << e << " ";
cout << endl;
}
直接上代码
namespace mlist {
template
class list {
public:
struct _Node;
public:
typedef _Node* _Nodeptr;
typedef size_t _size_type;
typedef _Ty value_type;
public:
struct _Node {
_Nodeptr _Next, _Prev;
_Ty _value;
};
struct Acc {
typedef _Node*& _Nodepref;//节点指针的引用
typedef _Ty& _Vref; //值的引用
static _Nodepref _Next(_Nodeptr P) {
return (_Nodepref)(P->_Next);
}
static _Nodepref _Prev(_Nodeptr P) {
return (_Nodepref)(P->_Prev);
}
static _Vref Value(_Nodeptr P) {
return (_Vref)(P->_value);
}
};
class iterator {
public:
iterator() :_Ptr(nullptr)
{}
iterator(_Nodeptr P) :_Ptr(P)
{}
_Ty& operator*() {
return Acc::Value(_Ptr);
}
_Ty* operator->()
{
return &Acc::_Value(_Ptr);
}
iterator& operator++() {
_Ptr = Acc::_Next(_Ptr);
return *this;
}
iterator operator++(int) {
iterator tmp = *this;
_Ptr++;
return tmp;
}
iterator& operator--()
{
_Ptr = Acc::_Prev(_Ptr);
return *this;
}
iterator operator--(int)
{
iterator _Tmp = *this;
--* this;
return _Tmp;
}
bool operator !=(const iterator& p) {
return _Ptr != p._Ptr;
}
bool operator ==(const iterator& p) {
return _Ptr == p._Ptr;
}
_Nodeptr mynode()const {
return _Ptr;
}
private:
_Nodeptr _Ptr;
};
public:
iterator begin() {
return iterator(Acc::_Next(_Head));
}
iterator end() {
return iterator(_Head);
}
public:
list() :_Head(_Buynode()), _Size(0)
{}
~list()
{
/*erase(begin(), end());
_Freenode(_Head);
_Head = 0, _Size = 0;*/
}
size_t size() {
size_t size = 0;
_Nodeptr p = _Head->_Next;
while (p != end()) {
size++;
p = p->_Next;
}
return size;
}
bool empty() {
return size() == 0;
}
void clear() {
iterator t = begin();
while (t != end()) {
erase(t++);
}
}
void push_back(const _Ty& a) {
insert(end(), a);
}
void push_front(const _Ty& a) {
insert(begin(), a);
}
看这里!看这里! 这里有个零初始化
iterator insert(iterator P, const _Ty& X = _Ty()) {
_Nodeptr T = P.mynode();
Acc::_Prev(T) = _Buynode(T, Acc::_Prev(T));
//_Nodeptr T = _Buynode(P.mynode(), Acc::_Prev(P.mynode()));
//Acc::_Next(Acc::_Prev0(T)) = T;
//Acc::_Prev(Acc::_Next(T)) = T;
T = Acc::_Prev(T);
Acc::_Next(Acc::_Prev(T)) = T;
Acc::Value(T) = X;
++_Size;
return iterator(T);
}
iterator erase(iterator P) {
_Nodeptr S = (P++).mynode();
Acc::_Prev(Acc::_Next(S)) = Acc::_Prev(S);
Acc::_Next(Acc::_Prev(S)) = Acc::_Next(S);
--_Size;
free(S);
return P;
}
看这里!看这里!看这里!
_Nodeptr _Buynode(_Nodeptr _Narg = 0, _Nodeptr _Parg = 0) {
_Nodeptr _S = (_Nodeptr)malloc(sizeof(struct _Node));
Acc::_Next(_S) = _Narg != 0 ? _Narg : _S;
Acc::_Prev(_S) = _Parg != 0 ? _Parg : _S;
return _S;
}
/* _Nodeptr _Buynode(_Nodeptr _Narg,_Parg) {
_Nodeptr _S = (_Nodeptr)mallco(sizeof(struct _Node));
_S->_Next = _Narg != 0 ? _Narg : _S;
_S->_Prev = _parg != 0 ? _Parg : _s;
}*/
private:
_Nodeptr _Head;
_size_type _Size;
};
很好,你最好是看完上面的代码再看的这句话,仔细看里面的Acc,Buynode,insert是不是很美丽的代码,特别是insert的代码和我注释起来的对比一下。它源码的封装是不是很严谨。
迭代器,顾名思义,是一个允许重复迭代的器具,我们没个容器内都有迭代器的实现,类似于list中++、--、+=等的重载、前序遍历、后序遍历等的实现,都有迭代器的功劳,这里我们可以把他理解为一个指针(当然它不仅仅是指针啊),我称它为面向对象的指针。
由于源码不太好理解,这里模拟实现一下,可当参考。(上面模拟实现list中也有迭代器,那个也会很接近源码)
List 的迭代器
迭代器有两种实现方式,具体应根据容器底层数据结构实现:
1. 原生态指针,比如:vector
2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下
方法:
1. 指针可以解引用,迭代器的类中必须重载operator*()
2. 指针可以通过->访问其所指空间成员,迭代器类中必须重载oprator->()
3. 指针可以++向后移动,迭代器类中必须重载operator++()与operator++(int)
至于operator--()/operator--(int)释放需要重载,根据具体的结构来抉择,双向链表可
以向前 移动,所以需要重载,如果是forward_list就不需要重载--
4. 迭代器需要进行是否相等的比较,因此还需要重载operator==()与operator!=()
*/
template
class ListIterator
{
typedef ListNode* PNode;
typedef ListIterator Self;
public:
ListIterator(PNode pNode = nullptr)
: _pNode(pNode)
{}
ListIterator(const Self& l)
: _pNode(l._pNode)
{}
T& operator*() { return _pNode->_val; }
T* operator->() { return &(operator*()); }
Self& operator++()
{
_pNode = _pNode->_pNext;
return *this;
}
Self operator++(int)
{
Self temp(*this);
_pNode = _pNode->_pNext;
return temp;
}
Self& operator--();
Self& operator--(int);
bool operator!=(const Self& l) { return _pNode != l._pNode; }
bool operator==(const Self& l) { return _pNode != l._pNode; }
PNode _pNode;
};
空间配置器,对于list基本所有的有关空间的操作都可以通过空间配置器实现,你一个小小的指针我都给你封装起来了,我这个操作空间的大工程不得包装的更精美,于是乎,空间配置器应运而生
等什么呢,我问你等什么呐,你不会以为我会把空间配置器代码也放出来吧,知道他是什么就行了,不想敲了。
前面说过,面向对象的指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
void TestListIterator1()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list l(array, array + sizeof(array) / sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
//erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,
//必须先给其赋值
l.erase(it);
++it;
}
}
// 改正
void TestListIterator()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list l(array, array + sizeof(array) / sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
l.erase(it++); // it = l.erase(it);
}
}
总结一下,stl各个容器所含有的主要器件相差不多,学会list不吃亏
对于文章内容有疑惑或者是建议请评论或者私信俺