【C++】【STL】双向链表你还在手撕代码❓️STL list容器那些藏在文档里的实用方法,你用过几个❓️

【C++】【STL】双向链表你还在手撕代码❓️STL list容器那些藏在文档里的实用方法,你用过几个❓️

目录

前言:

1、list简介

⭐核心特性

2、常用接口介绍

2.1、list类对象的常用构造

2.2、list对象容量操作

2.3、list iterator(迭代器)

2.5、链表的遍历

2.4、链表的增删查改

3、拓展接口说明

3.1、尾插——emplace_back

3.2、合并两个已排序的链表——merge

3.3、移除链表中连续的重复元素——unique

3.4、将一个链表的元素或元素范围移动到另一个链表的指定位置——splice

3.5、链表元素的排序——sort


前言:

list容器提供了关于双向链表相关操作的各种接口函数,这样就会大大提高我们工作和学习的效率。只要我们学会了这些实用接口(增删查改)的使用,就再也不用自己去实现了,回想自己手撕代码的心酸过程😅,想必大家已经迫不及待地想要去学习了。

注意:标有⭐的内容需要重点掌握!!!

我们话不多说,进入正题👇️👇️👇️

1、list简介

在 C++ 标准库中,std::list 是一个双向链表容器。定义在头文件#include<list>中,它的元素在内存中不是连续存储的,而是通过节点之间的指针相互链接,这使得它在插入和删除操作上有独特的优势。

std::list 是一个双向链表容器,即其底层结构为双向链表(带头双向循环链表),需要注意,双向链表的头节点(哨兵位)只用来占位,而不存储有效数据。所以,双向链表的头插即是在上图的d1节点之前插入节点;头删即删除d1节点;尾插即在d3节点之后插入节点;尾删即删除d3节点。
⭐核心特性双向链表结构:每个元素包含前驱和后继指针,支持双向遍历动态内存分配:元素分散存储,无需预先分配固定大小的内存高效的插入删除:在任意位置插入 / 删除元素的时间复杂度为 O(1)(已知位置时)不支持随机访问:访问特定元素需要从头 / 尾遍历,时间复杂度为 O(n)
🌟🌟std::list的文档(方便大家随时查阅相关接口函数):

https://legacy.cplusplus.com/reference/list/list/(强调一下:自己查文档很重要呦!!!)

2、常用接口介绍

2.1、list类对象的常用构造

实现list对象在定义的同时初始化

构造函数(construct)(⭐)                                           功能
list()默认构造,构造空链表(即哨兵位)
list(const list<value_type>& lt)拷贝构造,用已经存在的对象 lt 初始化
list(size_t n, const value_type& val=value_type())

填充构造函数,用n个value_type类型的值去初始化

list(InputIterator first,InputIterator last)迭代器区间构造,使用迭代器范围 [first, last) 内的元素来初始化 list对象
list(std::initializer_list<value_type> ilist)

初始化列表构造函数(C++11 引入)

void test1() { list<int> lt1; // 构造空链表 list<int> lt2(5, 1); // 使用5个1初始化lt2 list<int> lt3(lt2); // 构造lt3对象,并用lt2初始化 list<int> lt4(lt2.begin(), lt2.end()); // 使用lt2的迭代器区间初始化lt4 list<int> lt5({ 1,2,3,4,5 }); // 使用初始化列表初始化lt5 }

2.2、list对象容量操作

            函数                                           功能
⭐size()获取双向链表的有效节点个数
⭐empty()判断链表是否为空
void test2() { list<int> lt({ 1,2,3,4,5 }); cout << lt.size() << endl; if (lt.empty()) cout << "为空" << endl; else cout << "非空" << endl; }

2.3、list iterator(迭代器)

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。

              函数                                                  功能
           ⭐lt.begin()begin获取头节点下一个节点的迭代器(地址)
           ⭐lt.end()end获取头节点的迭代器(地址)
             lt.rbegin()rbegin()返回的是反向迭代器,指向链表的头节点
             lt.rend()rend获取头节点下一个节点的迭代器(地址)

2.5、链表的遍历

在前面的string容器和vector容器中,由于两者的底层结构均为数组,所以,string和vector都重载了 "[ ]"运算符,通过下标的方式来访问某个元素。但是,由于list底层结构为双向链表,导致他的元素在内存中并不是连续存储的,所以我们不能用下标的方式来访问。

下面我来演示一下链表的遍历方式:

 ⭐方法一:范围for + auto

void test3() { list<int> lt({ 1,2,3,4,5 }); for (auto e : lt) { cout << e << " "; } } 

输出结果:

⭐方法二:迭代器

void test4() { list<int> lt({ 1,2,3,4,5 }); list<int>::iterator it = lt.begin(); while (it != lt.end()) { cout << *it << " "; it++; } }

输出结果:

2.4、链表的增删查改

                       函数                                              功能
               front获取d1节点的值
              back获取d3节点的值
push_front(const value_type x)在d1节点之前插入值为x的新节点
              pop_front删除d1节点
push_back(const value_type x)在d3节点之后插入一个值为x的新节点
              pop_back删除d3节点
insert(iterator pos, const value_type x)在pos指向的节点之前插入一个值为x的新节点
              erase(iterator pos)删除pos指向的节点
              swap交换两个对象的值
              clear清理链表中的有效节点

3、拓展接口说明

3.1、尾插——emplace_back

前面我们已经介绍了一个尾插的接口(push_back),那为什么list容器还要实现一个emplace_back尾插接口呢?

假设我们额外定义一个AA类,然后,将AA类型的数据分别使用push_back和emplace_back尾插到lt对象,看会发生什么?下面我们来看具体的示例

struct AA { AA(int a1 = 1, int a2 = 1) :_a1(a1) ,_a2(a2) {} int _a1; int _a2; };
void test5() { AA a1; list<AA> lt; lt.push_back(a1); lt.push_back(AA(20, 20)); lt.push_back(30, 30); // 报错 }
这里有一种应用emplace_back特殊情景,此时只有emplace_back才能够实现。当我们想要直接将构造a对象的数据作为参数传给尾插函数时,push_back就会报错,而emplace_back则支持这种操作。
// emplace_back void test6() { list<AA> lt; lt.emplace_back(30, 30); }

通过调试,可以看到emplace_back将用来构造a对象的参数30和30插入了链表。

3.2、合并两个已排序的链表——merge

需要注意:merge只能用来合并两个已经排好序的链表,同时,合并之后一个链表将会为空。
void test7() { 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(); // 将已经有序的second合并到first链表 first.merge(second);// (second is now empty) for (auto e : first) cout << e << " "; }

3.3、移除链表中连续的重复元素——unique

注意:只能连续的重复元素去掉,并只保留一个。
void test8() { list<int> lt; lt.push_back(1); lt.push_back(5); lt.push_back(5); lt.push_back(4); lt.push_back(5); lt.push_back(6); for (auto e : lt) cout << e << " "; cout << endl; lt.unique(); // 去重 for (auto e : lt) cout << e << " "; }

3.4、将一个链表的元素或元素范围移动到另一个链表的指定位置——splice

注意移动之后一个链表将会为空。
void test9() { std::list<int> mylist1, mylist2; std::list<int>::iterator it; for (int i = 1; i <= 4; ++i) mylist1.push_back(i); for (int i = 1; i <= 3; ++i) mylist2.push_back(i * 10); it = mylist1.begin(); ++it; mylist1.splice(it, mylist2); // 链表mylist2节点转移给链表mylist1的第二个节点之前 for (auto e : mylist1) { cout << e << " "; } }

3.5、链表元素的排序——sort

我们知道C++在标准库中已经实现了排序算法,为什么list还要再自己实现呢?这就是因为迭代器的性质造成的。下面我们来介绍一下迭代器的类型

根据前面对string和vector的学习,我们知道,根据迭代器的功能,将迭代器分为

// iterator————普通对象 // const_iterator————const对象 // reverse_iterator————普通对象的反向迭代器 // const_reverse_iterator————const对象的反向迭代器

根据迭代器性质将迭代器分为:

// 按性质分为:(向上兼容) /*随机迭代器:string/vector/deque... 支持++ / -- / + / - ; 双向迭代器:list/map/set... 支持++ / -- ; 单向迭代器:forward_list/unordered_map 支持++ */
#include<algorithm> // sort的头文件 void test01() { list<int> lt; lt.push_back(6); lt.push_back(3); lt.push_back(5); lt.push_back(2); lt.push_back(1); // sort :仅支持随机迭代器 //sort(lt.begin(), lt.end()); // list类对象的迭代器为双向迭代器,所以报错 string st("ahdncxvjsvf"); sort(st.begin(), st.end()); // string类对象的迭代器为随机迭代器 cout << st << endl; // reverse : 支持双向迭代器(也兼容随机迭代器) reverse(lt.begin(), lt.end()); reverse(st.begin(), st.end()); }
void test02() { list<int> lt; lt.push_back({6, 3, 5, 2, 1}); // list容器中的sort lt.sort(); for (auto e : lt) cout << e << " "; }

4、总结

list容器提供了丰富而且非常实用的接口,我相信只要我们自己不断查文档,并不断应用,就一定能够掌握这些接口的用法。那么,本期的分享就到此结束,如果大家觉得写的还不错的话,点个小爱心❤️支持一下吧👇️👇️👇️,我们下期再见🤗🤗🤗

Read more

【Git#5】远程操作 & 标签管理

【Git#5】远程操作 & 标签管理

📃个人主页:island1314 ⛺️ 欢迎关注:👍点赞 👂🏽留言 😍收藏 💞 💞 💞 * 生活总是不会一帆风顺,前进的道路也不会永远一马平川,如何面对挫折影响人生走向 – 《人民日报》 🔥 目录 * 一、理解分布式版本操作系统 * 二、远程操作 * 1. 概述 * 2. 新建远程仓库 * 3. 克隆远程仓库 * 4. 向远程仓库推送 * 5. 拉取远程仓库 * 6. 一些问题 * 6.1 本地和远程分支分叉 * 三、git 配置 * 1. 忽略特殊文件 * 2. 给命令配置别名 * 四、标签管理 * 1. 理解标签 * 2. 创建标签 * 3. 操作标签 一、理解分布式版本操作系统 我们目前所说的所有内容(工作区,暂存区,

By Ne0inhk

开源数字图书馆完全指南:构建知识共享平台的去中心化解决方案

开源数字图书馆完全指南:构建知识共享平台的去中心化解决方案 【免费下载链接】openlibraryOne webpage for every book ever published! 项目地址: https://gitcode.com/gh_mirrors/op/openlibrary 开源数字图书馆作为知识共享平台的核心载体,通过去中心化存储技术实现全球图书资源的开放访问。本文将系统解析Open Library项目的技术架构、多场景应用价值及社区参与路径,为不同需求的用户提供从部署到定制的完整实施指南。 价值主张:为什么选择开源数字图书馆 知识平等获取的技术实现 传统图书馆受限于物理空间与地域限制,导致超过60%的学术资源仅能被特定机构访问。开源数字图书馆通过分布式网络架构,将全球出版资源转化为可自由获取的数字资产,目前已收录超过2000万册图书元数据,建立起覆盖80%人类文明出版物的知识网络。 可持续发展的知识生态系统 项目采用MIT开源协议,确保核心代码永久可访问。不同于商业电子书平台的订阅制模式,其去中心化存储架构可避免单点故障导致的知识断代风险,数据冗余机制使

By Ne0inhk
Spring Boot 实现DOCX转PDF(基于docx4j的轻量级开源方案)

Spring Boot 实现DOCX转PDF(基于docx4j的轻量级开源方案)

好的,这是一份详细、实用且权威的 Java Spring Boot 实现 DOCX 转 PDF(基于 docx4j)的(基于docx4j的轻量级开源方案)全面指南。 目录 1. 引言 * 1.1 DOCX 与 PDF 格式简介 * 1.2 转换需求与应用场景 * 1.3 方案选型:为什么选择 docx4j? 2. 环境准备 * 2.1 基础环境要求 * 2.2 创建 Spring Boot 项目 * 2.3 添加 docx4j 及相关依赖 3. 核心转换实现

By Ne0inhk
用 Rust 构建 Git 提交历史可视化工具

用 Rust 构建 Git 提交历史可视化工具

在软件开发中,版本控制系统的历史记录往往承载着项目的演进脉络。然而,当项目规模扩大、分支增多时,纯文本的 git log 输出很难直观地展现提交之间的复杂关系。今天,我想分享一个用 Rust 构建的轻量级工具 —— git-graph-rs,它能把 Git 仓库的提交历史转换为可视化的图结构,为代码审查、项目复盘和工程决策提供直观的支持。 目录 * 为什么需要可视化? * 技术方案的选择 * 1. 利用系统 Git 命令 * 2. 模块化的 Rust 架构 * 3. 双格式输出策略 * 核心实现解析 * Git 数据获取的艺术 * 图结构的一致性保证 * 合并提交的可视化区分 * 工程化思维体现 * 错误处理的前置化 * 参数设计的克制 * 输出格式的稳定性 * 实际应用场景 * 1. CI/CD 集成 * 2. 代码审查辅助 * 3. 项目文档化

By Ne0inhk