🍬 mooridy-CSDN博客
🧁C++专栏(更新中!)
目录
list的介绍
list的使用
list的构造
list 容量
list 访问
list 增删查改
迭代器
迭代器失效问题
list模拟实现
list与vector的对比
emplace_back和push_back的区别
list的介绍
list底层——带头双向循环链表
list的使用
list的构造
构造函数( (constructor)) | 接口说明 |
list (size_type n, const value_type& val =
value_type())
|
构造的
list
中包含
n
个值为
val
的
元素
|
list() | 构造空的list |
list (const list& x) | 拷贝构造函数 |
list (InputIterator first, InputIterator last) |
用
[first, last)
区间中的元素构造
list
|
list<int> lt1; // 构造空列表
list<int> lt2 (4,100); // 构造有4个100的列表
list<int> lt3 (lt2.begin(),lt2.end()); // 用迭代器构造
list<int> lt4 (third); // 拷贝构造
list 容量
函数声明 | 接口说明 |
empty | 检测list是否为空,是返回true,否则返回false |
size | 返回list中有效节点的个数 |
list 访问
函数声明 | 接口说明 |
front | 返回list的第一个节点中值的引用 |
back | 返回list的最后一个节点中值的引用 |
list 增删查改
函数声明 | 接口说明 |
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中的有效元素 |
list<int> first (3,100);
list<int> second (5,200);
it = first.begin();
first.insert(it,5);//在it前加1个5
first.insert(it,2,6);//在it前加2个6
first.swap(second);
迭代器
1.
之前我们学习vector容器时可以简单的将迭代器理解为指针。然而在list中,这有的理解显然是有失偏颇的。如list的介绍中所提到的,list的底层实现是带头双向循环链表,而我们知道链表的空间是不连续的,如果还以为迭代器是指针,那么++的位置只能是结点的下一个位置,但这个理解显然不对。
那又为什么我们却能像vector一样的去使用list的迭代器呢?
而且*就是访问的数据,++和- -就能找到结点的后一个结点和前一个结点。
我们能用类似vector
迭代器的方式操作list
迭代器,是因为C++的类封装和操作符重载。迭代器封装了复杂的容器遍历逻辑,通过重载++
、--
和*
等操作符,让我们能以直观方式访问元素,而无需关心其底层实现细节。
具体是如何实现的,可以看看下面的list模拟实现部分。
2.
在使用过程中,需注意list的迭代器只有++,--的功能,一步步的移动迭代器,是无法像vector的迭代器一样实现+或-功能进行跨越的加减。
迭代器按性质划分:
性质 | 代表容器 | 功能 |
单向ForwardIterator | forward_lisr/unordered_map... | ++ |
双向BidirectionalIterator | list/map/set... | ++/-- |
随机RandomAcessIterator | vector/string/deque | ++/--/+/- |
底层结构可以决定使用哪些算法。
迭代器失效问题
insert不会失效——只是修改了指针指向关系,而没有修改迭代器本身
erase——指向删除元素的迭代器失效
list模拟实现
//list.h
#include<iostream>
using namespace std;
namespace AA
{
// List的节点类
template<class T>
struct ListNode
{
ListNode(const T& val = T()){
_val = val;
_prev = nullptr;
_next=nullptr;
}
ListNode<T>* _prev;
ListNode<T>* _next;
T _val;
};
//List的迭代器类
template<class T,class Ref, class Ptr>
class ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T,Ref,Ptr> Self;
public:
ListIterator(Node* pNode = nullptr) {
_pNode = pNode;
}
ListIterator(const Self& l) {
*this = l;
}
Ref operator*() {
return _pNode->_val;
}
Ptr operator->() {
return &_pNode->_val;
}
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;
}
private:
Node* _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;
public:
///
// List的构造
list() {
_pHead = new Node;
_pHead->next = _pHead;
_pHead->prev = _pHead;
_size = 0;
}
list(int n, const T& value = T()) {
_pHead = new Node;
Node* pcur = _pHead;
for (int i = 0; i < n; i++) {
Node* newnode = new Node;
newnode->val = value;
newnode->next = _pHead;
newnode->prev = pcur;
pcur->next = newnode;
pcur = newnode;
_pHead->prev = pcur;
}
_size = n;
}
template <class Iterator>
list(Iterator first, Iterator last);
list(const list<T>& l);
list<T>& operator=(const list<T> l);
~list() {
clear();
delete _pHead;
_pHead = nullptr;
_size = 0;
}
///
// List Iterator
iterator begin() {
return _pHead->next;
}
iterator end() {
return _pHead->prev;
}
const_iterator begin() {
return _pHead->next;
}
const_iterator end() {
return _pHead->prev;
}
///
// List Capacity
size_t size()const {
return _size;
}
bool empty()const {
return size == 0;
}
// List Access
T& front() {
return _pHead->next->_val;
}
const T& front()const {
return _pHead->next->_val;
}
T& back() {
return _pHead->prev->_val;
}
const T& back()const {
return _pHead->prev->_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()); }
// 在pos位置前插入值为val的节点
iterator insert(iterator pos, const T& val) {
Node* newnode = new Node;
newnode->_val = val;
Node* pcur = pos._pNode;
Node* prev = pcur->_prev;
newnode->_next = pcur;
newnode->_prev = prev;
prev->_next = newnode;
pcur->_prev = newnode;
++_size;
return newnode;
}
// 删除pos位置的节点,返回该节点的下一个位置
iterator erase(iterator pos) {
Node* pcur = pos._pNode;
Node* prev = pcur->_prev;
Node* next = pcur->_next;
delete pcur;
pcur = nullptr;
prev->_next = next;
next->_prev = prev;
--_size;
return next;
}
void clear() {
Node* pcur = _pHead->next;
while (pcur!=_pHead) {
pcur = erase(pcur);
}
}
void swap(list<T>& l) {
std::swap(_pHead, l._pHead);
std::swap(_size, l._size);
}
private:
size_t _size;
Node* _pHead;
};
};
list与vector的对比
vector | list | |
底层结构 | 动态顺序表,一段连续空间 |
带头结点的双向循环链表
|
随机访问 |
支持随机访问,访问某个元素效率
O(1)
|
不支持随机访问,访问某个元
素效率
O(N)
|
插入和删除 |
任意位置插入和删除效率低,需要搬移元素,时间
复杂度为
O(N)
,插入时有可能需要增容,增容需要
开辟新空间,拷贝元素,释放旧空间,导致效率更
低
|
任意位置插入和删除效率高,
不需要搬移元素,时间复杂度
为
O(1)
|
空间利用率 |
底层为连续空间,不容易造成内存碎片,空间利用
率高,缓存利用率高
|
底层节点动态开辟,小节点容
易造成内存碎片,空间利用率
低,缓存利用率低
|
迭代器 |
原生态指针
|
对原生态指针
(
节点指针
)
进行
封装
|
迭代器失效 |
在插入元素时,要给所有的迭代器重新赋值,因为
插入元素有可能会导致重新扩容,致使原来迭代器
失效,删除时,当前迭代器需要重新赋值否则会失
效
|
插入元素不会导致迭代器失
效,删除元素时,只会导致当
前迭代器失效,其他迭代器不
受影响
|
应用场景 |
需要高效存储,支持随机访问,不关心插入删除效
率
|
大量插入和删除操作,不关心
随机访问
|
emplace_back和push_back的区别
push_back() 向容器尾部添加元素时,首先会创建这个元素,然后再将这个元素拷贝或者移动到容器中(如果是拷贝的话,事后会自行销毁先前创建的这个元素);
而 emplace_back() 在实现时,则是直接在容器尾部创建这个元素,省去了拷贝或移动元素的过程。这样效率更高。
同时在使用上,有下图代码所展示的小区别:
请注意:emplace_back() 是 C++ 11 标准新增加的,如果程序需要兼顾之前的版本,还是应该使用 push_back()。