[C++] 深入浅出list容器

news2025/2/26 22:15:15

Kevin的技术博客.png

文章目录

  • list介绍
  • list接口的使用
    • 构造函数
    • `iterator`迭代器
    • `capacity`
    • **element access**
    • modifiers
  • `list`中的迭代器失效问题
  • 常见容器及其迭代器类型特性
    • 单向迭代器(Forward Iterator)
    • 双向迭代器(Bidirectional Iterator)
    • 随机访问迭代器(Random Access Iterator)
    • 常见迭代器
      • const_iterator
      • reverse_iterator
      • const_reverse_iterator
  • 模拟实现`list`时的问题
    • 模版多参数传参与按需实例化
      • 模板多参数传递
      • 按需实例化
    • `list`的排序
    • `emplace_back`与`push_back`
      • `push_back`
      • `emplace_back`
      • 性能比较
      • 使用场景
      • 举例
    • 通过重载再次理解`->`与`.`
      • `.` 操作符
      • `->` 操作符
      • 主要区别
      • `operator*()`
      • `operator->()`
      • `operator->()`的实际使用
  • 模拟实现`list`框架


list介绍

列表是序列容器,允许在序列中的任何位置进行恒定时间插入和擦除操作,以及双向迭代。该容器用双向链表实现。

list接口的使用

构造函数

构造函数接口说明
list(size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list()构造空的list
list(const list& x)拷贝构造函数
list(InputIterator first, InputIterator last)用[first, last)区间中的元素构造

iterator迭代器

函数声明接口说明
begin返回第一个元素的迭代器
end返回最后一个元素下一个位置的迭代器
rbegin返回第一个元素的reverse_iterator,即end位置
  1. beginend为正向迭代器,对迭代器执行++操作,迭代器向后移动
  2. rbegin(end)rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

capacity

函数声明接口说明
empty()检测list是否为空,如果是返回true,否则返回false
size()返回list中有效节点的个数

element access

函数声明接口说明
front()返回list的第一个节点中值的引用
back()返回list的最后一个节点中值的引用

modifiers

函数声明接口说明
push_front(val)在list首元素前插入值为val的元素
pop_front()删除list中第一个元素
push_back(val)在list尾部插入值为val的元素
pop_back()删除list中最后一个元素
insert(position, val)在list的position位置插入值为val的元素
erase(position)删除list的position位置的元素
swap(list)交换两个list中的元素
clear()清空list中的有效元素

list中的迭代器失效问题

listerase()操作可能会使迭代器失效。

  • 因为list的底层结构是双向带头循环链表,所以在list中进行insert操作的时候不会导致迭代器失效,只有在删除的时候才会失效,而且失效的知识指向被删除节点的迭代器,其他迭代器不会受影响。
// erase 模拟实现
iterator erase(iterator pos)
{
	assert(pos != end());

	Node* prev = pos._node->_prev;
	Node* next = pos._node->_next;

	prev->_next = next;
	next->_prev = prev;
	delete pos._node;

	--_size;

	return next;
}
it = lt.begin();
while (it != lt.end())
{
	if (*it % 2 == 0)
	{
		it = lt.erase(it);
	}
	else
	{
		++it;
	}
}

image.png
根据官方文档所述,erase会用迭代器作为返回值,返回删除的迭代器的下一个位置的迭代器。所以在删除后可以更新迭代器,保证迭代器不会失效。

常见容器及其迭代器类型特性

单向迭代器(Forward Iterator)

  • 功能:只能向前遍历容器中的元素。
  • 容器std::forward_list, std::unordered_map, std::unordered_set, std::unordered_multimap, std::unordered_multiset
std::forward_list<int> fl = {1, 2, 3};
auto it = fl.begin(); // it 是单向迭代器
while (it != fl.end()) 
{
    // 可以递增 it
    ++it;
}

双向迭代器(Bidirectional Iterator)

  • 功能:可以向前和向后遍历容器中的元素。
  • 容器std::list, std::map, std::set, std::multimap, std::multiset
std::list<int> lst = {1, 2, 3};
auto it = lst.begin();
// 可以递增和递减 it
++it; // 向前
--it; // 向后

随机访问迭代器(Random Access Iterator)

  • 功能:提供对元素的随机访问能力,可以进行快速的非连续访问。
  • 容器std::vector, std::string, std::deque, std::array
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin() + 2; // 直接跳到第三个元素
--it; // 递减
vec.end() - 1; // 指向最后一个元素

常见迭代器

const_iterator

  • 功能:只读迭代器,不能用来修改容器中的元素。
  • 适用性:所有容器都提供了 const_iterator 类型。

reverse_iterator

  • 功能:反向迭代器,允许从容器的末尾向前遍历元素。
  • 适用性:提供双向或随机访问迭代器的容器。

const_reverse_iterator

  • 功能:只读反向迭代器,不能用来修改容器中的元素。
  • 适用性:提供双向或随机访问迭代器的容器。
std::vector<int> vec = {1, 2, 3, 4, 5};
auto it = vec.begin(); // 随机访问迭代器

// 随机访问迭代器的操作
it += 2; // 跳到第三个元素
*it -= 1; // 修改元素

// 反向迭代器
auto rit = vec.rbegin(); // reverse_iterator
++rit; // 向前(实际上是向后元素的方向)

模拟实现list时的问题

模版多参数传参与按需实例化

在实现iteratorconst_iterator时,为了避免两个类模板代码的冗余和相似性高,可以通过控制模版的传参,利用按需实例化来进行书写该模拟实现部分。

模板多参数传递

list_iterator 模板有三个类型参数:

  1. T - 表示链表中存储的数据类型。
  2. Ref - 表示对数据类型的引用类型,通常为 T&const T&
  3. Ptr - 表示指向数据类型的指针类型,通常为 T*const T*

这些参数允许用户根据需要定制迭代器的行为,例如是否允许修改数据(通过 Ref)或者返回常量或非常量指针(通过 Ptr),由此可以实例化出list_iteratorconst_list_iterator

按需实例化

模板类或函数在实际使用时才被编译器实例化。这意味着只有当用户显式地创建一个特定类型的模板实例时,编译器才会生成相应的代码。

typedef list_iterator<T, T&, T*> iterator;
typedef list_iterator<T, const T&, const T*> const_iterator;

例如通过传递不同的参数来进行实例化出list_iteratorconst_list_iterator

list的排序

list为双向链表,std::algorithm::sort()排序要求的是随机迭代器,而list为双向迭代器,所以无法直接使用算法库的sort()进行排序。
[C++] vector入门&迭代器失效问题详解-CSDN博客

在以上文章里有提及关于对于排序效率低的容器的排序方法。

对于list的排序可以使用vector拷贝list内的数据,排序后再重新数据按顺序拷贝会list

vector<int> v(lt2.begin(), lt2.end());

// 排序
sort(v.begin(), v.end());

// 拷贝回lt2
lt2.assign(v.begin(), v.end());

vector的排序不仅解决了list的排序问题,还使排序更加高效。

emplace_backpush_back

emplace_backpush_back 都是 C++ STL 容器(如 vectordequelist 等)中用来在容器的末尾添加元素的方法。它们的主要区别在于元素的构造方式和性能。

push_back

  • 功能:将一个元素的副本或移动到容器的末尾。
  • 使用方式
std::vector<int> vec;
vec.push_back(10);  // 将10的副本添加到容器末尾
  • 构造方式:先在容器末尾分配空间,然后将元素复制或移动到新位置。
  • 优点
    • 简单易用,适用于任何类型的元素。
    • 可以添加一个已经存在的元素的副本或移动版本。

emplace_back

  • 功能:在容器末尾就地构造一个元素。
  • 使用方式
std::vector<std::pair<int, int>> vec;
vec.emplace_back(10, 20);  // 直接在容器末尾构造一个pair<int, int>
  • 构造方式:直接在容器末尾的空间内构造元素,不需要先复制或移动。
  • 优点
    • 避免了不必要的复制或移动操作,特别是在构造复杂对象时,可以显著提高性能。
    • 可以直接传递构造参数,方便构造复杂类型。
    • 避免了临时对象的创建,减少了内存使用。

性能比较

  • push_back:如果元素类型是简单的类型(如 intfloat 等),复制操作对性能的影响不大。但如果元素类型是复杂的类型(如自定义类),复制操作可能会影响性能。
  • emplace_back:对于复杂类型,使用 emplace_back 可以避免复制或移动操作,直接在容器末尾构造元素,从而提高性能。

使用场景

  • push_back:当你需要添加一个已经存在的元素的副本或移动版本时,使用 push_back
  • emplace_back:当你需要构造一个新元素,并且这个元素的构造过程复杂或需要传递多个参数时,使用 emplace_back

举例

list<A> lt;
A aa1(1, 1);
lt.push_back(aa1);
lt.push_back(A(2, 2));
//lt.push_back(3, 3);

lt.emplace_back(aa1);
lt.emplace_back(A(2, 2));
cout << endl;
// 支持直接传构造A对象的参数emplace_back
lt.emplace_back(3, 3);

设置打印信息,运行如下:
image.png
可以发现,在使用emplace_back(3, 3)的时候会直接进行构造,而不会像使用push_back一样先构造出一个对象,再拷贝构造进行插入。
push_back()执行的时候需要一个现有的元素或者隐式的构造一个元素再拷贝到插入的空间。


emplace_back 通常在需要构造复杂类型或避免不必要的复制和移动操作时更优,而 push_back 在添加简单类型或已经存在的元素时更为方便。

通过重载再次理解->.

->.操作符都是用来访问对象的成员,但是使用的前提不同。

. 操作符

.操作符用于直接访问对象实例的成员。它需要一个对象实例或结构体,而不是指针。

struct MyStruct {
    int x;
    int y;
};

MyStruct obj;
obj.x = 10; // 通过 . 访问成员

obj是一个结构体或类的对象,通过obj.x直接访问其成员x

-> 操作符

->操作符用于通过指针访问对象的成员。它的功能实际上是先解引用指针,然后访问成员。

struct MyStruct {
    int x;
    int y;
};

MyStruct* p = new MyStruct();
p->x = 10; // 通过 -> 访问成员

p是一个指向结构体或类的指针,通过p->x访问指针指向对象的成员xp->x等同于(*p).x,即先解引用指针,再访问成员。

主要区别

  1. 操作对象类型
  • . 操作符作用于对象的实例
  • -> 操作符作用于对象的指针
  1. 使用场景
  • 当你有一个对象实例时,使用.来访问成员。
  • 当你有一个对象的指针时,使用->来访问成员。
  1. 底层实现
  • . 直接访问对象的成员,不涉及解引用。
  • **->**** 隐式地解引用指针,然后访问成员。**

operator*()

Ref operator*()
{
    return _node->_data;
}
  • 这个函数返回当前节点数据的引用(Ref),即_node->_data
  • 当你使用*ita时,它会调用operator*(),返回迭代器指向的数据。

operator->()

Ptr operator->()
{
    return &_node->_data;
}
  • 这个函数返回指向当前节点数据的指针(Ptr),即&_node->_data
  • 当你使用ita->时,它会调用operator->(),返回数据的指针,从而访问数据的成员。

operator->()的实际使用

while (ita != lta.end())
{
    cout << ita->_a1 << ":" << ita->_a2 << endl;
    cout << ita.operator->()->_a1 << ":" << ita.operator->()->_a2 << endl;

    ++ita;
}
  1. **ita->_a1****ita->_a2**
  • 由于operator->()返回了指向_node->_data的指针,所以ita->_a1等价于(*ita)._a1
  • 通过重载operator->(),用简化了的语法,使得ita->_a1这种写法成为可能,而不需要显式地写成(*ita)._a1ita.operator->()->_a1。这使得代码更具可读性和直观性,尤其是在访问嵌套结构或类成员时。
  1. **ita.operator->()->_a1****ita.operator->()->_a2**
  • 这是显式调用operator->(),获取指向数据的指针,然后访问其成员。
  • 这种写法展示了运算符重载的具体调用过程。

模拟实现list框架

整体模拟实现list的框架如图,将迭代器与节点包装成类模板进行使用:
image.png


image.png

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

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

相关文章

肆[4],VisionMaster全局触发测试说明

1&#xff0c;环境 VisionMaster4.3 2&#xff0c;实现功能 2.1&#xff0c;全局触发进行流程控制执行。 2.2&#xff0c;取像完成&#xff0c;立即运动到下一个位置&#xff0c;同步进行图片处理。 2.3&#xff0c;发送结果的同时&#xff0c;还需要显示图像处理的痕迹。 …

力扣爆刷第168天之TOP200五连刷106-110(每日温度单调栈、盛水最多滑动窗口、全排列回溯)

力扣爆刷第168天之TOP200五连刷106-110&#xff08;每日温度单调栈、盛水最多滑动窗口、全排列回溯&#xff09; 文章目录 力扣爆刷第168天之TOP200五连刷106-110&#xff08;每日温度单调栈、盛水最多滑动窗口、全排列回溯&#xff09;一、138. 随机链表的复制二、739. 每日温…

⌈ 传知代码 ⌋ 记忆注意力用于多模态情感计算!

&#x1f49b;前情提要&#x1f49b; 本文是传知代码平台中的相关前沿知识与技术的分享~ 接下来我们即将进入一个全新的空间&#xff0c;对技术有一个全新的视角~ 本文所涉及所有资源均在传知代码平台可获取 以下的内容一定会让你对AI 赋能时代有一个颠覆性的认识哦&#x…

【TS】TypeScript数组类型:掌握数据集合的类型安全

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 TypeScript数组类型&#xff1a;掌握数据集合的类型安全引言1. TypeScript数组类…

Three.js结合物理引擎实现掉落效果

<template> </template><script setup> import * as THREE from three import gsap from gsap //导入轨道控制器 import { OrbitControls } from three/examples/jsm/controls/OrbitControls // 导入 dat.gui import { GUI } from three/addons/libs/lil-gui…

好用的抠图软件在哪里找?这篇文章就有几款好用的抠图工具

在图像处理的世界中&#xff0c;抠图技术无疑是一项至关重要的技能。 无论是设计师、摄影师还是普通的图像编辑爱好者&#xff0c;都可能需要从一张图片中精确地分离出某个对象或元素。但是&#xff0c;手动抠图不仅耗时而且技术要求高&#xff0c;这时候&#xff0c;一款优秀…

PTrade常见问题系列17

是否支持量化帐号的指定服务器分发? 是否可以支持部分量化帐号不根据原有分发规则&#xff0c;而是直接指定分发&#xff1f; 1、若需要增加VIP服务器专用于新增的帐号进行分配&#xff0c;可以参考【量化】量化Nginx用户指定服务器处理步骤.docx&#xff1b; 2、若所有服务…

【音视频之SDL2】Windows配置SDL2项目模板

文章目录 前言 SDL2 简介核心功能 Windows配置SDL2项目模板下载SDL2编译好的文件VS配置SDL2 测试代码效果展示 总结 前言 在开发跨平台的音视频应用程序时&#xff0c;SDL2&#xff08;Simple DirectMedia Layer 2&#xff09;是一个备受欢迎的选择。SDL2 是一个开源库&#x…

“AI+”时代,人工智能前景怎么样?

随着“互联网”到“AI”的转型&#xff0c;时代发展迎来了新的阶段。 在政策、技术和市场的三重驱动之下&#xff0c;人工智能正在快速响应各领域的广泛诉求。虽然人工智能的兴起“打消”了一些传统领域的念想&#xff0c;但同时也开辟了更加多元化的市场。 当下互联网大厂人…

AgentBench: Evaluating LLMs As Agents

AgentBench: Evaluating LLMs As Agents Github&#xff1a; https://github.com/THUDM/AgentBench 榜单&#xff1a;https://llmbench.ai/agent/data demos&#xff1a;https://llmbench.ai/agent/demo 备注&#xff1a;该论文介绍为AgentBench v0.2版本 一、介绍 现如今&am…

计算机网络01

文章目录 浏览器输入URL后发生了什么&#xff1f;Linux 系统是如何收发网络包的&#xff1f;Linux 网络协议栈Linux 接收网络包的流程Linux 发送网络包的流程 浏览器输入URL后发生了什么&#xff1f; URL解析 当在浏览器中输入URL后&#xff0c;浏览器首先对拿到的URL进行识别…

sdwan

分支互联网络解决方案 - 华为企业业务 分支互联网络解决方案 随着5G、AI、物联网等新兴技术与云紧密结合&#xff0c;企业业务智能化和云化加速。 企业分支WAN流量激增&#xff0c;传统以MPLS专线为主的广域互联网络难以支撑业务发展。SD-WAN成为应对云时代的必然选择。 SD…

将 magma example 改写成 cusolver example eqrf

1&#xff0c;简单安装Magma 1.1 下载编译 OpenBLAS $ git clone https://github.com/OpenMathLib/OpenBLAS.git $ cd OpenBLAS/ $ make -j DEBUG1 $ make install PREFIX/home/hipper/ex_magma/local_d/OpenBLAS/1.2 下载编译 magma $ git clone https://bitbucket.org/icl…

专业且免费的重复文件查找与删除工具,文本,图片,音频和视频等

AllDup是一款专业的重复文件查找与删除工具。作为一款免费软件&#xff0c;AllDup以其出色的功能和简洁的操作界面广受欢迎。它不仅可以有效地识别和删除电脑硬盘以及外部设备如USB闪存驱动器中的重复文件&#xff0c;还能对多媒体文件如图片、音频和视频等进行特殊处理&#x…

GUI图形化界面操作(下部)

目录 ​编辑 前言 Swing 窗口 注意点 新增的组件 进度条组件 开关按钮 多面板和分割面板 多面板 分割面板 ​编辑 选项窗口 对话框带三个选项是&#xff0c;否&#xff0c;取消。 对话框提示输入文本: 前言 修炼中&#xff0c;该篇文章为俺很久前的学习笔记 Swi…

Tomcat的安装配置教程

一、服务器的安装 tomcat官方安装网站&#xff1a;http://tomcat.apache.org/ 点击选择想要安装的版本 选择与本机的字节匹配的压缩包进行安装 二、 环境配置 打开系统 进行高级系统配置 配置环境变量 新建系统变量 增加新变量&#xff0c;复制tomcat文件的安装路径为…

HTML,CSS,JavaScript实现——井字棋游戏

和大家分享一个经典的游戏项目——井字棋游戏。这个项目不仅能带你回味童年的乐趣&#xff0c;还能帮助你练习 HTML、CSS 和 JavaScript 编程。 项目介绍 井字棋游戏是一个两人对战游戏&#xff0c;玩家轮流在一个3x3的网格上标记 X 或 O。先将三个标记连成一条直线&#xff…

彻底解决Google浏览器自动删除下载文件或下载失败

需求背景 最近发现在阿里巴巴国际站聊天过程中,客户发的文件或软件,Goole浏览器居然无法下载,或者下载一会就提示失败,莫名其妙。错误提示如下:仔细看发现是【已拦截未经验证的下载内容】。 解决方案: 1、打开浏览器设置 2、打开隐私安全 3、配置安全浏览 4、配置完成-…

面试:CUDA Tiling 和 CPU tiling 技术详解

目录 一、CUDA Tiling 和 CPU Tiling 技术概述 &#xff08;一&#xff09;技术原理 &#xff08;二&#xff09;应用场景 &#xff08;三&#xff09;优势和劣势 二、Tiling 技术在深度学习中的应用 三、Tiling 技术的缺点 一、CUDA Tiling 和 CPU Tiling 技术概述 Til…