本文主要介绍了list(list的介绍及使用、list的模拟实现、 list与vector的对比),内容全由作者原创(无AI),并带有配图帮助博友们更好的理解,点个关注不迷路,下面进入正文~~
目录
[1. list的介绍及使用](#1. list的介绍及使用)
[1.1 list的介绍](#1.1 list的介绍)
[1.2 list的使用](#1.2 list的使用)
[1.2.1 list的构造](#1.2.1 list的构造)
[1.2.2 list iterator的使用](#1.2.2 list iterator的使用)
[1.2.3 list capacity](#1.2.3 list capacity)
[1.2.4 list element access](#1.2.4 list element access)
[1.2.5 list modifiers](#1.2.5 list modifiers)
[1.2.6 list的迭代器失效](#1.2.6 list的迭代器失效)
[2. list的模拟实现](#2. list的模拟实现)
[3. list与vector的对比](#3. list与vector的对比)
1. list的介绍及使用
1.1 list的介绍
1.2 list的使用
list的结构虽然与前面学过的vector和string有所差异,但其实使用的接口大多数都是一样的。相同的我们首先先学会常用的接口即可。
1.2.1 list的构造
|-----------------------------------------------------------|-----------------------------|
| 构造函数 | 接口说明 |
| 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 |
1.2.2 list iterator的使用
list的iterator我们可以先理解成指向一个节点的指针。
|---------------|-------------------------------------------------------------------------|
| 函数声明 | 接口说明 |
| begin + end | 返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器 |
| rbegin + rend | 返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的reverse_iterator,即begin位置 |
注意:
-
begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
-
rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动
关于一些迭代器的理解:
从功能上讲,迭代器可以分成四类:iterator、reverse_iterator、const_iterator、const_reverse_iterator
从性质上讲,迭代器可以分成三类:
单向迭代器:forward_list/unordered_map,这一类迭代器直能进行++的操作
双向迭代器:list/map/set,这一类迭代器可以支持++或--的操作
随机迭代器:vector/string/deque,这一类迭代器可以支持++、--、+、-的操作
不同性质的迭代器可以支持不同的算法

其中Random Access的权限是最高的,能支持单向迭代器的算法一定能支持随机迭代器,但能支持随机迭代器的算法不一定能支持单向迭代器。

例如:sort算法只能支持随机迭代器,因为算法内部需要使用 进行访问,因此它不支持单向和双向迭代器使用
reverse算法可以支持双向迭代器使用,因此随机迭代器也可以使用,但单向迭代器就不可以使用
cpp
void test_list1()
{
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;
for (auto e : lt)
{
cout << e << ' ';
}
cout << endl;
/*it = lt.begin();
lt.erase(it + 3);*/
// 不支持,要求随机迭代器
//sort(lt.begin(), lt.end());
string s("dadawdfadsa");
cout << s << endl;
sort(s.begin(), s.end());
cout << s << endl;
}
如果我们真的想通过+-在指定的位置指向插入和删除,可以使用下面的方法
cpp
void test_list3()
{
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);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
int k = 3;
auto it = lt.begin();
while (k--)
{
it++;
}
lt.insert(it, 30);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
it = find(lt.begin(), lt.end(), 5);
if (it != lt.end())
{
lt.erase(it);
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
1.2.3 list capacity
|-------|------------------------------|
| 函数声明 | 接口说明 |
| empty | 检测list是否为空,是返回true,否则返回false |
| size | 返回list中有效节点的个数 |
1.2.4 list element access
|-------|--------------------|
| 函数声明 | 接口说明 |
| front | 返回list的第一个节点中值的引用 |
| back | 返回list的最后一个节点中值的引用 |
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中的有效元素 |
list中还有一些操作,需要用到时大家可参阅list的文档说明。下面给出一些其他接口的使用方法,有兴趣可以阅读一下
cpp
void test_list4()
{
list<int> lt;
lt.push_back(1);
lt.push_back(20);
lt.push_back(3);
lt.push_back(5);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
//lt.sort();
//for (auto e : lt)
//{
// cout << e << " ";
//}
//cout << endl;
//less<int> ls;
//greater<int> gt;
//lt.sort(gt);
//for (auto e : lt)
//{
// cout << e << " ";
//}
//cout << endl;
lt.reverse();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
std::list<double> first, second;
first.push_back(3.1);
first.push_back(2.2);
first.push_back(2.9);
second.push_back(3.7);
second.push_back(7.1);
second.push_back(1.4);
first.sort();
second.sort();
first.merge(second);
for (auto e : first)
{
cout << e << " ";
}
cout << endl;
}
void test_list5()
{
list<int> lt;
lt.push_back(1);
lt.push_back(20);
lt.push_back(3);
lt.push_back(5);
lt.push_back(5);
lt.push_back(4);
lt.push_back(5);
lt.push_back(6);
lt.sort();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
lt.unique();
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
void test_list6()
{
// 一个链表节点转移给另一个链表
std::list<int> mylist1, mylist2;
std::list<int>::iterator it;
// set some initial values:
for (int i = 1; i <= 4; ++i)
mylist1.push_back(i); // mylist1: 1 2 3 4
for (int i = 1; i <= 3; ++i)
mylist2.push_back(i * 10); // mylist2: 10 20 30
it = mylist1.begin();
++it; // points to 2
mylist1.splice(it, mylist2); // mylist1: 1 10 20 30 2 3 4
// mylist2 (empty)
// "it" still points to 2 (the 5th element
// 调整当前链表节点的顺序
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);
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
int x = 0;
cin >> x;
it = find(lt.begin(), lt.end(), x);
if (it != lt.end())
{
//lt.splice(lt.begin(), lt, it);
lt.splice(lt.begin(), lt, it, lt.end());
}
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
}
1.2.6 list的迭代器失效
前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无 效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入 时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭 代器,其他迭代器不会受到影响。
cpp
void TestListIterator1()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array + sizeof(array) / sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
l.erase(it);
++it;
}
}
// 改正
void TestListIterator()
{
int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array + sizeof(array) / sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
l.erase(it++);
// it = l.erase(it);
}
}
2. list的模拟实现
list的模拟实现和string有些许不同,因为list的迭代器不能自增或自减到下一个节点,因此我们要将list的迭代器封装成一个类,在类里面自主实现迭代器的接口。
下面是list的模拟实现的代码
cpp
#pragma once
namespace cyh
{
template<class T>
struct list_node
{
T _data;
list_node* _next;
list_node* _prev;
/*list_node(const T& data = T())
{
_data = data;
_next = nullptr;
_prev = nullptr;
}*/
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;
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(const list<T>& lt)
{
empty_init();
for (auto e : lt)
{
push_back(e);
}
}
list(initializer_list<T> il)
{
empty_init();
for (auto& e : il)
{
push_back(e);
}
}
list<T>& operator=(list<T> lt)
{
swap(lt);
return *this;
}
~list()
{
clear();
delete _head;
_head = nullptr;
}
void push_back(const T& x)
{
insert(end(), x);
}
void push_front(const T& x)
{
insert(begin(), x);
}
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* prev = cur->_prev;
Node* newnode = new Node(x);
newnode->_prev = prev;
prev->_next = newnode;
newnode->_next = cur;
cur->_prev = newnode;
++_size;
return newnode;
}
void pop_back()
{
erase(--end());
}
void pop_front()
{
erase(begin());
}
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 swap(list<T>& lt)
{
std::swap(_head, lt._head);
std::swap(_size, lt._size);
}
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
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 = 1;
};
template<class container>
void print_container(const container& con)
{
auto it = con.begin();
while (it != con.end())
{
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 += 10;
cout << *it << " ";
++it;
}
cout << endl;
for (auto e : lt)
{
cout << e << " ";
}
cout << endl;
print_container(lt);
list<AA> lta;
lta.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->_a1 << ":" << ita->_a2 << endl;
cout << ita.operator->()->_a1 << ":" << ita.operator->()->_a2 << endl;
++ita;
}
cout << endl;
}
void test_list2()
{
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 += 100;
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 test_list3()
{
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);
lt1 = lt3;
print_container(lt1);
print_container(lt3);
}
void func(const list<int>& lt)
{
print_container(lt);
}
void test_list4()
{
// 直接构造
list<int> lt0({ 1,2,3,4,5,6 });
// 隐式类型转换
//list<int> lt1 = { 1,2,3,4,5,6,7,8 };
const list<int>& lt3 = { 1,2,3,4,5,6,7,8 };
func(lt0);
func({ 1,2,3,4,5,6 });
//print_container(lt1);
auto il = { 10, 20, 30 ,40};
//initializer_list<int> il = { 10, 20, 30 };
cout << typeid(il).name() << endl;
cout << sizeof(il) << endl;
}
}
3. list与vector的对比
vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及 应用场景不同,其主要不同如下:
|-------|-----------------------------------------------------------------------|-------------------------------------------|
| | vector | list |
| 底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
| 随机访问 | 支持随机访问,访问某个元素效率O(1) | 不支持随机访问,访问某个元素效率O(N) |
| 插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容: 开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
| 空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低 |
| 迭代器 | 原生态指针 | 对原生态指针(节点指针)进行封装 |
| 迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效 | 插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
| 使用场景 | 需要高效存储,支持随机访问,不关心插入删除效率 | 大量插入和删除操作,不关心随机访问 |
下面这两个代码分别可以验证vector和list排序时间,以及先把list变成vector和没有list变成vector的排序时间:
cpp
void test_op1()
{
srand(time(0));
const int N = 1000000;
list<int> lt1;
vector<int> v;
for (int i = 0; i < N; ++i)
{
auto e = rand() + i;
lt1.push_back(e);
v.push_back(e);
}
int begin1 = clock();
// 排序
sort(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
lt1.sort();
int end2 = clock();
printf("vector sort:%d\n", end1 - begin1);
printf("list sort:%d\n", end2 - begin2);
}
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();
// 拷贝vector
vector<int> v(lt2.begin(), lt2.end());
// 排序
sort(v.begin(), v.end());
// 拷贝回lt2
lt2.assign(v.begin(), v.end());
int end1 = clock();
int begin2 = clock();
lt1.sort();
int end2 = clock();
printf("list copy vector sort copy list sort:%d\n", end1 - begin1);
printf("list sort:%d\n", end2 - begin2);
}


我们可以发现,无论是哪种情况,vector的排序效率都比list要快。所以当我们需要排序list时,可以先把list的数据拷贝到vector中,再将排序好的vector拷贝到list中。
结语
这篇文章全文由作者手写,图片由画图软件所制,无AI制作,希望各位博友能有所收获
欢迎各位博友的讨论,觉得不错的小伙伴,别忘了点赞关注哦~
