欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 健康 > 养生 > SLT—List详解

SLT—List详解

2024/10/25 16:20:49 来源:https://blog.csdn.net/2401_83431652/article/details/141991113  浏览:    关键词:SLT—List详解

1.list概述

相较于 vector 的连续线性空间,list 就显得复杂很多,它的好处是每次插入或删除一个数据,就配置或释放一个元素空间。因此,list 对于空间的运用有绝对的精准,一点也不浪费。对于任何位置的元素进行插入或者元素移除,list 永远是常数时间(O(N))。

list 和 vector 是两个最常被使用的容器,什么时机下最适合使用哪一种容器,必须视元素的多寡、元素的构造复杂度、 元素存取行为的特征而定。

下图是 list 示意图 : 环状链表的尾端加上一个空白节点,符合“前闭后开”区间。

2.list的使用

list 中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,已达到可扩展的能力。以下为list中一些常见的重要接口

(1) list的构造

void TestList1()
{list<int> l1;                         // 构造空的l1list<int> l2(4, 100);                 // l2中放4个值为100的元素list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3list<int> l4(l3);                    // 用l3拷贝构造l4// 以数组为迭代器区间构造l5int array[] = { 16,2,77,29 };list<int> l5(array, array + sizeof(array) / sizeof(int));// 列表格式初始化C++11list<int> l6{ 1,2,3,4,5 };// 用迭代器方式打印l5中的元素list<int>::iterator it = l5.begin();while (it != l5.end()){cout << *it << " ";++it;}       cout << endl;// C++11范围for的方式遍历for (auto& e : l5)cout << e << " ";cout << endl;
}

(2) list iterator

暂时将迭代器理解成一个指针,该指针指向 list 中的某个节点。

 

【注意】
1. begin 与 end 为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end) 与 rend(begin) 为反向迭代器,对迭代器执行++操作,迭代器向前移动
// list迭代器的使用
// 注意:遍历链表只能用迭代器和范围for
void PrintList(const list<int>& l)
{// 注意这里调用的是list的 begin() const,返回list的const_iterator对象for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it){cout << *it << " ";// *it = 10; 编译不通过}cout << endl;
}void TestList2()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));// 使用正向迭代器正向list中的元素// list<int>::iterator it = l.begin();   // C++98中语法auto it = l.begin();                     // C++11之后推荐写法while (it != l.end()){cout << *it << " ";++it;}cout << endl;// 使用反向迭代器逆向打印list中的元素// list<int>::reverse_iterator rit = l.rbegin();auto rit = l.rbegin();while (rit != l.rend()){cout << *rit << " ";++rit;}cout << endl;
}

(3) list capacity

(4) list element access(元素访问)

(5) list modifiers(修改器)

#include <list>
#include <iostream>
#include <algorithm>
using namespace std;
int main()
{list<int> ilist; cout << "size=" << ilist.size() << endl;// size=0ilist.push_back(0); ilist.push_back(1); ilist.push_back(2); ilist.push_back(3); ilist.push_back(4); cout << "size=" << ilist.size() << endl;//size=5list<int>::iterator ite; for (ite = ilist.begin(); ite != ilist.end(); ++ite){cout << *ite << ' ';//0 1 2 3 4}cout << endl;ite = find(ilist.begin(), ilist.end(), 3);if (ite != ilist.end()){ilist.insert(ite, 99);}cout << "size=" << ilist.size() << endl;// size=6cout << *ite << endl;// 3for (ite = ilist.begin(); ite != ilist.end(); ++ite)//0 1 2 99 3 4cout << *ite << ' ';cout << endl;ite = find(ilist.begin(), ilist.end(), 1); if (ite != ilist.end())cout << *(ilist.erase(ite)) << endl;//2for (ite = ilist.begin(); ite != ilist.end(); ++ite)//0 2 99 3 4cout << *ite << ' ';cout << endl;
}

(6) list迭代器失效

前面说过,此处可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在 list 中进行插入时是不会导致 list 的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响这在 vector 是不成立的,因为 vector 的常茹操作可能造成记忆体重新配置,导致原有的迭代器全部失效。
void TestListIterator1()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> 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<int> l(array, array + sizeof(array) / sizeof(array[0]));auto it = l.begin();while (it != l.end()){l.erase(it++); // it = l.erase(it);}
}

3.list的模拟实现

(1) list的节点(ListNode)

list 本身和 list 的节点是不同的结构,需要分开设计,以下是根据 STL 设计的 list 的节点(ListNode)结构:

template <class T>
struct ListNode // struct默认成员是公有的
{ListNode(const T& val = T()):_prev(nullptr),_next(nullptr),_data(val){}ListNode<T>* _prev;ListNode<T>* _next;T _data;
};

(2) list的迭代器(ListIterator)

list 不能再像 vector 一样以普通指针作为迭代器,因为其节点不保证在存储空间里连续存在。list迭代器必须有能力指向list节点,并有能力进行正确的递增、递减、取值、成员存取等操作。所谓“list 正确的递增、递减、取值、成员存取”操作是指,递增时指向下一个节点,递减时指向下一个节点,取值时取的是节点的数据值,成员取用时取的是节点的成员

由于STL list是一个双向链表,迭代器必须具备迁移,后移的能力,所以 list 提供的是 Birdirectional Iterator(双向)

List 的迭代器

迭代器有两种实现方式,具体应根据容器底层数据结构实现:

1. 原生态指针,比如:vector

2. 将原生态指针进行封装,因迭代器使用形式与指针完全相同,因此在自定义的类中必须实现以下方法:

(1)指针可以解引用,迭代器的类中必须重载 operator*()

(2)指针可以通过->访问其所指空间成员,迭代器类中必须重载 oprator->()

(3)指针可以++向后移动,迭代器类中必须重载 operator++() 与 operator++(int)

至于 operator--() / operator--(int) 释放需要重载,根据具体的结构来抉择,双向链表可以向前 移动,所以需要重载,如果是forward_list(单向)就不需要重载--

(4)迭代器需要进行是否相等的比较,因此还需要重载 operator==() 与 operator!=()

//List的迭代器类模版,实例化了两个类
template<class T, class Ref, class Ptr>
class ListIterator
{typedef ListNode<T>* PNode;typedef ListIterator<T, Ref, Ptr> Self;public:ListIterator(PNode pNode = nullptr):_pNode(pNode){}ListIterator(const Self& l):_pNode(l._pNode){}T& operator*(){//取得是节点的数值return (*_pNode)._data;}T* operator->(){return &(operator*());}Self& operator++(){_pNode = (*_pNode)._next;return *this;}Self operator++(int)//前置++{Self tmp = *this;_pNode = (*_pNode)._next;return tmp;//返回++之前的}Self& operator--(){_pNode = (*_pNode)._prev;return *this;}Self operator--(int){Self tmp = *this;_pNode = _pNode._prev;return tmp;//返回++之前的}bool operator!=(const Self& l){return !(_pNode == l._pNode);}bool operator==(const Self& l){return _pNode == l._pNode;}PNode _pNode;//迭代器的内部要有一个普通指针,指向list的节点
};

(3) list的数据结构

STL list 不仅是一个双向链表,而且还是一个环状双向链表。所以它只需要一个指针,就可以完整表现整个链表。

//list类
template<class T>
class list // class 默认是私有的
{typedef ListNode<T> Node;
public:typedef Node* PNode;//实现...private:PNode _pHead;//只需要一个指针,完成整个双向环状链表size_t _size;//STL中没有,自己实现为了方便加的};

(4) list的构造与内存管理 

如果将指针刻意置于尾端的一个空白节点,_pHead便能符合STL对于“前闭后开”去区间的要求,成为 list 迭代器。

    // List的构造list(){CreateHead();//产生一个空链表(一个指向自己的空节点)}list(int n, const T& value = T()){CreateHead();while (n--){push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){CreateHead();while (first != last){push_back(*first);first++;}}//深拷贝list(const list<T>& l){CreateHead();list<T> tmp(l.begin(), l.end());swap(tmp);}list<T>& operator=(list<T> l){swap(l);return *this;}~list(){clear();delete[] _pHead;_pHead = nullptr;}private:void CreateHead(){_pHead = new Node[1];//配置一个节点空间,头尾指向自己_pHead->_next = _pHead;_pHead->_prev = _pHead;}

当我们以 push_back( )将新元素插入于list尾端时,此时函数内部调用 insert( ) :

void push_back(const T& val){ insert(end(), val);}

insert( ) 是一个重载函数,有多种形式,其中最简单的一种如下,首先配置并构造一个节点,然后在尾端进行适当的指针操作,将新节点插入进去:

// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{PNode tmp = new Node[1];tmp->_data = val;PNode pcur = pos._pNode->_prev;pcur->_next = tmp;tmp->_prev = pcur;pos._pNode->_prev = tmp;tmp->_next = pos._pNode;++_size;return tmp;
}

插入完成之后,新节点将位于插入点所指向之节点的前方—这是STL对于“插入操作”的标准规范。

(5) list的元素操作

list 提供的元素操作有很多,这里只实现部分操作。

// List Modify
void push_back(const T& val)
{insert(end(), val);
}
void pop_back()
{erase(--end());
}
void push_front(const T& val)
{insert(begin(), val);
}
void pop_front()
{erase(begin());
}
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{PNode tmp = new Node[1];tmp->_data = val;PNode pcur = pos._pNode->_prev;pcur->_next = tmp;tmp->_prev = pcur;pos._pNode->_prev = tmp;tmp->_next = pos._pNode;++_size;return tmp;
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{PNode pcur = pos._pNode->_prev;PNode pnext = pos._pNode->_next;pcur->_next = pnext;pnext->_prev = pcur;delete[] pos._pNode;--_size;return pnext;
}
//清除整个链表
void clear()
{auto cur = begin();while (cur != end()){cur = erase(cur);}
}

(6) 模拟实现.h

#include<iostream>
#include<list>
#include<algorithm>
#include<assert.h>
using namespace std;namespace zyt
{//List的节点类template <class T>struct ListNode // struct默认成员是公有的{ListNode(const T& val = T()):_prev(nullptr), _next(nullptr), _data(val){}ListNode<T>* _prev;ListNode<T>* _next;T _data;};//List的迭代器类模版,实例化了两个类template<class T, class Ref, class Ptr>class ListIterator{typedef ListNode<T>* PNode;typedef ListIterator<T, Ref, Ptr> Self;public:ListIterator(PNode pNode = nullptr):_pNode(pNode){}ListIterator(const Self& l):_pNode(l._pNode){}T& operator*(){return (*_pNode)._data;}T* operator->(){return &(operator*());}Self& operator++(){_pNode = (*_pNode)._next;return *this;}Self operator++(int)//前置++{Self tmp = *this;_pNode = (*_pNode)._next;return tmp;//返回++之前的}Self& operator--(){_pNode = (*_pNode)._prev;return *this;}Self operator--(int){Self tmp = *this;_pNode = _pNode._prev;return tmp;//返回++之前的}bool operator!=(const Self& l){return !(_pNode == l._pNode);}bool operator==(const Self& l){return _pNode == l._pNode;}PNode _pNode;};//list类template<class T>class list // class 默认是私有的{typedef ListNode<T> Node;typedef Node* PNode;public:typedef ListIterator<T, T&, T*> iterator;typedef ListIterator<T, const T&, const T&> const_iterator;public:///// List的构造list(){CreateHead();//产生一个空链表(一个指向自己的空节点)}list(int n, const T& value = T()){CreateHead();while (n--){push_back(value);}}template <class Iterator>list(Iterator first, Iterator last){CreateHead();while (first != last){push_back(*first);first++;}}//深拷贝list(const list<T>& l){CreateHead();list<T> tmp(l.begin(), l.end());swap(tmp);}list<T>& operator=(list<T> l){swap(l);return *this;}~list(){clear();delete[] _pHead;_pHead = nullptr;}///// List Iteratoriterator begin(){return ((*_pHead)._next);}iterator end(){return _pHead;}const_iterator begin() const{return ((*_pHead)._next);}const_iterator end() const{return _pHead;}///// List Capacitysize_t size()const{return _size;}bool empty()const{return _pHead->_next == _pHead;}// List AccessT& front(){return *begin();}const T& front()const{return *begin();}T& back(){return *end();}const T& back()const{return *end();}// List Modifyvoid push_back(const T& val){insert(end(), val);}void pop_back(){erase(--end());}void push_front(const T& val){insert(begin(), val);}void pop_front(){erase(begin());}// 在pos位置前插入值为val的节点iterator insert(iterator pos, const T& val){PNode tmp = new Node[1];tmp->_data = val;PNode pcur = pos._pNode->_prev;pcur->_next = tmp;tmp->_prev = pcur;pos._pNode->_prev = tmp;tmp->_next = pos._pNode;++_size;return tmp;}// 删除pos位置的节点,返回该节点的下一个位置iterator erase(iterator pos){PNode pcur = pos._pNode->_prev;PNode pnext = pos._pNode->_next;pcur->_next = pnext;pnext->_prev = pcur;delete[] pos._pNode;--_size;return pnext;}//清除整个链表void clear(){auto cur = begin();while (cur != end()){cur = erase(cur);}}void swap(list<T>& l){std::swap(_pHead, l._pHead);std::swap(_size, l._size);}private:void CreateHead(){_pHead = new Node[1];//配置一个节点空间,头尾指向自己_pHead->_next = _pHead;_pHead->_prev = _pHead;}private:PNode _pHead;size_t _size;};
}

(7) 测试.cpp

// 打印各类容器(类)
template<class T>
void PrintList(const zyt::list<T>& l)
{auto it = l.begin();while (it != l.end()){cout << *it << " ";++it;}cout << endl;
}void TestList3()
{int array[] = { 1, 2, 3, 4, 5 };//zyt::list<int> l(array, array + sizeof(array) / sizeof(array[0]));zyt::list<int> l;l.push_back(1);l.push_back(2);l.push_back(3);l.push_back(4);l.push_back(5);PrintList(l);auto pos = l.begin();l.insert(l.begin(), 0);PrintList(l);++pos;l.insert(pos, 2);PrintList(l);l.erase(l.begin());l.erase(pos);PrintList(l);// pos指向的节点已经被删除,pos迭代器失效cout << *pos << endl;auto it = l.begin();while (it != l.end()){it = l.erase(it);}cout << l.size() << endl;
}// PushBack()/PopBack()/PushFront()/PopFront()
void TestList2()
{// 测试PushBack与PopBackzyt::list<int> l;l.push_back(1);l.push_back(2);l.push_back(3);PrintList(l);l.pop_back();l.pop_back();PrintList(l);l.pop_back();cout << l.size() << endl;// 测试PushFront与PopFrontl.push_front(1);l.push_front(2);l.push_front(3);PrintList(l);l.pop_front();l.pop_front();PrintList(l);l.pop_front();cout << l.size() << endl;
}// 测试List的构造
void TestList1()
{zyt::list<int> l1;zyt::list<int> l2(10, 5);PrintList(l2);int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };zyt::list<int> l3(array, array + sizeof(array) / sizeof(array[0]));PrintList(l3);zyt::list<int> l4(l3);PrintList(l4);l1 = l4;PrintList(l1);
}
int main()
{TestList1();return 0;
}

4.list与vector的区别 

vector 与 list 都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com