【C++从0到王者】第十四站:list基本使用及其介绍

文章目录

  • 一、list基本介绍
  • 二、list基本使用
    • 1.尾插头插接口使用
    • 2.insert接口使用
    • 3.查找某个值所在的位置
    • 4.erase接口使用以及迭代器失效
    • 5.reverse
    • 6.sort
    • 7.merge
    • 8.unique
    • 9.remove
    • 11.splice
  • 三、list基本使用完整代码

一、list基本介绍

如下所示,是库里面对list的基本介绍
在这里插入图片描述

链表是序列容器,允许在序列内的任何位置进行常量时间的插入和擦除操作,以及两个方向的迭代。

链表容器被实现为双链表;双链表可以将它们包含的每个元素存储在不同且不相关的存储位置。排序是通过与前面元素的链接和后面元素的链接的每个元素的关联在内部保持的。

它们与forward_list非常相似:主要区别在于forward_list对象是单链表,因此它们只能向前迭代,以换取更小和更高效。

与其他基本标准序列容器(array、vector和deque)相比,链表在容器内的任何位置插入、提取和移动元素(迭代器已经获得)方面通常表现更好,因此在大量使用链表的算法(如排序算法)中也表现更好。

与其他序列容器相比,列表和forward_lists的主要缺点是它们无法通过位置直接访问元素;例如,要访问链表中的第六个元素,必须从已知位置(如开始或结束)迭代到该位置,这需要在两者之间的距离上花费线性时间。它们还消耗一些额外的内存来保存与每个元素相关联的链接信息(对于包含小元素的大型列表来说,这可能是一个重要因素)。

在这里插入图片描述

二、list基本使用

我们先简单的使用一下一下list,list的使用与vector基本是一致的,需要注意的是,对于list是没有[]运算符去访问的,因为其底层是一个双向带头循环链表。

1.尾插头插接口使用

void testlist1()
{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_front(7);lt.push_front(8);list<int>::iterator it = lt.begin();while(it != lt.end()){cout << *it << " ";it++;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;
}

在这里插入图片描述

2.insert接口使用

从下面,我们已经看出来,对于list它的接口已经全部是迭代器去访问了,可以是在某个迭代器处插入一个值,可以是插入n个val,也可以是插入一个迭代器区间
在这里插入图片描述
在这里需要注意的是,由于list本身是一个链表的特性,所以如果想要在第五个位置插入一个值,以下写法是错误的。如果是vector确实可以这样插入
在这里插入图片描述

如果是我们自己去写这个list的话,我们可以加上这个运算符重载,但是库里面觉得这个代价太大,所以没有加

但是如果非要在第五个位置处插入,我们可以这样写,这样直接插入的代价是比较低的,因为其只需要改变连接关系即可

void testlist2()
{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();for (int i = 0; i < 5; i++){it++;}lt.insert(it, 10);it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;
}

在这里插入图片描述

3.查找某个值所在的位置

其实在C++中list并没有直接提供这个接口,这是因为C++中是将容器和算法进行了分离。而这两个是通过迭代器进行连接起来的。
在这里插入图片描述

因为迭代器并不暴露容器底层的细节。就能让算法直接访问容器里面的元素。
在这里插入图片描述

注意库里面使用的是不等于,而非小于,这是为了适应其他各种各样的容器而设置的

比如list容器中,如果不采用这种设计结构,那么由于本来每个地址都是离散的,并不能确保每个结点的地址究竟是如何的,而产生严重的错误。
在这里插入图片描述

我们现在来使用一下

void testlist3()
{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 pos = find(lt.begin(), lt.end(), 3);lt.insert(pos, 300);list<int>::iterator it = lt.begin();it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;
}

在这里插入图片描述

那么我们现在再来探讨一下,vector里使用insert后会存在失效问题,那么链表中还有吗。

我们可以测试一下

在这里插入图片描述

测试结果显示,迭代器并未失效,其实这里也挺好理解的,因为list的insert中它是一个结点一个结点的插入进去。而vector里则是看容量的,容量不够扩容时候,需要整体将空间移动位置,才导致的迭代器失效。而list不存在扩容问题。是不会将之前的数据都给换位置的。故而不导致迭代器失效。

4.erase接口使用以及迭代器失效

如下所示,erase可以删除某个迭代器位置的值,也可以删除某一个迭代器区间。
在这里插入图片描述

erase的使用很简单,我们这里主要研究以下,erase是否存在迭代器失效问题呢?其实是存在的,因为erase删除某一个结点后,由于list是离散的,所以之前的那个pos已经无法找到其他的结点的,这个迭代器已经是一个野指针了。故而失效。但是同样的库里面的解决方法就是增加一个返回值,返回已删除元素的下一个元素迭代器。
在这里插入图片描述

当我们想要删除所有的偶数的时候,我们也很容易的可以写出以下代码
在这里插入图片描述

5.reverse

注意了啊,这里是逆置,不是reserve(扩容)

这个接口的作用就是逆置链表
在这里插入图片描述

list里面虽然设计了这个接口,但是事实上这个接口是没有必要涉及的,有一点冗余,因为在算法库里面也设计了这个算法。而且这两个算法的思路是一模一样的
在这里插入图片描述

如下是测试代码

void testlist4()
{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());for (auto e : lt){cout << e << " ";}cout << endl;
}

在这里插入图片描述

6.sort

如下是list里面的sort,可以直接排升序或者降序,使用很方便
在这里插入图片描述

但是算法库里面也设计了一个sort,但是这个是冗余的吗?先说结论,算法库里面的sort对于list而言是用不了的。会直接报错
在这里插入图片描述

不相信的话,我们可以测试一下
在这里插入图片描述
那么为什么用不了呢?我们可以深入去查看一下
我们从错误列表里面可以定位到是这里报错了
在这里插入图片描述

算法库里面使用的是快排,快排是无法适应list里面的这个场景的。

事实上,我们不难注意到,在算法库中,虽然每个算法都是一个函数模板,但是函数模板的迭代器名字有所差异
在这里插入图片描述

事实上,这是因为我们对迭代器从功能上进行了分类。注意这里的是由迭代器的性质(容器的底层结构决定的),从而进行的划分。他是一个隐式的划分
在这里插入图片描述

而上面的模板参数中对于迭代器的分类其实也显而易见了,Input就是所有迭代器都可以去用的。Bid这种迭代器就适合双向的迭代器去调用。Radom就适合随机迭代器去使用。

所以list适合双向迭代器,用不了随机的。

那么我们怎么知道哪个容器是哪种类型的呢?其实库里面已经全部说了

下面是list的迭代器
在这里插入图片描述

注意上面的不是彻底定死了,比如vector是随机迭代器,但他可以认为是特殊的双向迭代器。所以也是可以调用reverse接口的。
在这里插入图片描述
下面是vector的迭代器
在这里插入图片描述

再比如库里面也有单链表,forward_list,就是一个典型的单向迭代器
在这里插入图片描述

所以现在我们就清楚了为什么不能使用算法库里面的sort了,所以链表里面的sort就有点意义了。

当然呢,也只是有点意义罢了,其实也没有多大意义,为什么这么说呢?因为如果我们把list的数据拷贝给一个vector,然后用vector去排序,我们会发现,vector的效率远远高于list的效率

我们可以用如下代码去测试一下:注意需要在release环境下去测试,因为debug下有很多调试信息会影响两个的实际速度

void testlist5()
{srand(time(0));const int N = 1000000;vector<int> v; v.reserve(N);list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand();lt2.push_back(e);lt1.push_back(e);}// 拷贝到vector排序,排完以后再拷贝回来int begin1 = clock();// 先拷贝到vectorfor (auto e : lt1){v.push_back(e);}// 排序sort(v.begin(), v.end());// 拷贝回去size_t i = 0;for (auto& e : lt1){e = v[i++];}int end1 = clock();int begin2 = clock();lt2.sort();int end2 = clock();printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}

在这里插入图片描述

可见确实vector要优于list。因为库里面用的是快排,list里面用的是归并。快排还是要稍微优于归并一点的。

7.merge

如下所示,它的作用其实就是两个链表进行归并,注意归并前必须先进行排序
在这里插入图片描述

// list::merge
#include <iostream>
#include <list>// compare only integral part:
bool mycomparison(double first, double second)
{return (int(first) < int(second));
}int main()
{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);// (second is now empty)second.push_back(2.1);for (auto e : second){std::cout << e << " ";}std::cout << std::endl;first.merge(second, mycomparison);std::cout << "first contains:";for (std::list<double>::iterator it = first.begin(); it != first.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';return 0;
}

如下是运行结果,我们可以发现一些现象,首先是归并之后,second的结点都被归并走了,它里面也就没有数据了。
注意,在第二次合并中,函数mycomparison没有考虑2.1比2.2或2.9低,因为进行了强制类型转换,所以它被插入到它们之后,在3.1之前。
在这里插入图片描述

8.unique

这个函数的功能是去重,但是要注意首先得先进行排序,才能进行去重。否则效率极低
在这里插入图片描述

9.remove

remove其实相当于find+erase。先找到要移除的结点,然后删除掉。
在这里插入图片描述如下代码所示

void testlist6()
{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.remove(3);lt.remove(10);for (auto e : lt){cout << e << " ";}cout << endl;
}

找到3以后,移除掉这个结点,如果这个值不存在,即找不到,就什么也不做。
在这里插入图片描述

11.splice

这个是粘接、转移的意思,它可以将一个链表的某个结点拿走,交给另外一个链表
在这里插入图片描述

这三个函数的意思分别是,第一个函数是,将x链表全部转移到该链表的pos位置之前,第二个函数是将x链表的i结点转移到pos之前,第三个函数是,将x链表的某个迭代器区间,转移到pos之前。

如下代码所示,是该函数可以使用的情形

void testlist7()
{list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);list<int> lt2;lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);list<int> lt3;lt3.push_back(100);lt3.push_back(200);lt3.push_back(300);for (auto e : lt1){cout << e << " ";}	cout << endl;for (auto e : lt2){cout << e << " ";}cout << endl;list<int>::iterator it = lt1.begin();it++;lt1.splice(it, lt2);for (auto e : lt1){cout << e << " ";}cout << endl;list<int>::iterator it3 = lt3.begin();it3++;lt1.splice(it, lt3, it3);for (auto e : lt1){cout << e << " ";}cout << endl;lt1.splice(it, lt3, lt3.begin(), lt3.end());for (auto e : lt1){cout << e << " ";}cout << endl;it = lt1.begin();lt1.splice(it, lt1, ++lt1.begin(), lt1.end());for (auto e : lt1){cout << e << " ";}cout << endl;
}

在这里插入图片描述

三、list基本使用完整代码

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
#include<algorithm>
#include<list>
#include<vector>
using namespace std;void testlist1()
{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_front(7);lt.push_front(8);list<int>::iterator it = lt.begin();while(it != lt.end()){cout << *it << " ";it++;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;
}
void testlist2()
{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();for (int i = 0; i < 5; i++){it++;}lt.insert(it, 10);it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;
}
void testlist3()
{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 pos = find(lt.begin(), lt.end(), 3);lt.insert(pos, 300);*pos *= 10;list<int>::iterator it = lt.begin();it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;//lt.erase(pos);//*pos = 10;it = lt.begin();while (it != lt.end()){if (*it % 2 == 0){it = lt.erase(it);}else{it++;}}it = lt.begin();while (it != lt.end()){cout << *it << " ";it++;}cout << endl;
}
void testlist4()
{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());for (auto e : lt){cout << e << " ";}cout << endl;//sort(lt.begin(), lt.end());
}void testlist5()
{srand(time(0));const int N = 1000000;vector<int> v; v.reserve(N);list<int> lt1;list<int> lt2;for (int i = 0; i < N; ++i){auto e = rand();lt2.push_back(e);lt1.push_back(e);}// 拷贝到vector排序,排完以后再拷贝回来int begin1 = clock();// 先拷贝到vectorfor (auto e : lt1){v.push_back(e);}// 排序sort(v.begin(), v.end());// 拷贝回去size_t i = 0;for (auto& e : lt1){e = v[i++];}int end1 = clock();int begin2 = clock();lt2.sort();int end2 = clock();printf("vector sort:%d\n", end1 - begin1);printf("list sort:%d\n", end2 - begin2);
}void testlist6()
{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.remove(3);lt.remove(10);for (auto e : lt){cout << e << " ";}cout << endl;
}
void testlist7()
{list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);list<int> lt2;lt2.push_back(10);lt2.push_back(20);lt2.push_back(30);list<int> lt3;lt3.push_back(100);lt3.push_back(200);lt3.push_back(300);for (auto e : lt1){cout << e << " ";}	cout << endl;for (auto e : lt2){cout << e << " ";}cout << endl;list<int>::iterator it = lt1.begin();it++;lt1.splice(it, lt2);for (auto e : lt1){cout << e << " ";}cout << endl;list<int>::iterator it3 = lt3.begin();it3++;lt1.splice(it, lt3, it3);for (auto e : lt1){cout << e << " ";}cout << endl;lt1.splice(it, lt3, lt3.begin(), lt3.end());for (auto e : lt1){cout << e << " ";}cout << endl;it = lt1.begin();lt1.splice(it, lt1, ++lt1.begin(), lt1.end());for (auto e : lt1){cout << e << " ";}cout << endl;
}
int main()
{testlist7();return 0;
}//
// list::merge
//#include <iostream>
//#include <list>
//
// compare only integral part:
//bool mycomparison(double first, double second)
//{
//    return (int(first) < int(second));
//}
//
//int main()
//{
//    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);
//
//     (second is now empty)
//
//    second.push_back(2.1);
//    for (auto e : second)
//    {
//        std::cout << e << " ";
//    }
//    std::cout << std::endl;
//    first.merge(second, mycomparison);
//
//    std::cout << "first contains:";
//    for (std::list<double>::iterator it = first.begin(); it != first.end(); ++it)
//        std::cout << ' ' << *it;
//    std::cout << '\n';
//
//    return 0;
//}

好了,本期内容就到这里了

如果对你有帮助的话,不要忘记点赞加收藏哦!!!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/15865.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

pytorch学习——正则化技术——权重衰减

一、概念介绍 权重衰减&#xff08;Weight Decay&#xff09;是一种常用的正则化技术&#xff0c;它通过在损失函数中添加一个惩罚项来限制模型的复杂度&#xff0c;从而防止过拟合。 在训练参数化机器学习模型时&#xff0c; 权重衰减&#xff08;weight decay&#xff09;是…

消防应急照明设置要求在炼钢车间电气室的应用

摘 要:文章以GB51309—2018《消防应急照明和疏散指示系统技术标准》为设计依据&#xff0c;结合某炼钢车间转炉项目的设计过程&#xff0c;在炼钢车间电气室的疏散照明和备用照明的设计思路、原则和方法等方面进行阐述。通过选择合理的消防应急疏散照明控制系统及灯具供配电方案…

国内办公协作系统评测:5 款软件推荐

办公协作系统是现代信息化办公的必备工具之一&#xff0c;对于企业来说&#xff0c;选择一款好用的办公协作系统非常重要。然而&#xff0c;在众多的办公协作系统中&#xff0c;哪个好用是一个让人头痛的问题。总体而言&#xff0c;国内的办公协作系统已经相当成熟和完善&#…

golang interface类型的nil

golang中interface变量&#xff0c;底层两个对象来存&#xff0c;一个是type、一个是value&#xff0c;只有type、value都为nil时&#xff0c;interface变量才是nil package mainimport ("fmt""reflect" )type People interface {Show() }type Student str…

ALLEGRO之Logic

本文主要讲述ALLEGRO的Logic菜单。 &#xff08;1&#xff09;Net Logic&#xff1a;暂不清楚&#xff1b; &#xff08;2&#xff09;Net Schedule&#xff1a;暂不清楚&#xff1b; &#xff08;3&#xff09;AssignDifferential Pair&#xff1a;暂不清楚&#xff1b; &a…

layui框架学习(35:数据表格_列参数设置)

Layui中的table数据表格模块支持对表格及列进行基础参数设置以提高数据的可视化及可操作性&#xff0c;本文学习并记录与列相关的主要基础参数的用法及效果。   基础参数field设置待显示到列中的数据的字段名&#xff0c;主要针对数据表格url属性中返回的数据集合或data属性设…

《向量数据库指南》——Milvus Cloud 2.3 和 2.4 版本的重要变化

Milvus Cloud2.3 和 2.4 版本的重要变化。 首先是 Milvus Cloud2.3 将支持 Json 数据类型,在此基础上亦会支持 Schemaless。此前,用户在使用 Milvus Cloud的过程中会先定一个静态 Schema,此时,如果在实际业务层面如果多了几个 feature 或者 Metadata,就意味着数据需要重新…

echarts柱状图横坐标文字过长的解决办法

背景&#xff1a;echarts图中横坐标显示的文字过长&#xff0c;导致字都堆积在一块如下图所示 解决办法 一&#xff1a;可以尝试修改‘axisLabel’的‘rotate’和‘interval’参数&#xff0c;‘rotate’参数可以设置标签的旋转角度&#xff0c;可以避免标签之间的重叠&#x…

uni-app之微信小程序实现‘下载+保存至本地+预览’功能

目录 一、H5如何实现下载功能 二、微信小程序实现下载资源功能方面与H5有很大的不同 三、 微信小程序实现文件&#xff08;doc,pdf等格式&#xff0c;非图片&#xff09;下载&#xff08;下载->保存->预览&#xff09;功能 四、图片预览、保存、转发、收藏&#xff1…

Stephen Wolfram:神经网络

Neural Nets 神经网络 OK, so how do our typical models for tasks like image recognition actually work? The most popular—and successful—current approach uses neural nets. Invented—in a form remarkably close to their use today—in the 1940s, neural nets …

django自定义app,创建子应用

1.工程里创建apps包 &#xff1b; 2.创建子应用&#xff0c;pycharm terminal 运行&#xff1a;python ./nanage.py startapp app名称&#xff1b; 3.子应用移动到apps包里&#xff1b; 4.settings.py里设置INSTALLED_APPS如“apps.users”&#xff0c;该名字跟子应用apps.py文…

红宝石阅读笔记

第七章 迭代器与生成器 7.3 生成器 乍一看理解&#xff0c;仔细想没理解&#xff0c;然后自己让n2&#xff0c;还原nTimes&#xff0c;等价于 function* nTimes() {if (true) {yield* (function* A() {if (true) {yield* (function* B() { })();yield 0;}})();yield 1;} } 最…

NO1.使用命令行创建Maven工程

①在工作空间目录下打开命令窗口 ②使用命令行生成Maven工程 mvn archetype:generate 运行 MVN 原型&#xff1a;生成命令,下面根据提示操作 选择一个数字或应用过滤器&#xff08;格式&#xff1a;[groupId&#xff1a;]artifactId&#xff0c;区分大小写包含&#xff09;&a…

Jquery笔记

DOM对象通过jquery获取 所有的代码都是基于引入jquery.js文件 var mydiv $(#div);//直接获取到DOM对象元素id var mydiv$(.div)&#xff1b;//通过class获取DOM对象&#xff0c;如果有同名class只会获取第一个 var mysapn$(span);//通过元素的标签名获取DOM对象 var divarr$(…

无涯教程-jQuery - outerWidth( margin])方法函数

outerWidth([margin])方法获取第一个匹配元素的外部宽度(默认情况下包括边框和填充)。 此方法适用于可见和隐藏元素。由于父项被隐藏的元素不支持此功能。 outerWidth( [margin] ) - 语法 selector.outerWidth( [margin] ) 这是此方法使用的所有参数的描述- margin - 此…

JS学习之ES6

一、ES简介 ES6是一个泛指&#xff0c;指EDMAJavaScript之后的版本。它是JS的语言标准。 Nodejs 简介&#xff1a;它是一个工具&#xff0c;主攻服务器&#xff0c;使得利用JS也可以完成服务器代码的编写。 安装&#xff1a; 安装Nodejs的同时&#xff0c;会附带一个npm命令…

抓紧收藏,Selenium无法定位元素的几种解决方案

01、frame/iframe表单嵌套 WebDriver只能在一个页面上对元素识别与定位&#xff0c;对于frame/iframe表单内嵌的页面元素无法直接定位。 解决方法&#xff1a; driver.switch_to.frame(id/name/obj) switch_to.frame()默认可以直接取表单的id或name属性。如果没有可用的id和…

一文教你搭建工程化开发环境!

搭建工程化开发环境 下载 Node.js 官方下载地址 https://nodejs.org/zh-cn/download/releases node.js 版本迭代的非常快&#xff0c;目前官方已经推出到 v19.2.0 版本了&#xff0c;相对是一个比较新的版本了。建议下载 v14.18.3 版本&#xff0c;至少这个版本目前在很多项…

Android跨进程传大图思考及实现——附上原理分析

1.抛一个问题 这一天&#xff0c;法海想锻炼小青的定力&#xff0c;由于Bitmap也是一个Parcelable类型的数据&#xff0c;法海想通过Intent给小青传个特别大的图片 intent.putExtra("myBitmap",fhBitmap)如果“法海”(Activity)使用Intent去传递一个大的Bitmap给“…

iOS开发-聊天emoji表情与自定义动图表情左右滑动控件

iOS开发-聊天emoji表情与自定义动图表情左右滑动控件 之前开发中遇到需要实现聊天emoji表情与自定义动图表情左右滑动控件。使用UICollectionView实现。 一、效果图 二、实现代码 UICollectionView是一种类似于UITableView但又比UITableView功能更强大、更灵活的视图&#x…