C++中的list
ClanguageのKing. 2024-08-23 15:35:01 阅读 56
C++中的list
一丶 list的介绍和使用1. list的介绍2. list的使用I. list的构造II. list iterator的使用III.list capacityIV.list element accessV. list modifiers
VI. list迭代器失效的问题
二丶list的模拟实现三丶list与vector的对比
一丶 list的介绍和使用
1. list的介绍
1.list是可以在常数范围内任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素。
3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,forward_lis已让其更简单高效。
4. 与其他的序列式容器相比(比方说array,vector,deque), list通常在任意位置进行插入丶移除元素的执行效率更好。
5. 与其他序列式容器相比,list和forward_list的最大缺陷是不支持任意位置的随机访问。这跟链表的随机访问是一致的,效率极低。
库中的list属于双向循环带头链表。
关于list的示意图如下:
2. list的使用
我们这里只叙述list常用的一些功能。
I. list的构造
构造函数(constructor)
1. list(size_type n, const value& val = value_type())
构造的list中含有n个值为val的元素
2. list()
构造空的list
3. list(const list& x)
拷贝构造函数
4. list(InputIterator first, InputIterator last)
用[first, last)区间中的元素构造list
II. list iterator的使用
我们可以把iterator暂时理解为指针,该指针指向list某个节点。
iterator
1. bgein+end
begin()返回第一个元素的迭代器,end()返回最后一个元素的下一个位置的迭代器。
2. rbegin+rend
rbegin()返回最后一个元素对应的反向迭代器,rend()返回第一个元素之前的反向迭代器。
关于正丶反迭代器指向的示意图:
这里对于正反向迭代器的++和–的移动方向进行说明:
begin()和end()都是正向迭代器,对迭代器执行++操作时,迭代器向后移动;执行- -操作则向前移动。rbegin()和rend()都是反向迭代器,对迭代器进行++操作时,迭代器是向前移动的;执行- -的操作时,则是向后移动。
III.list capacity
list中的容量问题。
1. empty
检测list是否为空,是返回true,否则返回false
2. size
返回list中有效节点的个数
IV.list element access
关于list中元素的访问
1. front
返回list的第一个节点中值的引用
2. back
返回list的最后一个节点中值的引用
V. list modifiers
关于list的修改操作
1. push_back
在list首元素前插入值为val的元素
2. pop_front
删除list中的第一个元素
3. push_back
在list尾部插入值为val的元素
4.pop_back
删除list中最后一个元素
5.insert
在list position位置中插入值为val的元素
6.erase
删除list position位置的元素
7. swap
交换两个list中的元素
8. clear
清空list中的有效元素
VI. list迭代器失效的问题
我们在前面说过,迭代器可以暂时理解为指针。list中的迭代器失效即迭代器指向的节点的无效,即该节点被删除了。因为list的底层结构是带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的;只有在删除时才会失效,而且失效的只是被删除的迭代器,其他位置的迭代器不会受到影响。
<code>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())
{
it = l.erase(it);
++it;
}
}
二丶list的模拟实现
声明一下,我们在List.h中实现各个功能,同时存放测试代码,并在List.cpp中直接测试。
List.h
#pragma once
#include <assert.h>
namespace bit
{
template<class T>
struct ListNode
{
//C++定义一个类 既可以用class 也可以用struct
//当有公有又有私有时 通常使用class
//当基本都是公有是 一般使用struct
ListNode<T>* _next;
ListNode<T>* _prev;
T _data;
ListNode(const T& data = T())//实现为匿名对象 确保其通用性
:_next(nullptr)
,_prev(nullptr)
,_data(data)
{ }
};
//将list的结点封装起来 重载++和--的方法
//使其结点通过++或--到达访问下一个或上一个结点的目的
//template<class T>
template<class T, class Ref, class Ptr>
struct ListIterator//此类作为迭代器 无需析构函数释放结点
{ //结点管理要在list中 不要越级管理
typedef ListNode<T> Node;
typedef ListIterator<T, Ref, Ptr> Self;
Node* _node;
ListIterator(Node* node)
:_node(node)
{ }
//不必重载+n或是-n
//因为这样效率太低了
//库中的迭代器也是不支持+n和-n的
//前置++
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;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
};
//为何要采用封装Node的写法呢?
//我们实现的是list 双向链表
//既然是链表 那么个节点的物理地址是不连续的
//仅仅++或-- 是获取不到下一个或上一个结点的
//因此必须封装结点 并为其重载++和--操作符
//在重载中实现对应的功能
//list的反向迭代器
//反向迭代器的++就是正向迭代器的--,反向迭代器的--就是
//正向迭代器的++。因此反向迭代器的实现可以借助正向迭代器。即:
//反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口
//进行包装即可。
template<class T, class Ref, class Ptr>
struct ReverseListIterator//此类作为迭代器 无需析构函数释放结点
{ //结点管理要在list中 不要越级管理
typedef ListNode<T> Node;
typedef ReverseListIterator<T, Ref, Ptr> Self;
Node* _node;
ReverseListIterator(Node* node)
:_node(node)
{ }
//不必重载+n或是-n
//因为这样效率太低了
//库中的迭代器也是不支持+n和-n的
//前置++
Self& operator++()
{
_node = _node->_prev;
return *this;
}
//前置--
Self& operator--()
{
_node = _node->_next;
return *this;
}
//后置++
Self operator++(int)
{
Self tmp(*this);
_node = _node->_prev;
return tmp;
}
//后置--
Self operator--(int)
{
Self tmp(*this);
_node = _node->_next;
return tmp;
}
Ref operator*()
{
return _node->_data;
}
Ptr operator->()
{
return &_node->_data;
}
bool operator!=(const Self& it)
{
return _node != it._node;
}
bool operator==(const Self& it)
{
return _node == it._node;
}
};
//template<class T>
//class ListConstIterator//此类作为迭代器 无需析构函数释放结点
//{//结点管理要在list中 不要越级管理
//typedef ListNode<T> Node;
//typedef ListConstIterator<T> Self;
//Node* _node;
//public:
//ListConstIterator(Node* node)
//:_node(node)
//{}
////不必重载+n或是-n
////因为这样效率太低了
////库中的迭代器也是不支持+n和-n的
////前置++
//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;
//}
//const T& operator*() const
//{
//return _node->_data;
//}
//const T* operator->() const
//{
//return &_node->_data;
//}
//bool operator!=(const Self& it)
//{
//return _node != it._node;
//}
//bool operator==(const Self& it)
//{
//return _node == it._node;
//}
//};
template<class T>
class list
{
typedef ListNode<T> Node;
public:
/*typedef ListIterator<T> iterator;
typedef ListConstIterator<T> const_iterator;*/
//注意:不能写成下面这种形式
//typedef ListConstIterator<const T> const_iterator;
//如果这样实现 那么在实例化的时候
//迭代器的类型为const 而Node的类型不是const
//那么类型就会不统一
//鉴于ListIterator和ListConstIterator内部都有很多
//重复的功能 因此我们把它们实现在同一个类中
//这样可以减少代码的冗余程度
typedef ListIterator<T, T&, T*> iterator;//常规迭代器
typedef ListIterator<T, const T&, const T*> const_iterator;//const迭代器
typedef ReverseListIterator<T, T&, T*> reverse_iterator;
typedef ReverseListIterator<T, const T&, const T*> const_reverse_iterator;
reverse_iterator rbegin()
{
//iterator it(_head->_next);
//return it;
return reverse_iterator(_head->_prev);
}
reverse_iterator rend()
{
return reverse_iterator(_head);
}
const_reverse_iterator crbegin() const
{
//iterator it(_head->_next);
//return it;
return const_reverse_iterator(_head->_next);
}
const_reverse_iterator crend() const
{
return const_reverse_iterator(_head);
}
iterator begin()
{
//iterator it(_head->_next);
//return it;
return iterator(_head->_next);
}
iterator end()
{
return iterator(_head);
}
const_iterator cbegin() const
{
//iterator it(_head->_next);
//return it;
return const_iterator(_head->_next);
}
const_iterator cend() const
{
return const_iterator(_head);
}
//empty_init 用于开辟_head结点 因为各个拷贝构造
//都需要先开辟_head结点 因此将此功能提取封装出来
void empty_init()
{
_head = new Node();
_head->_next = _head;
_head->_prev = _head;
}
list()
{ //_head作为哨兵位
/*_head = new Node();
_head->_next = _head;
_head->_prev = _head;*/
empty_init();
}
//拷贝构造
//拷贝构造的具体实现是这样的:
//首先先开辟_head结点 之后再不断地尾插
//push_back内部完成了结点链接操作的封装
// 因此可以直接复用尾插操作
list(const list<T>& lt)
{
empty_init();
auto it1 = lt.cbegin();
while(it1!=lt.cend())
{
//list<T>& lt中的每个结点进行插入
//通过迭代器 来拿到每个结点对应的数据域 插入到this中
push_back(it1._node->_data);
it1++;
}
//由于实现的迭代器begin的问题
//若想运行这里的for循环必须去掉参数中的const
//因为这里的范围for只会获取const_iterator
//而begin是iterator
//造成const_iterator到iterator的转换问题
/*for (const auto& e : lt)
{
push_back(e);
}*/
}
list(initializer_list<T> il)
{
empty_init();
for (const auto& e : il)
{
push_back(e);
}
}
//传值传参功能实现是这样的:
//直接利用swap函数进行交换
//注意这里的参数不是引用类型
//也就是说 这里会调用拷贝构造生成一个局部的临时对象lt
//然后 我们直接将*this和lt进行一个swap操作的值交换
//交换其中的_head _head是头结点
//拿到了它 就等于拿到了整个链表
list<T>& operator=(list<T> lt)
{
swap(_head, lt._head);
return *this;
}
//~list的功能是这样的:
//先清理各个结点 最后把_head清理了 让整个链表都清理干净
~list()
{
clear();
delete _head;
_head = nullptr;
}
//clear的功能是将每个结点的清理掉 除了_head结点
void clear()
{
auto it = begin();
while (it != end())
{
it = erase(it);
}
}
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;*/
insert(end(), x);
}
void pop_back()
{
erase(--end());
}
void push_front(const T& x)
{
insert(begin(), x);
}
void pop_front()
{
erase(begin());
}
//insert操作鉴于没有扩容缩容的操作
//因此不存在迭代器失效的问题
iterator insert(iterator pos, const T& x)
{
Node* cur = pos._node;
Node* newnode = new Node(x);
Node* prev = cur->_prev;
prev->_next = newnode;
newnode->_next = cur;
cur->_prev = newnode;
newnode->_prev = prev;
//返回新插入的结点迭代器
return iterator(newnode);
}
//erase要对pos结点进行delete操作
//那么必然会出现pos迭代器失效的问题
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);
}
private:
Node* _head;
};
//const迭代器 可以修改迭代器的指向 但不能修改迭代器指向的空间内容
void Func(const list<int>& lt)
{
list<int>::const_iterator it = lt.cbegin();
//const迭代器 可以修改迭代器的指向 但不能修改指向的内容
while (it != lt.cend())
{
//*it += 10;//const属性 不允许修改的操作
cout << *it << " ";
++it;
}
cout << endl;
}
void test_list1()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
list<int>::iterator it = lt1.begin();
while (it != lt1.end())
{
cout << *it << " ";
cout << endl;
cout << it.operator->() << endl;
//当T为内置类型时 it和it.operator->()是没有区别的
//当T为自定义类型时 it-> 等价于 it.operator->()->
++it;
}
cout << endl;
}
struct Pos
{
Pos(int row = 0, int col = 0)
:_row(row)
,_col(col)
{ }
int _row;
int _col;
};
void test_list2()
{
list<Pos> lt1;
lt1.push_back(Pos(100, 100));
lt1.push_back(Pos(200, 200));
lt1.push_back(Pos(300, 300));
list<Pos>::iterator it = lt1.begin();
while (it != lt1.end())
{
//cout << (*it)._row << ":" << (*it)._col << endl;
//这种写法只是为了通用性和可读性
cout << it->_row << ":" << it->_col << endl;
//这里是如何通过->进行调用的呢?
//上面的原型是这样的:
cout << it.operator->()->_row << ":" << it.operator->()->_col << endl;
//首先我们重载了->。 它的功能是这样的:返回数据域的指针
//第一次it->operator() 我们拿到了数据域的指针
//然后因为这里的数据是一个名为Pos的结构体 结构体本身满足使用->来访问成员
//因此第二次-> 来拿到各项成员
//上面的一行 使用一个-> 是一种简写的方法
it++;
}
cout << endl;
}
void test_list3()
{
使用常规迭代器访问常规对象
//list<int> lt1;
//lt1.push_back(1);
//lt1.push_back(2);
//lt1.push_back(3);
//lt1.push_back(4);
//lt1.push_back(5);
//list<int>::iterator it1 = lt1.begin();
//while (it1 != lt1.end())
//{
//*it1 += 10;
//cout << *it1 << " ";
//it1++;
//}
//cout << endl;
//Func(lt1);
//使用const型迭代器访问const型对象
//list<int>::const_iterator it2 = lt2.begin();
//while (it2 != lt2.end())
//{
////*it2 += 10;
//cout << *it2 << " ";
//it2++;
//}
//cout << endl;
}
void test_list4()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
Func(lt1);
lt1.push_front(10);
lt1.push_front(20);
lt1.push_front(30);
Func(lt1);
lt1.pop_front();
lt1.pop_front();
Func(lt1);
lt1.pop_back();
lt1.pop_back();
Func(lt1);
}
void test_list5()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
Func(lt1);
list<int>::reverse_iterator it = lt1.rbegin();
while (it != lt1.rend())
{
cout << *it << " ";
it++;
}
cout << endl;
}
void test_list6()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
list<int> lt2(lt1);
Func(lt1);
Func(lt2);
lt1.push_back(6);
Func(lt1);
Func(lt2);
}
void test_list7()
{
list<int> lt1;
lt1.push_back(1);
lt1.push_back(2);
lt1.push_back(3);
lt1.push_back(4);
lt1.push_back(5);
list<int> lt3;
lt3.push_back(10);
lt3.push_back(20);
lt3.push_back(30);
Func(lt1);
Func(lt3);
lt1 = lt3;
Func(lt1);
Func(lt3);
}
void test_list8()
{
list<int> lt1 = { 1,2,3,4,5 };
Func(lt1);
}
}
List.cpp
list中是双向迭代器,而不是随机迭代器
因此不允许使用sort全局方法 list在内部实现了自己的sort方法
此外 list的迭代器允许++ -- 但不允许+n -n
list为双向链表
//
//#include <iostream>
//#include <list>
//
//using namespace std;
//
//void test_list1()
//{
//list<int> lt1 = { 10,2,3,3,5,5,4,5,6 };
//list<int>::iterator it = lt1.begin();
//
//while (it != lt1.end())
//{
//cout << *it << " ";
//++it;
//}
//cout << endl;
//
//lt1.unique();//去重 不是有序的情况下 去重去除的不干净 因为它要求相同值必须都挨在一起
//
//for (auto e : lt1)
//{
//cout << e << " ";
//}
//cout << endl;
//
////sort(lt1.begin(), lt1.end());//不行 因为只有随机迭代器才能使用sort全局方法
//lt1.sort();//默认升序
//for (auto e : lt1)
//{
//cout << e << " ";
//}
//cout << endl;
//
//lt1.sort(greater<int>()); //利用仿函数实现降序
//for (auto e : lt1)
//{
//cout << e << " ";
//}
//cout << endl;
//
////去重
//lt1.unique();//该方法的使用前提是:list必须是有序的
//for (auto e : lt1)
//{
//cout << e << " ";
//}
//cout << endl;
//}
//
//void test_list2()
//{
////list.sort()的底层是归并排序 用处不大
////在数据量较大时 list的sort效率极低 远不如sort vector
////全局中的sort使用的是快排
//}
//
//int main()
//{
//test_list1();
//
//return 0;
//}
#include <iostream>
using namespace std;
#include "List.h"
int main()
{
/*bit::test_list1();*/
//bit::test_list2();
//bit::test_list3();
//bit::test_list4();
/*bit::test_list5();*/
/*bit::test_list6();*/
/*bit::test_list7();*/
bit::test_list8();
return 0;
}
三丶list与vector的对比
vector和list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及应用场景不同,下面我们来阐述一下他们的不同点:
vector | list | |
---|---|---|
底层结构 | 动态顺序表,一段连续空间 | 带头结点的双向循环链表 |
随机访问 | 支持随机访问,访问某个元素的效率为O(1) | 不支持随机访问,访问某个元素的效率为O(N) |
插入和删除 | 任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容。增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低 | 任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1) |
空间利用率 | 底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高 | 底层结点动态开辟,小结点容易造成内存碎片,空间利用率低,缓存利用率低 |
迭代器 | 原生态指针 | 对原生态指针(结点指针进行封装) |
迭代器失效 | 在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值,否则会失效 | 插入元素不会导致迭代器失效;删除元素时,只会导致当前迭代器失效,其他迭代器不受影响 |
使用场景 | 需要高效存储,支持随机访问,不关心插入和删除效率 | 大量插入和删除操作,不关心随机访问 |
本博客仅供个人参考,如有错误请多多包含。
Aruinsches-C++日志-5/30/2024
声明
本文内容仅代表作者观点,或转载于其他网站,本站不以此文作为商业用途
如有涉及侵权,请联系本站进行删除
转载本站原创文章,请注明来源及作者。