完整教程:C++STL之list

news/2025/10/18 13:27:03/文章来源:https://www.cnblogs.com/lxjshuju/p/19149575

目录

  • C++ STL list 详解
    • 1. list 概述
    • 2. 常用接口说明(含函数原型)
      • 2.1 构造函数
      • 2.2 元素访问
      • 2.3 容量操作
      • 2.4 修改操作
      • 2.5 迭代器操作
      • 2.6 特殊操作(list特有)
    • 3. 性能特点
    • 4. 适用场景
    • 5.模拟实现
      • 节点和链表
      • 迭代器设计
      • 代码封装

C++ STL list 详解

1. list 概述

list 是 C++ STL 中的双向链表容器,底层实现为带头双向循环链表。支持快速插入和删除(O(1)时间复杂度),不支持随机访问(不能使用下标操作),插入和删除操作不会使迭代器失效(除了被删除元素的迭代器)

与 vector 和 deque 相比,list 在任意位置的插入删除性能更优,但随机访问性能较差。

2. 常用接口说明(含函数原型)

2.1 构造函数

// 默认构造函数
list<T> lst;                            // 创建空list// 填充构造函数list<T> lst(size_type n, const T& value = T()); // n个元素,值为value// 范围构造函数list<T> lst(InputIterator first, InputIterator last);// 拷贝构造函数list<T> lst(const list& other);// 初始化列表构造函数list<T> lst(initializer_list<T> init);

示例:

list<int> lst1;                    // 空listlist<int> lst2(5, 10);             // {10, 10, 10, 10, 10}list<int> lst3(lst2.begin(), lst2.end()); // 拷贝lst2list<int> lst4{1, 2, 3, 4, 5};     // 初始化列表

2.2 元素访问

// 访问第一个元素
reference front();
const_reference front() const;
// 访问最后一个元素  
reference back();
const_reference back() const;

示例:

list<int> lst = {1, 2, 3, 4, 5};cout << lst.front();    // 输出: 1cout << lst.back();     // 输出: 5lst.front() = 10;       // 修改第一个元素lst.back() = 50;        // 修改最后一个元素

2.3 容量操作

// 判断是否为空
bool empty() const;
// 返回元素个数
size_type size() const;
// 返回可容纳的最大元素数
size_type max_size() const;

2.4 修改操作

插入操作

// 在头部插入
void push_front(const T& value);
// 在尾部插入  
void push_back(const T& value);
// 在指定位置插入
iterator insert(iterator pos, const T& value);
void insert(iterator pos, size_type n, const T& value);
void insert(iterator pos, InputIterator first, InputIterator last);

示例:

list<int> lst = {1, 2, 3};lst.push_front(0);      // 头部插入: {0, 1, 2, 3}lst.push_back(4);       // 尾部插入: {0, 1, 2, 3, 4}auto it = lst.begin();advance(it, 2);         // 迭代器前进2位lst.insert(it, 99);     // 在位置2插入: {0, 1, 99, 2, 3, 4}

删除操作

// 删除头部元素
void pop_front();
// 删除尾部元素
void pop_back();
// 删除指定位置元素
iterator erase(iterator pos);
iterator erase(iterator first, iterator last);
// 删除所有元素
void clear();
// 删除特定值
void remove(const T& value);
// 删除满足条件的元素
template<class Predicate> void remove_if(Predicate pred);

示例:

list<int> lst = {1, 2, 3, 4, 5};lst.pop_front();        // 删除头部: {2, 3, 4, 5}lst.pop_back();         // 删除尾部: {2, 3, 4}auto it = lst.begin();advance(it, 1);lst.erase(it);          // 删除位置1: {2, 4}lst.remove(2);          // 删除所有2: {4}

2.5 迭代器操作

// 获取指向第一个元素的迭代器
iterator begin();
const_iterator begin() const;
// 获取指向末尾的迭代器(最后一个元素的下一个位置)
iterator end();
const_iterator end() const;
// 反向迭代器
reverse_iterator rbegin();
const_reverse_iterator rbegin() const;
reverse_iterator rend();
const_reverse_iterator rend() const;

2.6 特殊操作(list特有)

拼接操作

// 将另一个list拼接到指定位置
void splice(iterator pos, list& other);
void splice(iterator pos, list& other, iterator it);
void splice(iterator pos, list& other, iterator first, iterator last);

示例:

list<int> lst1 = {1, 2, 3};list<int> lst2 = {4, 5, 6};auto it = lst1.begin();advance(it, 1);lst1.splice(it, lst2);  // lst1: {1, 4, 5, 6, 2, 3}

排序和去重

// 排序
void sort();
template<class Compare> void sort(Compare comp);// 去重(需要先排序)void unique();template<class BinaryPredicate> void unique(BinaryPredicate pred);// 合并两个有序listvoid merge(list& other);template<class Compare> void merge(list& other, Compare comp);

示例:

list<int> lst = {3, 1, 4, 1, 5, 9, 2, 6};lst.sort();             // 排序: {1, 1, 2, 3, 4, 5, 6, 9}lst.unique();           // 去重: {1, 2, 3, 4, 5, 6, 9}list<int> lst2 = {0, 7, 8};lst.merge(lst2);        // 合并: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}

反转

void reverse();  // 反转链表

3. 性能特点

​ C++ STL 中的 list 在频繁插入和删除的场景下表现出色,这得益于其底层实现的带头双向循环链表结构。由于链表节点在内存中非连续存储,插入和删除操作只需调整相邻节点的指针,无需移动其他元素,因此时间复杂度为 O(1)。不过,这种结构也导致 list 不支持随机访问,查找元素必须从头或从尾遍历,效率为 O(n)。此外,每个元素除了存储数据外还需维护两个指针,存在一定的内存开销,但其迭代器稳定性高,插入和删除不会影响其他元素的迭代器,非常适合动态操作频繁的应用场景。

4. 适用场景

​ list 特别适用于那些需要频繁在序列中间或两端进行插入和删除操作的场景,例如实现任务调度队列、维护有序数据集合但需动态调整顺序、或构建需要频繁重排的元素列表。在文本编辑器中管理每一行内容、游戏开发中维护动态对象列表,或是高频交易系统中处理不断变化的订单队列时,list 都能够提供高效的性能支持。相反,如果应用场景中需要频繁按位置随机访问元素,或对内存连续性有较高要求,则应优先考虑 vector 或 deque 等其他容器。

list vs vector

特性listvector
内存布局非连续连续
随机访问O(n)O(1)
头部插入删除O(1)O(n)
尾部插入删除O(1)O(1)(平摊)
中间插入删除O(1)O(n)
迭代器失效只有删除的元素失效插入删除可能使所有迭代器失效
内存开销每个元素两个指针容量可能大于元素数

list vs deque

特性listdeque
内存布局完全非连续分段连续
随机访问O(n)O(1)
头部插入删除O(1)O(1)
尾部插入删除O(1)O(1)
中间插入删除O(1)O(n)
迭代器稳定性较高较低

5.模拟实现

节点和链表

选择使用带头双向循环链表,因此我们的节点需要的属性有next, prev,val,而链表则需一个指向哨兵位(不存放实际数据,只为了方便边缘数据的插入删除)的指针即可,也可以加上元素个数size,这个在判空和返回大小的时候比较方便,计算链表大小无需遍历。

template <class T>struct ListNode{T _val = 0;ListNode *_next = nullptr;ListNode *_prev = nullptr;ListNode(const T &val = T()) : _val(val){}};template <class T>class ListIterator{Node *_head;size_t size;};

迭代器设计

在模拟实现的时候,迭代器需要被封装成一个类,从而进行一些运算符重载(++, --, *)来符合stl的规定,因此我们需要定义一个ListIterator的类,其内部的成员属性只需节点的指针即可。

这样就存在2种实现方式:

  1. 封装成内部类
  2. 在类外实现,类内使用,重命名使用(typedef)

两个版本:const和非const

但是迭代器有const和非const的版本,我们首先要搞清楚这两个迭代器的区别

std::list<int> ls1 = {1,2,3};const std::list<int> ls2 = {2,3,4};list<int>::iterator it1	 = ls1.begin();const list<int>::iterator it2 = ls2.begin();

上示两个迭代器,it1是普通迭代器,可以进行元素的访问,修改,而it2是const迭代器,只可以进行读取数据,不能修改数据,但是本身可以修改,容易与const iterator造成混淆,迭代器模拟的是指针的行为,因此可以有以下类比

iterator it1; // 相当于int* it1, 可以进行元素访问,修改
const_iterator it1; // 相当于 const int* it1, 所指向的内容不可以修改,但是指针本身可以修改(指针++,或者--),迭代器同理
const iterator it1; // 相当于 int* const it1, 是一个常量指针,指针自身不可修改,但是其指向的内容可以修改,迭代器同理

我们要实现的是第二种const_iterator, 因此需要2个类分别实现两个版本

class ListIterator
{
typedef ListNode<T> Node;typedef ListIterator<T> self;public:Node *_node;ListIterator(Node *node = nullptr) : _node(node){}T& operator*(){return _node->_val;}T* operator->(){return &(_node->_val);}// ……};class ConstListIterator{typedef ListNode<T> Node;typedef ConstListIterator<T> self;public:Node *_node;ListIterator(Node *node = nullptr) : _node(node){}const T& operator*(){return _node->_val;}const T* operator->(){return &(_node->_val);}// ……};

我们会发现,除了一些返回值的类型不同之外,其他部分完全相同,因此我们可以再次借助模板,让编译器替我们生成如上所示的两个类, 类型的传递在list类中传入,具体请看后文

template <class T, class Ref, class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> self;public:Node *_node;ListIterator(Node *node = nullptr) : _node(node){}Ref operator*(){return _node->_val;}Ptr operator->(){return &(_node->_val);}//……};

成员访问操作符:

如果我们链表中所存放的是个内置类型,那么我们可能会希望迭代器具有以下功能:

struct A
{
int a;
int b;
A(int x = 0, int y = 0):a(x), b(y)
{}
};
int main()
{
list<A> lt;lt.push_back({1, 1});lt.push_back({2, 2});lt.push_back({3, 3});list<A>::iterator it = lt.begin();while(it != lt.end()){cout<< (*it).a <<" " << (*it).b <<endl; //迭代器正常使用,但是看起来不美观,下一行的方式更加直观 cout<< it->a <<" " << it->b <<endl;  //我们在使用自定义元素链表的时候}}

我们可以返回元素存储的地址实现成员访问操作符的功能

class ListIterator
{
typedef ListNode<T> Node;typedef ListIterator<T> self;Node *_node;public:T* operator->(){return &(_node->_val); //返回存储val的地址,我们就可以使用成员发访问操作符->}}

但是我们显示调用就会发现一个问题

list<A>::iterator it = lt.begin();it.operator->()->a;    //it.operator->()显示调用,会返回val的地址,正常的省略调用应该是 it->得到地址,it.operator->()->b;    //然后解引用it->->,会出现两个操作符!

it->a = 1;
it->b = 2;

这样的调用是可以正常使用的

it->->a = 1;

it->->b = 2;

这样则会报错,因为编译器为我们做了优化,省略了一个操作符,更符合我们的使用习惯,也增加了代码的可读性

代码封装

解决完上述问题,封装代码基本没有了障碍,我们就可以封装一个具有基本功能的list,代码如下:

#pragma once
#include <iostream>#include <cassert>namespace mylist{template <class T>struct ListNode{T _val = 0;ListNode *_next = nullptr;ListNode *_prev = nullptr;ListNode(const T &val = T()) : _val(val){}};template <class T, class Ref, class Ptr>class ListIterator{typedef ListNode<T> Node;typedef ListIterator<T, Ref, Ptr> self;public:Node *_node;ListIterator(Node *node = nullptr) : _node(node){}Ref operator*(){return _node->_val;}Ptr operator->(){return &(_node->_val);}self &operator++(){_node = _node->_next;return *this;}self &operator++(int){self &tmp = *this;_node = _node->_next;return tmp;}self &operator--(){_node = _node->_prev;return *this;}self &operator--(int){self &tmp = *this;_node = _node->_prev;return tmp;}bool operator==(const self &it){return it._node == _node;}bool operator!=(const self &it){return it._node != _node;}};template <class T>class list{typedef ListNode<T> Node;Node *_head = nullptr;size_t _size = 0;public:friend class ListIterator<T, T&, T*>;typedef ListIterator<T, T&, T*> iterator; //这里进行一个重命名,在类内使用的时候就会自动传出相应类型typedef ListIterator<T, const T&, const T*> const_iterator;list(){Init();}list(size_t n, const T &val = T()){Init();while (n--){push_back(val);}}list(iterator first, iterator last){Init();while (first != last){push_back(*first);first++;}}list(const list<T> &lt){Init();iterator it = lt.begin();while (it != lt.end()){push_back(*it);it++;}}void Init(){_head = new Node;_head->_next = _head;_head->_prev = _head;}iterator begin() const{return _head->_next;}iterator end() const{return _head;}const_iterator cbegin() const{return _head->_next;}const_iterator cend() const{return _head;}void push_back(const T &val){// Node *newnode = new Node(val);// if (newnode != nullptr)// {//     _head->_prev->_next = newnode;//     newnode->_prev = _head->_prev;//     _head->_prev = newnode;//     newnode->_next = _head;// }insert(end(), val);}void push_front(const T &val){insert(begin(), val);}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator insert(iterator pos, const T &val){Node *newnode = new Node(val);Node *next = pos._node;Node *prev = next->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = next;next->_prev = newnode;_size++;return newnode;}iterator erase(iterator pos){assert(!empty());Node *next = pos._node->_next;Node *prev = pos._node->_prev;prev->_next = next;next->_prev = prev;delete pos._node;_size--;return next;}bool empty(){return _size == 0;}size_t size(){return _size;}void print(){for (auto &e : *this){std::cout << e << " ";}std::cout << std::endl;}};}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/939553.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

13 Static 关键字的作用

13 Static 关键字的作用Static 快捷访问变量和方法 被修饰的变量和方法可直接由类名访问 public class Student{private static int age;}public static void main(String[] args){Sysrem.out.println(Student.age); }…

DS:一个处理php前端数据的实用类

这需要根据项目规模和复杂度来权衡,不是绝对的。 让我分析一下各种情况的适用方案: 📊 封装决策指南 1. 小型项目/简单表单 - 不需要过度封装 <?php // 简单直接的做法 $required = [title, content, category…

rk3399 安卓7 添加 exfat 格式U 盘支持

主要的修改: 1,内核添加 exfat 文件系统支持2,vold 添加 exfat 文件系统支持3,需要 exfat 文件检查,格式化工具的支持,vold 中需要用到 参考:git clone https://gitee.com/rock_telp/android_add_exfat 1,内核…

2025年10月ai优化推荐榜:基于全平台实测数据的中立对比与选购指南

一、引言 当生成式引擎成为用户获取信息的主入口,企业能否在DeepSeek、豆包、通义千问、元宝、Kimi等主流AI平台同时获得稳定曝光,直接决定品牌增长天花板。对CMO、采购负责人及创业者而言,控制试错成本、保障优化效…

2025年10月ai优化推荐对比榜:十强服务商数据化拆解与选择策略

一、引言 生成式引擎优化正在重塑企业获客路径,品牌方、增长负责人与采购决策者普遍面临“平台碎片化、效果难量化、技术门槛高”三大痛点:投入预算需要可控,跨平台声量需要一致,转化数据需要闭环。2025年10月,主…

深入解析:图书馆自习室|基于SSM的图书馆自习室座位预约小程序设计与实现(源码+数据库+文档)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

21-java-grpc-demo-1

java #grpc Java gRPC 实战:从零构建高效的RPC服务 代码仓库 https://gitee.com/suveng/java_grpc_demo_1 引言:为什么选择gRPC? 在现代分布式系统中,服务间通信已成为架构设计的核心环节。传统的RESTful API虽然简…

AI元人文:价值舞台

AI元人文:价值舞台 序 “在Ai元人文构想中,价值博弈,是一场价值原语依序登场、动态站位、持续演出的戏剧;其涌现的决策,是此刻舞台状态的定格,而非唯一的终局。” 价值博弈,是一个动态的、有生命的舞台剧。每一…

2025年10月AI搜索优化推荐榜单:基于全平台实测数据的中立对比与决策指南

一、引言 当生成式引擎成为用户获取信息的主入口,企业能否在DeepSeek、豆包、通义千问、元宝、Kimi等多平台同时获得稳定曝光,直接决定品牌流量成本与转化效率。创业者、市场负责人及采购经理的核心诉求集中在三点:…

【AI绘画】你有多久没有打开SD了?

【AI绘画】你有多久没有打开SD了?两年前学习SD的各种参数被搞到心态爆炸,再看现在即梦、可灵、豆包已经成为普通人日常AI工具,简简单单就能生成一张AI图片,Stable Diffusion似乎已经被人遗忘了这周哩布哩布发布升级…

2025年10月豆包关键词排名优化推荐对比榜:企业选购的客观决策参考

一、引言 在生成式搜索流量快速分发的当下,豆包关键词排名优化已成为品牌获取AI入口曝光的核心手段。对需要控制获客成本、保障内容质量、提升转化效率的市场部与增长团队而言,选择一家技术扎实、数据透明、服务可落…

2025年10月豆包关键词排名优化推荐榜单:从核心技术到服务流程的系统化评价

一、引言 在生成式引擎优化(GEO)迅速成为企业流量入口的2025年,能否在豆包等主流AI平台获得稳定前排展示,直接决定品牌曝光效率与获客成本。本次调研面向市场负责人、增长团队及采购决策者,核心需求集中在三点:降…

php数据验证 + 过滤 + 参数绑定

绝对不建议直接操作 $_POST! 这是一个非常重要的安全实践。 ❌ 为什么不建议直接使用 $_POST 直接使用 $_POST 的问题: // ❌ 危险做法 - 千万不要这样写! $sql = "INSERT INTO users (username, email) VALUE…

Microsoft AI Genius | 用 MCP 解锁实时数据,重新定义交互边界

当智能体逐渐成为企业数字化转型的核心助手,一个关键问题愈发凸显:为什么很多智能体的回答总是“差点意思”?答案往往藏在“信息差”里:传统智能体受限于单一知识库,无法动态获取外部最新数据,导致面对复杂问题时…

2025年10月北京geo优化公司推荐榜:基于全平台实测数据的中立对比与选购指南

一、引言 当生成式引擎成为用户获取信息的主入口,北京市场正涌现大量宣称掌握“全平台同步优化”的geo服务商。对需要稳定获客的品牌方、急于降低获客成本的中小企业以及计划布局AI搜索赛道的创业者而言,如何在技术描…

排序算法(golang达成)

pre { white-space: pre !important; word-wrap: normal !important; overflow-x: auto !important; display: block !important; font-family: "Consolas", "Monaco", "Courier New", …

8线程的8皇后程序

#include <stdio.h> #include <pthread.h>int ON[] = { 0x8000, 0x4000, 0x2000, 0x1000, 0x0800, 0x0400, 0x0200, 0x0100 }; int _b[8][8], cnt[8]; pthread_t tid[8];void search (int i, int cy) { //…

2025年10月geo优化供应商推荐榜:十强对比评测与中立选购指南

一、引言 生成式引擎优化正在把“搜索可见度”改写为“AI 可见度”,品牌能否在 DeepSeek、豆包、通义千问、元宝、Kimi 等主流模型里被准确引用,已成为流量入口之争的新前线。对年度营销预算千万级以上的品牌方、增长…

2025年拉链厂家推荐排行榜,TAB拉链,大棕拉链,金属拉链,树脂拉链,服装拉链,尼龙拉链,防水拉链,隐形拉链,男装拉链,女装拉链公司推荐榜!

随着消费升级和全球化的推进,服装辅料行业正经历着前所未有的变革。高质量、智能化、可持续发展的拉链产品成为市场的新宠。为了帮助采购商筛选出优质的TAB拉链、大棕拉链、金属拉链、树脂拉链、服装拉链、尼龙拉链、…

2025年10月geo优化服务商推荐榜单:基于全平台实测数据的中立对比与避坑指南

一、引言 生成式引擎优化(GEO)已成为企业在AI搜索生态中获得可见度的关键路径。对计划2025年第四季度启动品牌曝光、产品发布或招商引流的决策者而言,选择一家技术扎实、数据透明、行业经验丰富的服务商,直接决定预…