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 5
    cout << endl;

    // 场景1:车头加乘客(push_front):在1前面加100
    train.push_front(100);
    cout << "车头加100后:";
    for (auto car : train) cout << car << " "; // 输出:100 1 2 3 4 5
    cout << endl;

    // 场景2:车头卸乘客(pop_front):把100卸掉
    train.pop_front();
    cout << "车头卸100后:";
    for (auto car : train) cout << car << " "; // 输出:1 2 3 4 5
    cout << endl;

    // 场景3:车尾加乘客(push_back):在5后面加66
    train.push_back(66);
    cout << "车尾加66后:";
    for (auto car : train) cout << car << " "; // 输出:1 2 3 4 5 66
    cout << endl;

    // 场景4:车尾卸乘客(pop_back):把66卸掉
    train.pop_back();
    cout << "车尾卸66后:";
    for (auto car : train) cout << car << " "; // 输出:1 2 3 4 5
    cout << endl;

    // 场景5:查看小火车状态
    cout << "小火车当前长度:" << train.size() << endl; // 输出:5
    if (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 3
    cout << 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 3
    cout << 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 3
    cout << endl;

    // 场景3:插入另一个list的所有元素(把lst2的车厢插车头)
    list<int> lst2(2, 300); // lst2:300、300
    train.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 5
    cout << endl;

    // 场景1:删除指定位置的单个元素(删除第一节车厢1)
    train.erase(train.begin());
    cout << "删除第一节车厢后:";
    for (auto car : train) cout << car << " "; // 输出:2 3 4 5
    cout << endl;

    // 场景2:删除指定范围的元素(删除2和3,保留4和5)
    auto it_start = train.begin();       // 指向2
    auto 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 5
    cout << 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 9
    cout << 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 11
    cout << endl;

    // 场景3:反转实现降序(先升序再反转)
    sorted_train.reverse();
    cout << "反转实现降序:";
    for (auto car : sorted_train) cout << car << " "; // 输出:11 9 5 3 2 1
    cout << endl;

    // 场景4:自定义降序排序(直接用自定义规则)
    train.sort(DescSort);
    cout << "自定义降序排序后:";
    for (auto car : train) cout << car << " "; // 输出:11 9 5 3 2 1
    cout << 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的前面插入一个100
	lst.push_front(100);
	//删除链表头部的一个元素100
	lst.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;
	}

	//不用auto
	for (list<int>::iterator it = lst.begin(); it != lst.end(); it++) {
		cout << *it << " ";
	}
	cout << endl;

	//使用auto
	for (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;
}
相关推荐
Madison-No72 小时前
【C++】关于list的使用&&底层实现
数据结构·c++·stl·list·模拟实现
2301_796512522 小时前
Rust编程学习 - 如何学习有关函数和闭包的高级特性,这包括函数指针以及返回闭包
服务器·学习·rust
LBuffer2 小时前
破解入门学习笔记题三十四
java·笔记·学习
_pass_2 小时前
flask 框架的ORM 学习及应用
学习·flask·orm
再睡一夏就好3 小时前
【C++闯关笔记】unordered_map与unordered_set的底层:哈希表(哈希桶)
开发语言·c++·笔记·学习·哈希算法·散列表
mjhcsp3 小时前
C++ 贪心算法(Greedy Algorithm)详解:从思想到实战
c++·ios·贪心算法
potato_15543 小时前
现代C++核心特性——内存篇
开发语言·c++·学习
沐怡旸3 小时前
【穿越Effective C++】条款13:以对象管理资源——RAII原则的基石
c++·面试
摇滚侠3 小时前
Spring Boot3零基础教程,为什么有Reactive-Stream 规范,响应式编程,笔记101
java·spring boot·笔记