list学习总结
一. list是什么?
1.list概述
list
是可以在常数时间范围
内在任意位置进行插入和删除
的序列式容器
,并且该容器可以前后双向迭代
。list
的底层是双向链表
结构,双向链表中每个元素存储在互不相关的独立节点
中,在节点中通过指针
指向前一个
元素和后一个
元素。list
与forward_list
非常相似,最主要的不同在于forward_list
是单链表
,只能单向迭代
。
2.list相对其他容器的优缺点
- 与其他的
序列式容器
相比(array,vector,deque)
,list
通常在任意位置
进行插入、移除
元素的执行效率更好,一般在常数时间
内。 list和forward_list
最大的缺陷
是不支持任意位置的随机访问
。比如:要访问list
的第6个
元素,必须从已知的位置(比如头部或者尾部)
迭代到该位置,在这段位置上迭代需要线性的时间
开销;list
还需要一些额外的空间
,以保存每个节点的相关联信息(指向前一个结点或后一个结点的指针域)
。list
相对于vector
还有一个好处就是空间的利用率比较高。每删除或者插入
一个元素,就配置或释放
一个空间。
3.list的数据结构
SGI
版本的list
不仅仅是一个双向链表
,而且还是一个环状双向链表
,也就是一条双向循环链表
。
注:本图截取自《STL源码剖析一书》
二.list的使用(常见使用接口)
1.list的常见构造函数
list()
:构造空的list
list (size_type n, const value_type& val = value_type())
:构造一个含有n
个val
值的list
list (const list& x)
:拷贝构造函数list (InputIterator first, InputIterator last)
:迭代器区间构造
void test1()
{
list<int> l1;//空构造
list<int> l2(4, 10);//构造4个值为10的结点
list<int> l3(l2);//拷贝构造
list<int> l4(l2.begin(), l2.end());//使用l2的迭代器区间[begin,end)构造
//C++11语法糖遍历
for (auto& e : l4)
{
cout << e << " ";
}
cout << endl;
int arr[] = { 1, 2, 3, 4 };//使用数组为迭代器区间构造
list<int> l5(arr, arr + (sizeof(arr) / sizeof(int)));
//迭代器遍历
list<int>::iterator it = l5.begin();
while (it != l5.end())
{
cout << *it << " ";
++it;
}
cout << endl;
}
2.list常见的iterator
2.1 常见接口
begin()
:返回第一个元素的迭代器end()
:返回最后一个元素下一个位置的迭代器rbegin()
:返回第一个位置的反向迭代器,既end() - 1
rend()
:返回最后一个元素下一个位置的reverse_iterator
,即end()
位置cbegin() (C++11)
:返回第一个元素的const_iterator
cend() (C++11)
:返回最后一个元素下一个位置的const_iterator
2.2 迭代器在list的位置
begin与end
为正向
迭代器,对迭代器执行++
操作,迭代器向后
移动rbegin()与rend()
为反向
迭代器,对迭代器执行++
操作,迭代器向前
移动cbegin与cend
为const
的正向
迭代器,与begin和end
不同的是:该迭代器指向节点中的元素值不能修改
crbegin与crend
为cons
t的反向
迭代器,与rbegin和rend
不同的是:该迭代器指向节点中的元素值不能修改
2.3 iterator的使用
void test2()
{
int arr[] = { 1, 2, 3, 4 };
list<int> l1(arr, arr + (sizeof(arr) / sizeof(int)));
//正向迭代器 1 2 3 4
list<int>::iterator it = l1.begin();
while (it != l1.end())
{
//*it *= 2;可以修改
cout << *it << " ";
++it;
}
cout << endl;
//正向const迭代器 1 2 3 4(但是*it不能修改)
list<int>::const_iterator cit = l1.cbegin();
while (cit != l1.cend())
{
//*cit *= 2; 不能修改
cout << *cit << " ";
++cit;
}
cout << endl;
//反向迭代器 4 3 2 1
list<int>::reverse_iterator rit = l1.rbegin();
while (rit != l1.rend())
{
//*rit *= 2; 可以修改
cout << *rit << " ";
++rit;
}
cout << endl;
//反向const迭代器 4 3 2 1
list<int>::const_reverse_iterator crit = l1.crbegin();
while (crit != l1.crend())
{
//*crit *= 2; 不可以修改
cout << *crit << " ";
++crit;
}
cout << endl;
}
3.list的size和empty接口
bool empty() const
:检测list是否为空,是返回true,否则返回falsesize_t size() const
:返回list中有效节点的个数
void test3()
{
int arr[] = { 1, 2, 3, 4 };
list<int> l1(arr, arr + (sizeof(arr) / sizeof(int)));
int size = l1.size();
//输出l1的大小
cout << size << endl;
//判断l1是否为空,不为空遍历打印
if (l1.empty())
{
cout << "list为空" << endl;
}
else
{
for (auto e : l1)
{
cout << e << " ";
}
cout << " ";
}
}
4.list获取数据元素(front&&back)
reference front()
:返回list第一个结点值的引用const_reference front() const
:返回list的第一个节点中值的const引用reference back()
:返回list的最后一个节点中值的引用const_reference back() const
:返回list的最后一个节点中值的const引用
void test4()
{
int arr[] = { 1, 2, 3, 4 };
list<int> l1(arr, arr + (sizeof(arr) / sizeof(int)));
l1.front() = 10;//将l1中的第一个数据改为10
const int& x = l1.back();//获取l1中最后一个结点值的const引用
//x = 10;const引用不可改变
cout << x << endl;
}
5.list的增删改查接口
5.1 接口说明
-
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的结点 -
void insert (iterator position, size_type n, const value_type& val)
:在position位置插入值为val的n个结点 -
void insert (iterator position, InputIterator first, InputIterator last)
:在list position位置插入 [first, last)区间中元素 -
iterator erase (iterator position)
:删除position位置上的结点 -
iterator erase (iterator first, iterator last)
: 删除list中[first, last)区间中的元素 -
void swap (list& x)
:交换两条list中的结点 -
void resize (size_type n, value_type val = value_type())
:将list中有效元素个数改变 到n个,多出的元素用val 填充 -
void clear()
:清空list中的有效元素
下边3个接口为C++11新特性的接口:
template <class... Args> void emplace_front (Args&&... args)
:在list中的第一个结点前根据参数构造新的结点template <class... Args> void emplace_back (Args&&... args)
:在list最后一个结点后根据参数直接构造新的结点template <class... Args> iterator emplace( const_iterator position, Args&&... args)
:在list的任意位置根据参数直接构造新的结点
/*测试emplace_back、emplace_front、emplace */
class Date {
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{
cout << "Date(int, int, int):" << this << endl;
}
Date(const Date& d)
: _year(d._year)
, _month(d._month)
, _day(d._day)
{
cout << "Date(const Date&):" << this << endl;
}
private:
int _year;
int _month;
int _day;
};
//push_back尾插:先构造好元素,然后将元素拷贝到节点中,插入时先调构造函数,再调拷贝构造函数
//emplace_back尾插:先构造节点,然后调用构造函数在节点中直接构造对象
//emplace_back比push_back更高效,少了一次拷贝构造函数的调用
void test8()
{
list<Date> l;
Date d(2018, 10, 20);
l.push_back(d); //构造--拷贝构造
l.emplace_back(2018, 10, 21); //插入时直接构造
l.emplace_front(2018, 10, 19); //插入时直接构造
}
5.2 测试功能
/*测试push_front、push_back、pop_front、pop_back*/
void test5()
{
int arr[] = { 1, 2, 3, 4 };
list<int> l1(arr, arr + (sizeof(arr) / sizeof(int)));
l1.push_front(0);
l1.push_back(5);
PrintList(l1);//0 1 2 3 4 5
l1.pop_front();
l1.pop_back();
PrintList(l1);//1 2 3 4
}
/*测试insert、erase*/
void test6()
{
int arr[] = { 1, 2, 3, 4 };
list<int> l1(arr, arr + (sizeof(arr) / sizeof(int)));
//获得值为3的迭代器
list<int>::iterator pos = find(l1.begin(), l1.end(), 3);
//在pos前插入值为0的结点
l1.insert(pos, 0);
PrintList(l1);//1 2 0 3 4
//在pos前插入5个值为8的结点
l1.insert(pos, 2, 8);
PrintList(l1);//1 2 0 8 8 3 4
//在pos前插入[v.begin(), v.end)区间中的结点
vector<int> v(2, 7);
l1.insert(pos, v.begin(), v.end());
PrintList(l1);//1 2 0 8 8 7 7 3 4
//删除pos位置的元素
l1.erase(pos);
PrintList(l1);//1 2 0 8 8 7 7 4
//删除迭代器区间的值
l1.erase(l1.begin() , l1.end());
PrintList(l1);//list为空
}
/* 测试resize、swap、clear */
void test7()
{
int arr[] = { 1, 2, 3, 4 };
list<int> l1(arr, arr + (sizeof(arr) / sizeof(int)));
PrintList(l1);//1 2 3 4
//将l1中元素个数增加到10个,多出的元素用默认值填充
//如果list中放置的是内置类型,默认值为0
//如果list中放置自定义类型元素,调用缺省构造函数
l1.resize(6);
PrintList(l1);//1 2 3 4 0 0
//将l1中的元素增加到8个,多出的元素用8来填充
l1.resize(8, 8);
PrintList(l1);//1 2 3 4 0 0 8 8
//将l1中的元素减少到5个
l1.resize(5);
PrintList(l1);//1 2 3 4 0
// 用vector中的元素来构造list
vector<int> v{ 4, 5, 6 };
list<int> l2(v.begin(), v.end());
PrintList(l2);//4 5 6
//交换l1和l2的元素
l1.swap(l2);
PrintList(l1);//4 5 6
PrintList(l2);//1 2 3 4 0
// 将l2中的元素清空
l2.clear();
cout << l2.size() << endl;//0
PrintList(l2);//空
}
6.list的迭代器失效问题
vector的insert和erase
迭代器都会失效,vector在insert
时会出现扩容
的问题,所以使用迭代器时可能会出现野指针问题,vector在erase
时由于vs的检查机制,虽然将pos
迭代器位置的数据删除后,会将后边的元素移动到前边,不会存在野指针,但是vs编译器
会检查出来,这个迭代器已经失效
。相对而说,list
的insert
不会失效,因为list不存在扩容
的问题,但是erase
也会存在失效,失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。例如:(1-2-3,删除2之后,1-3,迭代器依旧指向2,但是2已经被删除了,所以会出现类似野指针的问题)
void test9()
{
int arr[] = { 1, 2, 3, 4 };
list<int> l1(arr, arr + (sizeof(arr) / sizeof(int)));
list<int>::iterator it = l1.begin();
while (it != l1.end())
{
// erase()函数执行后,it所指向的节点已被删除
//因此it无效,在下一次使用it时,必须先给其赋值
//l1.erase(it);
//++it;这样做编译器报错迭代器失效
/*可以这么修改*/
//it = l1.erase(it);//erase删除之后,返回下一个位置的迭代器
l1.erase(it++);//也可以这么写,相当于it = l1.erase(it);
}
}
三.list的模拟实现
1.list的结点类
list本身
和list的结点
是两个不同的结构,要实现list
,必须先要实现它的结点类
。要实现的是一个双向带头循环链表
,所以结点设计必须包括三个域,既prev指针域(指向前一个几点)、next指针域(指向后一个结点)、data(数据域)
。
template<class T>
//为什么不用struct而用class?
//如果不使用访问限定符使用struct,struct默认访问限定符为public
//如果使用访问此限定符使用class,class默认访问限定符为class
struct ListNode
{
//构造函数(T()相当于是一个匿名对象)
ListNode(const T& data = T())
:_data(data)
,_next(nullptr)
,_prev(nullptr)
{}
//不需要重载析构函数,直接使用默认生成的析构即可,因为成员变量只是3个指针
T _data; //数据域
ListNode<T>* _next;//指向前一个结点的指针
ListNode<T>* _prev;//指向回一个结点的指针
};
2.list的迭代器
list
不能像vector
一样以一个原生指针
作为迭代器,因为它的结点不能保证在存储空间中连续存在
。list
迭代器必须要能够指向list的结点
,并且有能力进行正确的递增、递减、取值、成员取用
等操作。递增操作时指向下一个结点
,递减操作时指向下一个操作
,取值时取的是结点的数据值
,成员取用的是结点的成员
。所以我们应该讲上述的成员和操作封装在一个类
中。我们可以将原生态指针进行封装
,因迭代器的使用形式与指针完全相同
,因此,在自定义的类中必须实现以下方法:
- 指针可以
解引用
,迭代器的类中必须重载operator*()
- 指针可以
++
向后移动,迭代器类中必须重载operator++()
与operator++(int)
和operator--()和operator--(int)
- 迭代器需要进行是否相等的比较,因此还需要重载
operator==()与operator!=()
/**
* 模拟实现list的迭代器类
*/
template<class T>
struct ListIterator
{
typedef ListNode<T> Node;
typedef ListIterator<T> iterator;
//构造
ListIterator(Node* node)
:_node(node)
{}
//拷贝构造
ListIterator(const iterator& i)
:_node(i._node)
{}
/**
* 重载迭代器的解引用、++、!=、==、--等
*/
// *it
T& operator*()
{
return _node -> _data;
}
// ++it
iterator operator++()
{
_node = _node -> _next;
return *this;
}
// it++
iterator operator++(int)
{
iterator tmp(*this);
_node = _node -> _next;
return tmp;
}
// --it
iterator operator--()
{
_node = _node -> _prev;
return *this;
}
// it--
iterator operator--(int)
{
iterator tmp(*this);
_node = _node -> _prev;
return tmp;
}
// it1 != it2
bool operator!=(const iterator& it)
{
return _node != it._node;
}
// it1 == it2
bool operator==(const iterator& it)
{
return _node == it._node;
}
Node* _node;
};
3.list构造函数
list
的常见构造函数
在上边的已经提及,可以根据其功能模拟实现它:
//迭代器定义为和库一样的名字可以支持语法糖
typedef ListIterator<T> iterator;
/**
* 构造函数的模拟实现
*/
//无参构造函数,构造一条只有头结点的空链表
List()
:_head(new Node)
{
_head -> _next = _head;
_head -> _prev = _head;
}
//构造一条含有n个值为data的结点的链表
List(int n, const T& data = T())
:_head(new Node)
{
_head -> _next = _head;
_head -> _prev = _head;
for(int i = 0;i < n;i++)
{
PushBack(data);
}
}
//迭代器区间构造
template<class InputIterator>
List(InputIterator first,InputIterator last)
:_head(new Node)
{
_head -> _next = _head;
_head -> _prev = _head;
while(first != last)
{
PushBack(*first);
++first;
}
}
//拷贝构造(现代方法)
List(List<T>& l)
:_head(new Node)
{
_head -> _next = _head;
_head -> _prev = _head;
List<T> tmp(l.begin(),l.end());
swap(_head, tmp._head);
}
//赋值运算符重载
List<T>& operator=(List<T>& l)
{
if(this != &l)
{
List<T> tmp(l);
swap(_head, l._head);
}
return *this;
}
//析构函数
~List()
{
Clear();
delete _head;
_head = nullptr;
}
4.list的元素操作
list
所提供的元素操作有很多,我们只需要模拟实现其核心的接口
即可。例如,push_front、push_back、earse、insert
等等。
/**
* List Modify
*/
void PushBack(const T& data)
{
Node* tail = _head -> _prev;
Node* newnode = new Node(data);
//_head tail newnode
tail -> _next = newnode;
newnode -> _prev = tail;
newnode -> _next = _head;
_head -> _prev = newnode;
}
void PopBack()
{
Node* del = _head -> _prev;
if(del != _head)
{
_head -> _prev = del -> _prev;
del -> _prev -> _next = _head;
delete del;
del = nullptr;
}
}
void PushFront(const T& data)
{
Node* first = _head -> _next;
Node* newnode = new Node(data);
// _head newnode first
_head -> _next = newnode;
newnode -> _prev = _head;
newnode -> _next = first;
first -> _prev = newnode;
}
void PopFront()
{
Node* second = _head -> _next -> _next;
_head -> _next = second;
delete second -> _prev;
second -> _prev = _head;
}
void Insert(iterator pos, const T& data)
{
Node* cur = pos._node;
Node* pre = cur -> _prev;
Node* newnode = new Node(data);
// pre newnode cur
cur -> _prev = newnode;
newnode -> _next = cur;
newnode -> _prev = pre;
pre -> _next = newnode;
}
void Erase(iterator pos)
{
Node* del = pos._node;
Node* next = del -> _next;
Node* pre = del -> _prev;
pre -> _next = next;
next -> _prev = pre;
delete del;
del = nullptr;
}
/**
* List Access
*/
T& Front()
{
return _head -> _next -> _data;
}
const T& Front() const
{
return _head -> _next -> _data;
}
T& Back()
{
return _head -> _prev -> _data;
}
const T& Back() const
{
return _head -> _prev -> _data;
}
/**
* List capacity
*/
//链表的长度
size_t Size() const
{
size_t count = 0;
Node* cur = _head -> _next;
while(cur != _head)
{
++count;
cur = cur -> _next;
}
return count;
}
// 判断链表是否为空
bool Empty()
{
return _head -> _next == _head;
}
//调整链表的有效长度
void Resize(int newsize, const T& data = T())
{
int oldsize = Size();
if(newsize < oldsize)
{
for(int i = 0;i < (oldsize - newsize);i++)
{
PopBack();
}
}
else
{
for(int i = 0;i < (newsize - oldsize);i++)
{
PushBack(data);
}
}
}
//清空链表
void Clear()
{
Node* cur = _head -> _next;
while(cur != _head)
{
_head = cur -> _next;
delete cur;
cur = _head;
}
_head -> _next = _head;
_head -> _prev = _head;
}
/**
* 迭代器
*/
iterator begin()
{
return iterator(_head -> _next);
}
iterator end()
{
return iterator(_head);
}
5.源代码
注:自己实现的源代码位于我的github:https://github.com/hansionz/Cpp_Code/tree/master/List