【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

除了 OpenClaw,今天 AI 热榜还有什么值得看?我把 5 个重点方向讲清楚了

除了 OpenClaw,今天 AI 热榜还有什么值得看?我把 5 个重点方向讲清楚了

🔥 个人主页:杨利杰YJlio❄️ 个人专栏:《Sysinternals实战教程》《Windows PowerShell 实战》《WINDOWS教程》《IOS教程》《微信助手》《锤子助手》《Python》《Kali Linux》《那些年未解决的Windows疑难杂症》🌟 让复杂的事情更简单,让重复的工作自动化 除了 OpenClaw,今天 AI 热榜还有什么值得看?我把 5 个重点方向讲清楚了 * 除了 OpenClaw,今天 AI 热榜还有什么值得看?我把 5 个重点方向讲清楚了 * 1. 我先说结论:今天这波 AI 热榜,最重要的不是“谁最火”,而是“风向变了” * 2. GoogleCloudPlatform / generative-ai:平台生态正在成为真正的护城河 * 3. MiroFish:群体智能和多智能体,开始从概念走向更具体的产品叙事

By Ne0inhk
Python+AI 实战:搭建属于你的智能问答机器人

Python+AI 实战:搭建属于你的智能问答机器人

欢迎文末添加好友交流,共同进步! “ 俺はモンキー・D・ルフィ。海贼王になる男だ!” 引言 * 在数字化转型浪潮中,智能问答机器人正成为企业客服、知识库检索乃至个人助理等场景的关键交互入口。它能让员工秒级获取技术解答、客户即时获得业务支持、学习者随时得到个性化辅导,极大提升信息获取效率与用户体验。 * 为何选择 Python 与开源 AI 模型?Python 拥有成熟的 AI 生态——Hugging Face Transformers、LangChain、FAISS 等工具大幅降低开发门槛;而本地部署的开源大模型(如 Phi-3、Mistral、Llama 系列)则保障了数据隐私、规避了 API 成本,特别适合对安全性或离线能力有要求的场景。 * 本文将手把手带你从零构建一个基于 RAG(检索增强生成)架构的本地智能问答系统:使用 Sentence-BERT 实现语义检索,FAISS 作为向量数据库,并集成轻量级开源语言模型生成答案。

By Ne0inhk
人工智能:自然语言处理在医疗领域的应用与实战

人工智能:自然语言处理在医疗领域的应用与实战

人工智能:自然语言处理在医疗领域的应用与实战 学习目标 💡 理解自然语言处理(NLP)在医疗领域的应用场景和重要性 💡 掌握医疗领域NLP应用的核心技术(如电子病历分析、疾病诊断辅助、药物相互作用检测) 💡 学会使用前沿模型(如BioBERT、ClinicalBERT)进行医疗文本分析 💡 理解医疗领域的特殊挑战(如医疗术语、数据隐私、法规要求) 💡 通过实战项目,开发一个电子病历文本分类应用 重点内容 * 医疗领域NLP应用的主要场景 * 核心技术(电子病历分析、疾病诊断辅助、药物相互作用检测) * 前沿模型(BioBERT、ClinicalBERT)在医疗领域的使用 * 医疗领域的特殊挑战 * 实战项目:电子病历文本分类应用开发 一、医疗领域NLP应用的主要场景 1.1 电子病历分析 1.1.1 电子病历分析的基本概念 电子病历(Electronic Health Records, EHR)是医疗领域的核心数据之一,包含了患者的基本信息、诊断记录、

By Ne0inhk