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

C++ STL list 容器学习笔记:双向链表的 “小火车“ 操控指南

各位未来的自己:当你翻开这篇笔记时,大概率正对着当初写的 test15 ()、test20 () 一脸懵 —— 咱当初为了省事儿,函数名起得跟 “密码本” 似的,注释更是惜字如金,连个 “这测啥” 都没写。现在我把这些 “加密代码” 解密,改成 “人话” 函数名,加了比代码还长的注释,还顺便吐槽了当初踩过的坑,主打一个 “以后复习不骂街,看一眼就知道咋操控这列双向链表小火车”!

一、先总览:咱的代码里藏了哪些 list 核心操作?

先列个 “操作清单”,省得你翻半天不知道重点,对着代码发呆:

list 构造函数(多种初始化姿势)、首尾增删(车头车尾上下车)、指定位置增删(车厢中间插删乘客)、list 遍历(巡视整列小火车)、排序与去重(给车厢按规则排序 + 清理重复车厢)、自定义排序(支持自定义数据类型排序,比如给同学按年龄身高排序)。

下面逐个拆解,每个操作都配 “改名后代码 + 大白话注释 + 踩坑记录”,保证你看一遍就回忆起怎么操控这列 “双向链表小火车”。

二、逐个攻破:list 容器核心操作详解(附改名代码)

1. list 构造函数:给小火车搭骨架(原 test15→TestListConstructor)

核心作用:

list 的构造就像 “搭建双向链表小火车的骨架”,支持多种搭建方式,从空骨架到复制现成骨架都能搞定,灵活度拉满。当初的坑:分不清带参构造的参数含义,比如list<int> lst(10)是建 10 个空车厢,不是建一个值为 10 的车厢。

改名后代码

cpp

运行

#include <list>
#include <iostream>
using namespace std;// 测试list的各种构造函数:搭建双向链表小火车的不同姿势
void TestListConstructor() {// 场景1:默认构造(空骨架小火车,没有任何车厢)list<int> empty_train;cout << "空小火车长度:" << empty_train.size() << endl; // 输出:0// 场景2:带参构造(建10节空车厢,车厢里默认值不确定)list<int> ten_empty_cars(10);cout << "10节空车厢小火车长度:" << ten_empty_cars.size() << endl; // 输出:10// 场景3:拷贝构造(复制现成小火车,一模一样)list<int> copy_train(ten_empty_cars);cout << "复制的小火车长度:" << copy_train.size() << endl; // 输出:10// 场景4:指定长度+默认值(建10节车厢,每节都装22)list<int> val_train(10, 22);cout << "10节装22的小火车:";for (auto car : val_train) cout << car << " "; // 输出:22 22 ... 22(10个)cout << endl;// 场景5:迭代器范围构造(从其他容器截取部分车厢组装小火车)list<int> range_train(val_train.begin(), val_train.end());cout << "迭代器构造的小火车长度:" << range_train.size() << endl; // 输出:10// 场景6:赋值构造(用=给小火车换骨架)list<int> assign_train = range_train;cout << "赋值构造的小火车长度:" << assign_train.size() << endl; // 输出:10
}// 调用测试
int main() {TestListConstructor();return 0;
}
复习重点(当初记混的规则):
  • list<T> lst(n):创建 n 个默认构造的元素(不是 n 作为元素值);
  • list<T> lst(n, val):创建 n 个值为 val 的元素,这才是 “批量装相同值”;
  • 拷贝构造和赋值构造的区别:拷贝构造是创建时复制,赋值构造是创建后替换。

2. 首尾增删:车头车尾上下车(原 test16→TestListPushPop)

核心作用:

list 的首尾增删就像 “小火车的车头车尾上下乘客”,效率极高(O (1)),不用移动中间车厢,只需调整车头车尾的挂钩(指针)。当初的坑:混淆push_frontpop_front,误把删除写成增加,导致小火车 “少了车头还不知道”。

改名后代码

cpp

运行

#include <list>
#include <iostream>
using namespace std;// 测试list首尾增删:小火车车头车尾上下乘客
void TestListPushPop() {// 初始化小火车:1、2、3、4、5五节车厢list<int> train = {1, 2, 3, 4, 5};cout << "初始小火车:";for (auto car : train) cout << car << " "; // 输出:1 2 3 4 5cout << endl;// 场景1:车头加乘客(push_front):在1前面加100train.push_front(100);cout << "车头加100后:";for (auto car : train) cout << car << " "; // 输出:100 1 2 3 4 5cout << endl;// 场景2:车头卸乘客(pop_front):把100卸掉train.pop_front();cout << "车头卸100后:";for (auto car : train) cout << car << " "; // 输出:1 2 3 4 5cout << endl;// 场景3:车尾加乘客(push_back):在5后面加66train.push_back(66);cout << "车尾加66后:";for (auto car : train) cout << car << " "; // 输出:1 2 3 4 5 66cout << endl;// 场景4:车尾卸乘客(pop_back):把66卸掉train.pop_back();cout << "车尾卸66后:";for (auto car : train) cout << car << " "; // 输出:1 2 3 4 5cout << endl;// 场景5:查看小火车状态cout << "小火车当前长度:" << train.size() << endl; // 输出:5if (train.empty()) {cout << "小火车是空的!" << endl;} else {cout << "小火车满载运行!" << endl;}// 场景6:两种遍历方式(当初纠结的迭代器用法)cout << "传统迭代器遍历:";for (list<int>::iterator it = train.begin(); it != train.end(); it++) {cout << *it << " "; // 输出:1 2 3 4 5}cout << endl;cout << "auto迭代器遍历(推荐!):";for (auto it = train.begin(); it != train.end(); it++) {cout << *it << " "; // 输出:1 2 3 4 5}cout << endl;
}// 调用测试
int main() {TestListPushPop();return 0;
}
复习重点:
  • 首尾增删都是 O (1) 效率,这是 list 的核心优势之一;
  • push_front/pop_front操作车头,push_back/pop_back操作车尾;
  • 遍历推荐用auto简化迭代器写法,不用记冗长的list<int>::iterator

3. 指定位置增删:车厢中间插删乘客(原 test17→TestListInsert,原 test18→TestListErase)

核心作用:

指定位置增删就像 “在小火车中间插入或删除一节车厢”,只需调整相邻车厢的挂钩(指针),效率 O (1)(前提是找到位置)。当初的坑:erase删除范围时,迭代器指向错误,导致删错车厢;插入时混淆迭代器指向的位置。

改名后代码(插入 + 删除合并)

cpp

运行

#include <list>
#include <iostream>
using namespace std;// 测试list指定位置插入:给小火车中间插车厢
void TestListInsert() {// 初始小火车:1、2、3三节车厢list<int> train = {1, 2, 3};cout << "初始小火车:";for (auto car : train) cout << car << " "; // 输出:1 2 3cout << endl;// 场景1:在指定位置插单个元素(在2前面插100)auto it1 = ++train.begin(); // it1指向第二节车厢(值2)train.insert(it1, 100);cout << "在2前面插100后:";for (auto car : train) cout << car << " "; // 输出:1 100 2 3cout << endl;// 场景2:在指定位置插n个相同元素(在车头插5个200)train.insert(train.begin(), 5, 200);cout << "车头插5个200后:";for (auto car : train) cout << car << " "; // 输出:200 200 200 200 200 1 100 2 3cout << endl;// 场景3:插入另一个list的所有元素(把lst2的车厢插车头)list<int> lst2(2, 300); // lst2:300、300train.insert(train.begin(), lst2.begin(), lst2.end());cout << "插入lst2后:";for (auto car : train) cout << car << " "; // 输出:300 300 200...(前面的元素)cout << endl;
}// 测试list指定位置删除:给小火车中间卸车厢
void TestListErase() {// 初始小火车:1、2、3、4、5五节车厢list<int> train = {1, 2, 3, 4, 5};cout << "初始小火车:";for (auto car : train) cout << car << " "; // 输出:1 2 3 4 5cout << endl;// 场景1:删除指定位置的单个元素(删除第一节车厢1)train.erase(train.begin());cout << "删除第一节车厢后:";for (auto car : train) cout << car << " "; // 输出:2 3 4 5cout << endl;// 场景2:删除指定范围的元素(删除2和3,保留4和5)auto it_start = train.begin();       // 指向2auto it_end = train.end();it_end--;it_end--; // it_end指向3的下一个位置(4)train.erase(it_start, it_end);cout << "删除2和3后:";for (auto car : train) cout << car << " "; // 输出:4 5cout << endl;
}// 调用测试
int main() {TestListInsert();cout << "------------------------" << endl;TestListErase();return 0;
}
复习重点:
  • insert(pos, val):在迭代器pos指向的元素前面插入值;
  • erase(pos)删除单个元素,erase(first, last)删除[first, last)范围的元素;
  • 删除后迭代器会失效,不能再用失效的迭代器访问元素。

4. 排序与去重:给小火车车厢整序 + 清理重复(原 test19→TestListSortUnique)

核心作用:

排序就像 “给小火车车厢按规则重新排列”,去重就像 “清理重复的车厢”,list 自带sortunique成员函数,比std::sort更适配双向链表。当初的坑:用std::sort给 list 排序(报错,因为 list 是双向迭代器,不支持随机访问);去重前没排序,导致重复车厢没被清理。

改名后代码

cpp

运行

#include <list>
#include <iostream>
using namespace std;// 自定义降序排序规则:大的在前
bool DescSort(const int& v1, const int& v2) {return v1 > v2;
}// 测试list排序与去重:给小火车整序+清理重复车厢
void TestListSortUnique() {// 初始小火车:11、2、5、3、1、9、9、9(混乱且有重复)list<int> train = {11, 2, 5, 3, 1, 9, 9, 9};cout << "初始小火车(混乱+重复):";for (auto car : train) cout << car << " "; // 输出:11 2 5 3 1 9 9 9cout << endl;// 场景1:去重(unique只能清理连续的重复元素,此时效果有限)train.unique();cout << "未排序直接去重:";for (auto car : train) cout << car << " "; // 输出:11 2 5 3 1 9(仅清理了连续的9)cout << endl;// 场景2:升序排序(从小到大排列)list<int> sorted_train = train; // 复制一份用于排序sorted_train.sort();cout << "升序排序后:";for (auto car : sorted_train) cout << car << " "; // 输出:1 2 3 5 9 11cout << endl;// 场景3:反转实现降序(先升序再反转)sorted_train.reverse();cout << "反转实现降序:";for (auto car : sorted_train) cout << car << " "; // 输出:11 9 5 3 2 1cout << endl;// 场景4:自定义降序排序(直接用自定义规则)train.sort(DescSort);cout << "自定义降序排序后:";for (auto car : train) cout << car << " "; // 输出:11 9 5 3 2 1cout << endl;// 场景5:排序后去重(正确姿势!)train.unique();cout << "排序后去重:";for (auto car : train) cout << car << " "; // 输出:11 9 5 3 2 1(无重复)cout << endl;
}// 调用测试
int main() {TestListSortUnique();return 0;
}
复习重点:
  • list 必须用自身的sort成员函数,不能用std::sort(双向迭代器不支持随机访问);
  • unique只能清理连续的重复元素,必须先排序再去重才有效;
  • 自定义排序规则需传一个返回 bool 的函数,参数是两个元素的引用。

5. 自定义数据类型排序:给 “同学车厢” 按规则排序(原 test20→TestListCustomSort)

核心作用:

支持给自定义数据类型(比如 Person 类)排序,就像 “给小火车里的同学按年龄、身高排序”,只需自定义排序规则。当初的坑:自定义排序函数返回值类型错误(返回 Person 而不是 bool),导致排序失败。

改名后代码

cpp

运行

#include <list>
#include <iostream>
#include <string>
using namespace std;// 自定义Person类:相当于“同学车厢”,包含姓名、年龄、身高
class Person {
public:string name;   // 姓名int age;       // 年龄double height; // 身高// 构造函数:初始化同学信息Person(string name, int age, double height) {this->name = name;this->age = age;this->height = height;}
};// 自定义排序规则:按年龄升序,年龄相同按身高降序
// 当初的bug:返回值写成Person,正确应该返回bool(判断p1是否应该在p2前面)
bool PersonSortRule(const Person& p1, const Person& p2) {if (p1.age == p2.age) {// 年龄相同,身高高的在前return p1.height > p2.height;} else {// 年龄不同,年龄小的在前return p1.age < p2.age;}
}// 测试list自定义数据类型排序:给同学车厢按规则排序
void TestListCustomSort() {// 初始化“同学小火车”Person p1("张三", 11, 1.6);    // 11岁,1.6米Person p2("李四", 11, 1.9);    // 11岁,1.9米Person p3("王五", 21, 1.5);    // 21岁,1.5米Person p4("江桂东", 21, 1.7);  // 21岁,1.7米list<Person> class_train;class_train.push_back(p1);class_train.push_back(p2);class_train.push_back(p3);class_train.push_back(p4);// 排序前cout << "排序前的同学车厢:" << endl;for (Person p : class_train) {cout << "年龄:" << p.age << ",姓名:" << p.name << ",身高:" << p.height << "米" << endl;}cout << endl;// 按自定义规则排序class_train.sort(PersonSortRule);// 排序后cout << "排序后的同学车厢:" << endl;for (Person p : class_train) {cout << "年龄:" << p.age << ",姓名:" << p.name << ",身高:" << p.height << "米" << endl;}
}// 调用测试
int main() {TestListCustomSort();return 0;
}
复习重点:
  • 自定义数据类型排序,必须提供一个比较函数,返回 bool 类型;
  • 比较函数的逻辑:返回 true 表示第一个元素应该排在第二个元素前面;
  • 排序规则可以多层嵌套(比如先按年龄,再按身高),满足复杂排序需求。

三、复习速查表(怕忘就看这个!)

操作核心作用关键语法 / 避坑点
构造函数搭建 list 小火车骨架1. list(n)建 n 个空元素;2. list(n, val)建 n 个 val 元素;3. 支持拷贝和迭代器构造
首尾增删车头车尾上下乘客1. push_front/pop_front操作车头;2. push_back/pop_back操作车尾;3. 效率 O (1)
指定位置增删中间插删车厢1. insert(pos, val)在 pos 前插入;2. erase删除后迭代器失效;3. 找到位置后效率 O (1)
排序给车厢整序1. 用list.sort(),不能用std::sort;2. 支持自定义排序函数
去重清理重复车厢1. 必须先排序再去重;2. 用list.unique(),只清理连续重复元素
自定义类型排序给自定义车厢按规则排序1. 自定义比较函数返回 bool;2. 逻辑是 “前元素是否应在了你元素前面”

四、结尾:以后操控 list 小火车再也不迷路!

这篇笔记把当初的test15test20全改成了 “看得懂” 的函数名,加了详细注释,还把踩过的坑(比如排序函数返回值错误、去重前没排序)标了出来。

list 就像一列 “双向链表小火车”,核心优势是中间插删高效(O (1)),适合需要频繁调整中间元素的场景。记住 “构造搭骨架、增删上下客、排序整顺序、去重清重复” 的口诀,下次操控 list 再也不用对着代码发呆啦!

下面是我学习list时的练习源码,感兴趣的可以自行取用,欢迎交流学习。

头文件

#pragma once
#include <string>
#include <iostream>
#include <math.h>
#include <ctime>
#include <list> //使用队列容器需要的头文件
using namespace std;/*声明函数方法
*/
void test15();
void test16();
void test17();
void test18();
void test19();
/*list练习题目1,利用list将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高。2,排序规则,按照年龄进行升序,如果年龄相同按照身高进行降序。
*/
void test20();

源文件

#include "list双向链表容器.h"/*函数方法定义实现
*//*list练习题目1,利用list将Person自定义数据类型进行排序,Person中属性有姓名、年龄、身高。2,排序规则,按照年龄进行升序,如果年龄相同按照身高进行降序。
*/
class Person {
public:string name;int age;double height;public:Person(string name, int age, double height) {this->name = name;this->age = age;this->height = height;}
};
//自定义排序  bug代码
//Person cmp2(const Person& p1, const Person& p2) {
//	if (p1.age < p2.age) {
//		return p1;
//	}
//	else if (p1.age == p2.age) {
//		if (p1.height > p2.height) {
//			return p1;
//		}
//	}
//}
//修改后的自定义排序
bool cmp2(const Person& p1, const Person& p2) {if (p1.age == p2.age) {return p1.height > p2.height;}else {return p1.age < p2.age;}
}
void test20() {//初始化几个人Person p1("张三", 11, 1.6);Person p2("李四", 11, 1.9);Person p3("王五", 21, 1.5);Person p4("江桂东", 21, 1.7);list<Person> lst;lst.push_back(p1);lst.push_back(p2);lst.push_back(p3);lst.push_back(p4);cout << "排序前:" << endl;for (Person p : lst) {cout << p.age << " " << p.name << " " << p.height << endl;}cout << endl << endl;lst.sort(cmp2);cout << "排序后:" << endl;for (Person p : lst) {cout << p.age << " " << p.name << " " << p.height << endl;}
}//自定义排序
bool cmp(const int& v1, const int& v2) {return v1 > v2;
}void test19() {list<int> lst1 = { 11,2,5,3,1,9,9,9 };lst1.unique();list<int> lst2(lst1);cout << "lst1初始排序 :";for (auto i = lst1.begin(); i != lst1.end(); i++) {cout << *i << " ";}cout << endl;//升序排序 从小到大 1 2 3 4 5 6...lst1.sort();cout << "lst1升序排序 :";for (auto i = lst1.begin(); i != lst1.end(); i++) {cout << *i << " ";}cout << endl;lst1.reverse();cout << "lst1.reverse()降序排序 :";for (auto i = lst1.begin(); i != lst1.end(); i++) {cout << *i << " ";}cout << endl;//自定义降序排序lst1.sort(cmp);cout << "lst1自定义降序排序:";for (auto i = lst1.begin(); i != lst1.end(); i++) {cout << *i << " ";}cout << endl;lst2.sort(cmp);cout << "lst2:";for (auto i = lst2.begin(); i != lst2.end(); i++) {cout << *i << " ";}cout << endl;}void test18() {list<int> lst = { 1,2,3,4,5};//删除指定位置元素lst.erase(lst.begin());//删除某个区域的数据list<int>::iterator it = lst.end();it--;it--;lst.erase(lst.begin(), it);for (auto i = lst.begin(); i != lst.end(); i++) {cout << *i << " ";}
}void test17() {list<int> lst = { 1,2,3 };//在指定位置插入一个固定的元素lst.insert(++lst.begin(), 100);//在指定位置,插入n个相同元素lst.insert(lst.begin(), 5, 200);list<int> lst2(2, 300);//把lst2的所有元素插入到lst的头部lst.insert(lst.begin(), lst2.begin(), lst2.end());for (auto i = lst.begin(); i != lst.end(); i++) {cout << *i << " ";}cout << endl;
}void test16() {list<int> lst = { 1,2,3,4,5 };//在1的前面插入一个100lst.push_front(100);//删除链表头部的一个元素100lst.pop_front();//在5的后面插入一个66 尾插法lst.push_back(66);//删除链表尾部的一个元素lst.pop_back();cout <<"lst.size():" << lst.size() << endl;if (lst.empty()) {cout << "lst为空" << endl;}else {cout << "lst不为空" << endl;}//不用autofor (list<int>::iterator it = lst.begin(); it != lst.end(); it++) {cout << *it << " ";}cout << endl;//使用autofor (auto i = lst.begin(); i != lst.end(); i++) {cout << *i << " ";}cout << endl;
}void test15() {//默认构造函数list<int> lst1;//带参构造函数list<int> lst2(10);	//链表长度为10个空元素//拷贝构造函数list<int> lst3(lst2);//指定长度和默认值list<int> lst4(10, 22);//迭代器的方式list<int> lst5(lst4.begin(), lst4.end());//赋值函数(重载“=”)list<int> lst6 = lst5;
}

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

相关文章:

  • Visual Studio Code (VS Code) 官方下载渠道
  • 网站制作的相关术语有哪些建一个网站多少钱
  • 企业网站建设属于什么费用搜索引擎优化的英文缩写
  • 告别“凭感觉”告警,金仓数据库替换MongoDB让运维更精准
  • 机器学习从业者大语言模型微调指南
  • Neo4j图数据库:简述增删改查
  • Mac版Color Folder v3.8安装教程(附dmg文件安装步骤和搜索关键词)
  • 金仓KES MongoDB兼容性深度解析与实践
  • Fiddler抓包实战教程 从安装配置到代理设置,详解Fiddler使用方法与调试技巧(HTTPHTTPS全面指南)
  • 对电子商务网站建设的感想4399网页游戏大全
  • 珠海专业医疗网站建设请人做ppt的网站
  • 印团网网站是哪家做的平面设计年终总结
  • 【u-boot】u-boot的I2C驱动框架剖析
  • JFrog vs Nexus vs Hadess,制品管理工具一文纵评
  • 【Docker】容器常用命令
  • Linux《Socket编程UDP》
  • Java IO 流进阶:Buffer 与 Channel 核心概念解析及与传统 IO 的本质区别
  • 【Linux基础开发工具 (一)】详解Linux软件生态与包管理器:从yum / apt原理到镜像源实战
  • 镇江网站营销推广电商怎么做如何从零开始视频
  • opencv 学习: 01 初识图片处理
  • 从 Wot UI 出发谈 VSCode 插件的自动化发布
  • Rust专项——用 Weak 打破引用环:树与图结构实战
  • c#调Lua返回个字符串
  • 单元测试(JUnit、Mockito、PowerMock )
  • 不只是语法糖:解构作为 Rust 安全与清晰度的基石
  • 企业微信消息群发助手(企业微信自建应用)winform.netcore实现(详细配置)
  • 基于Vue的教育学习网站04y8688l(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
  • 移动端网站生成器中国电商平台排行榜前100
  • Excel正则表达式.获取字符
  • K8s 资源管理与操作