目录
1.标准库中的list类
1.1.list类
list类的文档介绍:https://cplusplus.com/reference/list/list/?kw=list
注:
1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好。5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)6.如下图所示是list类的声明,list其实也是一个模板,是带头循环双向链表的模板,模板第一个参数是一个类(一般传的是需要存储数据的类型名,可以是int这种普通类型,也可以是string这种类),模板第二个参数也是一个类,是一个空间配置器(内存池),第二个参数给了缺省值,这个缺省值其实就是官方库给的vector,如果不想使用官方库的vector那么也可以自己写一个vector传过来
1.2.list类的常用接口说明
使用list前的说明:
1.使用list类需要包含list的头文件,代码为:#include<list>
2.list类是在std里面的,因此使用list类需要using namespace std将std展开(如果不展开每次使用list类需要std::list)
1. vector类对象的常见构造
函数名称
功能说明
list() 构造空的list list (size_type n, const value_type& val = value_type()) 构造的list中包含n个值为val的元素 list (const list& x) 拷贝构造函数 list (InputIterator first, InputIterator last) 用[first, last)区间中的元素构造list注:
1.list类的构造函数如下图所示
这里第一个构造函数给了一个参数该参数就是空间配置器(内存池)的参数,如果创建类对象的时候没有传,那么构造函数的时候还可以传,缺省值也与类声明那相同(allocator_type就是allocator<T>),如下图所示,因为该参数有缺省值,所以第一个这个构造函数其实就是上面的无参构造函数
2.list类对象的访问及遍历操作
方法 解释 迭代器 使用迭代器配合begin、end函数,来访问对象的字符串任意字符 范围for 使用范围for的语法进行遍历,本质上就是转换成了迭代器 注:
1.因为string类和vector类是指向一段连续的空间,所以可以用对象名+[ ]的形式访问数据。list类是链表,指向的空间是不连续的,如果使用对象名+[ ]的形式,要从前往后进行遍历效率很低。因此list类里面没有operator[ ]运算符重载函数,不能使用对象名+[ ]的形式访问数据,这里可以看出使用迭代器进行数据访问才是真正通用的,迭代器的访问如下图所示
这里while循环里面不能使用it<lt.end,因为list是带头循环双向链表,数据存储空间不是连续的,里面数据的内存地址都是是随机的,如下图所示,所以只能使用it!=lt.end来作为条件判断
2.支持迭代器就支持范围for,范围for的使用代码如下图所示
3.list类迭代器:
迭代器类型 迭代器名称 使用的函数 说明 普通正向迭代器 iterator begin函数、end函数 用于接收普通对象的begin/end函数返回值。其中begin指向第一个位置,end指向最后一个位置的后一个位置 普通反向迭代器 reverse_iterator rbegin函数、rend函数 用于接收普通对象的rbegin/rend函数返回值。其中rbegin指向最后一个位置,rend指向第一个位置的前一个位置 const修饰的正向迭代器 const_iterator begin函数、end函数 用于接收const修饰对象的begin/end函数返回值。其中begin指向第一个位置,end指向最后一个位置的后一个位置 const修饰的反向迭代器 const_reverse_iterator rbegin函数、rend函数 用于接收普通对象的rbegin/rend函数返回值。其中rbegin指向最后一个位置,rend指向第一个位置的前一个位置 注:
1.普通正向迭代器和普通反向迭代器的使用方式如下图所示
4.list类对象的容量操作
函数名称
功能说明
empty 检测list是否为空,是返回true,否则返回false size 返回list中有效节点的个数
5.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中的有效元素注:
1.头插push_front函数、尾插push_back函数使用方式如下图所示
2.头删pop_front函数、尾删pop_back函数使用方式如下图所示
6.list类对象的其他操作函数:
函数名称
功能说明
sort 对list中的元素进行排序(归并算法) reverse 对list中的元素进行逆置 merge 将两个list进行有序的合并 unique 对一个有序的list进行去重 remove 删除list中的某个节点 remove_if 删除list中满足某个条件的节点 splice 两个链表之间进行接合(连节点跟着转移) 注:
1.sort函数的函数声明如下图一所示,sort函数的使用方式如下图二所示
如果对大量数据进行排序不建议使用list,因为list进行排序效率是很低的,如下图一所示,在release发布版本下,对list类进行排序比对vector类进行排序要慢很多。
注意:
(1)算法库algorithm里的sort函数(快排算法)不支持对链表list进行排序,因为算法库algorithm里的sort函数的实现代码中要对迭代器相减,如下图二所示,list的迭代器不支持相减的操作。
(2)因为算法库algorithm里面的sort函数不支持list类,所以list类里面专门设计了一个sort函数
如果一定要对链表进行排序,可以先将链表list里的数据给到顺序表vector里面,对vector进行排序,然后再将vector里面的数据给到链表里面,这样比对链表直接排序效率更高,如下图所示
2.迭代器在实际中分为三类:
(1)单向迭代器:只支持++操作
(2)双向迭代器:支持++、支持--操作
(3)随机迭代器:支持++、支持--、支持+、支持-操作
使用单向迭代器的类:forward_list(单链表)、unorder_map、unordered_set
使用双向迭代器的类:list、map、set
使用随机迭代器的类:string、vector、deque
算法库algorithm里的sort函数声明如下图所示,其实这里从名字里面就暗示了只有传随机迭代器才能够使用。
如果迭代器类型名为BidirectionalIterator是在暗示需要传双向迭代器(随机迭代器满足双向迭代器的操作,所以可以传双向迭代器也就可以传随机迭代器)
如果迭代器类型名为RandomAccessIterator是在暗示需要传随机迭代器
注意:
(1)我们还经常见一种迭代器叫做InputIterator,这种迭代器是只写迭代器,这种迭代器可以传上面三种任意迭代器
(2)想要知道库里面的某个类是哪种迭代器,可以在前面给的网站中找到该类,然后在Member types栏中有说明,以list为例如下图所示
2.list类的模拟实现
2.1.vector类源代码解析
官方库中list链表节点的声明代码如下图所示
list类中的成员如下图一所示,其中link_type是list_node*,link_type是_list_node<T>,也就是说link_type是_list_node<T>*,是链表节点的指针类型
下图一所示是list的无参构造函数,其中empty_initialize()函数的定义如下图二所示,可以看出empty_initialize()函数是创建了一个头节点(哨兵位的头节点)
下图一所示是list的push_back函数,其中push_back函数复用了insert函数,insert函数如下图二所示,从push_back函数和insert函数可以看出list是一个双向循环链表
综上就可以看出list类模板是双向带头循环链表的模板
2.2.list类的模拟实现
test.cpp文件:
#include<iostream>
#include<list>
#include<vector>
#include<algorithm>
#include<time.h>
using namespace std;
#include"List.h"
namespace std
{
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
//list<int>::reverse_iterator rit = lt.rbegin();
auto rit = lt.rbegin();
while (rit != lt.rend())
{
cout << *rit << " ";
++rit;
}
cout << endl;
}
void test_list2()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.push_front(10);
lt.push_front(20);
lt.push_front(30);
lt.push_front(40);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.pop_back();
lt.pop_back();
lt.pop_front();
lt.pop_front();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list3()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_front(10);
lt.push_front(20);
lt.push_front(30);
lt.push_front(40);
lt.push_back(1);
lt.push_back(1);
lt.push_back(1);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.sort();
lt.unique();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void TestOP()
{
srand(time(0));
const int N = 10000000;
vector<int> v;
v.reserve(N);
list<int> lt1;
list<int> lt2;
for (int i = 0; i < N; ++i)
{
auto e = rand();
lt1.push_back(e);
lt2.push_back(e);
}
// 拷贝到vector排序,排完以后再拷贝回来
int begin1 = clock();
for (auto e : lt1)
{
v.push_back(e);
}
sort(v.begin(), v.end());
size_t i = 0;
for (auto& e : lt1)
{
e = v[i++];
}
int end1 = clock();
//直接排序
int begin2 = clock();
lt2.sort();
int end2 = clock();
printf("拷贝到vector排序再拷贝回链表:%d\n", end1 - begin1);
printf("直接链表排序:%d\n", end2 - begin2);
}
}
int main()
{
//std::test_list3();
//std::TestOP();
//bit::test_list2();
bit::test_list6();
return 0;
}
list.h文件:
#pragma once
#include <assert.h>
namespace bit
{
template<class T>
struct list_node
{
list_node<T>* _next;
list_node<T>* _prev;
T _data;
list_node(const T& val = T())
:_next(nullptr)
, _prev(nullptr)
, _data(val)
{}
};
template<class T, class Ref, class Ptr>
struct __list_iterator
{
typedef list_node<T> Node;
typedef __list_iterator<T, Ref, Ptr> self;
Node* _node;
__list_iterator(Node* node)
:_node(node)
{}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
//return &(operator*());
return &_node->_data;
}
self& operator++()
{
_node = _node->_next;
return *this;
}
self operator++(int)
{
self tmp(*this);
_node = _node->_next;
return tmp;
}
self& operator--()
{
_node = _node->_prev;
return *this;
}
self operator--(int)
{
self tmp(*this);
_node = _node->_prev;
return tmp;
}
bool operator!=(const self& it)
{
return _node != it._node;
}
bool operator==(const self& it)
{
return _node == it._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;
const_iterator begin() const
{
return const_iterator(_head->_next);
}
const_iterator end() const
{
return const_iterator(_head);
}
iterator begin()
{
return iterator(_head->_next);
//return _head->_next;
}
iterator end()
{
return iterator(_head);
}
list()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
//复用尾插的写法
/*list(const list<T>& lt)
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
for (auto e : lt)
{
push_back(e);
}
}*/
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
template <class InputIterator>
list(InputIterator first, InputIterator last)
{
empty_init();
while (first != last)
{
push_back(*first);
++first;
}
}
void swap(list<T>& lt)
{
std::swap(_head, lt._head);
}
// 现代写法
list(const list<T>& lt)
{
empty_init();
list<T> tmp(lt.begin(), lt.end());
swap(tmp);
}
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
}
void push_back(const T& x)
{
//Node* tail = _head->_prev;
//Node* newnode = new Node(x);
_head tail newnode
//tail->_next = newnode;
//newnode->_prev = tail;
//newnode->_next = _head;
//_head->_prev = newnode;
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
// 插入在pos位置之前
iterator insert(iterator pos, const T& x)
{
Node* newNode = new Node(x);
Node* cur = pos._node;
Node* prev = cur->_prev;
// prev newnode cur
prev->_next = newNode;
newNode->_prev = prev;
newNode->_next = cur;
cur->_prev = newNode;
return iterator(newNode);
}
iterator erase(iterator pos)
{
assert(pos != end());
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* next = cur->_next;
// prev next
prev->_next = next;
next->_prev = prev;
delete cur;
return iterator(next);
}
private:
Node* _head;
};
void print_list(const list<int>& lt)
{
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
//*it = 10; // 不允许修改
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list1()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
list<int>::iterator it = lt.begin();
while (it != lt.end())
{
*it = 20;
cout << *it << " ";
++it;
}
cout << endl;
print_list(lt);
}
struct AA
{
AA(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{}
int _a1;
int _a2;
};
void test_list2()
{
list<AA> lt;
lt.push_back(AA(1, 1));
lt.push_back(AA(2, 2));
lt.push_back(AA(3, 3));
lt.push_back(AA(4, 4));
list<AA>::iterator it = lt.begin();
while (it != lt.end())
{
//cout << (*it)._a1 << "-"<< (*it)._a2 <<" ";
cout << it->_a1 << "-" << it->_a2 << " ";
++it;
}
cout << endl;
}
void test_list3()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_front(1);
lt.push_front(2);
lt.push_front(3);
lt.push_front(4);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.pop_front();
lt.pop_front();
lt.pop_back();
lt.pop_back();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list4()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
// 要求在偶数的前面插入这个偶数*10
auto it1 = lt.begin();
while (it1 != lt.end())
{
if (*it1 % 2 == 0)
{
lt.insert(it1, *it1 * 10);
}
++it1;
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list5()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
// 删除所有的偶数
/*auto it1 = lt.begin();
while (it1 != lt.end())
{
if (*it1 % 2 == 0)
{
lt.erase(it1);
}
++it1;
}*/
auto it1 = lt.begin();
while (it1 != lt.end())
{
if (*it1 % 2 == 0)
{
it1 = lt.erase(it1);
}
else
{
++it1;
}
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.clear();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.push_back(10);
lt.push_back(20);
lt.push_back(30);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list6()
{
list<int> lt;
lt.push_back(1);
lt.push_back(2);
lt.push_back(3);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
list<int> lt1(lt);
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
list<int> lt2;
lt2.push_back(10);
lt2.push_back(20);
lt1 = lt2;
for (auto e : lt2)
{
cout << e << " ";
}
cout << endl;
for (auto e : lt1)
{
cout << e << " ";
}
cout << endl;
}
}
注:
1.list链表的迭代器是节点的指针,指向的是链表的节点,那么这样的话如果我们对迭代器解引用是拿不到该节点存储的数据的,因此我们需要对list的迭代器进行struct封装,在struct封装里面要对解引用操作符进行operator*运算符重载,对++操作符进行operator++运算符重载,对不等于操作符进行operator!=运算符重载(!=在迭代器while循环截止条件处使用)。使用迭代器我们还需要实现begin和end函数 ,迭代器的struct封装和begin、end函数代码如下图所示
上面代码的begin函数中,直接返回_head->next也是可以的,返回的是_head->next是一个指针,返回类型是一个迭代器,涉及到单参数的隐式类型的转换,正常来说先使用_head->next构造一个迭代器,然后构造好的迭代器拷贝构造给返回的迭代器,优化后的编译器直接使用_head->next参数构造返回的迭代器
注意:迭代器封装的struct里面不需要写析构函数、拷贝构造函数和赋值重载函数。不需要写析构函数是因为链表的结点不属于迭代器,不需要迭代器进行释放,使用默认类型的析构函数即可(默认类型的析构函数对内置成员变量_node不做处理,相当于什么也不干)。不需要写拷贝构造函数和赋值重载函数是因为就是要将里面的成员变量指针_node的值拷贝过来,进行浅拷贝,所以使用默认生成的拷贝构造函数和赋值重载函数即可
我们将__list_iterator<T>重定义成self,那么self其实就是自己迭代器的类型,因此可以将上面冗杂的__list_iterator<T>改成self。然后将后置++、前后置--、等于运算符重载、->操作符的重载这些函数实现即可,如下图一所示。
注意:
(1)如下图二所示,需要实现->操作符的重载是因为如果list里面存的数据是一个自定义类型AA,而我们要遍历打印list里面的数据,因为AA里面没有<<运算符重载,直接cout<<*it<<" "是不行的,但是我们可以cout<<(*it)._a1<<"-"<<(*it)._a2<<" "来遍历list进行打印,迭代器设计的目标是要像指针一样使用,指针对于cout<<(*it)._a1<<"-"<<(*it)._a2<<" "来说可以简写成cout<<*it->_a1<<"-"<<it->_a2<<" ",因此迭代器也需要进行->运算符重载。
(2)从下图一中可以看出我们实现的operator->函数返回的是该迭代器指向结点的地址(官方库里面是调用operator*函数,两种方式都可以),如果是这样那么遍历AA数组的话就需要cout<<*it->->_a1<<"-"<<it->->_a2<<" ",其实我们这样写是对的,编译器为了可读性会去进行优化,优化以后省略了一个->
从迭代器使用角度比较,list的迭代器与vector、string的迭代器使用方法相同;从底层算法角度比较,list的迭代器与vector、string的迭代器完全不同;从物理空间角度比较,在32位机器下list、vector、string的迭代器都是4个字节,并且这4个字节中都存的是一个地址。
从物理上list、vector、string的迭代器都是4个字节,但是他们的类型不一样,所以编译器进行的操作(例如解引用)也不一样,vector、string解引用就是简单的解引用操作,list解引用是去调用解引用运算符重载函数,这就是类型的力量。
2.实现了list类的迭代器封装,还需要实现list类的const迭代器封装。list类的const迭代器封装方法如下所示:
方法一:单独实现一个类,来支持迭代器指向结点的数据不能被修改,将list类的迭代器封装拷贝一份,将struct类的名称从__list_iterator改成__list_const_iterator,然后将__list_const_iterator重命名为const_iterator,并且封装里面成员函数的参数和返回都用const修饰即可,这种方法是可行的,但是这种方法没有很好的去复用,软件工程里面尽量不要去写重复的代码,因为重复的代码多了不方便去维护。
方法二:给list类迭代器封装__list_iterator的模板参数中增加两个参数Ref和Ptr,然后将__list_iterator<T, T&, T*>类型重定义成iterator,将__list_iterator<T, const T&, const T*>类型重定义成const_iterator。这里__list_iterator<T, T&, T*>和__list_iterator<T, const T&, const T*>使用的是同一个模板但是已经是不同的类型了,因此iterator和const_iterator也是不同的类型。
如果封装里面需要返回数据T类型的引用就用Ref,需要返回数据T类型的指针就用Ptr,这样如果是iterator类型的也就是__list_iterator<T, T&, T*>的迭代器,此时Ref是T&,Ptr是T*,里面的成员函数返回的都是不带const的引用或指针;如果是const_iterator类型的也就是__list_iterator<T, const T&, const T*>的迭代器,此时Ref是const T&,Ptr是const T*,里面的成员函数返回的都是带const的引用或指针。
然后我们还要实现list类里面的const版本的begin和end函数。普通版本和const版本的迭代器代码、普通版本和const版本的begin和end函数以及测试代码如下图所示
注意:不能将__list_iterator<const T, const T&, const T*>类型重定义成const_iterator,也就是不能在T的前面加const,如果加上const,假设T是int类型的,执行代码list<int>::const_iterator it = lt.begin(),list里面的数据是int类型的,调用lt对象的成员函数begin,如下图一所示,其中_head->_next返回的是list_node<int>*类型的指针,传给const_iterator进行匿名构造,const_iterator的构造函数如下图二所示,const_iterator类型的迭代器里面typedef list_node<T> Node,这里的T在迭代器封装里面是const int,也就是迭代器封装里面的Node结点里面的数据是const int类型的,将lt.begin()返回的list_node<int>*类型指针给迭代器封装里面的构造函数,构造函数用Node*类型的node来接收,Node*类型也就是list_node<const int>*,模板参数不同也是不同的类型,不同类型无法赋值系统报错
3.list类的insert函数和erase函数实现代码如下图所示
list类里面每个节点是独立的,insert函数没有迭代器失效的问题,不存在野指针问题也不存在意义变了的问题。如下图一所示,库里的insert函数还是给了返回值,返回的是新插入元素的迭代器,那么insert函数的实现优化成如下图二所示
list类里面erase函数有迭代器失效的问题,如下图所示,将迭代器it1传给erase函数的形参pos,取迭代器pos指向节点的指针给到cur,delete cur将该节点释放,但是it1迭代器还指向该节点,造成了类似野指针的迭代器失效问题
如下图一所示,库里的erase函数是用返回值来解决迭代器失效的问题,返回的是删除节点后面一个节点的迭代器,那么erase函数的实现优化和测试代码的优化如下图二所示
4.list类的clear函数和析构函数如下图一二所示
5.list类的拷贝构造函数和赋值运算符重载函数需要实现深拷贝功能,拷贝构造复用push_back的写法如下图一所示,拷贝构造现代写法如下图二所示,赋值运算符重载现代写法如下图三所示
6.list类使用迭代器区间进行构造的构造函数如下图所示