【C++】list 容器操作

0. 官方文档

https://cplusplus.com/r eference/list/list/?kw=list

1. list 介绍

list 就是为了解决 vector 的缺陷出现的。

vector 缺陷:

  1. 头部和中部的插入删除效率低。O(N),因为需要挪动数据。

  2. 插入数据空间不够需要增容,增容需要开新空间,拷贝数据,释放旧空间,会付出很大代价。

list 优点:

  1. list 头部、中间插入不再需要挪动数据,效率高。O(1)

  2. list 插入数据是新增节点,不需要扩容。

list 缺点:

  1. 不支持随机访问。

所以实际使用中 vector 和 list 是相辅相成的两个容器。

2. list 的迭代器

从支持的操作接口的角度分 迭代器 的类型:

  • 单向(forward_list)

  • 双向(list)

  • 随机(vector)

从使用的场景的角度分 迭代器 的类型:

  • 正向迭代器

  • 反向迭代器

  • const迭代器

【注意】

  1. begin与end为正向 迭代器 ,对迭代器执行++操作,迭代器向后移动

  2. rbegin与rend为反向 迭代器 ,对迭代器执行++操作,迭代器向前移动

迭代器的使用

正向、反向、const 迭代器

cpp 复制代码
/* 迭代器 */
void test_list1()
{
        list<int> lt1;
        lt1.push_back(1);
        lt1.push_back(2);
        lt1.push_back(3);
        lt1.push_back(4);
        lt1.push_front(0);
        lt1.push_front(-1);
        lt1.push_front(-2);
        lt1.push_front(-3);

        // 正向普通迭代器遍历
        list<int>::iterator it1 = lt1.begin();
        while (it1 != lt1.end())
        {
                cout << *it1 << " ";
                ++it1;
        }
        cout << endl;

        list<int> lt2(lt1);        // 拷贝构造
        print_list(lt2);        // const 迭代器

        list<int> lt3;
        lt3.push_back(10);
        lt3.push_back(20);
        lt3.push_back(30);
        lt3.push_back(40);

        lt1 = lt3;
        // 只要一个容器支持迭代器,旧可以使用范围for的操作
        for (auto e : lt1)
        {
                cout << e << " ";
        }
        cout << endl;

        // 反向迭代器
        list<int>::reverse_iterator rit = lt1.rbegin();
        while (rit != lt1.rend())
        {
                cout << *rit << " ";
                ++rit;
        }
        cout << endl;
}

头插头删,尾插尾删

cpp 复制代码
/* 头插头删,尾插尾删 */
void test_list2()
{
        // 头插与尾插
        list<int> lt1;
        lt1.push_back(1);
        lt1.push_back(2);
        lt1.push_front(0);
        lt1.push_front(-1);

        print_list(lt1);

        // 尾删
        lt1.pop_back();
        // 头删
        lt1.pop_front();
        print_list(lt1);
}

任意位置的插入删除

cpp 复制代码
/* 任意位置的插入删除 */
void test_list3()
{
        list<int> lt1;
        lt1.push_back(1);
        lt1.push_back(2);
        lt1.push_back(3);
        lt1.push_back(4);

        print_list(lt1);

        // find 函数的查找范围是左闭右开的,[first, last)
        list<int>::iterator pos = find(lt1.begin(), lt1.end(), 3);
        if (pos != lt1.end())        // 一定要判断,不然找不到会直接尾插
        {
                lt1.insert(pos, 30);        // 在 3 的前面插入
                // 与vector不同,这里的 insert 并不会使 pos 迭代器失效
                lt1.erase(pos);        // 删除 3
        }
        print_list(lt1);
}

链表排序(不实用,不推荐)

cpp 复制代码
/* 链表排序(不实用,不建议) */
void test_list4()
{
        list<int> lt1;
        lt1.push_back(4);
        lt1.push_back(2);
        lt1.push_back(5);
        lt1.push_back(1);

        lt1.sort();        // 排序
        for (auto e : lt1)
        {
                cout << e << " ";
        }
        cout << endl;

        lt1.reverse();        // 逆置
        for (auto e : lt1)
        {
                cout << e << " ";
        }
        cout << endl;
}

erase 导致迭代器失效问题

cpp 复制代码
void test_list5()
{
        list<int> lt1;
        lt1.push_back(4);
        lt1.push_back(2);
        lt1.push_back(5);
        lt1.push_back(1);
        lt1.push_back(6);
        lt1.push_back(3);

        // 删除其中所有偶数
        list<int>::iterator it = lt1.begin();
        while (it != lt1.end())
        {
                if (*it % 2 == 0)
                {
                        lt1.erase(it);        // 删除后 it 迭代器失效
                }
                // 无法++,会报错无法进行递加操作
                ++it;
        }
        
        print_list(lt1);
}

修改一下关键代码:

cpp 复制代码
// 删除其中所有偶数
list<int>::iterator it = lt1.begin();
while (it != lt1.end())
{
        if (*it % 2 == 0)
        {
                it = lt1.erase(it);        
        }
        else
        {
                ++it;
        }
}

迭代器源码实现(简化版本)

下图中上半部分是 vector 的迭代器,下半部分是 list 的迭代器。

vector 是一段连续的空间,所以可以直接把迭代器定义为类型的指针:

cpp 复制代码
typedef T value_type;
typedef value_type* iterator;

当我们 ++iterator 时,迭代器可以直接往下走到下一个元素。

但是链表是非连续空间,是由一个个节点链接而成的,如果我们需要++迭代器来获得链表中的下一个元素,就不能简单把迭代器定义为类型的指针。要解决这个问题,我们可以用一个类型去封装节点的指针构成一个自定义类型,然后重载*、++等运算符,就可以达到我们想要的效果。

相关推荐
吴声子夜歌2 小时前
ApacheCommons——commons-pool2(高性能通用对象池框架)
java·开发语言·apache
kruptos7 小时前
分布式系统怎么“选主“?Raft 共识一次讲清
开发语言·分布式·php·共识算法
吴声子夜歌8 小时前
ApacheCommons——commons-cli(命令行参数解析)
java·开发语言·apache
benchmark_cc9 小时前
REST API 和 Python SDK 应该怎么选?量化交易数据接口选型实战
开发语言·python·数据分析·pandas·量化交易·股票数据·quantdash
王的宝库9 小时前
Go 项目结构:从单文件到标准工程布局
开发语言·后端·golang
Tairitsu_H9 小时前
[C++] 深入理解红黑树:封装set与map
开发语言·c++·set·map·红黑树·模拟实现
泡海椒9 小时前
JQuick-Curl 性能分析:并发场景下的性能表现与调优,第三方接口调用不只要快写也要稳跑
java·开发语言·okhttp
小智老师PMP9 小时前
2026深度解析|PMP第八版与NPDP核心侧重点本质区别(管理类证书怎么选)
开发语言·分布式·算法·职场和发展·产品经理
HugoStudio_SWAN9 小时前
洛谷 B4450 / B3867 / B3923 智慧购物、储蓄与做题——从程序到生活
c++·学习·程序人生·算法·生活
江畔柳前堤10 小时前
前台·中台·后台:2026年AI原生时代的架构全景图
开发语言·人工智能·算法·机器学习·架构·scala·ai-native