目录
1. list
2. 构造函数
3. list的容量
4. list的增删
5. list的迭代器
5.1 迭代器的访问
5.2 迭代器失效
6. list的模拟实现
7. vector和list的对比
对于list,它也是一个容器,但list的底层就是双向链表结构,list我们按照前面学习的带头节点双向循环链表。
对于list的节点有三个成员变量,prev指针:指向前一个结点,即前驱结点;next指针:指向后一个结点,即后继结点;val:结点的数据。list同样存储相同类型的元素。同样我们要给list具体的类型。
list自己只有一个head节点成员。
构造函数名称 | 接口说明 |
list() | 构造空的list |
list (size_type n, const value_type& val = value_type()) | 构造的list中包含n个值为val的元素 |
list (const list& x) | 拷贝构造函数 |
list (InputIterator fifirst, InputIterator last) | 用[fifirst, last)区间中的元素构造list |
list s1;//构造空的list
list s2(10, 6);//构造的list中包含10个值为6的元素
list s3(s2);//拷贝构造
list s4(s2.begin(), s2.end());//根据迭代器构造
lsit是一个个结点组成,每插入一个结点,就开辟一个结点的空间。所以list不存在扩容这种问题。所以list只有size和empty两个和容量有关的接口。
函数名称 | 接口说明 |
bool empty() const | 检测list是否为空,是返回true,否则返回false |
size_t size() const | 返回list中有效节点的个数 |
list s1(10, 6);
cout << s1.size() << endl;//有效元素的个数
cout << s1.empty() << endl;//链表是否为空
接口名称 | 接口说明 |
void push_front (const value_type& val) | 在list首元素前插入值为val的元素 |
void pop_front() | 删除list中第一个元素 |
void push_back (const value_type& val) | 在list尾部插入值为val的元素 |
void pop_back() | 删除list中最后一个元素 |
iterator insert (iterator position, const value_type& val) | 在list position 位置中插入值为val的元素 |
iterator erase (iterator position) | 删除list position位置的元素 |
list s1;
//list的尾插
s1.push_back(1);
s1.push_back(2);
//list的尾删
s1.pop_back();
s1.pop_back();
//list任意位置插入
s1.insert(s1.begin(), 4);//相当于头插
//list的头插尾插和任意位置的删除相同做法。
//insert()的任意位置要通过++操作来改变,不能直接;
//如第五个位置
list::iterator cur = s1.begin();
int i = 5;
while(i--)
{
++cur;
}
list是结点组成的,结点与结点之间是通过指针进行连接。简单地说那就是list的存储不是连续的,这会引来一个问题,不能随机访问。也就是说我们要访问某个结点要通过迭代器。这个迭代器里封装的是结点。此处大家可将迭代器暂时理解成类似于指针
函数声明 | 接口说明 |
begin() | 返回第一个元素的迭代器 |
end() | 返回最后一个元素下一个位置的迭代器 |
上述的接口只能访问第一个结点和头节点。为了访问任意结点我们要配上循环。注意:迭代器不能直接加数字来达到访问。
(1) iterator tmp = list_.begin() + 5;------------------>这是错的。
(2) iterator tmp = list_.begin();
while(5) { // 这里代表五次循环,不是真正的循环语句,只是简单书写
++tmp; // 因为在底层的时候,++表示 tmp = tmp->next;这就是为什么不能直接加数字。
}
前面说过,迭代器失效即迭代器所指向的节点的无效,在list里代表该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
list s1;
s1.push_back(1);
s1.push_back(2);
s1.push_back(3);
s1.push_back(4);
cout << "push_back:";
PrintList(s1);
//list::iterator lit = s1.begin();//后面的删除使迭代器失效。
s1.erase(s1.begin());
list::iterator lit = s1.begin();//重新获取
//或者 list::iterator lit = s1.erase(s1.begin());
cout << *lit << endl;
//对比数据结构的带头双向循环链表
#pragma once
#include
using namespace std;
//结点类
//list的结点有三个成员:前驱,后继,数据
template
class ListNode
{
public:
ListNode(const T val = T())
:val_(val),
prev_(nullptr),
next_(nullptr)
{}
ListNode* prev_;
ListNode* next_;
T val_;
};
//list不能直接用[]访问,所以我们必须实现它的迭代器实现访问。
//迭代器类--->原生指针++运算,必须要是连续存储的的。但是list不是连续的。所以要自己封装迭代器
//迭代器封装的是list的结点,但本质还是指针,所以我们要实现指针能用的所有操作++,->,*等。
template //迭代器我们还是要用模板来实现
class ListIterator
{
public:
typedef ListNode* PNode;
typedef ListIterator Lit;
//迭代器的构造
ListIterator(PNode pnode = nullptr)
:pnode_(pnode)
{}
//迭代器的拷贝构造
ListIterator(const Lit& lit)
:pnode_(lit.pnode_)
{
//PNode tmpsnode_(lit.pnode_);
}
//迭代器的运算符
//解引用:取的是指针指向该节点的数据
Ref operator *()
{
return pnode_->val_;
}
//指针:->
Ptr operator ->()
{
return &(pnode_->val_);
}
//判断
bool operator !=(const Lit& lit)
{
return pnode_ != lit.pnode_;
}
//地址是否相同
bool operator ==(const Lit& lit)
{
return !(*this != lit);
}
//++操作。返回*this也可以,++迭代器的返回还是迭代器的本身
Lit& operator++()
{
pnode_ = pnode_->next_;
return *this;
}
Lit operator++(int)
{
Lit tmp(*this);
pnode_ = pnode_->next_;
return tmp;
}
PNode pnode_;//ListNode*
};
//前面的基本工具有了,现在来实现list类
//list类-----模板
template
class List
{
public:
//结点
typedef ListNode Node;
typedef ListNode* PNode;
//迭代器
typedef ListIterator iterator;//可读可写
typedef ListIterator const_iterator;//只读
//构造函数---空的list
List()
{
Create();
}
//构造函数----n个val的list
List(int n, const T& val = T())
{
Create();
for (int i = 0; i < n; ++i)
{
PushBack(val);
}
}
//拷贝构造函数
List(const List& list)
{
Create();
List tmp(list.cbegin(),list.cend());
Swap(tmp);
}
//通过迭代器的构造
template
List(Iterator firdt, Iterator end)
{
Create();
while (firdt != end)
{
PushBack(*firdt);
++firdt;
}
}
/*List(const_iterator firdt, const_iterator end)
{
Create();
while (firdt != end)
{
PushBack(*firdt);
++firdt;
}
}*/
T& Front()
{
return head_->next_->val_;
}
T& Back()
{
return head_->prev_->val_;
}
const T& Front() const
{
return head_->next_->val_;
}
const T& Back() const
{
return head_->prev_->val_;
}
void PushBack(const T& val)
{
PNode node = new Node(val);
node->next_ = head_;//插入的结点的后继指向头结点
node->prev_ = head_->prev_;//插入结点的前驱指向原本头结点的前驱
head_->prev_->next_ = node;//原本头节点的前驱的后继指向新结点
head_->prev_ = node;//头节点的前驱指向新结点
//Insert(end(), val);
}
void PopBack()
{
head_->prev_->prev_->next_ = head_;
head_->prev_ = head_->prev_->prev_;
//Erase(end());
}
void PushFront(const T& val)
{
PNode node = new Node(val);
node->next_ = head_->next_;
node->prev_ = head_;
head_->next_->prev_ = node;
head_->next_ = node;
//Insert(begin(),val);有了insert()可以直接
}
void PopFront()
{
head_->next_->next_->prev_ = head_;
head_->next_ = head_->next_->next_;
//Erase(begin())
}
iterator Insert(iterator pos, const T& val)
{
PNode node = new Node(val);
node->next_ = pos.pnode_;
node->prev_ = pos.pnode_->prev_;
pos.pnode_->prev_ ->next_= node;
pos.pnode_->prev_ = node;
return iterator(node);
}
iterator Erase(iterator pos)
{
if (pos != end())
{
PNode savenode = pos.pnode_->next_;
pos.pnode_->prev_->next_ = pos.pnode_->next_;
pos.pnode_->next_->prev_ = pos.pnode_->prev_;
delete pos.pnode_;
return iterator(savenode);
}
return pos;
}
void Resize(size_t n,const T& val = T())
{
PNode node = new Node;
size_t size = Size();
if (n < size)
{
for (size_t i = 0; i < size - n; ++i)
{
PopBack();
}
}
else
{
for (size_t i = 0; i < n; ++i)
{
PushBack(val);
}
}
}
//迭代器的相关接口
iterator begin()
{
return iterator(head_->next_);
}
iterator end()
{
return iterator(head_);
}
//下面是只读对象调用
const_iterator cbegin() const
{
return const_iterator(head_->next_);
}
const_iterator cend() const
{
return const_iterator(head_);
}
List& operator=(const List lst)
{
Swap(lst);
return *this;
}
~List()
{
if (head_)
{
PNode tmp = head_->next_;
PNode next;
while (tmp != head_)
{
next = tmp->next_;
delete tmp;
tmp = next;
}
delete head_;
head_ = nullptr;
}
}
//工具
void Swap(List& list)
{
swap(head_, list.head_);
}
size_t Size()
{
iterator move = begin();
iterator lend = end();
size_t len = 0;
while (move != lend)
{
++len;
++move;
}
return len;
}
private:
//头节点的指针
PNode head_;
//ListNode的构造是全缺省的可以不用传参数
void Create()
{
head_ = new Node;
head_->prev_ = head_->next_ = head_;
}
};
vector | list | |
底层结构 |
动态顺序表,一段连续空间 |
带头结点的双向循环链表 |
随机访问 |
支持随机访问,访问某个元素效率O(1) |
不支持随机访问,访问某个元素效率O(N) |
插入和删除 |
任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空 间,拷贝元素,释放旧空间,导致效率更低 |
任意位置插入和删除效率高,不需要搬移元素,时间复杂度为 O(1) |
空 间 利 用 率 |
底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 |
底层节点动态开辟,小节点容易造成内存碎片,空间利用率低, 缓存利用率低 |
迭 代 器 |
原生态指针 |
对原生态指针(节点指针)进行封装 |
迭代 器 失 效 |
在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删 除时,当前迭代器需要重新赋值否则会失效 |
插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用 场 景 |
需要高效存储,支持随机访问,不关心插入删除效率 |
大量插入和删除操作,不关心随机访问 |