序列式容器:list 双向链表的特性与用法

序列式容器:list 双向链表的特性与用法

在C++ STL的序列式容器家族中,list 是与 vector 齐名却风格迥异的核心成员。如果说 vector 是"动态数组",主打随机访问高效,那么 list 就是"双向链表",凭借任意位置插入/删除的高效性,成为解决特定场景问题的"利器"。

很多开发者在日常开发中,习惯于优先使用 vector,却对 list 的特性和适用场景一知半解,导致在需要频繁插入/删除元素的场景中,写出性能低下的代码;也有不少初学者被 list 的迭代器用法、内存模型搞得晕头转向。

今天这篇博客,我们就来全面拆解 list 双向链表------从底层原理、核心特性,到基础用法、进阶实操,再到避坑指南和场景对比,用通俗的讲解+可直接运行的示例代码,让你真正吃透 list,知道"什么时候用、怎么用、怎么用得高效",做到灵活运用、规避隐患。

一、list 是什么?核心定位与底层原理

list 是 STL 标准模板库中的序列式容器,底层基于双向循环链表实现(不同编译器细节略有差异,但核心是双向链表),本质是封装了双向链表的模板类。

与 vector 的"连续内存"不同,list 的每个元素(称为"节点")都独立存储在内存中,节点之间通过两个指针(前驱指针 prev、后继指针 next)相互连接,形成双向链表结构,最后一个节点的 next 指针指向第一个节点,构成循环(部分实现为非循环,但核心逻辑一致)。

list 的底层节点结构(简化理解)

我们可以用一个简单的结构体,直观理解 list 节点的底层实现(实际 STL 实现更复杂,包含分配器等,但核心逻辑不变):

cpp 复制代码
// list 节点简化结构(双向链表节点)
template <typename T>
struct ListNode {
    T data;          // 节点存储的数据
    ListNode* prev;  // 指向前驱节点的指针
    ListNode* next;  // 指向后继节点的指针
    
    // 构造函数
    ListNode(const T& val) : data(val), prev(nullptr), next(nullptr) {}
};

// list 本质是封装了节点的指针(头指针/尾指针)和节点个数
template <typename T>
struct List {
    ListNode<T>* head;  // 头节点指针
    ListNode<T>* tail;  // 尾节点指针
    size_t size;        // 节点个数(元素个数)
    // ... 其他成员函数(插入、删除、遍历等)
};

这种双向链表结构,决定了 list 的核心特性------不支持随机访问,但支持任意位置高效插入/删除,这也是它与 vector 最本质的区别。

list 的核心特性(与 vector 对比,一目了然)

为了让大家快速区分 list 和 vector 的差异,这里先做一个核心对比,后续会逐一展开讲解 list 的特性细节:

特性 list(双向链表) vector(动态数组)
内存结构 非连续内存(节点独立,指针连接) 连续内存(动态数组)
随机访问 不支持(只能通过迭代器遍历,O(n)) 支持(下标访问,O(1))
插入/删除(任意位置) 高效(仅修改指针,O(1)) 低效(需移动元素,O(n))
插入/删除(尾部) 高效(O(1)) 高效(无扩容时 O(1),扩容时 O(n))
内存开销 较大(每个节点额外存储两个指针) 较小(仅存储元素,无额外指针开销)
迭代器特性 双向迭代器(支持 ++、--,不支持 +n、-n) 随机访问迭代器(支持 ++、--、+n、-n)
扩容机制 无扩容(节点独立分配,按需新增/释放) 自动扩容(2倍/1.5倍,重新分配内存+拷贝元素)

二、list 核心特性详解(为什么用、什么时候用)

结合底层双向链表结构,我们逐一拆解 list 的核心特性,搞懂它的优势、劣势,以及适用场景,避免盲目使用。

1. 核心优势:任意位置插入/删除高效(O(1))

这是 list 最核心、最独特的优势,也是它存在的核心价值。由于 list 的节点是独立存储的,插入/删除元素时,无需移动其他元素,只需修改插入/删除位置前后节点的 prev 和 next 指针,就能完成操作,时间复杂度为 O(1)。

举个直观的例子:如果有一个包含10000个元素的容器,要在第5000个位置插入一个元素:

  • vector:需要移动第5000到第9999个元素(共5000个元素),时间复杂度 O(n),数据量越大,效率越低;

  • list:只需找到第5000个节点,修改它前后节点的指针,插入新节点,时间复杂度 O(1)(找到节点的时间除外,若已找到节点,插入本身是 O(1))。

注意:这里的 O(1) 是"插入/删除操作本身"的时间复杂度,若需要先找到插入/删除的位置(如通过遍历找到第n个元素),则遍历的时间复杂度是 O(n),但插入/删除操作本身依然是 O(1)。

2. 核心劣势:不支持随机访问,遍历效率低

由于 list 的内存是非连续的,节点之间通过指针连接,因此无法像 vector 那样通过下标 [ ] 直接访问元素。要访问 list 中的第n个元素,只能从头部(或尾部)开始,通过迭代器一步步遍历,直到找到目标节点,时间复杂度为 O(n)。

这也是 list 最大的劣势------如果你的场景需要频繁访问元素(如根据下标读取、查找元素),使用 list 会导致性能严重下降,此时优先选择 vector。

3. 其他特性:无扩容、内存开销大、迭代器稳定

  • 无扩容机制:list 的节点是按需分配、独立释放的,插入元素时,直接新建一个节点,修改指针即可;删除元素时,释放该节点的内存,无需像 vector 那样整体扩容、拷贝元素,因此不会出现 vector 那样的扩容性能损耗。

  • 内存开销大:每个节点除了存储元素本身,还需要额外存储两个指针(prev 和 next),用于连接前后节点。如果存储的元素是占用内存较小的类型(如 int、char),指针的额外开销会非常明显(比如 int 占4字节,两个指针占8字节,额外开销100%)。

  • 迭代器稳定(除了被删除节点的迭代器) :与 vector 不同,list 插入/删除元素时,不会影响其他节点的迭代器(因为无需移动元素,也无需重新分配内存)。只有指向"被删除节点"的迭代器会失效,其他迭代器依然有效,这也是 list 的一个重要优势。

list 的适用场景(精准使用,避免踩坑)

结合上述特性,list 适合以下场景,其他场景优先选择 vector 或其他容器:

  • 需要频繁在任意位置插入/删除元素(如日志插入、队列操作、频繁修改的列表);

  • 数据量不确定,且插入/删除操作远多于访问操作;

  • 需要保证迭代器稳定(插入/删除后,其他迭代器依然可用)的场景;

  • 无需随机访问元素,仅需遍历元素的场景(如遍历输出、批量处理)。

三、list 基础用法:从初始化到常用操作(附示例)

在使用 list 之前,需包含对应的头文件 #include <list>,并建议使用 std 命名空间(或显式调用 std::list)。下面从初始化开始,逐一讲解 list 的核心基础操作,搭配可直接复制运行的示例代码,上手更高效。

1. list 的初始化(5种常用方式)

list 的初始化方式与 vector 类似,但由于其内存结构不同,初始化时无需考虑容量,只需关注元素个数和初始值即可。

cpp 复制代码
#include <iostream>
#include <list>
using namespace std;

int main() {
    // 方式1:默认初始化,空list,元素个数为0
    list<int> l1;
    
    // 方式2:初始化n个元素,每个元素默认值为0(内置类型),自定义类型需有默认构造
    list<int> l2(5); // l2: [0, 0, 0, 0, 0]
    
    // 方式3:初始化n个元素,每个元素赋值为指定值(推荐,高效)
    list<int> l3(5, 3); // l3: [3, 3, 3, 3, 3]
    
    // 方式4:通过已有数组/vector/list初始化(拷贝构造)
    int arr[] = {1, 2, 3, 4, 5};
    list<int> l4(arr, arr + 5); // l4: [1, 2, 3, 4, 5](左闭右开区间)
    list<int> l5(l4); // 拷贝l4,l5与l4元素完全一致
    
    // 方式5:通过初始化列表初始化(C++11及以上支持,最简洁)
    list<int> l6 = {1, 2, 3, 4, 5};
    list<int> l7{10, 20, 30}; // 省略等号,同样有效
    
    // 打印测试(后续会讲遍历方式,此处暂用简单遍历)
    for (auto it = l7.begin(); it != l7.end(); ++it) {
        cout << *it << " ";
    }
    return 0;
}

2. list 核心成员函数(必学,重点区分与 vector 的差异)

list 的成员函数与 vector 有部分重合,但由于其特性不同,部分函数的用法和效率有明显差异,重点掌握以下高频函数,避免混淆。

(1)访问元素(2种方式,无下标访问)

注意:list 不支持下标 [ ] 访问,也不支持 at() 函数访问(因为无法随机访问),只能访问头部和尾部元素,或通过迭代器遍历访问。

  • front():访问第一个元素,时间复杂度 O(1)(高频使用);

  • back():访问最后一个元素,时间复杂度 O(1)(高频使用)。

cpp 复制代码
list<int> l = {1, 2, 3, 4, 5};
cout << l.front() << endl; // 输出1(第一个元素)
cout << l.back() << endl;  // 输出5(最后一个元素)

// 错误示例:list不支持下标访问和at()访问
// cout << l[0] << endl;     // 编译报错
// cout << l.at(2) << endl;  // 编译报错
(2)插入元素(3种高频方式,重点掌握)

list 的插入函数是其核心优势,支持头部、尾部、任意位置插入,操作高效,时间复杂度 O(1)(插入本身,找到位置除外)。

  • push_back():在 list 尾部插入一个元素,时间复杂度 O(1)(高频使用);

  • push_front():在 list 头部插入一个元素,时间复杂度 O(1)(vector 不支持此函数,或支持但效率极低);

  • insert():在指定位置插入元素/元素组,时间复杂度 O(1)(插入本身),需配合迭代器指定位置。

cpp 复制代码
list<int> l = {1, 2, 3};

// 1. push_back:尾部插入
l.push_back(4); // l: [1, 2, 3, 4]
l.push_back(5); // l: [1, 2, 3, 4, 5]

// 2. push_front:头部插入(vector无此函数,list核心优势)
l.push_front(0); // l: [0, 1, 2, 3, 4, 5]
l.push_front(-1); // l: [-1, 0, 1, 2, 3, 4, 5]

// 3. insert:指定位置插入(需配合迭代器)
// 步骤1:找到插入位置(遍历找到第3个元素,下标2,迭代器指向1)
auto it = l.begin();
++it; // 指向0
++it; // 指向1(第3个元素)

// 插入单个元素:在it位置插入10
l.insert(it, 10); // l: [-1, 0, 10, 1, 2, 3, 4, 5]

// 插入多个相同元素:在it位置插入2个20(it仍指向1,插入后1在20后面)
l.insert(it, 2, 20); // l: [-1, 0, 10, 20, 20, 1, 2, 3, 4, 5]

// 插入另一个list的元素:在尾部插入l2的所有元素
list<int> l2 = {6, 7, 8};
l.insert(l.end(), l2.begin(), l2.end()); // 尾部插入,l: [-1,0,10,20,20,1,2,3,4,5,6,7,8]
(3)删除元素(4种高频方式,高效且稳定)

list 的删除函数同样高效,支持头部、尾部、任意位置、指定值删除,且删除后除了指向被删除节点的迭代器,其他迭代器均有效。

  • pop_back():删除尾部最后一个元素,时间复杂度 O(1)(高频使用);

  • pop_front():删除头部第一个元素,时间复杂度 O(1)(vector 不支持此函数);

  • erase():删除指定位置/指定区间的元素,时间复杂度 O(1)(删除本身);

  • remove(val):删除 list 中所有值等于 val 的元素,时间复杂度 O(n)(需遍历所有节点)。

cpp 复制代码
list<int> l = {-1, 0, 10, 20, 20, 1, 2, 3, 4, 5};

// 1. pop_back:删除尾部元素
l.pop_back(); // l: [-1, 0, 10, 20, 20, 1, 2, 3, 4]

// 2. pop_front:删除头部元素
l.pop_front(); // l: [0, 10, 20, 20, 1, 2, 3, 4]

// 3. erase:删除指定位置元素
auto it = l.begin();
++it; // 指向10
l.erase(it); // 删除10,l: [0, 20, 20, 1, 2, 3, 4]
// erase:删除指定区间元素(从it到l.end()-1,左闭右开)
it = l.begin() + 1; // 错误!list迭代器不支持 +n,只能一步步遍历
it = l.begin();
++it; // 指向第一个20
auto it2 = l.end();
--it2; // 指向4
l.erase(it, it2); // 删除[20, 20, 1, 2, 3],l: [0, 4]

// 4. remove:删除所有值为20的元素(上面示例中已删除,此处换个例子)
list<int> l2 = {20, 10, 20, 30, 20, 40};
l2.remove(20); // 删除所有20,l2: [10, 30, 40]

// 注意:erase删除后,被删除节点的迭代器失效,其他迭代器有效
it = l2.begin();
l2.erase(it); // it指向10,删除后it失效,不能再使用
// cout << *it << endl; // 错误,迭代器失效
(4)容量与大小相关(3个核心函数)

list 无容量(capacity)概念(因为无需扩容,节点按需分配),因此只有大小相关的函数,用法与 vector 类似。

  • size():返回 list 中当前元素的个数(实际存储的数据量),时间复杂度 O(1);

  • empty():判断 list 是否为空(size() == 0),返回 bool 值,高效,推荐使用;

  • resize(n, val):调整 list 的 size(元素个数),若 n > 当前 size,新增元素为 val;若 n < 当前 size,删除尾部多余元素(无容量,直接删除节点)。

cpp 复制代码
list<int> l = {1, 2, 3};
cout << l.size() << endl;  // 输出3(元素个数)
cout << l.empty() << endl; // 输出0(非空)

// resize:调整size为5,新增元素为10
l.resize(5, 10); // l: [1, 2, 3, 10, 10]
cout << l.size() << endl;  // 输出5

// resize:调整size为2,删除尾部3个元素
l.resize(2); // l: [1, 2]
cout << l.size() << endl;  // 输出2
(5)其他常用操作(高频)
  • swap(l2):交换两个 list 的元素、size,时间复杂度 O(1)(仅交换头指针、尾指针和 size,无需拷贝元素);

  • clear():清空 list 中所有元素,释放所有节点内存,size 变为 0(与 vector 的 clear() 不同,vector 不清空容量,list 无容量,直接释放所有节点);

  • reverse():反转 list 中的元素,时间复杂度 O(n)(仅修改所有节点的 prev 和 next 指针,无需移动元素,高效)。

cpp 复制代码
list<int> l1 = {1, 2, 3};
list<int> l2 = {4, 5, 6};

// swap:交换两个list
l1.swap(l2); // l1: [4,5,6], l2: [1,2,3]

// reverse:反转list
l1.reverse(); // l1: [6,5,4]

// clear:清空list
l2.clear();
cout << l2.size() << endl; // 输出0(元素清空,节点内存释放)

3. list 的遍历方式(3种常用方式,适配双向迭代器)

由于 list 的迭代器是双向迭代器(仅支持 ++、-- 操作,不支持 +n、-n 操作),因此遍历方式与 vector 略有差异,重点掌握前两种方式。

cpp 复制代码
#include <iostream>
#include <list>
using namespace std;

int main() {
    list<int> l = {1, 2, 3, 4, 5};
    
    // 方式1:迭代器遍历(最通用,适配list特性,推荐)
    // 普通迭代器(可读可写)
    list<int>::iterator it;
    for (it = l.begin(); it != l.end(); ++it) {
        cout << *it << " "; // *it 访问迭代器指向的元素
    }
    cout << endl;
    
    // 常量迭代器(只读,不允许修改元素)
    list<int>::const_iterator cit;
    for (cit = l.cbegin(); cit != l.cend(); ++cit) {
        cout << *cit << " ";
    }
    cout << endl;
    
    // 方式2:范围for循环(C++11及以上,简洁,无需关心迭代器,推荐)
    for (int val : l) {
        cout << val << " "; // val是l中元素的拷贝,修改val不影响原list
    }
    cout << endl;
    // 范围for循环(修改原元素,需用引用)
    for (int& val : l) {
        val *= 2; // 修改原list中的元素,l变为[2,4,6,8,10]
    }
    
    // 方式3:反向迭代器(从尾部到头部遍历)
    list<int>::reverse_iterator rit;
    for (rit = l.rbegin(); rit != l.rend(); ++rit) {
        cout << *rit << " "; // 输出:10 8 6 4 2
    }
    cout << endl;
    
    // 错误示例:list迭代器不支持 +n 操作,无法直接跳转到指定位置
    // auto it2 = l.begin() + 2; // 编译报错,只能一步步遍历
    auto it2 = l.begin();
    ++it2;
    ++it2; // 遍历两次,指向6(正确方式)
    cout << *it2 << endl; // 输出6
    
    return 0;
}

四、list 进阶用法:排序、去重与自定义类型

list 虽然不支持随机访问,但依然可以与 STL 算法协同使用,同时也支持存储自定义类型,实现排序、去重等高级操作。需要注意的是,list 有自己的成员函数 sort(),效率比 STL 通用算法 sort() 更高。

1. list 排序:优先使用成员函数 sort()(高效)

STL 通用算法 sort() 要求容器支持随机访问迭代器(如 vector),而 list 的迭代器是双向迭代器,因此无法使用 STL 通用算法 sort(),只能使用 list 自带的成员函数 sort()。

list 的成员函数 sort() 是基于双向链表优化的排序算法,时间复杂度 O(n log n),效率比通用算法更高,支持默认升序和自定义排序规则。

cpp 复制代码
#include <iostream>
#include <list>
// #include <algorithm> // 无需包含,list自带sort()
using namespace std;

// 自定义排序规则:降序排序(函数形式)
bool cmpDesc(int a, int b) {
    return a > b; // a > b 时,a排在b前面,即降序
}

int main() {
    list<int> l = {3, 1, 4, 1, 5, 9, 2, 6};
    
    // 1. 默认升序排序(成员函数 sort())
    l.sort(); // 排序后:1 1 2 3 4 5 6 9
    for (int val : l) {
        cout << val << " ";
    }
    cout << endl;
    
    // 2. 自定义降序排序(传入排序函数)
    l.sort(cmpDesc); // 排序后:9 6 5 4 3 2 1 1
    for (int val : l) {
        cout << val << " ";
    }
    cout << endl;
    
    // 错误示例:无法使用STL通用算法sort()
    // sort(l.begin(), l.end()); // 编译报错,因为list迭代器不是随机访问迭代器
    
    return 0;
}

2. list 去重:配合 sort() + unique()(成员函数)

list 的去重逻辑与 vector 类似:先排序(让重复元素相邻),再去重。但 list 同样有自己的成员函数 unique(),无需使用 STL 通用算法 unique(),用法更简洁,效率更高。

cpp 复制代码
#include <iostream>
#include <list>
using namespace std;

int main() {
    list<int> l = {3, 1, 4, 1, 5, 9, 5, 3};
    
    // 步骤1:先排序,让重复元素相邻(必须先排序,否则去重无效)
    l.sort(); // 排序后:1 1 3 3 4 5 5 9
    
    // 步骤2:使用list成员函数unique()去重(删除相邻重复元素)
    l.unique(); // 去重后:1 3 4 5 9
    
    // 输出去重后的结果
    for (int val : l) {
        cout << val << " ";
    }
    
    return 0;
}

注意:list 的 unique() 函数仅删除相邻的重复元素,因此必须先排序,否则无法彻底去重(比如未排序的 list 中,重复元素不相邻,unique() 无法识别)。

3. list 存储自定义类型(结构体/类)

list 支持存储任意自定义类型(结构体、类),用法与 vector 类似,只需确保自定义类型有默认构造函数(如果手动定义了带参构造,需显式定义默认构造),同时可以自定义排序规则,适配 list 的 sort() 函数。

cpp 复制代码
#include <iostream>
#include <list>
#include <string>
using namespace std;

// 自定义结构体:学生信息
struct Student {
    string name;
    int age;
    int score;
    
    // 默认构造函数(必须有,否则list<Student> l(5)会报错)
    Student() : name(""), age(0), score(0) {}
    // 带参构造函数(方便初始化)
    Student(string n, int a, int s) : name(n), age(a), score(s) {}
};

// 自定义排序规则:按成绩降序排序(用于list.sort())
bool cmpStudent(const Student& a, const Student& b) {
    return a.score > b.score;
}

int main() {
    // 初始化存储自定义结构体的list
    list<Student> students;
    students.push_back(Student("张三", 18, 90));
    students.push_back(Student("李四", 19, 85));
    students.push_back(Student("王五", 18, 95));
    students.push_back(Student("赵六", 19, 88));
    
    // 遍历输出(用const引用,避免拷贝,提高效率)
    cout << "排序前:" << endl;
    for (const auto& stu : students) {
        cout << "姓名:" << stu.name << ",年龄:" << stu.age << ",成绩:" << stu.score << endl;
    }
    
    // 按成绩降序排序(使用自定义排序规则)
    students.sort(cmpStudent);
    
    // 遍历输出排序后结果
    cout << "\n排序后:" << endl;
    for (const auto& stu : students) {
        cout << "姓名:" << stu.name << ",年龄:" << stu.age << ",成绩:" << stu.score << endl;
    }
    
    return 0;
}

五、list 使用避坑指南(高频错误总结)

结合 list 的特性和用法,总结以下5个高频错误,以及对应的解决方案,帮你规避隐患,写出更安全、高效的代码。

1. 错误使用下标 [ ] 或 at() 访问元素

错误:习惯性地用 vector 的方式,通过下标 [ ] 或 at() 访问 list 元素,导致编译报错。

原因:list 是双向链表,内存非连续,不支持随机访问,因此不提供下标访问和 at() 函数。

解决方案:

  • 访问头部/尾部元素,用 front() / back();

  • 访问任意位置元素,通过迭代器遍历找到目标节点;

  • 若频繁需要随机访问,放弃 list,改用 vector。

2. 用 STL 通用算法 sort() 排序 list

错误:调用 STL 通用算法 sort(l.begin(), l.end()) 对 list 排序,导致编译报错。

原因:STL 通用算法 sort() 要求容器支持随机访问迭代器,而 list 的迭代器是双向迭代器,不满足要求。

解决方案:使用 list 自带的成员函数 sort(),支持默认排序和自定义排序规则,效率更高。

3. 迭代器失效后继续使用

错误:删除 list 中的节点后,继续使用指向该节点的迭代器,导致程序崩溃或未定义行为。

原因:list 删除节点后,被删除节点的迭代器会失效(节点内存被释放),其他节点的迭代器依然有效。

解决方案:

  • 删除节点后,避免使用指向该节点的迭代器;

  • erase() 函数会返回指向被删除节点下一个位置的迭代器,可利用返回值更新迭代器(如 it = l.erase(it))。

cpp 复制代码
list<int> l = {1, 2, 3, 4, 5};
auto it = l.begin();
++it; // 指向2

// 正确方式:利用erase()的返回值更新迭代器
it = l.erase(it); // 删除2,it指向3(下一个节点)
cout << *it << endl; // 输出3(正常使用)

// 错误方式:删除后继续使用原迭代器
// l.erase(it);
// cout << *it << endl; // 错误,迭代器失效

4. 盲目使用 list,忽略性能差异

错误:无论场景如何,都优先使用 list,导致在频繁访问元素的场景中,性能严重下降。

原因:list 的遍历效率低(O(n)),不适合频繁访问元素的场景,盲目使用会导致性能瓶颈。

解决方案:根据场景选择容器,遵循"频繁访问用 vector,频繁插入删除用 list"的原则,不确定时,优先测试两种容器的性能。

5. 存储指针类型时,未释放内存(内存泄漏)

错误:list 存储指针类型(如 list<int*>)时,只 clear() 清空元素,未释放指针指向的内存,导致内存泄漏。

原因:list 的 clear() 函数会释放节点内存,但不会释放指针指向的内存(指针本身只是一个地址,节点存储的是地址,不是实际数据)。

解决方案:清空 list 前,遍历所有元素,手动释放指针指向的内存;或使用智能指针(shared_ptr、unique_ptr),避免手动管理内存。

cpp 复制代码
// 错误示例:内存泄漏
list<int*> l;
l.push_back(new int(1));
l.push_back(new int(2));
l.clear(); // 仅释放节点内存,未释放new分配的内存,导致内存泄漏

// 正确示例:手动释放内存
list<int*> l;
l.push_back(new int(1));
l.push_back(new int(2));
// 遍历释放内存
for (int* p : l) {
    delete p; // 释放指针指向的内存
    p = nullptr; // 避免野指针
}
l.clear(); // 再清空元素

六、总结:list 的核心价值与使用建议

list 作为 STL 序列式容器的重要成员,其核心价值在于"任意位置插入/删除的高效性",填补了 vector 在频繁插入/删除场景中的性能短板。它的底层双向链表结构,决定了它"不支持随机访问、内存开销大,但迭代器稳定、无扩容损耗"的特性。

很多开发者对 list 的使用存在误区------要么盲目使用,要么完全不用。其实,只要找准适用场景,list 就能发挥巨大作用,成为解决问题的"利器"。最后,给大家几点使用建议,帮助你更好地运用 list:

  1. 场景优先:频繁在任意位置插入/删除,用 list;频繁访问元素,用 vector;两者都有需求,可考虑 deque(双端队列);

  2. 迭代器用法:记住 list 迭代器是双向迭代器,不支持 +n、-n,只能通过 ++、-- 遍历;删除节点后,及时更新迭代器,避免失效;

  3. 排序去重:优先使用 list 自带的 sort() 和 unique() 成员函数,不使用 STL 通用算法,效率更高、更适配;

  4. 内存管理:存储指针类型时,务必手动释放内存,或使用智能指针,避免内存泄漏;

  5. 性能权衡:如果插入/删除操作并不频繁,且需要偶尔访问元素,优先选择 vector(内存开销小,遍历效率更高)。

list 看似简单,但想要真正用好,需要深入理解其底层结构和特性,结合场景合理选择。希望本文的全面解析,能帮助你彻底吃透 list,在笔试面试和实际开发中,都能灵活运用这个 STL"利器",写出更高效、更安全的代码。

最后,附上本文所有核心示例代码汇总,方便大家复制测试、快速上手:

cpp 复制代码
// 本文核心示例代码汇总
#include <iostream>
#include <list>
#include <string>
using namespace std;

// 1. 初始化与基础操作
void testInitAndBase() {
    list<int> l1;
    list<int> l2(5, 3);
    list<int> l3 = {1, 2, 3, 4, 5};
    
    l3.push_back(6);
    l3.push_front(0);
    auto it = l3.begin();
    ++it;
    l3.insert(it, 10);
    l3.pop_back();
    l3.pop_front();
    
    for (int val : l3) {
        cout << val << " ";
    }
    cout << endl;
}

// 2. 排序与去重
void testSortAndUnique() {
    list<int> l = {3, 1, 4, 1, 5, 9, 5, 3};
    // 排序
    l.sort(); // 升序
    l.sort([](int a, int b) { return a > b; }); // 降序(lambda表达式)
    // 去重
    l.unique();
    for (int val : l) {
        cout << val << " ";
    }
    cout << endl;
}

// 3. 自定义结构体与排序
struct Student {
    string name;
    int score;
    Student() : name(""), score(0) {}
    Student(string n, int s) : name(n), score(s) {}
};

void testCustomType() {
    list<Student> students = {{"张三", 90}, {"李四", 85}, {"王五", 95}};
    // 按成绩降序排序
    students.sort([](const Student& a, const Student& b) {
        return a.score > b.score;
    });
    for (const auto& stu : students) {
        cout << stu.name << ":" << stu.score << endl;
    }
}

// 4. 指针类型list,避免内存泄漏
void testPointer() {
    list<int*> l;
    l.push_back(new int(1));
    l.push_back(new int(2));
    // 遍历释放内存
    for (int* p : l) {
        delete p;
        p = nullptr;
    }
    l.clear();
    cout << "内存释放完成" << endl;
}

int main() {
    testInitAndBase();
    testSortAndUnique();
    testCustomType();
    testPointer();
    return 0;
}
相关推荐
CHANG_THE_WORLD1 小时前
深入理解指向数组的指针以及寻址运算
c语言·开发语言
洛_尘2 小时前
测试6:自动化测试--概念篇(JAVA)
java·开发语言·测试
Zzz 小生2 小时前
LangChain Messages:消息使用完全指南
数据库·windows·microsoft
wjs20242 小时前
Lua 字符串处理详解
开发语言
014-code2 小时前
ESLint 详解
前端·eslint
程序员敲代码吗2 小时前
Qt Quick中QML与C++交互详解及场景切换实现
c++·qt·交互
不吃鱼的猫7482 小时前
【ffplay 源码解析系列】01-开篇-ffplay整体架构与启动流程
c++·架构·ffmpeg·音视频
GISer_Jing2 小时前
前端营销I(From AIGC)
前端·aigc·ai编程
明月_清风2 小时前
向 Native 借力:深度拆解 SIMD 加速与 Node.js 异步原生解析
前端·json