【C++】10. list
文章目录
- 一、list的介绍及使用
- 1、list的介绍
- 2、list对象的常用接口
- 1)constructor
- 2)Iterators
- 3)Capacity
- 4)Element access
- 4)Modifiers
- 5)Operations
- 6)list的排序问题
- 7)迭代器失效问题
- 二、list的模拟实现
- 1、模拟实现list
- 1)list.h
- 2)Test.cpp
- 2、list与vector的对比
一、list的介绍及使用
1、list的介绍
什么是list?根据之前学过的数据结构知识,在这里可以将list认为是带头双向循环链表。
list的文档介绍
使用STL的三个境界:能用,明理,能扩展 ,下面学习list,我们也是按照这个方法去学习。
2、list对象的常用接口
list中的接口比较多,此处类似,只需要掌握如何正确的使用,然后再去深入研究背后的原理,以达到可扩展的能力。以下为list中一些常见的重要接口。
1)constructor
构造函数(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<iostream>
#include<list>
using namespace std;int main()
{list<int> lt1;//默认构造list<int> lt2(10, 1);//构造n个vallist<int> lt3(5, 1);lt3=lt2;//赋值list<int> lt4(++lt2.begin(), --lt2.end());//迭代器区间构造//遍历list<int>::iterator it = lt2.begin();while (it != lt2.end()){cout << *it << " ";++it;}cout << endl;for (auto e : lt4){cout << e << " ";}cout << endl;return 0;
}
运行结果:
除此之外,C++11以后还支持一种初始化列表的方式来进行初始化,使用起来更加的简便,同样这种初始化生成的对象也支持各种接口。
如下:
#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt{ 1,2,3,4,5 };lt.push_back(6);for (auto e : lt){cout << e << " ";}cout << endl;return 0;
}
运行结果:
2)Iterators
这里可以暂时将迭代器理解成一个指针,该指针指向list中的某个节点。
函数声明 | 接口说明 |
---|---|
begin +end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
rbegin+ rend | 返回第一个元素的reverse_iterator(即end位置)+返回最后一个元素下一个位置的reverse_iterator(即begin位置) |
结构如图所示:

示例如下:
#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);//正向遍历auto it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;//反向遍历auto rit = lt.rbegin();while (rit != lt.rend()){cout << *rit << " ";++rit;}cout << endl;return 0;
}
运行结果:
注意:
- begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动。
- rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动。
3)Capacity
函数声明 | 接口说明 |
---|---|
empty | 检测list是否为空,是返回true,否则返回false |
size | 返回list中有效节点的个数 |
示例如下:
#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt(5, 1);cout << lt.empty() << endl;cout << lt.size() << endl;return 0;
}
运行结果:
4)Element access
函数声明 | 接口说明 |
---|---|
front | 返回list的第一个节点中值的引用 |
back | 返回list的最后一个节点中值的引用 |
示例如下:
#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);cout << lt.front() << endl;cout << lt.back() << endl;return 0;
}
运行结果:
4)Modifiers
函数声明 | 接口说明 |
---|---|
push_front | 在list首元素前插入值为val的元素 |
pop_front | 删除list中第一个元素 |
push_back | 在list尾部插入值为val的元素 |
pop_back | 删除list中最后一个元素 |
insert | 在 iterator position 位置中插入值为val的元素 |
erase | 删除 iterator position位置的元素 |
swap | 交换两个list中的元素 |
clear | 清空list中的有效元素 |
示例如下:
#include<iostream>
#include<list>
using namespace std;int main()
{list<int> lt;lt.push_front(1);//头插lt.push_front(2);lt.push_front(3);lt.push_front(4);lt.push_back(1);//尾插lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.pop_front();//头删lt.pop_back();//尾删list<int> lt1(5, 1);//第3个数据前插入数据auto it = lt1.begin();int k = 2;while (k--){it++;}lt1.insert(it, 10);//删除指定的数据int x = 0;cin >> x;it = find(lt.begin(), lt.end(), x);if (it != lt.end()){lt.erase(it);}list<int> lt2(5, 1);list<int> lt3(5, 2);lt2.swap(lt3);//交换数据list<int> lt4(5, 1);lt4.clear();//清空数据return 0;
}
5)Operations
函数声明 | 接口说明 |
---|---|
splice | 将元素从一个链表转移到另一个链表 |
remove | 删除具有特定值的元素 |
unique | 删除重复值 |
merge | 合并已排序的链表 |
sort | 对容器中的元素进行排序 |
示例如下:
void test1()
{list<int> lt{1, 8, 6, 2, 5, 4};//升序lt.sort();for (auto e : lt){cout << e << " ";}cout << endl;//降序lt.sort(greater<int>());for (auto e : lt){cout << e << " ";}cout << endl;list<double> first{ 3.1, 2,2, 2.9 }, second{ 3.7, 7.1, 1.4 };first.sort();second.sort();first.merge(second);//合并链表for (auto e : first){cout << e << " ";}cout << endl;for (auto e : second){cout << e << " ";}cout << endl;
}
运行结果:
void test2()
{list<int> lt{ 1,20,3,5,5,4,5,6 };//先排序再去重效率更高lt.sort();lt.unique();//去重lt.remove(20);//删除指定值for (auto e : lt){cout << e << " ";}cout << endl;
}
运行结果:
void test3()
{list<int> mylist1{ 1,2,3,4 }, mylist2{ 10,20,30 };mylist1.splice(++mylist1.begin(), mylist2);//转移链表for (auto e : mylist1){cout << e << " ";}cout << endl;//调整当前链表节点的顺序list<int> lt{ 1,2,3,4,5,6 };for (auto e : lt){cout << e << " ";}cout << endl;int x = 0;cin >> x;auto it = find(lt.begin(), lt.end(), x);if (it != lt.end()){lt.splice(lt.begin(), lt, it, lt.end());// it~lt.end()是要转移的节点}for (auto e : lt){cout << e << " ";}cout << endl;
}
运行结果:
6)list的排序问题
对于链表的排序而言,若直接对链表排序,那么效率是很低的,一般都是将其拷贝给vector进行排序,排序好后再将其拷贝回链表。我们来测试一下这种排序方式和直接对链表排序所消耗的时间对比。
void test_op2()
{srand(time(0));const int N = 1000000;list<int> lt1;list<int> lt2;for (int i = 0; i < N; i++){auto e = rand() + i;lt1.push_back(e);lt2.push_back(e);}int begin1 = clock();//拷贝给vectorvector<int> v(lt2.begin(), lt2.end());//对vector排序sort(v.begin(), v.end());//拷贝回lt2lt2.assign(v.begin(), v.end());int end1 = clock();int begin2 = clock();//直接对链表lt1排序lt1.sort();int end2 = clock();printf("list copy vector sort copy list sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}
运行结果:
可以看到采用第一种排序的方式效率大大提高了。
7)迭代器失效问题
前面提到过可将迭代器暂时理解成类似指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。
void TestListIterator1()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> lt(array, array + sizeof(array) / sizeof(array[0]));auto it = lt.begin();while (it != lt.end()){// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值lt.erase(it);++it;//非法行为}
}// 改正
void TestListIterator2()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> lt(array, array + sizeof(array) / sizeof(array[0]));auto it = lt.begin();while (it != lt.end()){it = lt.erase(it); }
}
二、list的模拟实现
1、模拟实现list
要模拟实现list,必须要熟悉list的底层结构以及其接口的含义,通过上面的学习,这些内容已基本掌握,现在我们来模拟实现list。
1)list.h
#pragma once
#include<assert.h>
#include<iostream>
#include<algorithm>
#include<list>
#include<string>
#include<vector>
using namespace std;namespace zsy
{//节点类template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data = T()):_data(data),_next(nullptr),_prev(nullptr){}};//迭代器类 ->迭代器的接口template<class T, class Ref, class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;//Ref:引用类型 T&/const T&; Ptr:指针类型 T*/const T*Node* _node;list_iterator(Node* node):_node(node){}Ref& operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){_node = _node->_next;//++return *this;//返回++后的迭代器}Self& operator--(){_node = _node->_prev;return *this;}//后置++ Self operator++(int){Self tmp(*this);//记录++前_node = _node->_next;//++return tmp;//返回++前的迭代器}Self operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{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;iterator begin(){return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){//空初始化(只有哨兵位)_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}//构造:初始化列表list(initializer_list<T> il){empty_init();for (auto& e : il){push_back(e);}}//拷贝构造 lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}//赋值 l3=l2(两个已存在的对象)list<T>& operator=(list<T> lt){swap(lt);return *this;}void clear(){auto it = begin();while (it != end()){it = erase(it);}}~list(){clear();delete _head;_head = nullptr;}void push_back(const T& x){/* Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;++_size; */insert(end(), x);}void push_front(const T& x){insert(begin(), x);}iterator insert(iterator pos, const T& x){Node* newnode = new Node(x);Node* cur = pos._node;Node* prev = cur->_prev;newnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;return newnode;}iterator erase(iterator pos){assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;return next;}void pop_back(){erase(end());}void pop_front(){erase(begin());}size_t size() const{return _size;}bool empty() const{return _size == 0;}private:Node* _head;//哨兵位size_t _size;};struct AA{int _a1 = 1;int _a2 = 2;};//打印容器template<class Container>void print_container(const Container& con){//typename Container::const_iterator it = con.begin();auto it = con.begin();while (it != con.end()){cout << *it << " ";++it;}cout << endl;}}
2)Test.cpp
#include"list.h"namespace zsy
{void test1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.insert(lt.end(), 5);lt.erase(lt.begin());auto it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;cout << lt.size() << endl;cout << lt.empty() << endl;}void test2(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);auto it1 = ++lt1.begin();auto it2 = lt1.begin()++;auto it3 = --lt1.end();auto it4 = lt1.end()--;cout << *it1 << endl;cout << *it2 << endl;cout << *it3 << endl;cout << *it4 << endl;print_container(lt1);}void test3(){list<AA> lta;//结构体类型AAlta.push_back(AA());lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());list<AA>::iterator ita = lta.begin();while (ita != lta.end()){cout << (*ita)._a1 << " " << (*ita)._a2 << endl;//-> (为了可读性省略了一个->)//cout << ita.operator->()->_a1 << " " << ita.operator->()->_a2 << endl;cout << ita->_a1 << " " << ita->_a2 << endl;++ita;}cout << endl;}void test4(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);//insert以后迭代器不失效list<int>::iterator it = lt.begin();lt.insert(it, 10);*it += 20;print_container(lt);//erase以后迭代器失效//删除偶数it = lt.begin();while (it != lt.end()){if (*it % 2 == 0){it = lt.erase(it);}else{++it;}}print_container(lt);}void test5(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);//拷贝构造print_container(lt1);print_container(lt2);list<int> lt3;lt3.push_back(10);lt3.push_back(20);lt3.push_back(30);lt3.push_back(40);lt3 = lt1;//赋值print_container(lt1);print_container(lt3);}void test6(){//初始化列表list<int> lt1({ 1,2,3,4 });//直接构造list<int> lt2{ 5,6,7,8 };//隐式类型转换print_container(lt1);print_container(lt2);}
}int main()
{//zsy::test1();//zsy::test2();//zsy::test3();//zsy::test4();//zsy::test5();zsy::test6();return 0;
}
2、list与vector的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,其主要不同如下:
vector | list | |
---|---|---|
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |