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

CPP中的List

一.list的介绍:

  1.list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

  2.list的底层是双向链表结构,带有哨兵位的头结点 。

  3. listforward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。

  4.由于底层是链表结构,list通常在任意位置进行插入,删除元素的执行效率更好。

  5. 与其他序列式容器相比,listforward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)

  6.在list这个容器里面,是不支持[]等操作的。

list的底层:(双向带头循环链表)

二.list的使用:

1.list的构造:

(1)empty container constructor (default constructor):构造空的list。

list<int> mylist;

(2) fill constructor:构造一个有n个元素的链表,每个元素的值都是val。

list<int> list4(10, 1);
for (auto e : list4) {cout << e << " ";
}
//如上代码就成功构造了一个有10个1的链表

(3) range constructor:使其元素数量与区间 [first,last) 内的元素数量相同,并且每个元素都是根据该区间内对应位置的元素原位构造(emplace-constructed)而成,保持相同的顺序。

vector<int> t1 = { 1,2,3,4,5,6,7,8,9 };
int a[] = { 1,2,3,4 };
list<int> list2(t1.begin(), t1.end());
list<int> list3(a, a + 4);
//值得关注的是,要求传递的是迭代器,但是也可以传数组的指针

(4)copy constructor (and copying with allocator):拷贝构造。(同其他的容器一模一样,不多介绍如何使用)

(5)暂时不介绍,后面再介绍。

(6)initializer list constructor:简单点来说就是把想存进list的值全部用一个花括号括起来,每个值用逗号分隔开,然后用这些值构造一个list。

list<int> list1 = { 1,2,3,4,5,6,7,8,9 };

2.capacity:

其中max_size其实用的不多,这点在vector里面也一样,上面两个和其他容器的用法一模一样。

3.element access:

这两个函数返回的分别是链表的第一个元素和最后一个元素。

4.Modifiers:

 (挑一些特殊和重要的讲不讲的和其他容器使用差不多)

(1)assign:在 C++ 标准库中,std::list 的 assign() 方法用于替换链表中的所有元素,支持多种赋值方式。它会清空原链表,再将新元素赋值给链表。

list<int> numbers;
numbers.assign(3, 100);
 std::vector<int> src = {1, 2, 3, 4};std::list<int> numbers;numbers.assign(src.begin(), src.end()); // 链表变为 {1, 2, 3, 4}
 std::list<char> chars;chars.assign({'a', 'b', 'c'}); // 链表变为 {'a', 'b', 'c'}

(2)emplace_front:在链表的首部直接构造一个新元素,跟push_front的功能差不多,但是二者的效率可能会有一些不同。

(3)insert:提供了很多不同的版本:

分别举例:


int main ()
{std::list<int> mylist;std::list<int>::iterator it;// set some initial values:for (int i=1; i<=5; ++i) mylist.push_back(i); // 1 2 3 4 5it = mylist.begin();++it;       // it points now to number 2           ^mylist.insert (it,10);                        // 1 10 2 3 4 5//在pos位置insert一个val// "it" still points to number 2                      ^mylist.insert (it,2,20);                      // 1 10 20 20 2 3 4 5//在pos位置insert指定数目的val--it;       // it points now to the second 20            ^std::vector<int> myvector (2,30);mylist.insert (it,myvector.begin(),myvector.end());// 1 10 20 30 30 20 2 3 4 5//在pos位置inset一段迭代器区间所对应的值std::cout << "mylist contains:";for (it=mylist.begin(); it!=mylist.end(); ++it)std::cout << ' ' << *it;std::cout << '\n';return 0;
}

list里面的list由于不会扩容,所以没有迭代器失效的问题,但是返回值也是迭代器。

(4)erase:

erase提供了两种方式,在pos位置删除和删除一段迭代器区间。如同vector一样,erase有迭代器失效的效果,所以返回的是pos位置的下一个迭代器位置。

(5)swap:交换两个链表的内容:

int main() {std::list<int> a = {1, 2, 3};std::list<int> b = {4, 5};a.swap(b); // 交换 a 和 b 的内容std::cout << "a: ";for (int x : a) std::cout << x << " "; // 输出: 4 5std::cout << "\nb: ";for (int x : b) std::cout << x << " "; // 输出: 1 2 3
}

5.operations:

(1)splice:

 std::list 的 splice() 方法用于将一个链表的元素转移到另一个链表中,且不会导致元素的复制或移动,而是直接修改链表的内部指针,因此效率极高(时间复杂度为 O (1))。这个函数用到的地方不是很多,但是后面会有大用处。将一个链表的元素转移到另一个链表中,另一个链表也可以是自己本身。

(2)remove:

  std::list 的 remove() 方法用于删除链表中所有等于特定值的元素。它会遍历整个链表,移除所有匹配的元素,并自动调整链表结构,保持元素间的连续性。需要传递的参数就是要删除的值val

  remove_if就是和上面的作用差不多,但是需要满足一定的条件。

(3)unique:

std::list 的 unique() 方法用于移除链表中相邻的重复元素,只保留其中一个。它会遍历链表,将相邻且值相等的元素压缩为单个元素,从而使链表中的元素唯一(仅针对相邻元素)。

(4)merge:

std::list 的 merge() 方法用于将另一个有序链表合并到当前链表中,合并后当前链表仍保持有序。使用这个函数的前提是,两个链表必须已经按相同顺序排序

int main() {std::list<int> a = {1, 3, 5};std::list<int> b = {2, 4, 6};// 将 b 合并到 a 中,a 仍保持有序a.merge(b);// 输出: 1 2 3 4 5 6for (int num : a) {std::cout << num << " ";}// b 变为空链表std::cout << "\nb size: " << b.size(); // 输出: 0
}

合并之后被合并的那个链表会变成空的链表。

(5)sort:

由于迭代器类型的原因,sort不能使用<algorithm>头文件里面的sort,所以单独写了一个sort,但是在list里面的这个sort是归并排序,在进行大量数据排序的时候效率并没有算法库里面的sort效率高,因此使用的时候经常将list里面的元素放到vector里面,如何排好序后再assign回list里面。

原因在下面解释。

(6)reverse:

不能使用算法库里面的reverse的原因和sort一样。

三.迭代器类型:

 迭代器有很多种,是根据容器的底层来决定的。inputiterator(输入迭代器),forwarditerator(前向迭代器),bidirectioniterator(双向迭代器),randmaccessiterator(随机访问迭代器)。

 inputiterator迭代器和forwarditerator都是只支持++,*的操作,双向迭代器支持++,--,*的操作,随机迭代器支持不仅支持前面所有的,还支持额外的随机访问,比如+n,> , <, 迭代器之间的减法等操作。

一个容器是否能使用算法库里面的函数,需要看迭代器类型,在前面列举的类型里面,按顺序是包含关系。这是按照迭代器的功能来划分的。

那么为什么list容器不能调用sort函数呢,list容器是双向迭代器,而sort要求的是随机迭代器,所以不能调用,反之,如果有一个随机迭代器的容器,那么它是可以取使用要求是双向迭代器的函数的。

四.补充一些知识:

---->   计算机的存储体系如图所示:

              

     ->平时最关注的就是主存以及本地磁盘。

     ->其中主存是带电的,存储速度很快,没有电就不能存储数据,一旦断电,RAM (主存一般指ram,然后ram又分为dram和sram)中存储的数据就会立即丢失,这就是为什么主存是易失性存储器。而本地磁盘是不带电的,没有电它也可以存储数据,在断电后仍能保存数据,属于非易失性存储器。

    ->我们书写一个顺序表或者链表,它都是存放在主存(也就是通常说的内存)里面的,数据结构就是在内存中对数据进行管理,所以当然是在主存上的。我们对顺序表,链表的数据进行遍历和修改的时候,是cpu来完成的,但是cpu是不能直接访问内存的,形象一点说,这是因为它太“快”了,而它觉得内存太慢了。这时候就有一个cpu高速缓存的概念了:      

cpu的机制是这样的,比如说我要访问一个指针,cpu先进缓存,如果这个指针在缓存里面,那么就叫做“命中”,这样cpu就可以直接访问了。如果“不命中”的情况下,内存的数据(当然不可能是内存的全部数据)会先进入缓存里面,然后再让cpu去“命中”。在这样的条件下就有一个缓存命中率的概念产生,对于顺序表和链表先说结论就是顺序表的缓存命中率高,而链表的缓存命中率低。内存在把数据给缓存的时候,假设要用的数据是4个字节,它就会从内存里面多拿连续的一部分给缓存(这是局部性原理预取相邻的数据的效果,但是具体多取多少,这是又是硬件等多方面因素决定的)。稍微应用一下豆包的话来类比一下这个局部性原理:

由于顺序表的结构优势,使得在上述原理下“命中率”变高,虽然在链表的一个节点的后面会多拿一些数据,但是包含下一个节点的概率不大,所以链表的“命中率低”。

--------取自《深度理解计算机系统》--------。

http://www.dtcms.com/a/269989.html

相关文章:

  • 我的第二份实习,学校附近,但是干前端!
  • 了解 RAC
  • FastAPI通用签名校验模块设计文档
  • 【python基础】python和pycharm的下载与安装
  • 在STM32 FreeRTOS环境中使用mutex和ringbuffer实现多任务的UART同步通信
  • JVM 整体架构详解:线程私有与线程共享内存区域划分
  • 【Android】【input子系统】【Android 焦点窗口问题分析思路】
  • 【linux网络】网络编程全流程详解:从套接字基础到 UDP/TCP 通信实战
  • 【Java安全】RMI基础
  • go go go 出发咯 - go web开发入门系列(二) Gin 框架实战指南
  • WiFi协议学习笔记
  • 点云的无监督语义分割方法
  • 寻找两个正序数组的中位数(C++)
  • 成都算力租赁新趋势:H20 八卡服务器如何重塑 AI 产业格局?
  • 基于 Rust 的Actix Web 框架的应用与优化实例
  • C++ 选择排序、冒泡排序、插入排序
  • mac安装docker
  • APISEC安全平台
  • 嵌入式学习笔记-MCU阶段-DAY01
  • WPF之命令
  • 使用elasticdump高效备份与恢复Elasticsearch数据
  • WebSocket详细教程 - SpringBoot实战指南
  • EPLAN 电气制图(四):EPLAN 总电源电路设计知识详解
  • mit6.5840-lab3-3D-SnapShot-25Summer
  • 常见前端开发问题的解决办法
  • 深度学习——神经网络1
  • JK触发器Multisim电路仿真——硬件工程师笔记
  • HMI安全设计规范:ISO 26262合规的功能安全实现路径
  • python2.7/lib-dynload/_ssl.so: undefined symbol: sk_pop_free
  • 查询依赖冲突工具maven Helper