C++笔记——STL list

作者:报错小能手日期:2025/11/11

1. list 基本概念

什么是list?

双向链表:每个元素包含指向前后元素的指针,形成链式结构

核心特性(与vector/deque对比):

特性vectordequelist
随机访问✅ O(1)✅ O(1)❌ O(n)
头部插入删除❌ O(n)✅ O(1)✅ O(1)
尾部插入删除✅ O(1)✅ O(1)✅ O(1)
中间插入删除❌ O(n)❌ O(n)✅ O(1)
内存布局连续分段连续完全分散

2. list 的基本使用

头文件和声明:

cpp

#include <list> using namespace std;

// 创建方式(与其他容器类似) list<int> lst1; // 空list list<int> lst2(10); // 10个元素,默认值0 list<int> lst3(5, 100); // 5个元素,每个都是100 list<int> lst4 = {1, 2, 3, 4, 5}; // 初始化列表 list<int> lst5(lst4); // 拷贝构造


3. list 的特有操作

高效的任意位置操作:

cpp

void list_special_operations() { list<int> lst = {1, 3, 5};

// 任意位置高效插入(与vector/deque的最大区别!)
auto it = lst.begin();
advance(it, 1);              // 移动到第2个位置
lst.insert(it, 2);           // 在位置1插入2: {1,2,3,5} - O(1)!

// 任意位置高效删除
it = lst.begin();
advance(it, 2);              // 移动到第3个位置  
lst.erase(it);               // 删除3: {1,2,5} - O(1)!

// 头部尾部操作(与deque相同)
lst.push_front(0);           // 头部插入: {0,1,2,5}
lst.push_back(6);            // 尾部插入: {0,1,2,5,6}
lst.pop_front();             // 头部删除: {1,2,5,6}
lst.pop_back();              // 尾部删除: {1,2,5}

}


4. list 的容量管理

与vector/deque的对比:

cpp

void list_capacity() { list<int> lst = {1, 2, 3, 4, 5};

// 这些与其他容器相同:
cout << "大小: " << lst.size() << endl;        // 5
cout << "是否为空: " << lst.empty() << endl;   // false
lst.resize(10);         // 扩容到10个元素
lst.resize(3);          // 缩容到3个元素

// list特有的容量操作:
cout << "最大大小: " << lst.max_size() << endl;

// 但没有这些(因为链表不需要):
// lst.capacity();     // ❌ 
// lst.reserve(100);   // ❌
// lst.shrink_to_fit();// ❌

}


5. list 的元素访问

这是与vector/deque的最大区别!

cpp

void list_access() { list<int> lst = {10, 20, 30, 40, 50};

// 只能顺序访问,不能随机访问!
cout << lst.front() << endl;   // 10 - 头部元素 ✅
cout << lst.back() << endl;    // 50 - 尾部元素 ✅

// 这些都不行!
// cout << lst[2] << endl;     // ❌ 没有下标访问!
// cout << lst.at(2) << endl;  // ❌ 没有at()方法!
// int* data = lst.data();     // ❌ 没有data()方法!

// 访问中间元素的正确方式:
auto it = lst.begin();
advance(it, 2);               // 移动到第3个位置 - O(n)!
cout << *it << endl;          // 30

}

访问方式对比:

cpp

void access_comparison() { vector<int> vec = {1,2,3,4,5}; list<int> lst = {1,2,3,4,5};

// vector - 随机访问
cout << vec[2] << endl;        // ✅ O(1) - 直接计算地址

// list - 顺序访问  
auto it = lst.begin();
advance(it, 2);               // ✅ O(n) - 需要遍历节点
cout << *it << endl;

}


6. list 的修改操作

链表特有的高效操作:

cpp

void list_modifications() { list<int> lst = {1, 2, 3, 4, 5};

// 1. 拼接操作 - 链表特有!
list<int> other = {6, 7, 8};
lst.splice(lst.end(), other);  // 把other拼接到lst末尾
// lst: {1,2,3,4,5,6,7,8}, other: 空
// O(1)操作!只是修改指针

// 2. 元素移动 - 链表特有!
auto it = find(lst.begin(), lst.end(), 3);
lst.splice(lst.begin(), lst, it);  // 把3移动到开头
// lst: {3,1,2,4,5,6,7,8} - O(1)!

}


7. list 的迭代器

双向迭代器(不是随机访问!):

cpp

void list_iterators() { list<int> lst = {10, 20, 30, 40, 50};

// 正向遍历 ✅
for (auto it = lst.begin(); it != lst.end(); ++it) {
    cout << *it << " ";
}

// 反向遍历 ✅  
for (auto rit = lst.rbegin(); rit != lst.rend(); ++rit) {
    cout << *rit << " ";
}

// 这些都不行! ❌
auto it = lst.begin();
// cout << it[2] << endl;    // 没有随机访问
// it = it + 3;              // 没有算术运算
// if (it1 < it2) { }        // 没有比较位置

// 只能这样移动:
advance(it, 2);              // O(n) - 需要一步步走
// 或者:
++it; ++it;                  // 也是O(n)

}

迭代器稳定性(重要优势!):

cpp

void iterator_stability() { list<int> lst = {1, 2, 3, 4, 5}; auto it = lst.begin(); advance(it, 2); // 指向3

cout << "当前指向: " << *it << endl;  // 3

// list的迭代器在插入删除时不会失效!
lst.push_front(0);           // 头部插入
lst.push_back(6);            // 尾部插入
lst.insert(lst.begin(), 99); // 任意位置插入

cout << "插入后仍然指向: " << *it << endl;  // 还是3! ✅

// 即使删除其他元素,迭代器仍然有效
lst.erase(lst.begin());      // 删除头部
cout << "删除后仍然指向: " << *it << endl;  // 还是3! ✅

// 只有删除迭代器指向的元素时才会失效
// lst.erase(it);            // 此时it失效,不能再使用

}

这是list相比vector/deque的巨大优势!


8. list 的特有算法

成员函数版本的算法(比通用算法更高效):

cpp

void list_algorithms() { list<int> lst = {5, 2, 8, 2, 1, 2, 9, 3};

// 1. 排序 - 成员函数版本
lst.sort();                  // 原地排序: {1,2,2,2,3,5,8,9}
// 比std::sort更高效,因为专门为链表优化

// 2. 去重 - 需要先排序
lst.unique();                // 去除连续重复: {1,2,3,5,8,9}

// 3. 合并 - 链表特有操作
list<int> lst2 = {4, 6, 7};
lst2.sort();                 // 必须先排序
lst.merge(lst2);             // 合并两个有序链表
// lst: {1,2,3,4,5,6,7,8,9}, lst2: 空

// 4. 反转
lst.reverse();               // 反转链表: {9,8,7,6,5,4,3,2,1}

}

为什么需要成员函数版本?

cpp

void why_member_algorithms() { list<int> lst = {3, 1, 4, 2};

// 错误:std::sort需要随机访问迭代器
// sort(lst.begin(), lst.end());  // ❌ 编译错误!

// 正确:使用list的成员函数
lst.sort();  // ✅ 专门为链表实现的排序算法

}


9. list 的内部实现原理

节点结构:

cpp

// list的每个节点大致如下: struct ListNode { T data; // 存储的数据 ListNode* prev; // 指向前一个节点 ListNode* next; // 指向后一个节点 };

内存布局:

text

list: [头节点] ↔ [节点1] ↔ [节点2] ↔ [节点3] ↔ ... ↔ [尾节点] ↑ ↑ ↑ ↑ prev/next prev/next prev/next prev/next

这种设计的优势:

cpp

void list_advantages() { list<int> lst;

// 1. 任意位置插入删除都是O(1)
for (int i = 0; i < 1000; i++) {
    auto it = lst.begin();
    advance(it, i % (lst.size() + 1));
    lst.insert(it, i);  // 在任意位置插入 - 总是O(1)!
}

// 2. 不需要重新分配内存
// 每个新元素独立分配节点,不会导致已有元素移动

}


10. 性能特点

时间复杂度:

操作时间复杂度说明
头部插入删除O(1)修改指针
尾部插入删除O(1)修改指针
中间插入删除O(1)找到位置后修改指针
随机访问O(n)需要从头遍历
查找O(n)顺序查找

空间开销:

cpp

void space_overhead() { list<int> lst = {1, 2, 3};

// 每个元素都有额外开销:
// - 指向前一个节点的指针
// - 指向后一个节点的指针  
// - 内存分配开销(每个节点独立分配)

// 对于int类型,额外开销可能比数据本身还大!
// 在64位系统上:int(4字节) + 两个指针(16字节) = 20字节/元素

}


11. list 的典型应用场景

适合使用list的场景:

1. 频繁在任意位置插入删除

cpp

class TextEditor { private: list<char> text;

public: void insert(char c, size_t position) { auto it = text.begin(); advance(it, position); text.insert(it, c); // O(1)插入! }

void deleteChar(size_t position) {
    auto it = text.begin();
    advance(it, position);
    text.erase(it);      // O(1)删除!
}

void display() {
    for (char c : text) cout << c;
    cout << endl;
}

};

2. LRU缓存实现

cpp

class LRUCache { private: list<pair<int, int>> cache; // (key, value)对 unordered_map<int, list<pair<int, int>>::iterator> map; int capacity;

public: LRUCache(int cap) : capacity(cap) {}

int get(int key) {
    if (map.find(key) == map.end()) return -1;
    
    // 移动到头部(最近使用)
    cache.splice(cache.begin(), cache, map[key]);
    return map[key]->second;
}

void put(int key, int value) {
    if (map.find(key) != map.end()) {
        // 键已存在,更新值并移动到头部
        map[key]->second = value;
        cache.splice(cache.begin(), cache, map[key]);
    } else {
        // 新键,插入到头部
        if (cache.size() == capacity) {
            // 删除最久未使用的(尾部)
            int lastKey = cache.back().first;
            cache.pop_back();
            map.erase(lastKey);
        }
        cache.emplace_front(key, value);
        map[key] = cache.begin();
    }
}

};

3. 音乐播放列表

cpp

class Playlist { private: list<string> songs; list<string>::iterator current;

public: Playlist(const vector<string>& initialSongs) { songs.assign(initialSongs.begin(), initialSongs.end()); current = songs.begin(); }

void addSong(const string& song, bool playNext = false) {
    if (playNext) {
        current = songs.insert(next(current), song);
    } else {
        songs.push_back(song);
    }
}

void removeCurrent() {
    current = songs.erase(current);
    if (current == songs.end()) current = songs.begin();
}

void next() {
    if (next(current) != songs.end()) ++current;
}

void previous() {
    if (current != songs.begin()) --current;
}

};


12. 注意事项和最佳实践

1. 避免频繁的随机访问

cpp

void bad_practice() { list<int> lst(1000);

// 错误:频繁随机访问
for (int i = 0; i < lst.size(); i++) {
    auto it = lst.begin();
    advance(it, i);      // 每次都是O(n)!
    cout << *it << " ";  // 总时间复杂度O(n²)!
}

// 正确:顺序访问
for (auto it = lst.begin(); it != lst.end(); ++it) {
    cout << *it << " ";  // 总时间复杂度O(n)
}

}

2. 利用迭代器稳定性

cpp

void use_iterator_stability() { list<int> lst = {1, 2, 3, 4, 5}; vector<list<int>::iterator> important_positions;

// 保存重要位置的迭代器
auto it = lst.begin();
advance(it, 2);
important_positions.push_back(it);  // 指向3

// 进行各种修改操作...
lst.push_front(0);
lst.push_back(6);
lst.insert(lst.begin(), 99);

// 迭代器仍然有效!
cout << *important_positions[0] << endl;  // 还是3!

}


13. 总结

list的核心优势:

  1. 任意位置O(1)插入删除
  2. 迭代器稳定性 - 插入删除不影响其他迭代器
  3. 不需要连续内存
  4. 特有的高效链表算法

list的缺点:

  1. 没有随机访问 - 只能顺序访问
  2. 内存开销大 - 每个元素都有指针开销
  3. 缓存不友好 - 内存不连续

以下是一些 std::list 的主要应用场景:

1)频繁插入和删除操作:std::list 允许在序列中的任意位置进行常数时间的插入和删除操作。这使得它非常适合需要频繁添加或移除元素的场景,如日志记录、任务队列、事件处理等。

(2)双向迭代:std::list 支持双向迭代,这意味着可以轻松地遍历列表,向前或向后移动。这种特性在处理需要前后关联的数据时非常有用,如文本编辑器中的撤销与重做操作。

(3)保持元素顺序:std::list 通过链表结构保持元素的插入顺序。这对于需要维护元素原始顺序的场景(如数据库查询结果、事件历史记录等)非常有用。

(4)动态数据结构:当数据结构的大小经常变化时,std::list 是一个很好的选择。与基于数组的容器(如 std::vector)相比,std::list 在插入和删除元素时不需要移动大量数据,因此性能更高。

(5)内存管理:在某些情况下,可能希望更好地控制内存使用。std::list 允许管理每个元素的内存分配,这在处理大量数据或需要精确控制内存使用的场景中非常有用。

选择指南:

使用 list 当:

  • ✅ 需要频繁在任意位置插入删除
  • ✅ 需要稳定的迭代器
  • ✅ 实现LRU缓存、undo操作等场景
  • ✅ 内存碎片化严重

使用 vector/deque 当:

  • ✅ 需要随机访问
  • ✅ 主要进行尾部操作
  • ✅ 重视缓存性能内存效率

性能对比总结:

操作vectordequelist
尾部插入删除⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
头部插入删除⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐
中间插入删除⭐⭐⭐⭐⭐
随机访问⭐⭐⭐⭐⭐⭐⭐⭐⭐
内存效率⭐⭐⭐⭐⭐⭐⭐⭐⭐⭐

1)vector 底层实现是数组;list 是双向链表。

2)vector 支持随机访问,list 不支持。

3)vector 是顺序内存,list 不是。

4)vector 在中间节点进行插入删除会导致内存拷贝,list 不会。

5)vector 一次性分配好内存,不够时才进行 2 倍扩容(或 1.5 倍);list 每次插入新节点都会进行内存申请。

6)vector 随机访问性能好,插入删除性能差;list 随机访问性能差,插入删除性能好

一句话总结:

list = 任意位置高效操作 + 稳定迭代器 - 随机访问能力

list在特定场景下是不可替代的工具,理解它的特性可以让你在合适的时候做出最佳选择!


C++笔记——STL list》 是转载文章,点击查看原文


相关推荐


各个系统的 docker安装
Qayrup2025/11/9

docker简介 Docker 是一组平台即服务的产品,它基于操作系统层级的虚拟化技术,将软件与其依赖项打包为容器。本文将介绍在 CentOS 8 操作系统中安装 Docker 服务,并解决镜像源无法访问的问题。 centos8 安装 1 检查版本 Docker 要求 CentOS 的内核版本至少高于 3.10,可以用命令uname -r查看 2 安装 Docker 所需依赖 //执行命令yum install -y yum-utils device-mapper-persistent-dat


理解PostgreSQL中的数据块
WarriorTan2025/11/7

PG的数据块大小,默认是8KB,可以调整为16K或者 32K吗? PostgreSQL的数据块大小默认为8KB,可以将其调整为16KB或32KB。数据块大小需要在‌编译安装‌PostgreSQL时通过配置参数指定,例如使用configure.ac中的--with-blocksize选项进行设置 。需要注意的是,一旦数据库初始化完成,数据块大小就无法再修改 。 数据块的行指针都包括哪些信息? 具体来说,行指针是一个32位的数字,其结构被划分为三个部分: 行内容的偏移量‌:占用15位(bit


C#.NET Random 深入解析:随机数生成原理与最佳实践
唐青枫2025/11/4

简介 Random 是 .NET 中 System 命名空间提供的一个类,用于生成伪随机数。它广泛应用于需要随机化操作的场景,如生成随机数据、模拟、游戏开发或测试用例生成。 伪随机数生成 在计算机中,Random 类用于生成伪随机数,这些数值在一定程度上看起来是随机的,但它们实际上是通过数学公式从一个初始种子值计算得到的,因此称之为“伪随机数”。 广泛应用 Random 类常用于游戏开发、模拟、加密等场景。在许多应用中,生成随机数或随机选择某个元素是常见的需求。 注意: Random


设计模式的原则有哪些?
你的人类朋友2025/10/31

前言 温馨提示 对于原本不太熟悉设计模式的人来说(比如在下),这些内容是需要一定的时间消化的!慢慢来 😆 👋 你好啊,我是你的人类朋友! 今天说说设计模式的原则有哪些! 在开发用户权限系统时,你是否遇到过这样的问题: 当创建新的管理员用户类型时,发现它无法兼容普通用户的所有方法,导致系统中到处需要判断用户类型? 让我们了解设计模式的基本原则,构建更健壮的软件架构~ 健壮是啥意思? 健壮是指软件系统在面对变化和复杂性时,能够保持稳定运行的能力。也就是耐造的能力。 正文 SOLID 原则


Java的包装类
麦麦鸡腿堡2025/10/29

包装类(Wrapper)的分类: 1.针对八种基本数据类型相应的引用类型--包装类 2.有了类的特点,就可以调用类中的方法 *黄色框内都是number的子类,number是Ojbect子类,黑色框中的包装类是独立的,Ojbect子类 //boolean-Boolean-父类Object //char-Character-父类Object //byte-Byte-父类number-父类Object //int-Integer-父类number-父类Object //long-Long


17_AI智能体开发架构搭建之Flask集成swagger在线文档实践
腾飞开源2025/10/26

一、为什么需要Swagger集成? 在微服务架构和前后端分离的现代开发模式中,API文档承担着关键角色: 开发效率:前后端并行开发,减少沟通成本 接口契约:明确的请求/响应规范,避免歧义 测试便利:直接在文档界面测试API 团队协作:新成员快速理解接口设计 客户端生成:自动生成多种语言客户端代码 AI智能体系统设计相关文章: 👉《01_AI智能体系统设计之系统架构设计》 👉《02_AI智能体系统设计之钉钉消息处理流程设计》 👉《03_AI智能体系统设计之Ag


C#.NET SqlKata 使用详解:优雅构建动态 SQL 查询
唐青枫2025/10/23

简介 在复杂项目中,为了保持 SQL 灵活性与可读性,开发者往往需要手写大量拼接字符串或使用 ORMs 附带的 LINQ,但两者各有局限:手写拼接易出错、难以维护;LINQ 在某些场景下生成的 SQL 不够直观或性能不佳。 SqlKata 是一款轻量级、数据库无关的查询构建器(Query Builder),提供—— 流式 API,链式调用拼装 SQL 可切换编译器,支持多种数据库方言(SQL Server、PostgreSQL、MySQL、SQLite、Oracle 等) 语


深度解析 DNS服务:从基础原理到自建 DNS 服务器完整指南
coder4_2025/10/22

目录 一、DNS服务基础知识 1、DNS 2、域名 ①域名组成 ②域名管理与结构 3、DNS服务工作原理 ①主机中DNS客户端配置 ②DNS服务解析域名流程 4、DNS查询类型 ①递归查询 ②迭代查询 5、DNS解析类型 6、hosts文件 7、完整的域名查询请求流程 二、DNS检测工具 1、dig命令 2、host命令 3、nslookup命令 4、rndc命令 5、whois命令 三、自建DNS服务 1、ubuntu中的systemd-reso


Python爬虫入门:从零到数据采集
Moniane2025/10/21

Python爬虫基础指南 Python爬虫是自动化获取网络数据的技术,广泛应用于数据采集、市场分析等领域。以下是核心实现步骤: 1. 核心库选择 import requests # 发送HTTP请求 from bs4 import BeautifulSoup # HTML解析 import pandas as pd # 数据存储 2. 基础爬取流程 # 发送请求 response = requests.get("https://example.com/books") res


GPT-2 中的残差权重初始化
mwq301232025/10/19

GPT-2 中的残差权重初始化 1. 概述 在深度残差网络中,每一层的输出都会被加到其输入上。如果不对这些层的权重进行特殊处理,随着网络深度的增加,残差路径上累加的信号方差可能会不断增大,导致数值不稳定和训练发散。 为了解决这个问题,GPT-2 的作者在论文中提出了一种针对残差连接路径上的层进行特殊缩放的初始化方法。 2. 哪些是“残差权重”? 在 GPT-2 的一个 Decoder Block 中,有两个子层的输出会直接被加到残差流(Residual Stream)上。因此,这两层的权重就是我

首页编辑器站点地图

Copyright © 2025 聚合阅读

License: CC BY-SA 4.0