其实在日常中,链表的题目做的比较多,但是使用STL自带链表的还是比较少,所以里面的一些API不大熟悉。这边也简要介绍一些。

基本的一些API

先列举的这些和上面几篇用法几乎一样,所以不再累述。

赋值相关

  1. list(beg,end);//构造函数将[beg, end)区间中的元素拷贝给本身。
  2. list(n,elem);//构造函数将n个elem拷贝给本身。
  3. list(const list &lst);//拷贝构造函数。
  4. assign(beg, end);//将[beg, end)区间中的数据拷贝赋值给本身。
  5. assign(n, elem);//将n个elem拷贝赋值给本身。
  6. list& operator=(const list &lst);//重载等号操作符
  7. swap(lst);//将lst与本身的元素互换

插入、删除相关

  1. push_back(elem);//在容器尾部加入一个元素
  2. pop_back();//删除容器中最后一个元素
  3. push_front(elem);//在容器开头插入一个元素
  4. pop_front();//从容器开头移除第一个元素
  5. insert(pos,elem);//在pos位置插elem元素的拷贝,返回新数据的位置。
  6. insert(pos,n,elem);//在pos位置插入n个elem数据,无返回值。
  7. insert(pos,beg,end);//在pos位置插入[beg,end)区间的数据,无返回值。
  8. clear();//移除容器的所有数据
  9. erase(beg,end);//删除[beg,end)区间的数据,返回下一个数据的位置。
  10. erase(pos);//删除pos位置的数据,返回下一个数据的位置。
  11. remove(elem);//删除容器中所有与elem值匹配的元素。

list提供了可以从头部或尾部直接加入元素,也可以在头部或尾部直接删除元素。

把remove函数拿出来讲一下,如何删除自定义数据。如果我们的数据是内置类型,那很好删除,找到该elem,用 == 比较,为真就表示找到了,进行删除。而自定义数据用 == 是不能直接比较的,我们应该先重载==,再进行删除。我们重载的时候,要注意,传入的参数都必须是const类型,否则编译不会通过,因为编译器发觉我们会有改变数据的风险,导致比较结果出错!

大小操作

  1. size();//返回容器中元素的个数
  2. empty();//判断容器是否为空
  3. resize(num);//重新指定容器的长度为num,
  4. 若容器变长,则以默认值填充新位置。
  5. 如果容器变短,则末尾超出容器长度的元素被删除。
  6. resize(num, elem);//重新指定容器的长度为num,
  7. 若容器变长,则以elem值填充新位置。
  8. 如果容器变短,则末尾超出容器长度的元素被删除。

这边的size函数有个特殊点:

  • GCC下时间复杂度为O(n).
  • VC下时间复杂度为O(1).

在GCC下,size函数的复杂度为O(n),它是通过调用标准库的distance(begin(),end())算法来计算长度的。而在VC类的编译器下,维护了一个成员变量_Mysize,保存长度。就可以直接查出来,时间复杂度为O(1).

这其实是一种取舍,要先讲一下list::splice函数,这是让两个不同的链表进行拼接的函数。GCC为了维护这个函数,使拼接后长度更容易计算,因此没有给一个成员变量来保存大小,如果给了,那拼接后的长度是两个size相加吗?不一定,因为splice拼接方法有多种。所以没有给出。

存取操作

  1. front();//返回第一个元素。
  2. back();//返回最后一个元素。

注意是直接返回元素。

STL中list是一个双向循环链表

我们用一段代码来证明它是一个双向循环链表:从头结点遍历2倍的长度单位,看是否会循环再打印一次。

先说一下几个类型:

  1. //链表结点类型
  2. list<int>::_Nodeptr node;

而结点类有三个成员:下一节点指针,上一节点指针,数据:

  1. node->_Next;
  2. node->_Prev;
  3. node->_Myval;

再看例子:

  1. int main(){
  2. list<int> myList;
  3. for (int i = 0; i < 10; i ++){
  4. myList.push_back(i);
  5. }
  6. list<int>::_Nodeptr node = myList._Myhead->_Next;
  7. for (int i = 0; i < myList._Mysize * 2;i++){
  8. cout << "Node:" << node->_Myval << endl;
  9. node = node->_Next;
  10. if (node == myList._Myhead){
  11. node = node->_Next;
  12. }
  13. }
  14. return 0;
  15. }

首先我们用一个for循环,将数据存进去。

然后我们用一个节点指向头结点的的下一节点。list的头结点_Myhead是不存任何东西的,只是为了插入方便而已,所以这么整。所以我们要指向第一个有数据的节点。

当我们遍历一次链表长度时,到达尾节点时,若是循环链表,则会再回到头部,看一下执行结果:

可以清楚的看到,我循环了2倍长度,它遍历了两次。所以,list是一个循环链表。

list的反转和排序函数

reverse和sort函数

reverse函数就是反转函数,较为简单,没什么特殊的。sort函数排序也是一样,默认从小到大排序。

而我们可以是指定排序顺序的。有两种方法,一种是回调函数的方法,另一种是仿函数的方法。

现在假设我们有一个链表:

  1. list<int> myList;
  2. for (int i = 0; i < 5; i++){
  3. myList.push_back(i);
  4. }
  5. list.sort();

我们执行完sort的结果,会是从小到大排序的链表。也就是说,当sort的参数为空时,会执行默认的排序,那现在我们就给它传递一个参数,改变排序规则。

现在就来介绍上述的两种方法。

回调函数方法

先写出这个回调函数:

  1. bool mycmp(int a, int b)
  2. {
  3. return a > b;
  4. }
  • 返回值:bool(因为这里需要比较)。
  • 参数:int型(取决于你的链表存放的数据类型)。
  • 排序规则:从大到小,所以return a > b;

我们针对整型进行大到小的排序,所以,我们要返回a > b即可。

随后,我们再程序中执行:

  1. mylist.sort(mycmp);

这样就行了,就可以从大到小排序了。

仿函数方法

记住仿函数不是一个函数,是一个类。它通过重载()来生效。因为底层中使用了大量的cmp(a,b)这种形式来比较a和b的大小。来看一下:

  1. class MyCmp
  2. {
  3. public:
  4. bool operator()(int a, int b)
  5. {
  6. return a > b;
  7. }
  8. };

同样的,仿函数里面重载():

  • 返回值:bool(因为这里需要比较)。
  • 参数:int型(取决于你的链表存放的数据类型)。
  • 排序规则:从大到小,所以return a > b;

    可以看到,和回调函数的规则几乎一样。

调用形式为:

  1. mylist.sort(MyCmp());

为什么多了一对小括号呢?我们需要传入的是一个对象,而回调函数传进去就直接是一个函数对象,仿函数是一个类,我们加一对括号生成一个匿名对象,从而传递正确的参数。

下面看一个reverse和sort的使用例子:

  1. int main()
  2. {
  3. list<int> myList;
  4. for (int i = 0; i < 5; i++){
  5. myList.push_back(i);
  6. }
  7. //反转
  8. myList.reverse();
  9. list<int>::_Nodeptr node = myList._Myhead->_Next;
  10. cout << "反转之后:" << endl;
  11. for (size_t i = 0; i < myList._Mysize; ++i)
  12. {
  13. cout << node->_Myval << " ";
  14. node = node->_Next;
  15. }
  16. //再排序(从小到大)
  17. myList.sort();
  18. node = myList._Myhead->_Next;
  19. cout << endl << "默认排序:" << endl;
  20. for (size_t i = 0; i < myList._Mysize; ++i)
  21. {
  22. cout << node->_Myval << " ";
  23. node = node->_Next;
  24. }
  25. //再排序(从大到小)
  26. //myList.sort(mycmp); //使用回调函数方法,传入一个函数对象
  27. myList.sort(MyCmp()); //使用仿函数方法,传入一个匿名对象
  28. node = myList._Myhead->_Next;
  29. cout << endl << "从大到小排序:" << endl;
  30. for (size_t i = 0; i < myList._Mysize; ++i)
  31. {
  32. cout << node->_Myval << " ";
  33. node = node->_Next;
  34. }
  35. return 0;
  36. }

执行结果:

为什么不用标准库算法sort呢?

如果用系统提供的sort,那么参数就为迭代器:

  1. sort(mylist.begin(),mylist.end());

但实际上不能完成的。因为系统提供的sort函数对迭代器有要求:必须是可以随机访问的迭代器! 而list容器不提供随机访问的能力,所以不能使用。但是往往这类东西都会自己实现一个sort,所以不用担心。

一些复杂需求排序

讲完刚刚指定排序规则的排序,现在我们可以有更复杂的排序需求了:

若干学生,优先按照成绩大到小排序,成绩相同按照年龄小到大排序,年龄相同按照姓名升序排序,姓名相同按照班级降序排序。

这就是个不断比较的过程而已,没什么特殊的地方,回调函数如下:

  1. bool ComplicatedCmp(Stu &stu1, Stu &stu2)
  2. {
  3. if (stu1._iScore == stu2._iScore)
  4. {
  5. if (stu1._iAge == stu2._iAge)
  6. {
  7. if (stu1._strName == stu2._strName)
  8. {
  9. return stu1._iClass > stu2._iClass;
  10. }
  11. else
  12. {
  13. return stu1._strName < stu2._strName;
  14. }
  15. }
  16. else
  17. {
  18. return stu1._iAge < stu2._iAge;
  19. }
  20. }
  21. else
  22. {
  23. return stu1._iScore > stu2._iScore;
  24. }
  25. }

主函数:

  1. int main()
  2. {
  3. list<Stu> mylist;
  4. mylist.push_back(Stu("Tom", 4, 90, 13));
  5. mylist.push_back(Stu("Jerry", 5, 84, 11));
  6. mylist.push_back(Stu("Giao", 1, 99, 10));
  7. mylist.push_back(Stu("Fuck", 6, 35, 15));
  8. mylist.push_back(Stu("Bill", 2, 96, 17));
  9. mylist.push_back(Stu("Null", 2, 96, 16));
  10. mylist.push_back(Stu("Null", 0, 96, 17));
  11. mylist.sort(ComplicatedCmp);
  12. for (auto it = mylist.begin(); it != mylist.end(); ++it)
  13. {
  14. cout << "Score:" << it->_iScore << " Age:" << it->_iAge << " Name:" << it->_strName << " Class:" << it->_iClass << endl;
  15. }
  16. return 0;
  17. }

可以看到结果为:

已经按照我们想要的排序进行排序了。

我不熟悉的list的更多相关文章

  1. 高薪诚聘熟悉ABP框架的.NET高级开发工程师(2016年7月28日重发)

    招聘单位是ABP架构设计交流群(134710707)群主阳铭所在的公司-上海运图贸易有限公司 招聘岗位:.NET高级开发工程师工作地点:上海-普陀区 [公司情况]上海运图贸易有限公司,是由易迅网的创始 ...

  2. 学习Spring——两个你熟悉的不能再熟悉的场景使用

    最近公众号受邀获取了留言和赠送模板的权限,小开心(欢迎去公众号JackieZheng围观). 我们大致的了解了Spring这个框架对于依赖注入的使用和诠释可谓是淋漓尽致.因为有了Spring的这个IO ...

  3. vim 常用命令逐渐熟悉以及常用的配置记录

    本篇博客记录的是我自己学习vim的常用命令的过程中,各个命令的逐渐熟悉的顺序(有一部分已经熟悉的命令没有列出),已经对vim编辑器本身的一些设置的记录 1.G 快速将光标切换到文件的最底部 2.u 撤 ...

  4. Android探索之ContentProvider熟悉而又陌生的组件

    前言: 总结这篇文章之前我们先来回顾一下Android Sqlite数据库,参考文章:http://www.cnblogs.com/whoislcj/p/5506294.html,Android程序内 ...

  5. NSUserDefaults:熟悉与陌生(转)

    转载自:http://swiftcafe.io/2016/04/04/nsuserdefaults/?hmsr=toutiao.io&utm_medium=toutiao.io&utm ...

  6. 20145205 实验一 Java开发环境的熟悉

    实验内容 命令行下Java程序开发 IDEA下Java程序开发.调试 练习(通过命令行和Eclipse两种方式实现,在Eclipse下练习调试程序) 实现凯撒密码,并进行测试 实验要求 使用JDK编译 ...

  7. [dpdk] 熟悉SDK与初步使用 (四)(L3 Forwarding源码分析)

    接续前节:[dpdk] 熟悉SDK与初步使用 (三)(IP Fragmentation源码分析) 前文中的最后一个问题,搁置,并没有找到答案.所以继续阅读其他例子的代码,想必定能在其他位置看到答案. ...

  8. [dpdk] 熟悉SDK与初步使用 (二)(skeleton源码分析)

    接续前节:[dpdk] 熟悉SDK与初步使用 (一)(qemu搭建实验环境) 程序逻辑: 运行参数: 关键API: 入口函数: int rte_eal_init(int argc, char **ar ...

  9. [dpdk] 熟悉SDK与初步使用 (三)(IP Fragmentation源码分析)

    对例子IP Fragmentation的熟悉,使用,以及源码分析. 功能: 该例子的功能有二: 一: 将IP分片? 二: 根据路由表,做包转发. 路由表如下: IP_FRAG: Socket : ad ...

  10. Bean熟悉替换,只替换部分属性,其他属性值不改变

    Bean熟悉替换,只替换部分属性,其他属性值不改变 需要加入:asm.jar  cglib-2.1.jar,用来map和bean之间的转换(比spring和反射的效率好,因为加入了缓存) packag ...

随机推荐

  1. Spring 最常用的 7 大类注解,史上最强整理!

    随着技术的更新迭代,Java5.0开始支持注解.而作为java中的领军框架spring,自从更新了2.5版本之后也开始慢慢舍弃xml配置,更多使用注解来控制spring框架. 而spring的的注解那 ...

  2. Makefile 书写规则

    1.1 Makefile的规则 在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则. target ... : prerequisites ...   command   ...

  3. 双01字典树最小XOR(three arrays)--2019 Multi-University Training Contest 5(hdu杭电多校第5场)

    题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=6625 题意: 给你两串数 a串,b串,让你一一配对XOR使得新的 C 串字典序最小. 思路: 首先这边 ...

  4. 从入门到自闭之Python高阶函数

    高阶函数:内部帮忙做了一个for循环 filter:筛选过滤 语法: filter(function,iterable) function: 1.指定过滤规则(函数的内存地址) 2.用来筛选的函数,在 ...

  5. Antdesign Form 实现页面控件的赋值加载

    使用Antdesign Form时,当页面加载时,需要从后台获取数据,对Form中控件的默认赋值.看似比较简单的需求,而且Antdesign 官方文档中也有相应介绍,然后对于Form 的CheckBo ...

  6. Sklearn使用良心完整入门教程

    The complete .ipynb file can be download through my share in onedrive:https://1drv.ms/u/s!Al86h1dThX ...

  7. O012、Linux如何实现VLAN

    参考https://www.cnblogs.com/CloudMan6/p/5313994.html   LAN 表示 Local Area Network ,本地局域网,通常使用 Hub 或者 Sw ...

  8. PHP高级进阶之路

    一:常见模式与框架 学习PHP技术体系,设计模式,流行的框架 常见的设计模式,编码必备 Laravel.ThinkPHP开发必不可少的最新框架 YII.Symfony4.1核心源码剖析 二:微服务架构 ...

  9. 时间切片分割long work

    思想 时间切片的核心思想是:如果任务不能在50毫秒内执行完,那么为了不阻塞主线程,这个任务应该让出主线程的控制权,使浏览器可以处理其他任务.让出控制权意味着停止执行当前任务,让浏览器去执行其他任务,随 ...

  10. IE浏览器清除缓存及历史浏览数据

    IE浏览器清除缓存方法如下: 打开IE浏览器,依次点击"工具-Internet选项-常规-删除",如下图所示, 有的时候发现你明明已经执行了删除,但是实际上还是有缓存数据,一般是因 ...