文章目录
1. list的介绍及使用
1.1 list的介绍
- list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
- list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向
其前一个元素和后一个元素。 - list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高
效。 - 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率
更好。 - 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list
的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间
开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这
可能是一个重要的因素)
1.2 list常见的接口使用
1.2.1 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<iostream>
#include<list>
using namespace std;
int main()
{
list<int> v;
list<int> v1(10, 5);
list<int> v2(v1);
list<int> v3(v1.begin(), v1.end());
return 0;
}

1.2.2 list iterator的使用
此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。
| 函数声明 | 接口说明 |
|---|---|
| begin + end | 返回list第一个元素的迭代器+返回list最后一个元素下一个位置的迭代器 |
| rbegin + rend | 返回list最后一个元素的迭代器+返回第一个元素的上一个位置 |
- begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动。
- rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动。
代码演示:
#include<iostream>
#include<list>
using namespace std;
int main()
{
list<int> v;
v.push_back(1);//尾插
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
auto it = v.begin();
while (it != v.end())
{
cout << *it;
it++;
}
cout << endl;
auto it2 = v.rbegin();
while (it2 != v.rend())
{
cout << *it2;
it2++;
}
cout << endl;
return 0;
}

1.2.3 list capacity
| 函数声明 | 接口说明 |
|---|---|
| size | 返回list中有效节点的个数 |
| empty | 检测list是否为空,是返回true,否则返回false |
代码演示:
#include<iostream>
#include<list>
using namespace std;
int main()
{
list<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
if (v.empty() == false)
{
cout << "no empty";
}
else if (v.empty() == true)
{
cout << "empty";
}
cout << endl;
cout << v.size();
return 0;
}

1.2.4 list element access
| 函数声明 | 接口说明 |
|---|---|
| front | 返回list的第一个节点中值的引用 |
| back | 返回list的最后一个节点中值的引用 |
代码演示:
#include<iostream>
#include<list>
using namespace std;
int main()
{
list<int> v;
v.push_back(1);
v.push_back(2);
v.push_back(3);
v.push_back(4);
v.push_back(5);
v.push_back(6);
v.back() = 9;
v.front() = 0;
cout << v.back()<<endl;
cout << v.front();
return 0;
}

1.2.5 list modifiers
| 函数声明 | 接口说明 |
|---|---|
| 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中的有效元素 |
代码演示:
#include<iostream>
#include<list>
using namespace std;
template<class T>
void PrintfList(const list<T>&l)
{
for (auto e : l)
{
cout << e << " ";
}
cout << endl;
}
int main()
{
list<int> v(3,0);//000
list<int> s(3, 'a');//97 97 97
v.push_front(1);// 1000 ;push_front 在list首元素前插入值为val的元素
PrintfList(v);
v.push_back(3);//10003 ;push_back 在list尾部插入值为val的元素
PrintfList(v);
v.pop_back();//1000 ;pop_back 删除list中最后一个元素
PrintfList(v);
v.pop_front();//000 ;//pop_front 删除list中第一个元素
PrintfList(v);
v.insert(++v.begin(),2);//0200 insert 在list position 位置中插入值为val的元素
PrintfList(v);
v.erase(++v.begin());//000 erase 删除list position位置的元素
PrintfList(v);
v.swap(s);
PrintfList(v);//97 97 97 swap 交换两个list中的元素
v.clear();//nullptr clear
PrintfList(v);
return 0;
}

2. list的模拟实现
2.1 list的类
template<class T>
class list
{
public:
typedef ListNode<T> node;
private:
node* head;
size_t _size = 0;
};
template<class T>
struct ListNode
{
typedef ListNode<T> node;
node* _next;//指向该节点下一个node节点。
node* _prev;//指向该节点上一个node节点。
T _date;//存放数据
ListNode(T v=T())//list l(v)v为T类型的数据时,生成的l的T _date为v。
:_next(nullptr),
_prev(nullptr),
_date(v)
{
}
};
创建一个list类,里面成员为node* head(用来存放数据,为list的哨兵位,哨兵位不存放数据,从第二个no
de开始存放)和_size(list数据大小);其中node为一个结构体。
2.2 list的begin和end
//begin end
iterator begin()
{
return head->_next;
}
iterator end()
{
return head;
}
const_iterator begin()const
{
return head->_next;
}
const_iterator end()const
{
return head;
}

list中的head头节点下一个节点为begin,最后一个存放数据的节点下一个节点为end,也就是head节点。这里我们写了iterator和const_iterator两种,函数后加const可构成函数重载,cont list调用const函数,普通list调用的是非const函数。
2.3 list迭代器的模拟
2.3.1 list迭代器模拟代码

这里我们实现一个iterator类存放指向list中的节点。
template<class T,class ref,class ptr>
struct ListIterator
{
typedef ListNode<T> node;
typedef ListIterator<T,ref,ptr> iterator;
node* _node;//iterator中的成员为一个node* 指针指向list节点。
ListIterator(node* nd)
:_node(nd)//调用析构函数,使iterator中_node指向list地址和nd一样的节点。
{
}
iterator& operator++()
{
_node = _node->_next;//迭代器指向_node的next节点,这里++是前置重载。
return *this;
}
iterator operator++(int)//这里(int)为后置++的重载符号。
{
iterator tmp(_node);
_node = _node->_next;
return tmp;
}
iterator& operator--()//迭代器指向_node的prev节点,这里--是前置重载。
{
_node = _node->_prev;
return *this;
}
iterator operator--(int)//这里(int)为--的后置重载符号。
{
iterator tmp(_node);
_node = _node->_prev;
return tmp;
}
ref operator * ()//解引用,返回节点内的T date,节点内存放的值。
{
return _node->_date;
}
ptr operator -> ()
{
return &_node->_date;
}
bool operator !=(const iterator&r)
{
return _node != r._node;//判断两个迭代器存放list节点地址是否不相同。
}
bool operator ==(const iterator& r)//判断两个迭代器存放list节点地址是否相同。
{
return _node == r._node;
}
};
2.3.2 迭代器中的实现细节
- template<class T,class ref,class ptr>:这个模板中ref为T&,ptr为T*,因为iterator中大部分符号重载时,const_iterator和iterator的返回值是一样的,只有operator *和operator ->两个是不同的,const_iterator的operator *返回的是const T&,iterator的是T&;const_iterator的operator ->返回的是const T*,iterator的是T*。 因此正常情况下我们需要给list和const_list写两套iterator,这里我们只需要利用模板实例化两套iterator就可以实现了。
template<class T>
class list
{
public:
typedef ListNode<T> node;
typedef ListIterator<T,T&,T*> iterator;//在这里实例化一个非const的iterator。
typedef ListIterator<T,const T&,const T*> const_iterator;
//在这里实例化一个const的iterator。
iterator begin()
{
return head->_next;
}
iterator end()
{
return head;
}
const_iterator begin()const
{
return head->_next;
}
const_iterator end()const
{
return head;
}
private:
node* head;
size_t _size = 0;
};
- 如何理解这个重载?
ptr operator -> ()
{
return &_node->_date;
}

我们自定义了一个类型aa如图,我们这样调用it->a1时,先重载,返回一个&_node->_date,也就是aa类型,然后这里代码就应该是aa a2,很明显代码很难理解,而这里编译器并不是这样运行的。真正的运行逻辑是:这里的代码其实是it.operator->()->a1,这里就是先返回aa类型的地址,然后再用箭头访问aa中的数据。
2.4 list的初始化
- 创建一个list,首先要先创建list的head:
void empty_init()
{
head = new node;
head->_next = head;
head->_prev = head;
}
让头节点的下一个和上一个都指向自己。
- list()
list()
{
empty_init();
};
创建一个只有头节点的list。
- list(int n, const T& value = T()
list(int n, const T& value = T())
{
empty_init();//创建头节点
while (n != 0)
{
push_back(value);//尾插,后面实现。
n--;
}
};
创建一个数量为n,内容为value的list。
- list(Iterator first, Iterator last)
template <class Iterator>
list(Iterator first, Iterator last)
{
empty_init();
while (first != last)
{
insert(end(), *first);
first++;
}
};
拷贝一个list的[first,last)这个迭代器区间的内容。
- list(const list& l)
list(const list<T>& l)
{
empty_init();
const_iterator it = l.begin();
while (it != l.end())
{
push_back(it._node->_date);
it++;
}
};
拷贝队列l,初始化list。
初始化代码:
list<int> l1;
list<int> l2(5,9);
list<int> l3(l2.begin(),l2.end());
list<int> l4(l3);
2.5 list的插入删除
- iterator insert(iterator pos,const T& v)
iterator insert(iterator pos,const T& v)
{
node * newnode = new node(v);
node* prev = pos._node->_prev;
node* next = pos._node;
prev->_next = newnode;
newnode->_prev = prev;
next->_prev = newnode;
newnode->_next = next;
_size++;
return pos;
}
在迭代器pos指向的位置前插入一个数据,并返回pos迭代器,pos迭代器指向的内容不变。
- iterator erase(iterator pos)
iterator erase(iterator pos)
{
node* next = pos._node->_next;
node* prev = pos._node->_prev;
prev->_next = next;
next->_prev = prev;
delete pos._node;
_size--;
return next;
}
删除迭代器pos指向的节点,返回pos指向节点的下一个节点。
- void push_back( T v)和void push_front(T v)
void push_back( T v)
{
insert(end(),v);
}
void push_front(T v)
{
insert(begin(), v);
}
头插和尾插。
- void pop_back()和void pop_front()
void pop_back()
{
erase(end()._node->_prev);
}
void pop_front()
{
erase(begin());
}
尾删和头删。
2.6 list的析构函数
void clear()
{
iterator it = begin();
while (it != end())
{
it = erase(it);
}
};
~list()
{
clear();
delete head;
head = nullptr;
_size = 0;
};
先调用clear函数将list中的除head节点外所有节点内存释放,然后再将头节点释放。
2.7 list的交换和赋值
- void swap(list&l)
void swap(list&l)
{
std::swap(head,l.head);
std::swap(_size,l._size);
}
调用库里面的swap使两个list中2各个对象指向的指针直接交换。
- list& operator=(list<T> l)
list<T>& operator=(list<T> l)
{
swap(l);
return *this;
};
将一个list的值赋值list,这里使(list<T> l)是传的值,这里拷贝构造一个临时的list<T> l,然后让它和需要被赋值的list内容交换,出函数后,临时的l,会调用析构函数,将被赋值list原来的内容释放掉。
2.8 list的大小和是否为空
size_t size()
{
return _size;
}
bool empty()
{
return _size == 0;
}
size()返回list中有效节点个数,empty()如果节点只有head节点,则为空,返回true,否则返回false。
955

被折叠的 条评论
为什么被折叠?



