文章目录
- list的相关介绍
- list的使用
- list构造
- list iterator的使用
- list capacity
- list element access
- list modifiers
- list迭代器失效
- sort问题
- list模拟实现的完整代码
- list与vector的对比
- list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向
其前一个元素和后一个元素。- list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高
效。- 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率
更好。- 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list
的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间
开销;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 |
构造函数的基本使用
#include
#include
using namespace std;
int main()
{
list<int> lt1;
list<int> lt2(5, 100);
list<int> lt3(lt2.begin(), lt2.end());
list<int> lt4(lt2);
for (auto ch : lt2)
{
cout << ch << " ";
}
cout << endl;
for (auto ch : lt3)
{
cout << ch << " ";
}
cout << endl;
for (auto ch : lt4)
{
cout << ch << " ";
}
cout << endl;
return 0;
}
构造函数的模拟实现
简单介绍一下下面的代码,empty_init
是创造个头节点(哨兵位),因为每个构造函数都要注意头结点的问题,所以为了方便写个函数。
void empty_init()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
list()//默认构造
{
empty_init();
}
//传区间构造
template <class Iterator>
list(Iterator first, Iterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
// lt2(lt1)
/*list(const list& lt)
{
empty_init();
for (auto e : lt)
{
push_back(e);
}
}*/
//现代的构造函数写法
void swap(list<T>& tmp)
{
std::swap(_head, tmp._head);
}
//这里传引用并不会出现问题,因为下面的区间构造是值拷贝
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。
函数声明 | 接口说明 |
begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置 |
迭代器的相关使用
int main()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
it++;
}
cout << endl;
list<int>::reverse_iterator rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
rit++;
}
cout << endl;
return 0;
}
list迭代器的模拟实现
这里是将迭代器做了个封装,因为list的每个节点的地址并不一定是是连续的(大概率是不连续的)所以对list++,–等操作实现不了。对它进行封装就能实现了。
【注意】
- begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
- rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
//typedef __list_iterator iterator;
//typedef __list_iterator const_iterator;
//模板有三个参数,看着很麻烦,但是对代码简洁方便起了很大作用。
//T是list的储存类型,Ref是控制节点的值是否能修改,Ptr控制返回的地址是否能修改
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_node<T> node;
typedef __list_iterator<T, Ref, Ptr> iterator;
node* _node;//成员变量
//构造函数将节点初始化
__list_iterator(node* n)
:_node(n)
{
}
Ref operator*()
{
return _node->_date;
}
//->最后返回的是date的地址
Ptr operator->()
{
return &_node->_date;
}
//前置加加
iterator& operator++()
{
_node = _node->_next;
return *this;
}
iterator operator++(int)//后置加加标志
{
iterator tmp(*this);
_node = _node->_next;
return tmp;
}
//前置减减
iterator& operator--()
{
_node = _node->_prev;
return *this;
}
iterator operator--(int)//后置减减标志
{
iterator tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const iterator& s)
{
return _node != s._node;
}
bool operator==(const iterator& s)
{
return _node == s._node;
}
};
下面是对操作符重载->,进行的应用
struct custom
{
int a;
int b;
custom(int _a = 0, int _b = 0)
:a(_a)
, b(_b)
{}
};
void print_list(const list<custom>& lt)
{
list<custom>::const_iterator it = lt.begin();
while (it != lt.end())
{
//也可以写成(*it).a
//这里本应该是it->->a,为了增强可读性,变成了一个。
cout << it->a << ":" << it->b << endl;
//本体是it.operator->()->a
//it.operator->()是等于custom*
//it.operator++(0)是一个意思
++it;
}
cout << endl;
}
函数声明 | 接口说明 |
empty | 检测list是否为空,是返回true,否则返回false |
size | 返回list中有效节点的个数 |
int main()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
list<int> lt1;
cout << lt.size()<<endl;
cout << lt1.size() << endl;
cout << lt.empty() << endl;
cout << lt1.empty() << endl;
return 0;
}
函数声明 | 接口说明 |
front | 返回list的第一个节点中值的引用 |
back | 返回list的最后一个节点中值的引用 |
int main()
{
std::list<int> mylist;
mylist.push_back(77);
mylist.push_back(22);
// now front equals 77, and back 22
mylist.front() -= mylist.back();
std::cout << "mylist.front() is now " << mylist.front() << '\n';
return 0;
}
函数声明 | 接口说明 |
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中的有效元素 |
int main()
{
list<int> lt;
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
lt.push_front(10);
lt.push_back(100);
for (auto ch : lt)
{
cout << ch << " ";
}
cout << endl;
lt.pop_back();
lt.pop_front();
for (auto ch : lt)
{
cout << ch << " ";
}
cout << endl;
lt.insert(++lt.begin(),100);
lt.erase(--lt.end());
for (auto ch : lt)
{
cout << ch << " ";
}
cout << endl;
list<int> lt1(3, 100);
lt.swap(lt1);
for (auto ch : lt)
{
cout << ch << " ";
}
cout << endl;
lt.clear();
return 0;
}
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节
点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代
器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
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);
}
}
这里问题就是std中已经有了sort,为什么list中又写了一个sort。
理论而言,模板语法上可以传任意类型参数,但是内部使用迭代器对迭代器有要求
list的迭代器是双向迭代器,而std中的迭代器是随机迭代器,
在随机迭代器当中使用的是快排,源码中使用了减法,这是双向迭代器没有的,
所以也就导致list当中有自己的sort。
在这里举个例子,下面是双向迭代器,可以使用,双向迭代器或者随机迭代器,这里随机迭代器是一个特殊的双向迭代器,
如果它是个随即迭代器就只能使用随机迭代器。
单向迭代器,就是三种迭代器都可以使用。
但是list中的sort并不常用,原因是它的效率不高,想要追求效率高,就把list到中的数据拷贝到vector当中,排完序在拷贝回来。这样的效率会有很大的提升。
for (auto e : lt1)
{
v.push_back(e);
}
sort(v.begin(), v.end());
size_t i = 0;
for (auto& e : lt1)
{
e = v[i++];
}
#pragma once
#include
using namespace std;
namespace zzm
{
template<class T>
struct list_node
{
list_node* _prev;
list_node* _next;
T _date;
list_node(const T& x = T())
:_prev(nullptr)
, _next(nullptr)
, _date(x)
{
}
};
template<class T, class Ref,class Ptr>
struct __list_iterator
{
typedef list_node<T> node;
typedef __list_iterator<T, Ref,Ptr> iterator;
node* _node;
__list_iterator(node* n)
:_node(n)
{
}
Ref operator*()
{
return _node->_date;
}
Ptr operator->()
{
return &_node->_date;
}
iterator& operator++()
{
_node = _node->_next;
return *this;
}
iterator operator++(int)//后置加加标志
{
iterator tmp(*this);
_node = _node->_next;
return tmp;
}
iterator& operator--()
{
_node = _node->_prev;
return *this;
}
iterator operator--(int)//后置减减标志
{
iterator tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const iterator& s)
{
return _node != s._node;
}
bool operator==(const iterator& s)
{
return _node == s._node;
}
};
template<class T>
class list
{
typedef list_node<T> node;
public:
typedef __list_iterator<T, T& ,T*> iterator;
typedef __list_iterator<T, const T&,const T*> const_iterator;
//typedef const iterator const>iterator;
//是行不通的,T*const ×
//应该是const T* √
/*list()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}*/
iterator begin()
{
//iterator it(_head->_next);
//return it;
return iterator(_head->_next);
//匿名构造,生命周期只有这一行
}
iterator end()
{
return iterator(_head);
}
const_iterator begin() const
{
//iterator it(_head->_next);
//return it;
return const_iterator(_head->_next);
//匿名构造,生命周期只有这一行
}
const_iterator end() const
{
return const_iterator(_head);
}
void empty_init()
{
_head = new node;
_head->_next = _head;
_head->_prev = _head;
}
list()
{
empty_init();
}
template <class Iterator>
list(Iterator first, Iterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
// lt2(lt1)
/*list(const list& lt)
{
empty_init();
for (auto e : lt)
{
push_back(e);
}
}*/
void swap(list<T>& tmp)
{
std::swap(_head, tmp._head);
}
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
// lt1 = lt3
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
void push_back(const T& a)
{
/*node* tail = _head->_prev;
node* new_node = new node(a);
_head->_prev = new_node;
tail->_next = new_node;
new_node->_next = _head;
new_node->_prev = tail;*/
insert(end(),a);
}
void push_front(const T& a)
{
insert(begin(), a);
}
void insert(iterator pos,const T&x)
{
node* cur = pos._node;
node* drev = cur->_prev;
node* new_node = new node(x);
new_node->_next = cur;
new_node->_prev = drev;
drev->_next = new_node;
cur->_prev = new_node;
}
void erase(iterator pos)
{
node* next = pos._node->_next;
node* prev = pos._node->_prev;
next->_prev = prev;
prev->_next = next;
delete pos._node;
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
//头节点是不清的
void clear()
{
iterator it = begin();
while (it != end())
{
//it = erase(it);
erase(it++);
//删除的是临时拷贝
}
}
private:
node* _head;
};
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
//(*it) *= 2;
cout << *it << " ";
++it;
}
cout << endl;
}
}
vector | list | |
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高, | 缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
最后:文章有什么不对的地方或者有什么更好的写法欢迎大家在评论区指出 |