当前位置: 首页 > news >正文

【C++】list容器的基本使用

一、list是什么

list的底层结构是带头双向循环链表。

相较于 vector 的连续线性空间,list 就显得复杂很多,它是由一个个结点构成,每个结点申请的空间并不是连续的,它的好处是每次插入或删除一个数据,就配置或释放一个元素空间,我们不用在意扩容问题了,也不用单独写扩容函数了,list对于任何位置的元素进行插入或者元素移除,时间复杂度都是O(1),它的缺点是不支持随机访问,即通过下标直接访问对应元素,这是由于它的底层是链表,链表中的结点在空间上分配是不连续的。

二、list容器的基本使用

1、构造函数

这里的allocator是空间配置器,我们这里先不用管它(认为它不存在),等到后面的篇章会具体讲解它的作用。 其中,(1)是默认构造 (2)是n个val构造 (3)是迭代器区间构造 (4)是拷贝构造。

2、析构函数

释放每个结点申请的空间资源。

3、赋值重载

它的操作数是两个已有的list对象。

4、迭代器

(1)使用

list的迭代器和vector的迭代器在用法上一模一样。

使用迭代器可以进行遍历:

#include <iostream>
#include <list>
using namespace std;void test_list1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;
}int main()
{test_list1();return 0;
}

运行结果:

 

支持迭代器,就支持范围for,因为范围for的底层就是迭代器。

void test_list1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);for (auto e : lt){cout << e << " ";}cout << endl;
}int main()
{test_list1();return 0;
}

运行结果:

 

(2)分类

迭代器从功能上进行分类,分为iterator、const_iterator、reverse_iterator、const_reverse_iterator这四类,它还可以从性质上进行分类,分为单向迭代器、双向迭代器、随机迭代器。

单向迭代器包括(forward):forward_list / unordered_map... 它只支持++

双向迭代器(bidirectional)包括:list / map / set... 它只支持++ / --

随机迭代器(random access)包括:vector / string / deque... 它支持++ / -- / + / -

它们的底层结构决定它们的性质,它们的底层结构也决定它们可以使用哪些算法。

比如:

C++标准库中sort的模板参数是RandomAccessIterator,从它的名字中可以看出它只接收随机迭代器,如果传一个单向迭代器,它也可以接收,但运行时会发生错误即程序不能正常运行。

C++标准库中reverse的模板参数是BidirectionalIterator,从它的名字中可以看出它只接收双向迭代器,如果传一个单向迭代器,它也可以接收,但运行时会发生错误即程序不能正常运行。如果传一个随机迭代器,也可以正常使用,因为随机迭代器的范围更广,凡是支持单向迭代器或者双向迭代器的都支持随即迭代器。

出现InputIterator,说明单向/双向/随机迭代器都可以使用这个函数。

5、成员函数

(1)empty()

判断容器内节点个数是否为空,若为空返回true,否则返回false。

(2)size()

返回容器内节点个数。

(3)front()

reference 就是list<T>中的T,T是模板参数。front就是用来返回第一个结点的值。

(4)back()

 返回最后一个结点的值。

(5)assign()

用于赋值。(1)是用一段迭代区间赋值 (2)是用n个val赋值

(6)push_front()

头插,头部插入一个新结点。

(7)pop_front()

头删,删除头部的结点。 

(8)push_back()

尾插,尾部插入一个新结点。

(9)pop_back()

尾删,删除尾部的结点。

(10)emplace_back()

它的功能和push_back()差不多,但有不同之处,现阶段我们就把它当作push_back()使用。

struct AA
{AA(int a1, int a2):_a1(a1), _a2(a2){}
private:int _a1;int _a2;
};void test_list2()
{list<AA> lt;AA aa1(1, 1);lt.push_back(aa1);lt.push_back(AA(2,2));//lt.push_back(3,3); //err,这里会报错lt.emplace_back(aa1);lt.emplace_back(AA(2,2));lt.emplace_back(3,3); //ok,与push_back()的不同之处,它支持直接传构造A对象的参数
}int main()
{test_list2();return 0;
}

(10)insert()

(1)是在pos位置前插入val (2)是在pos位置前插入n个val (3)是在pos位置前插入一段迭代区间。 

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);for (auto e : lt)cout << e << " ";cout << endl;//在第3个位置前(整体位置是从0开始编号的)插入30//lt.insert(lt.begin() + 3, 30); //list容器不支持+,所以这行代码会报错list<int>::iterator it = lt.begin();int k = 3;while (k--){++it;//list容器支持++,所以这行代码会报错}lt.insert(it, 30);  //(1)for (auto e : lt)cout << e << " ";cout << endl;
}int main()
{test_list3();return 0;
}

运行结果: 

(11)erase()

 (1)删除pos位置上的值 (2)删除一段迭代区间

void test_list4()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);for (auto e : lt)cout << e << " ";cout << endl;int x;cin >> x;list<int>::iterator it = find(lt.begin(),lt.end(),x);if (it != lt.end()) //找到了lt.erase(it); //(1)elsecout << "不存在:" << x << endl;for (auto e : lt)cout << e << " ";cout << endl;}int main()
{test_list4();return 0;
}

输入4,运行结果: 

(12)clear()

清空所有结点数据。

(13)resize()

将结点个数更新到n,假设原先节点个数为k。

1、k > n

释放k - n个结点

2、k < n

增加n - k个节点,结点中的内容自己可以传,也可以不传,用默认的缺省值。

因为list的底层是链表,所以没有扩容这一说法。

(14)reverse()

逆置结点的顺序。

void test_list5()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);for (auto e : lt)cout << e << " ";cout << endl;lt.reverse();for (auto e : lt)cout << e << " ";cout << endl;//reverse(lt.begin(),lt.end()); //这里的算法库中的reverse也支持list容器的逆置
}int main()
{test_list5();return 0;
}

运行结果:

 

(15)sort()

 由于算法库中的sort不支持list容器的迭代器,所以这里它自己实现了一个。

void test_list6()
{list<int> lt;lt.push_back(20);lt.push_back(40);lt.push_back(50);lt.push_back(30);lt.push_back(10);for (auto e : lt)cout << e << " ";cout << endl;//默认排升序lt.sort(); //(1)for (auto e : lt)cout << e << " ";cout << endl;}int main()
{test_list6();return 0;
}

运行结果: 

 

如果想要排降序,就要借助仿函数:

void test_list6()
{list<int> lt;lt.push_back(20);lt.push_back(40);lt.push_back(50);lt.push_back(30);lt.push_back(10);for (auto e : lt)cout << e << " ";cout << endl;//排降序greater<int> gt;lt.sort(gt);for (auto e : lt)cout << e << " ";cout << endl;}int main()
{test_list6();return 0;
}

运行结果:

 

(16)merge()

合并两个链表,前提是这两个链表需要有序。 

void test_list7()
{list<int> first, second;first.push_back(3);first.push_back(6);first.push_back(1);second.push_back(2);second.push_back(5);second.push_back(4);first.sort();second.sort();first.merge(second); //合并后,second这个链表就空了,相当于将它移到first上了for (auto e : first)cout << e << " ";cout << endl;cout << "----------" << endl;for (auto e : second) //second中已经没有数据了cout << e << " ";cout << endl;cout << "----------" << endl;
}int main()
{test_list7();return 0;
}

运行结果: 

 

 (17)unique()

去重,删除容器中与val值一样的结点,只保留第一个。删除时要保证重复的数据紧挨着,否则会删不干净,建议删除前先排序。

void test_list8()
{list<int> lt;lt.push_back(5);lt.push_back(1);lt.push_back(4);lt.push_back(2);lt.push_back(4);lt.push_back(3);lt.push_back(3);lt.push_back(5);for (auto e : lt)cout << e << " ";cout << endl;lt.sort();lt.unique();for (auto e : lt)cout << e << " ";cout << endl;
}int main()
{test_list8();return 0;
}

运行结果: 

(18)remove()

对val进行查找,如果找到就删除,否则什么也不做。 

(19)remove_if()

给定一个条件,如果满足就删除,否则什么也不做。

(20)splice()

 (1)是将x中全部结点转移到当前容器中的pos位置之前的位置处 (2)是将x中i位置的结点转移到当前容器中的pos位置之前的位置处 (3)是将x中的一段迭代区间上的结点转移到当前容器中的pos位置之前的位置处

//将一个链表的结点转移给另一个链表
void test_list9()
{list<int> mylist1, mylist2;list<int>::iterator it;//先插入一些值for (int i = 1; i <= 4; ++i)mylist1.push_back(i);      // mylist1: 1 2 3 4for (int i = 1; i <= 3; ++i)mylist2.push_back(i * 10);   // mylist2: 10 20 30cout << "初始值:" << endl;for (auto e : mylist1)cout << e << " ";cout << endl;for (auto e : mylist2)cout << e << " ";cout << endl;cout << "---------------" << endl;it = mylist1.begin();++it;                     mylist1.splice(it, mylist2); //(1)// mylist1: 1 10 20 30 2 3 4// mylist2中的数据已经没有了// "it" 仍然指向2 cout << "第一次调用splice后:" << endl;for (auto e : mylist1)cout << e << " ";cout << endl;for (auto e : mylist2)cout << e << " ";cout << endl;cout << "---------------" << endl;mylist2.splice(mylist2.begin(), mylist1, it); //(2)// mylist1: 1 10 20 30 3 4// mylist2: 2// "it"现在是无效的cout << "第二次调用splice后:" << endl;for (auto e : mylist1)cout << e << " ";cout << endl;for (auto e : mylist2)cout << e << " ";cout << endl;cout << "---------------" << endl;
}int main()
{test_list9();return 0;
}

运行结果: 

根据它的功能,我们还可以调整当前链表的结点顺序:

//调整当前链表结点的顺序
void test_list10()
{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);cout << "转移前:" << endl;for (auto e : lt)cout << e << " ";cout << endl;//现要求将6这个结点转移到1这个结点的前面去lt.splice(lt.begin(), lt, --lt.end());cout << "转移后:" << endl;for (auto e : lt)cout << e << " ";cout << endl;
}int main()
{test_list10();return 0;
}

三、排序效率

这里我们将list容器中的sort与算法库中的sort进行效率对比,看看哪个更优。

(1)对比一

#include <vector>
#include <iostream>
#include <algorithm>
#include <list>
using namespace std;void test_op1()
{srand((unsigned int)time(0));const int N = 1000000;list<int> lt1;vector<int> v;for (int i = 0;i < N;++i) //随机生成100万个数据{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();cout << "vector sort:" << end1 - begin1 << endl;cout << "list sort:" << end2 - begin2 << endl;
}int main()
{test_op1();return 0;
}

在Debug环境下的运行结果:

可以看到list排序优势更大,那是因为在Debug环境下,优化不是很大,运行结果不具有参考价值,在release环境下,双方优化都是最大的,这才公平。

在Realse环境下运行结果:

实际上,算法库中的sort效率是更好的。 

(2)对比二

#include <vector>
#include <iostream>
#include <algorithm>
#include <list>
using namespace std;void test_op2()
{srand((unsigned int)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<int> v(lt2.begin(), lt2.end()); //迭代器区间构造sort(v.begin(), v.end()); //用算法库中的sort进行排序lt2.assign(v.begin(), v.end()); //再拷贝回lt2int end1 = clock();int begin2 = clock();lt1.sort(); //直接调用list中的sort进行排序int end2 = clock();cout << "list copy vector sort copy list sort:" << end1 - begin1 << endl;cout << "list sort:" << end2 - begin2 << endl;
}int main()
{test_op2();return 0;
}

在Release环境下运行结果: 

这差距就出来了, 在begin1~end1这段时间内做的事情比begin2~end2这段时间内做的还要多,结果花费的时间还少,说明了当数据量比较大时,list中的sort排序效率很低。不如先把它的值拷贝给vector容器,然后排序,排完再拷贝回来。

四、结语

本篇内容到这里就结束了,主要讲了list容器的基本使用,希望对大家有些许收获,祝大家天天开心!

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 音视频入门基础:AAC专题(7)——FFmpeg源码中计算AAC裸流每个packet的size值的实现
  • 【Python语言初识(二)】
  • 快速响应:提升前端页面加载速度技巧的必知策略方案
  • 【React】React18.2.0核心源码解读
  • 01-Mac OS系统如何下载安装Python解释器
  • AI大模型之旅--milvus向量库安装
  • 【Mysql-索引总结】
  • Centos 7 搭建Samba
  • 主流卷积神经网络CNN总结
  • MySQL5.7中增加的JSON特性的处理方法JSON_EXTRACT和JSON_ARRAY_APPEND以及MYSQL中JSON操作的方法大全
  • 小程序服务零工市场
  • 神经网络 归一化层
  • shell脚本(9.20)
  • 机器翻译之多头注意力(MultiAttentionn)在Seq2Seq的应用
  • 音视频入门基础:AAC专题(4)——ADTS格式的AAC裸流实例分析
  • 网络传输文件的问题
  • Mac 鼠须管 Rime 输入法 安装五笔输入法 教程
  • nginx 负载服务器优化
  • vue从创建到完整的饿了么(11)组件的使用(svg图标及watch的简单使用)
  • XML已死 ?
  • 编写符合Python风格的对象
  • 第三十一到第三十三天:我是精明的小卖家(一)
  • 缓存与缓冲
  • 买一台 iPhone X,还是创建一家未来的独角兽?
  • 免费小说阅读小程序
  • 区块链将重新定义世界
  • 设计模式 开闭原则
  • 深入浅出webpack学习(1)--核心概念
  • 算法---两个栈实现一个队列
  • 微信小程序--------语音识别(前端自己也能玩)
  • 原生js练习题---第五课
  • 正则学习笔记
  • nb
  • 7行Python代码的人脸识别
  • ​ ​Redis(五)主从复制:主从模式介绍、配置、拓扑(一主一从结构、一主多从结构、树形主从结构)、原理(复制过程、​​​​​​​数据同步psync)、总结
  • ​LeetCode解法汇总1276. 不浪费原料的汉堡制作方案
  • ​VRRP 虚拟路由冗余协议(华为)
  • ​必胜客礼品卡回收多少钱,回收平台哪家好
  • ​你们这样子,耽误我的工作进度怎么办?
  • # Panda3d 碰撞检测系统介绍
  • #、%和$符号在OGNL表达式中经常出现
  • #vue3 实现前端下载excel文件模板功能
  • (Arcgis)Python编程批量将HDF5文件转换为TIFF格式并应用地理转换和投影信息
  • (黑马C++)L06 重载与继承
  • (一) 初入MySQL 【认识和部署】
  • (转)memcache、redis缓存
  • (自用)learnOpenGL学习总结-高级OpenGL-抗锯齿
  • **PyTorch月学习计划 - 第一周;第6-7天: 自动梯度(Autograd)**
  • .Net CoreRabbitMQ消息存储可靠机制
  • .net 反编译_.net反编译的相关问题
  • .Net的DataSet直接与SQL2005交互
  • .net通过类组装数据转换为json并且传递给对方接口
  • /bin、/sbin、/usr/bin、/usr/sbin
  • @configuration注解_2w字长文给你讲透了配置类为什么要添加 @Configuration注解
  • @RequestParam,@RequestBody和@PathVariable 区别