探索C++的工具箱:双向链表容器类list(1)
引言
在C++中,std::list 是一个标准库提供的容器类,属于C++ STL(标准模板库)。std::list 是一种独特而强大的容器,它使用双向链表结构来管理元素。无论是在处理动态数据集合,还是在需要频繁进行插入和删除操作时,std::list 都展现了无与伦比的灵活性和效率。与其他线性数据结构如数组和向量相比,std::list 让开发者能够在不影响整体性能的情况下,轻松地在数据集中间添加或移除元素。
在这篇博客中,会以介绍与string类和vector类中相通的函数为主,在下一篇博客,探索C++的工具箱:双向链表容器类list(2)-CSDN博客中,会以介绍string类 和vector类中没有的、list新引入的内容为主。
1. list 简介
C++ 的 std::list 是一个双向链表(doubly linked list)的实现,与 vector 不同,list 提供 O(1) 的插入和删除操作,但不支持随机访问。使用时需要包含头文件<list>。
如果将“list”比喻为一个“购物清单”,而“list中的元素”就相当于“购物清单上的每一项商品”。
在这个比喻中:
- 整个购物清单(list)是一个容器,帮助我们整理和管理购物的内容。
- 每一项商品(list中的元素)则是清单中具体要购买的内容。
就像我们可以在购物清单上添加、删除或修改商品,列表中的元素也可以随时增删改。
std::list具有以下特点:
-
双向链表:std::list 中的每个元素都包含指向前一个元素和后一个元素的指针。这使得在链表的任意位置进行插入和删除操作非常快速(时间复杂度为O(1)),但访问随机元素的效率较低(时间复杂度为O(n))。
-
动态大小:std::list 可以根据需要动态增长或收缩,能够有效管理动态变化的数据集。
-
不连续存储:与std::vector 不同,std::list 中的元素在内存中不必连续存储,这为插入和删除操作防止内存重排提供了便利。
-
支持迭代器:std::list 支持常规和反向迭代器,可以方便地遍历链表元素。
2.list的构造
基本语法:
default (1)
explicit list (const allocator_type& alloc = allocator_type());
fill (2)
explicit list (size_type n);list (size_type n, const value_type& val,const allocator_type& alloc = allocator_type());
range (3)
template <class InputIterator>list (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
copy (4)
list (const list& x);
list (const list& x, const allocator_type& alloc);move (5)
list (list&& x);
list (list&& x, const allocator_type& alloc);initializer list (6)
list (initializer_list<value_type> il,const allocator_type& alloc = allocator_type());
explicit 关键字用于构造函数,目的是为了防止类型转换(隐式转换)。如果一个构造函数被声明为 explicit,那么它只能通过显式的方式来调用,而不能用作隐式类型转换。
以下是构造和使用 std::list 的几种常见方法(默认使用std):
(1)默认构造函数
创建一个空的 std::list,可以使用以下代码:
list<int> myList;
(2)填充构造函数
explicit list (size_type n);list (size_type n, const value_type& val,const allocator_type& alloc = allocator_type());
上面的构造方法用来创建一个包含n个元素的list,并且元素会被初始化为默认值,在int类型中默认值是0。
下面的构造方法用来创建一个又n个元素且值会被初始化value的list
list<int> myList(5); // 创建一个包含5个元素的 list list<int> myList(5, 100); // 创建一个包含5个100的 list
(3)迭代器范围构造函数
template <class InputIterator>list (InputIterator first, InputIterator last,const allocator_type& alloc = allocator_type());
#include <iostream>
#include <list>
#include <vector> int main() { vector<int> vec = {1, 2, 3, 4, 5}; list<int> myList(vec.begin(), vec.end()); // 使用范围构造函数 return 0;
}
(4)复制构造函数
list (const list& x);
list (const list& x, const allocator_type& alloc);
第二个构造函数允许自定义分配器。
#include <iostream>
#include <list> int main() { list<int> original = {1, 2, 3, 4, 5}; list<int> copiedList(original); // 使用复制构造函数return 0;
}
(5)移动构造函数
list (list&& x);
list (list&& x, const allocator_type& alloc);
同上,可以自定义分配器。移动构造函数是在 C++11 中引入的,它用于高效地构造对象,将资源(如动态分配的内存)从一个对象“转移”到另一个对象,而不是进行复制。这种方式能够提高性能,尤其是在处理管理资源的对象时(例如,标准库容器、字符串等)。
#include <iostream>
#include <list> int main() { std::list<int> original = {1, 2, 3, 4, 5}; std::list<int> movedList(std::move(original)); // 使用移动构造函数 return 0;
}
(6)初始化列表构造函数
list (initializer_list<value_type> il,const allocator_type& alloc = allocator_type());
list<int> myList = {1, 2, 3, 4, 5}; // 使用初始化列表构造函数
(7)赋值运算符重载构造函数
copy (1)
list& operator= (const list& x);move (2)
list& operator= (list&& x);initializer list (3)
list& operator= (initializer_list<value_type> il);//与初始化列表结合
#include <iostream>
#include <list> int main() { // 创建两个 list std::list<int> list1 = {1, 2, 3, 4, 5}; // 初始化 list1 std::list<int> list2; // 创建空 list2 // 使用赋值运算符将 list1 的内容复制到 list2 list2 = list1; // 执行 copy assignment return 0;
}
list容器的迭代器以及空间有关函数等与string类 和vector类的使用相似,在此不再赘述,不了解的小伙伴可以去看以下博客:
C++深入学习string类成员函数(1):默认与迭代_std::string初始化函数-CSDN博客
探索C++的存储箱:动态数组容器类vector-CSDN博客
3.list的修饰函数
3.1 emplace
`emplace` 允许在容器中直接构造对象,而不是先构造对象再进行拷贝或移动。这样可以避免不必要的性能开销。
- emplace_front(): 在链表头部直接构造元素。
- emplace_back(): 在链表尾部直接构造元素。
- emplace(): 在指定位置直接构造元素。
1. emplace_front()
在链表的开头直接构造元素:
std::list<std::string> mylist;
mylist.emplace_front("Hello");
mylist.emplace_front(5, 'A'); // 直接构造一个字符串 "AAAAA"
2. emplace_back()
在链表的末尾直接构造元素:
mylist.emplace_back("World");
mylist.emplace_back(4, 'B'); // 直接构造字符串 "BBBB"
3.emplace()
在链表的指定位置直接构造元素:
auto it = mylist.begin();
mylist.emplace(it, "Inserted"); // 在开头插入字符串 "Inserted"
emplace 与 push 系列的区别
- push_front() / push_back(): 需要先创建一个对象,然后再将其拷贝或移动到容器中。
- emplace_front() / emplace_back(): 直接在容器中的位置构造对象,省去了拷贝或移动的过程,适合构造复杂对象时提高效率。
使用场景
当你需要直接在容器中构造元素,并且想避免额外的临时对象构造和销毁时,`emplace` 系列函数非常有用,尤其是在需要构造复杂对象或容器时,性能优势明显。
3.2 assign
`std::list` 的 `assign()` 函数用于替换链表中的元素。它提供了三种重载方式来支持不同的输入类型。以下是每种 `assign()` 函数的具体说明:
range (1)
template <class InputIterator>void assign (InputIterator first, InputIterator last);fill (2)
void assign (size_type n, const value_type& val);initializer list (3)
void assign (initializer_list<value_type> il);
1.范围构造函数
- 功能:将链表中的元素替换为 `[first, last)` 范围内的元素。此函数使用两个迭代器来指定范围,可以从另一个容器或范围中拷贝元素。
std::list<int> mylist;std::vector<int> vec = {1, 2, 3, 4, 5};mylist.assign(vec.begin(), vec.end()); // 复制 vector 中的所有元素到 list
2.填充构造函数
- 功能:将链表中的元素替换为 `n` 个值为 `val` 的元素。此函数将链表填充为 `n` 个相同的元素。
std::list<int> mylist;mylist.assign(5, 100); // 用 5 个值为 100 的元素填充 list
3.初始化列表构造函数
- 功能:将链表中的元素替换为初始化列表 `il` 中的元素。此函数允许使用 C++11 引入的初始化列表语法进行赋值。
std::list<int> mylist;mylist.assign({1, 2, 3, 4, 5}); // 通过初始化列表填充 list
4.assign() 函数的使用场景
- 范围赋值(Range Assign):适用于从另一个容器或范围内复制数据,例如从 `vector` 或 `array` 中将某个范围的元素赋值到 `list` 中。
- 填充赋值(Fill Assign):适合需要将链表中的所有元素设置为相同的值时使用,例如初始化某个固定大小的列表。
- 初始化列表赋值(Initializer List Assign):简化了使用固定值集合初始化 `list` 的过程,尤其是在初始化时可以快速赋值。
3.3 push与pop
在 C++ std::list 中,push 和 pop,分别用于向链表中添加元素和移除元素。它们有两种常用的形式:操作链表头部和尾部的元素。
(1)push 系列
1. push_front()
- 功能:在链表头部插入一个元素。
std::list<int> mylist;mylist.push_front(10); // 在链表头部插入 10mylist.push_front(20); // 在链表头部插入 20,链表现在是 {20, 10}
2. push_back()
- 功能:在链表尾部插入一个元素。
std::list<int> mylist;mylist.push_back(10); // 在链表尾部插入 10mylist.push_back(20); // 在链表尾部插入 20,链表现在是 {10, 20}
(2)pop 系列
1. pop_front()
- 功能:移除链表头部的元素。
- 注意:此操作不返回被移除的元素。如果链表为空,调用此函数会导致未定义行为。
std::list<int> mylist = {10, 20, 30};mylist.pop_front(); // 移除头部的 10,链表现在是 {20, 30}
2. pop_back()
- 功能:移除链表尾部的元素。
- 注意:与 `pop_front()` 一样,此操作不返回被移除的元素,链表为空时调用会导致未定义行为。
std::list<int> mylist = {10, 20, 30};mylist.pop_back(); // 移除尾部的 30,链表现在是 {10, 20}
3.4 其它函数
- insert():在链表的指定位置插入元素或范围内的多个元素。
- swap():交换两个链表的内容。
- erase():删除链表中的某个元素或一段范围内的元素。
- resize():调整链表大小,可能会插入或删除元素。
- clear():清空链表,删除所有元素。
1. insert()
- 功能:在指定位置插入一个或多个元素。
- 重载形式:
single element (1)
iterator insert (const_iterator position, const value_type& val);
fill (2)
iterator insert (const_iterator position, size_type n, const value_type& val);
range (3)
template <class InputIterator>
iterator insert (const_iterator position, InputIterator first, InputIterator last);
1. 插入单个元素:
在 `position` 迭代器处插入 `val`,返回指向新插入元素的迭代器。
2. 插入多个相同值的元素:
在 `position` 处插入 `n` 个 `val`。
3. 插入范围内的元素:
将 `[first, last)` 范围内的元素插入到 `position` 处。
2. swap()
- 功能:交换两个 `list` 容器的内容。
std::list<int> list1 = {1, 2, 3};std::list<int> list2 = {4, 5, 6};list1.swap(list2); // list1 变为 {4, 5, 6},list2 变为 {1, 2, 3}
3. erase()
- 功能:删除指定位置的元素或指定范围内的元素。
- 重载形式:
iterator erase (const_iterator position);
iterator erase (const_iterator first, const_iterator last);
1. 删除单个元素:
删除 `position` 处的元素,返回指向被删除元素后面的元素迭代器。
2. 删除范围内的元素:
删除 `[first, last)` 范围内的所有元素,返回指向删除范围后第一个元素的迭代器。
4. resize()
- 功能:调整链表的大小。如果新大小比当前小,删除多余的元素;如果新大小比当前大,则插入默认值的元素或指定值的元素。
- 用法:
void resize (size_type n);
void resize (size_type n, const value_type& val);
1. 仅调整大小:
调整链表大小为 `n`,如果新大小比当前大,则插入默认值的元素。
2. 调整大小并指定新元素的值:
调整链表大小为 `n`,并用 `val` 填充新插入的元素。
std::list<int> mylist = {1, 2, 3};mylist.resize(5, 100); // 调整大小为 5,列表变为 {1, 2, 3, 100, 100}mylist.resize(2); // 缩小大小为 2,列表变为 {1, 2}
5. clear()
- 功能:清空链表,删除所有元素,链表大小变为 0,但不改变链表的容量。
std::list<int> mylist = {1, 2, 3, 4};mylist.clear(); // 清空列表,列表现在为空
通过本篇文章,我们详细了解了 C++ `list` 容器的基本操作与应用场景。`list` 提供了双向链表的灵活性,能够高效地进行插入、删除等操作,尤其在需要频繁修改容器中的元素时表现优异。然而,正如我们所讨论的,它在随机访问性能上不如其他顺序容器(如 `vector`),因此在选择容器时,应根据实际需求权衡各个方面。
本篇博客就到此为止了,与vector、string容器不同、list新引入的内容都在探索C++的工具箱:双向链表容器类list(2)-CSDN博客中,有兴趣的小伙伴可以跳转学习
相关文章:

探索C++的工具箱:双向链表容器类list(1)
引言 在C中,std::list 是一个标准库提供的容器类,属于C STL(标准模板库)。std::list 是一种独特而强大的容器,它使用双向链表结构来管理元素。无论是在处理动态数据集合,还是在需要频繁进行插入和删除操作时…...

大厂高频算法考点--单调栈
什么是单调栈: 单调栈就是借助一个栈,在仅仅使用当前栈的条件下,时间复杂度是N(n),将每个节点最有离这他最近的大于或者是小于的数据返回,将已知数组的元素放到栈里。再自我实现的代码里面我们使用数组实现…...

Unity使用Git及GitHub进行项目管理
git: 工作区,暂存区(存放临时要存放的内容),代码仓库区1.初始化 git init 此时展开隐藏项目,会出现.git文件夹 2.减小项目体积 touch .gitignore命令 创建.gitignore文件夹 gitignore文件夹的内容 gitignore中添加一下内容 # This .gitignore file should be place…...

如何将本地 Node.js 服务部署到宝塔面板:完整的部署指南
文章简介: 将本地开发的 Node.js 项目部署到线上服务器是开发者常见的工作流程之一。在这篇文章中,我将详细介绍如何将本地的 Node.js 服务通过宝塔面板(BT 面板)上线。宝塔面板是一个强大的服务器管理工具,具有简洁的…...

SpringBoot项目启动报错:命令行太长解决
文章目录 SpringBoot项目启动报错:命令行太长解决1. 第一种方法1. 第二种方法1-1 旧版本Idea1-2 新版本Idea 3. 重新启动SpringBoot项目即可解决 SpringBoot项目启动报错:命令行太长解决 报错信息: 1. 第一种方法 1. 第二种方法 找到项目…...

使用Docker启动的Redis容器使用的配置文件路径等问题以及Python使用clickhouse_driver操作clickhouse数据库
一、使用Docker启动的Redis容器使用的配置文件路径等问题 1.docker启动的redis使用的配置文件路径是什么 使用docker搭建redis服务,本身redis启动的时候可以指定配置文件的, redis-server /指定配置文件路径/redis.conf。 但手上也没有一个redis配置文件…...

硬盘格式化后能恢复数据吗?4款好用的数据恢复软件,格式化后也能安心
咱们今天来谈谈一个挺烦人的问题——硬盘格式化后能恢复数据吗?别担心,能的!只要你用对方法,就算硬盘被清空了,那些重要文件还是能找回来的。下面,我就给你们介绍几款超给力的数据恢复软件,让你…...

【选择C++游戏开发技术】
在选择C游戏开发技术时,以下几个因素是需要考虑的: 1. 游戏类型:不同类型的游戏可能需要不同的技术。例如,2D游戏通常采用基于精灵的引擎,而3D游戏通常采用基于物理模拟的引擎。根据游戏类型选择适合的技术是很重要的…...

Oracle数据库系统表空间过大,清理SYSTEM、SYSAUX表空间
一.前言 在oracle数据库中,system为系统表空间,存放着一些我们经常用到的系统表和视图,sysaux为辅助表空间,辅助着系统表空间。这两个表空间不宜添加数据文件,会使系统表空间过于臃肿,从而影响数据库的使用…...

LaTeX参考文献工具和宏包bibmap项目简介
LaTeX参考文献工具和宏包bibmap项目简介 LaTeX 中的参考文献生成方式主要有三种:第一种是手动写thebibliography环境的,第二种事基于bibtex程序的,第三种则是基于biblatex宏包和biber程序的。本文介绍的bibmap项目则提供了第四种方法。目前b…...

微软的 Drasi:一种轻量级的事件驱动编程方法
微软的开源数据变化处理平台有望提供一种全新的方式来构建和管理可产生持续事件流的云应用程序。 Microsoft Azure 孵化团队是微软超大规模云中比较有趣的组成部分之一。它介于传统软件开发团队和研究组织之间,致力于构建大规模分布式系统问题的解决方案。 这些解决…...

vue3 笔记-插槽
结构类似的模块,我们可以考虑用插槽,以便后续复用: 代码: 1.插槽 <script setup> defineProps({title: {required: true,type: String},number: {required: true,type: Number} }) </script><template><d…...

C# 字符串常用方法
文章目录 Length:获取字符串中字符的个数(不包括末尾的空字符)ToLower() 和 ToUpper():将字符串转换为小写或大写形式Substring(int startIndex, int length):从指定索引开始截取指定长度的子字符串Remove(int startIn…...

字节跳动青训营——入营考核解答(持续更新中~~~)
考核内容: 在指定的题库中自主选择不少于 15 道算法题并完成解题,其中题目难度分配如下: 简单题不少于 10 道中等题不少于 4 道困难题不少于 1 道 解答代码 8.进制求和转换(难) 代码实现: import jav…...

JavaWeb合集15-Apache POI
十五、Apache POI Apache POI是一个处理Miscrosoft Office各种文件格式的开源项目。简单来说就是,我们可以使用POI在Java 序中对Miscrosoft Office各种文件进行读写操作。一般情况下,POI都是用于操作Excel文件。 使用场景:银行网银系统导出交…...

Threejs 实现3D 地图(01)创建基本场景
"d3": "^7.9.0", "three": "^0.169.0", "vue": "^3.5.10" <script setup> import { onMounted,ref } from vue import * as THREE from three import * as d3 from "d3"; //莫开托坐标 矫正地图…...

snmpdelta使用说明
1.snmpdelta介绍 snmpdelta命令是用来获取下一个节点的OID的值。 2.snmpdelta安装 1.snmpdelta安装 命令: yum -y install net-snmp net-snmp-utils [root@logstash ~]# yum -y install net-snmp net-snmp-utils Loaded plugins: fastestmirror Loading mirror speeds f…...

Hadoop集群安装
集群规划 node01node02node03角色主节点从节点从节点NameNode√DataNode√√√ResourceManager√NodeManager√√√SecondaryNameNode√Historyserver√ 上传安装包到node01 解压到指定目录 tar -zxvf /bigdata/soft/hadoop-3.3.3.tar.gz -C /bigdata/server/ 创建软链接 cd…...

VuePress集成到Vue项目的方法
VuePress 可以作为一个独立的静态站点生成器来使用,也可以集成到现有的 Vue 项目中。以下是将 VuePress 集成到 Vue 项目的几种方法: 1. 作为本地依赖集成 如果你想在现有的 Vue 项目中使用 VuePress 来管理文档,你可以将 VuePress 安装为本…...

【ROS】ROS局域网下多机通讯方法
最近工作中需要用到多机通讯,这里稍微总结一下使用方法。 目录 一、网络配置 二、修改两个设备的hosts文件 三、修改两个ros设备的.bashrc 四、launch文件中给节点设定运行的设备 一、网络配置 首先确保两个ros设备连接到同一局域网下,然后查询两个…...

linux 系统怎么使用
Linux系统的使用涉及多个方面,包括文件管理、目录操作、用户管理、进程管理、网络配置等。以下是对Linux系统基础使用的详细介绍: 一、文件管理 查看文件和目录 ls:列出当前目录的内容。ls -l:以长格式列出当前目录的内容&#x…...

Java线程池知识点梳理
Java线程池知识点梳理 什么是线程池? 线程在系统中创建的成本是相对比较高的,所以使用”池化“的思想,设计线程池,有大量任务需要执行时,可以直接从线程池中使用已经创建好的线程直接去执行。减少线程的创建和销毁带…...

SFT、RLHF、DPO、IFT —— LLM 微调的进化之路_如何搭建自己的dpo
TL;DR • SFT、RLHF 和 DPO 都是先估计 LLMs 本身的偏好,再与人类的偏好进行对齐; • SFT 只通过 LLMs 生成的下一个单词进行估计,而 RLHF 和 DPO 通过 LLMs 生成的完整句子进行估计,显然后者的估计会更准确; • 虽然…...

CSS 选择器简单回顾
引言 当我们探讨网页设计和开发时, CSS(层叠样式表) 无疑是一个不可或缺的技术, 它使我们能够精确控制网页的外观和布局, 为用户创造出独特的视觉体验、以及良好的交互体验!! 而一个完整的 CSS 规则则是由两个主要部分组成: 选择器和声明块 那么今天我们就来盘点下常见的几种选…...

uniapp配置微信小程序分包(分包优化)
1.manifest.json中 源码视图中找到mp-weixin,新增代码"optimization":{"subPackages":true},如下图所示 "optimization" : {"subPackages" : true } 2.pages.json中 分包内静态文件示例 "subPackages&…...

MySQL-10.DML-添加数据insert
一.DML(INSERT) -- DDL:数据操作语言 -- DML:插入数据 - insert -- 1.为tb_emp表的username,name,gender字段插入值 insert into tb_emp (username,name,gender) values (wuji,无忌,1); -- 这样会报错,因为create_ti…...

ARM/Linux嵌入式面经(四八):tp-link联洲国际
文章目录 1. **模电基础**:请解释共射电路的工作原理,并描述如何计算其放大倍数。工作原理放大倍数计算面试官追问及回答2. **DCDC损耗**:有哪些方法可以降低DCDC转换器的损耗?3. **示波器使用**:如何用示波器正确测量DCDC的开关纹波?4. **IIC通信**:IIC通信协议中是否需…...

代码实践篇四 形状检测与规则重建
本节内容主要涉及形状检测(Shape Detection)与形状重建(Shape Reconstruction),具体算法步骤会在后续章节介绍。CGAL在6.0重点更新了形状重建部分的一些模块——动态空间分割与动态形状重建等,也会在后续详…...

JVM(HotSpot):GC之垃圾回收阶段
文章目录 前言一、标记清除算法(Mark Sweep)二、标记整理算法(Mark Compact)三、复制算法(Copy) 前言 标记出垃圾对象之后,就要进行清理。 那么,如何清理? 这里也有相应的算法。 主要有三种。 一、标记清除算法(Mark Sweep) 原理说明&…...

Go 项目如何集成类似mybatisPlus插件呢?GORM走起!!
导读: 在 Go 项目中,虽然没有像 MyBatis Plus 这样特定的 ORM 插件,但可以使用功能相似的 Go ORM 框架,比如 GORM,它支持链式查询、自动迁移、预加载等功能,与 MyBatis Plus 有相似之处。通过一些插件或扩…...