【C++】7-list
1. 了解list
- list是序列容器,允许在序列内的任意位置进行常量时间的插入和删除,并进行前后两个方向的迭代。
- list的底层存储数据的内容是双向链表结构,双向链表中的每个元素存储在互不相关的独立结点中(即存储在不同且不相关的存储位置),且结点中通过指针指向其前一个元素和后一个元素。
- list和forward_list相似:主要区别在于forward_list的对象是单链表,只能向前迭代,以使其更简单且高效。
- 与其他基本标准序列容器(array、vector和deque)相比,list在容器内的任何位置插入、提取和移动元素(迭代器已经获得)方面通常表现更好,因此在大量使用插入、提取、移动等的算法(如排序算法)中也表现更好。
- 与其他序列容器相比,list和forward_lists的主要缺点是它们不支持元素的随机访问(即通过位置直接访问元素),例如,要访问列表中的第六个元素,必须从已知位置(如开始或结束)迭代到该位置,这需要在两者之间的距离上花费线性时间。它们还消耗一些额外的内存来保存与每个元素相关联的链接信息(对于存储类型较小元素的大型list来说,这可能是一个重要因素)。
- 带头结点
2. list的使用
与string、vector一样,list也有许多接口,但我们目前只需掌握其中比较常用且重要的接口即可。
(list的接口与vector类似,在vector那一文章中已经做了详细介绍,因此下面只是简要说明)
2.1 构造(constructor)
函数 | 功能说明 |
---|---|
explicit list (const allocator_type& alloc = allocator_type()); | 默认构造 |
explicit list (size_type n, const value_type& val = value_type(), const allocator_type& alloc = allocator_type()); | 初始化为n个val |
template list (InputIterator first, InputIterator last, const allocator_type& alloc = allocator_type()); | 利用迭代器的范围构造 |
list (const list& x); | 拷贝构造 |
operator= | 构造(赋值) |
上述函数可等价于: |
---|
list(const value_type& val = value_type()); |
list(size_t n, const value_type& val = valye_type()); |
template list(InputIterator first, InputIterator last, const val = value_type()); |
list(const list& x); |
operator= |
2.2 迭代器(iterators)
list的迭代器不支持双目运算符
+-
(后面的modifiers会讲到)
函数 | 功能说明 |
---|---|
begin | 返回指向第一个元素的迭代器 |
end | 返回指向最后一个元素的下一个位置的迭代器 |
rbegin | 返回指向最后一个元素的下一个位置的迭代器 |
rend | 返回指向第一个元素的迭代器 |
cbegin | 返回指向(const 对象的)第一个元素的迭代器。指向的内容不可修改 |
cend | 返回指向(const 对象的)最后一个元素的下一个位置的迭代器。指向的内容不可修改 |
2.3 容量(capacity)
函数 | 功能说明 |
---|---|
empty | 判断list是否为空 |
size | list大小(有效数据/结点个数) |
2.4 元素访问(element acess)
函数 | 功能说明 |
---|---|
front | 取得第一个元素(头结点的下一个元素) |
back | 取得最后一个元素(头结点的上一个元素) |
2.5 修改(modifiers)
函数 | 功能说明 |
---|---|
assign | 重新分配list的内容 |
push_front | 头插 |
pop_front | 头删 |
push_back | 尾插 |
pop_back | 尾删 |
insert | 任意位置插入 |
erase | 任意位置删除 |
resize | 重新设置list大小 |
clear | 清空内容 |
swap | 交换两个结点的内容 |
测试用例:
void test_list1()
{
list<int> It1;
cout << "Initialize::>>>>>>>>>>>>>>>>>>>>>>>" << endl;
cout << "list<int> It1" << endl;
Print(It1);
// 1. assign
cout << "assign:>>>>>>>>>>>>>>>>>>>>>>>" << endl;
It1.assign(3, 10);
cout << "It1.assign(3, 10)" << endl;
Print(It1);
// 2. push
cout << "push_back和push_front:>>>>>>>>>>>>>>>>>>>>>>>" << endl;
It1.push_back(1);
It1.push_back(2);
It1.push_front(3);
It1.push_front(4);
Print(It1);
// 3. pop
cout << "pop:>>>>>>>>>>>>>>>>>>>>>>>" << endl;
It1.pop_back();
It1.pop_front();
Print(It1);
// 4. insert
// iterator insert (iterator position, const value_type& val);
// void insert (iterator position, size_type n, const value_type& val);
cout << "insert:>>>>>>>>>>>>>>>>>>>>>>>" << endl;
list<int>::iterator it = ++It1.begin();// 迭代器无法使用双目运算符'+'/'-'
// iterator insert (iterator position, const value_type& val);
cout << "it = It1.insert(it, 100 + i)" << endl;
for (size_t i = 0; i < 4; ++i)
{
// insert看实际情况选择是否重新接收it
// it = It1.insert(it, 100 + i);
It1.insert(it, 100 + i);
}
Print(It1);
// template <class InputIterator> void insert (iterator position, InputIterator first, InputIterator last);
list<int> It2;
It2.push_back(10);
It2.push_back(20);
It2.push_back(30);
It2.push_back(40);
cout << "It1.insert(++It1.begin(), It2.begin(), It2.end())" << endl;
It1.insert(++It1.begin(), It2.begin(), It2.end());
Print(It1);
// 5. erase
// iterator erase(iterator position);
// iterator erase(iterator first, iterator last);// 这里不做介绍
cout << "erase:>>>>>>>>>>>>>>>>>>>>>>>" << endl;
it = find(It1.begin(), It1.end(), 100);// 使用算法库里的find()找到数据为100的结点
for (size_t i = 0; i < 3; ++i)
{
it = It1.erase(it);// 删除成功后,返回原被删除的结点的下一个结点的迭代器
}
Print(It1);
// 6. resize
// void resize (size_type n, value_type val = value_type());
cout << "resize:>>>>>>>>>>>>>>>>>>>>>>>" << endl;
It1.resize(4);
cout << "It1.resize(4)" << endl;
Print(It1);
It1.resize(5);
cout << "It1.resize(5)" << endl;
Print(It1);
It1.resize(8, 1000);
cout << "It1.resize(8, 1000)" << endl;
Print(It1);
// 7. swap
cout << "swap:>>>>>>>>>>>>>>>>>>>>>>>" << endl;
It1.swap(It2);
cout << "It1.swap(It2)" << endl;
cout << "It1:" << endl;
Print(It1);
cout << "It2:" << endl;
Print(It2);
// 8. clear
cout << "clear:>>>>>>>>>>>>>>>>>>>>>>>" << endl;
It1.clear();
Print(It1);
}
输出结果:
3. list进阶 – 模拟实现
以上内容可以帮你了解怎么使用list,但如果想要熟练使用,还需要了解list的底层实现。
// 反向迭代器类
// 这是一个容器适配器
// 关于反向迭代器的底层原理,可以看【C++】stack_queue文章
template<class Iterator, class Ref, class Ptr>
class ReverseIterator
{
typedef ReverseIterator<Iterator, Ref, Ptr> Self;
public:
ReverseIterator(Iterator it)
:_iterator(it)
{}
Ref operator*()
{
Iterator tmp(_iterator);
return *(--tmp);
}
Ptr operator->()
{
return &(operator*());
}
// 前置
Self& operator++()
{
--_iterator;
return *this;
}
Self operator++(int)
{
Self tmp(_iterator);
--tmp;
return tmp;
}
// 前置
Self& operator--()
{
++_iterator;
return *this;
}
Self operator--(int)
{
Self tmp(_iterator);
++tmp;
return tmp;
}
bool operator==(const Self& s) const
{
return _iterator == s._iterator;
}
bool operator!=(const Self& s) const
{
return _iterator != s._iterator;
}
private:
Iterator _iterator;
};
/
namespace wzw
{
// List的结点类
template<class T>
struct ListNode
{
// 提供默认构造
ListNode(const T& val = T())
:_pPre(nullptr)
,_pNext(nullptr)
,_val(val)
{}
ListNode<T>* _pPre;
ListNode<T>* _pNext;
T _val;
};
// List的迭代器类
template<class T, class Ref, class Ptr>// Ref相当于T&,Ptr相当于T*
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)
{}
Ref operator*()
{
return _pNode->_val;
}
Ptr operator->()
{
return &_pNode->_val;
}
// 前置++
Self& operator++()
{
_pNode = _pNode->_pNext;
return *this;
}
// 后置++
Self operator++(int)
{
Self tmp(*this);
_pNode = _pNode->_pNext;
return tmp;
}
// 前置--
Self& operator--()
{
_pNode = _pNode->_pPre;
return *this;
}
// 后置--
Self operator--(int)
{
Self tmp(*this);
_pNode = _pNode->_pPre;
return tmp;
}
// 迭代器是否相等取决去迭代器指向的结点的地址
bool operator!=(const Self& l) const
{
return !(*this == l);
}
bool operator==(const Self& l) const
{
return _pNode == l._pNode;
}
public:
PNode _pNode;
};
//list类
template<class T>
class list
{
typedef ListNode<T> Node;
typedef Node* PNode;
public:
typedef ListIterator<T, T&, T*> iterator;
typedef ListIterator<T, const T&, const T*> const_iterator;
typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;
public:
// List的构造
// explicit list (const allocator_type& alloc = allocator_type());
list()
{
// 空链表有一个头结点
CreateHead();
}
// explicit list (size_type n, const value_type& val = value_type(),
// const allocator_type& alloc = allocator_type());
list(int n, const T& value = T())
{
CreateHead();
for (size_t i = 0; i < n; ++i)
{
push_back(value);
}
}
// template <class InputIterator>
// list(InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
CreateHead();
while (first != last)
{
push_back(*first);
++first;
}
}
// list (const list& x);
// list(const list& l)也是正确的
list(const list<T>& l)
{
CreateHead();
list<T> tmp(l.begin(), l.end());
swap(tmp);
}
// list& operator= (const list& x);
// 此写法是优化过的写法 -- 只是写着更简洁了,不考虑效率
list<T>& operator=(list<T> l)
{
clear();
swap(l);
return *this;
}
~list()
{
clear();
delete _pHead;
_pHead = nullptr;
}
// List Iterator
iterator begin()
{
return iterator(_pHead->_pNext);
}
iterator end()
{
return iterator(_pHead);
}
const_iterator begin() const
{
return const_iterator(_pHead->_pNext);
}
const_iterator end() const
{
return const_iterator(_pHead);
}
// 一般来说,rbegin()应该返回指向最后一个元素的迭代器end()-1,而非end(),这里之所以返回的是end(),而非end()-1,只是为了美观(正向和反向保持对称美)
reverse_iterator rbegin()
{
return reverse_iterator(end());
}
reverse_iterator rend()
{
return reverse_iterator(begin());
}
const_reverse_iterator rbegin() const
{
return reverse_iterator(end());
}
const_reverse_iterator rend() const
{
return reverse_iterator(begin());
}
// List Capacity
size_t size() const // 频繁调用,代价较大
{
size_t size = 0;
const_iterator it = begin();
while (it != end())
{
++size;
++it;
}
return size;
}
bool empty() const
{
return begin() == end();
}
// List Access
T& front()
{
return _pHead->_pNext->_val;
}
const T& front() const
{
return _pHead->_pNext->_val;
}
T& back()
{
return _pHead->_pPre->_val;
}
const T& back() const
{
return _pHead->_pPre->_val;
}
// 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());
}
// iterator insert (iterator position, const value_type& val);
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val)
{
PNode newNode = new Node(val);
PNode cur = pos._pNode;
PNode pre = cur->_pPre;
newNode->_pNext = cur;
newNode->_pPre = pre;
pre->_pNext = newNode;
cur->_pPre = newNode;
return iterator(newNode);
}
// iterator erase (iterator position);
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos)
{
assert(!empty());
PNode cur = pos._pNode;
PNode pre = cur->_pPre;
PNode next = cur->_pNext;
pre->_pNext = next;
next->_pPre = pre;
delete cur;
cur = nullptr;
return iterator(next);
}
void clear()
{
while (begin() != end())
{
pop_back();
}
}
// void swap (list& x);
void swap(list<T>& l)
{
std::swap(_pHead, l._pHead);
}
private:
void CreateHead()
{
_pHead = new Node();
_pHead->_pNext = _pHead;
_pHead->_pPre = _pHead;
}
PNode _pHead;
};
}
4. vector和list的比较
vector
优点:
支持元素下标的随机访问
尾插尾删效率较高
cpu高速缓存命中率高
即当获得数组首元素的地址时,将首元素存入高速缓冲区,同时将首元素紧接着后面的一些元素也存入高速缓冲区。之后若要访问这些被存入高速缓冲区的元素时,便可直接从缓冲区访问,而不必从内存访问,速度更快。
缺点:
- 前面部分的插入删除数据效率低,需要挪动数据,时间复杂度O(N)
- 扩容耗费时间(大可能是异地扩容),还可能存在空间浪费
list
优点:
- 空间按需申请/释放,无需扩容
- 任意位置插入数据效率高,时间复杂度O(1)
缺点:
- 不支持元素的下标随机访问
- cpu高速缓存命中率低(底层数据存储物理空间非连续)