https://cplusplus.com/reference/list/list/?kw=list
List
Lists are sequence containers that allow constant time insert and erase operations anywhere within the sequence, and iteration in both directions.
1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向
其前一个元素和后一个元素。
3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高
效。
4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率
更好。
5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list
的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间
开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)
#include<iostream>
#include<list>//双向循环链表
//list和vector的用法十分类似
//区别就是不能使用[]来按照坐标随机访问数据
#include<algorithm>
using namespace std;
void test_list1()
{
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
l1.push_back(5);
l1.push_back(6);
list<int>::iterator it = l1.begin();
while (it != l1.end())
{
cout << *it << " ";
it++;
}
cout << endl;
for (auto ch : l1)//如果list里面的对象是int就可以直接auto ch,但是
//如果是string,vector这种大对象,最好还是加上引用,要不每一次赋值
//都要拷贝auto & ch;
{
cout << ch << " ";
}
cout << endl;
}
void test_list2()
{
list<int>l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
l1.push_back(5);
list<int>::reverse_iterator rit = l1.rbegin();
while (rit != l1.rend())
{
//*rit *= 2;
cout << *rit << " ";
rit++;
}
}
void test_list3()
{
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
l1.push_front(0);
for (auto ch : l1)
{
cout << ch << " ";
}
}
void test_list4()
{
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
l1.push_back(5);
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
list<int>::iterator pos1 = find(l1.begin(), l1.end(), 2);
//这里的pos不存在迭代器失效问题(类似vector的情况)
//因为list链表的数据结构是通过指针指引相互链接,插入的话是new一个新的
//结点插入,所以当插入完之后pos的位置不变
if (pos1 != l1.end())
{
l1.insert(pos1, 99);
}
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
*pos1 = 30;
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
auto pos2 = find(l1.begin(), l1.end(), 2);
if (pos2 != l1.end())
{
l1.erase(pos2);
}
//erase这里会失效,因为这个pos已经被干掉了。直接访问会报错
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
}
//算法库里面已经有一个sort了,为什么list里面还有一个sort?
//因为算法的sort是无法对list进行sort的
//算法的sort要求物理空间必须连续
void test_list5()
{
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
}
int main()
{
test_list4();
return 0;
}
list的使用如同之前的string,vector一样规范化了
list的模拟实现:
#pragma once
#include<iostream>
#include<algorithm>
#include<assert.h>
using namespace std;
namespace lrx
{
template<class T>
struct list_node//node
{
T _data;
list_node<T>* _next;
list_node<T>* _prev;
list_node(const T& x=T())
:_data(x)
,_next(nullptr)
,_prev(nullptr)
{
}
};
/*
* vector/string的迭代器:
* vector<T>/string::iterator it= vector/string.begin()
* while(it!=vector/string.end())
* {
* cout<<*it<<" ";
* it++;
* }
*/
//list的迭代器不像vector,string这种的原生指针,list的迭代器需要封装
//这里用struct是因为在这个命名空间里面list可以正常使用
//因为参考vector,string这种的iterator里面访问时候
//list<T>::iterator it=list.begin();
//while(it!=list.end()) vector和string存储数据连续,可以用it<list.end()
//这里我们看出list的迭代器需要封装!=的功能,迭代器还得要有解引用的功能(用来修改这个数)
//STL:
//typedef __list_iterator<T, T&, T*> iterator;
//typedef __list_iterator<T, const T&, const T*> const iterator;
template<class T,class Ref,class Ptr>
struct __list_iterator
{
typedef list_node<T> node;
typedef __list_iterator<T,Ref,Ptr> iterator;
//find检查以下的(必须跟stl保持一致)
typedef bidirectional_iterator_tag iterator_category;
typedef T value_type;
typedef Ptr pointer;
typedef Ref reference;
typedef ptrdiff_t difference_type;
node* _node;
__list_iterator(node* node)
:_node(node)
{}
bool operator!=(const iterator& it) const
{
return _node != it._node;
}
//*it<=> it.operator*()
Ref operator*()
//T&operator*()//可读可写
{
return _node->_data;
}
iterator &operator++()//日期类++得到是日期类,迭代器++得到是迭代器
{
_node= _node->_next;
return *this;
}
//后置--
iterator& operator--(int)
{
iterator tmp(*this);
_node = _node->_prev;
return tmp;
}
iterator& operator--()
{
_node = _node->_prev;
return *this;
}
//后置++返回的是++之前的值
iterator& operator++(int)
{
iterator tmp(*this);//拷贝构造一个临时值
_node = _node->_next;
return tmp;//++之后返回++之前的值
}
bool operator==(const iterator& it)
{
return _node != it._node;
}
Ptr operator ->()
//T* operator ->()
{
return &(operator*());
}
//迭代器的这个结构不需要写析构函数
};
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);
}
iterator end()
{
return iterator(_head);//end:最后一个值的下一个值
}
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;
}
}
list()
{
empty_init();
}
void swap(list<T>& x)
{
std::swap(_head, x._head);
}
//lt2(lt1)
list(const list<T>& lt)
{
empty_init();//this.empty_init()(lt2)
list<T> tmp(lt.begin(), lt.end());//构造出的tmp就是lt2想要的
std::swap(_head, tmp._head);
//或者 this->swap(tmp);
//这里的tmp的局部对象,出了函数自动调用析构函数自动销毁
}//现代写法:先把构造好一样的链表,之后只需要拷贝的那个链表的指针直接指向构造好的链表即可;
//lt1=lt3
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);//不用像之前c语言中直接去写一个函数,c++中会直接调用构造函数的
//_head tail newnode
tail->_next= newnode;
newnode->_prev = tail;
newnode->_next = _head;
_head->_prev = newnode;
}
iterator insert(iterator pos, const T& x)
{
node* cur = pos._node;
node* prev = cur->_prev;
node* newnode = new node(x);
//prev newnode cur
prev->_next = newnode;
newnode->_prev = prev;
newnode->_next = cur;
cur->_prev = newnode;
return iterator(newnode);
}//push_back可以复用
void push_front(const T& x)
{
insert(begin(), x);
}
iterator erase(iterator pos)
{
assert(pos != end());
node* cur = pos._node;
node *prev = cur->_prev;
node *next = cur->_next;
prev->_next = next;
next->_prev = prev;
delete cur;
return iterator(next);
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
private:
node* _head;
};
void test_list1()
{
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
l1.push_back(5);
l1.push_back(6);
list<int>::iterator it = l1.begin();
while (it != l1.end())
{
cout << *it << " ";
++it;
}
cout << endl;
it = l1.begin();
while (it != l1.end())
{
*it *= 2;
++it;
}
cout << endl;
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
}
void test_list2()
{
//迭代器是一个像指针一样的东西,指针什么情况下会用箭头?
//我们这里要考虑迭代器重载->(箭头)
struct pos//假如存一个坐标
{
int _a1;
int _a2;
pos(int a1 = 0, int a2 = 0)
:_a1(a1)
, _a2(a2)
{
}
};
int x = 10;
int* p1 = &x;
//正常情况下是直接使用*访问
//cout << *p1 << endl;
//结构体指针才会用指针,用来访问结构体成员的
pos aa;
pos* p2 = &aa;
p2->_a2;
list<pos> lt;
lt.push_back(pos(10, 20));
list<pos>::iterator i = lt.begin();
while (i != lt.end())
{
//cout << *i << " ";//这里会报错:<<没有重载。*it是节点的数据,节点是数据是pos(坐标对象),这里坐标对象没有重载
//可以不用写重载
cout << (*i)._a1 << ": " << (*i)._a2 << " " << endl;//加()是考虑到优先级的问题.的优先级大于*
//现在迭代器指向的数据是一个自定义类型是一个结构,结构是可以用箭头访问的,所以重载operator->就可以了
cout << i->_a1 << ": " << i->_a2 << " " << endl;
//这里还隐藏了一个->,不调用operator*的话,这里operator->应该这样写 :return &(_node->data);
//意味着operator->是返回数据的指针,T是pos,返回的是pos*,这里严格是应该这样写:it->->_a1;(第一个->是运算符重载(it.operator->())
//返回是T*,T*是结构体的指针,之后再—>访问里面的成员
//语法为了可读性,编译器进行了特殊处理,省略了一个->,类试前置++跟后置++,后置++多了一个参数,这两个编译器为了可读性进行了特殊处理
i++;
}
//test_list1里面的cout<<*it<<" ";的it相当于是int*,这里的相当于pos*,
}
void func(const list<int> lt)
{
list<int>::const_iterator it = lt.begin();
while (it != lt.end())
{
cout << *it << " ";
it++;
}
cout << endl;
}
void test_list3()
{
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
l1.push_back(5);
func(l1);
//这里是无法遍历的,这里是权限的放大问题,上面函数的参数是const,l是const对象end()是普通的成员函数,是无法正常调用的
//这里就需要const迭代器,普通迭代器可读可写,const迭代器是只读的。
//怎么控制const迭代器?普通迭代器可以遍历,可以改数据,const T*指向的内人 *it就不能赋值了
//怎么控制它不可修改呢?operator*返回的是数据的引用,这个地方如果是const T&operator*,返回的就是const的迭代器
//但是这里又不能只写个const T&operator()和T&operator()这两个,这两个不构成函数重载,如果拷贝以上代码,再写一个const的迭代器
//只是把这里的*和->做修改,代码过于冗余。STL采用了复用的形式,不需要重写写一个类,这两个类所有都是相同的,除了
//operator*和operator->。
//STL:(通过实例化的方式解决)
//typedef __list_iterator<T, T&, T*> iterator;//后面两个参数是用来控制迭代器的行为,
//typedef __list_iterator<T, const T&, const T*> const iterator;
//相当于把*和->两个实现,泛型化了(多了两个参数)
}
void test_list4()
{
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
l1.push_back(5);
l1.push_back(6);
l1.push_back(7);
l1.push_back(8);
l1.push_back(9);
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
l1.pop_back();
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
l1.pop_front();
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
auto pos = find(l1.begin(), l1.end(), 3);
if (pos != l1.end())
{
l1.insert(pos, 5);
}
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
auto pos2 = find(l1.begin(), l1.end(), 8);
if (pos2 != l1.end())
{
l1.erase(pos2);
}
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
l1.push_front(0);
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
}
void test_list5()
{
list<int> l1;
l1.push_back(1);
l1.push_back(2);
l1.push_back(3);
l1.push_back(4);
l1.push_back(5);
list<int> ll = l1;
for (auto ch : l1)
{
cout << ch << " ";
}
cout << endl;
l1.push_back(20);
l1.push_back(30);
ll = l1;
for (auto e : ll)
{
cout << e << " ";
}
}
}