C++ Lsit

List介绍:

list 是 C++ STL 中基于双向循环链表实现的容器,与 vector(动态数组)形成鲜明互补。前者擅长频繁的插入 / 删除操作,后者优势在随机访问。

本文将全面讲解 list 的核心特性、常用用法,通过维度化对比梳理 list 与 vector 的差异,并结合实战场景给出容器选型建议,帮你精准掌握 list 的使用场景和避坑要点。

一、list 核心认知:双向链表的本质

1. list 的底层特性

list 的底层是双向循环链表(每个节点包含数据、前驱指针、后继指针),无连续内存依赖,核心特性如下:

  • 不支持随机访问(无法用[]/at()访问元素),只能通过迭代器遍历,访问任意元素时间复杂度 O (n);
  • 任意位置插入 / 删除元素效率极高(仅需修改指针指向),时间复杂度 O (1)(前提是已找到目标位置);
  • 无扩容概念:每个元素独立分配内存,插入时直接新建节点,删除时释放节点,无需整体扩容 / 拷贝;
  • 迭代器稳定性:仅被删除节点的迭代器失效,其他迭代器不受影响(与 vector 形成核心差异)。

2. list 基础用法示例

cpp 复制代码
#include <iostream>
#include <list>
#include <algorithm> // 用于find
using namespace std;

int main() {
    // 1. 初始化
    list<int> lst1; // 空链表
    list<int> lst2(5, 10); // 5个元素,每个值为10
    list<int> lst3 = {1,2,3,4,5}; // 列表初始化

    // 2. 增删改查
    lst1.push_back(6); // 尾部插入,size=1
    lst1.push_front(0); // 头部插入,size=2 → {0,6}
    lst2.pop_back(); // 尾部删除,size=4
    lst3.pop_front(); // 头部删除,size=4 → {2,3,4,5}

    // 查找元素(需遍历,无随机访问)
    auto it = find(lst3.begin(), lst3.end(), 3);
    if (it != lst3.end()) {
        lst3.insert(it, 9); // 在3前插入9 → {2,9,3,4,5}
        lst3.erase(it); // 删除原3的位置(此时it指向3)→ {2,9,4,5}
    }

    // 遍历(仅支持迭代器/范围for)
    for (int num : lst3) cout << num << " "; // 输出:2 9 4 5
    cout << endl;

    // 3. 专属方法
    lst3.reverse(); // 反转链表 → {5,4,9,2}
    lst3.sort(); // 排序 → {2,4,5,9}
    lst1.merge(lst2); // 合并两个已排序的链表(需先排序)

    return 0;
}

3. list 的专属核心方法(区别于 vector)

list 提供了链表特有的操作方法,是其适配场景的关键:

方法 作用
push_front() 头部插入元素(vector 无此高效方法,头部插入需移动所有元素)
pop_front() 头部删除元素(同理,vector 头部删除效率低)
reverse() 反转链表(时间复杂度 O (n),无需额外内存)
sort() 链表专属排序(STL 的 sort 算法不支持 list,因需随机访问迭代器)
merge() 合并两个已排序的 list(原地合并,无额外内存分配)
splice() 转移另一个 list 的元素到当前链表(仅修改指针,效率极高)

二、list 与 vector 核心维度对比

对比维度 list(双向循环链表) vector(动态数组)
底层存储 非连续内存,每个节点独立分配(数据 + 前驱 + 后继指针) 连续内存(数组),物理地址连续
随机访问 不支持(无[]/at()),只能迭代器遍历,访问任意元素 O (n) 支持([]/at()),随机访问时间复杂度 O (1)
插入 / 删除性能 任意位置 O (1)(找到位置后仅改指针),头部 / 尾部插入删除效率极高 尾部 O (1)(平均),中间 / 头部 O (n)(需移动元素),触发扩容时额外增加拷贝开销
扩容机制 无扩容概念,插入元素直接新建节点,无内存拷贝 容量不足时扩容(通常 2 倍),需申请新内存→拷贝数据→释放旧内存
迭代器类型 双向迭代器(仅支持 ++/--,不支持 +- 整数偏移) 随机访问迭代器(支持 ++/--/+- 整数偏移,如it+3
迭代器失效 仅被删除节点的迭代器失效,其他迭代器完全有效 扩容后所有迭代器失效;插入 / 删除后,失效位置后的迭代器全部失效
内存开销 额外内存开销大(每个节点需存储两个指针) 内存开销小(仅存储数据,连续内存缓存友好)
空间利用率 无内存浪费(按需分配节点),但碎片化严重 可能存在内存浪费(扩容后 capacity>size),但连续内存利用率高
专属方法 push_front/pop_front/reverse/sort/merge/splice reserve/resize/shrink_to_fit(内存预分配 / 调整)

三、迭代器失效场景对比(易踩坑点)

迭代器失效是 list 和 vector 最核心的差异之一,也是新手最易出错的地方,单独梳理如下:

1. list 的迭代器失效场景(仅 1 种)

只有指向被删除节点的迭代器会失效,其余迭代器(包括前驱、后继)均不受影响:

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

int main() {
    list<int> lst = {1,2,3,4,5};
    auto it1 = lst.begin(); // 指向1
    auto it2 = lst.begin() + 1; // 错误:list迭代器不支持+偏移
    auto it2 = next(lst.begin(), 1); // 正确:指向2
    auto it3 = lst.end(); // 指向末尾

    lst.erase(it2); // 删除2,仅it2失效
    cout << *it1 << endl; // 正确:1(it1有效)
    // cout << *it2 << endl; // 错误:it2失效
    cout << *next(it1, 1) << endl; // 正确:3(迭代器有效)

    return 0;
}

2. vector 的迭代器失效场景(多且易踩)

对比之下,vector 的迭代器失效场景远多于 list:

  • 扩容(push_back/insert):所有迭代器失效;
  • 中间 / 头部插入:插入位置后的迭代器失效;
  • 中间 / 头部删除:删除位置后的迭代器失效;
  • 清空 / 析构:所有迭代器失效

四、实战场景选型:什么时候用 list?什么时候用 vector?

容器选型的核心是匹配场景的性能需求,以下是精准的选型指南:

优先选择 list 的场景

  1. 频繁在任意位置插入 / 删除元素:如高频的增删操作(如任务队列、实时数据更新),尤其是头部 / 中间操作;
  2. 需要迭代器高度稳定:插入 / 删除后,除被操作节点外,其他迭代器仍可正常使用;
  3. 元素数量不确定且无需随机访问:如仅需遍历、增删,无需按索引快速取值。

优先选择 vector 的场景

  1. 需要随机访问元素 :如按索引快速取值(vec[5])、二分查找(需随机访问迭代器);
  2. 遍历性能要求高:连续内存的缓存友好性,遍历速度远快于 list;
  3. 元素数量可预估:可通过 reserve 预分配容量,避免扩容开销;
  4. 内存开销敏感:vector 的内存额外开销远低于 list(无指针存储)。

五、List总结

  1. list 基于双向循环链表实现,核心优势是任意位置插入 / 删除效率 O (1)、迭代器稳定性高,核心劣势是无随机访问、内存开销大;
  2. vector 基于连续数组实现,核心优势是随机访问 O (1)、缓存友好,核心劣势是中间 / 头部增删效率低、迭代器易失效;
  3. 选型核心原则:需随机访问 / 遍历优先选 vector,需频繁任意位置增删优先选 list;
  4. list 使用需避坑:不支持 std::sort、注意内存碎片化、优先用 emplace 系列方法。

六、Lsit的模拟实现

1.基础链表节点:

cpp 复制代码
//链表节点
template<class T>
struct list_node
{
	T _data;
	list_node<T>* _next;
	list_node<T>* _prev;

	list_node(const T& data=T())
		:_data(data)
		,_next(nullptr)
		,_prev(nullptr)
	{ }
};

2.Lsit的迭代器:

cpp 复制代码
//迭代器
template<class T,class ref,class ptr>
struct list_iterator
{
	typedef list_node<T> Node;
	typedef list_iterator<T, ref, ptr> Self;
	Node* _node;
	
	list_iterator(Node* node)
		:_node(node)
	{}

	ref operator*()
	{
		return _node->_data;
	}

	Self& operator++(int)
	{
		Self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	Self& operator--(int)
	{
		Self tmp(*this);
		_node = _node->_next;
		return tmp;
	}

	Self& operator++()
	{
	    _node=_node->_next;
		return *this;
	}

	Self& operator--()
	{
		_node = _node->_prev;
		return *this;
	}

	bool operator==(const Self& s)const
	{
		return _node == s._node;
	}

	bool operator!=(const Self& s)const
	{
		return _node != s._node;
	}

};

3.List的基本框架:

cpp 复制代码
//链表
template<class T>
class list
{
	typedef list_node<T> Node;//表节点
public:
    //普通迭代器/const迭代器
	typedef list_iterator<T, T&, T*> iterator;
	typedef list_iterator<T,const T&,const T*> const_iterator;

	//构造
	list()
	{
		empty_init();
	}

    //空初始化
	void empty_init()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
		_size = 0;
	}

    //析构
	~list()
	{
		clear();
		delete _head;
		_head = nullptr;
	}

    //拷贝构造
	list(const list<T>& x)
	{
		empty_init();
		for (auto& e : x)
		{
			push_back(e);
		}
	}

    //清理数据
	void clear()
	{
		auto it = begin();
		while (it != end())
		{
			it = erase(it);
		}
	}
  
    //判空
	void empty()
	{
		return _size == 0;
	}

    //插入
	void insert(iterator pos, const T& x)
	{
		Node* newnode= new Node(x);
		Node* cur = pos._node;
		Node* prev = cur->_prev;

		newnode->_next = cur;
		cur->_prev = newnode;
		newnode->_prev = prev;
		prev->_next = newnode;
		++_size;
	}

    //删除pos位置节点
	iterator erase(iterator pos)
	{
		assert(pos!=end());
		Node* prev = pos._node->_prev;
		Node* next = pos._node->_next;

		prev->_next = next;
		next->_prev = prev;
		delete[] pos._node;
		--_size;

		return next;
	}


    void swap(const list<T>& x)
	{
		std::swap(_head, x._head);
		std::swap(_size, x._size);
	}

    //赋值
	list<T>& operator=(const list<T> x)
	{
		swap(x);
		return *this;
	}

    //尾插
	void push_back(const T& x)
	{
		insert(end(), x);
	}

    //头插
	void push_front(const T& x)
	{
		insert(begin(), x);
	}

    //头删
	void pop_front(const T& x)
	{
		erase(begin());
	}

    //尾删
	void pop_back(const T& x)
	{
		erase(--end());
	}

    //迭代器相关
	iterator begin()
	{
		return _head->_next;
	}

	iterator end()
	{
		return _head;
	}
	
	const_iterator begin()const
	{
		return _head->_next;
	}

	const_iterator end()const
	{
		return _head;
	}
    
    //大小
	size_t size()
	{
		return _size;
	}

private:
	Node* _head;
	size_t _size;
};
相关推荐
我爱C编程1 小时前
基于OMP正交匹配追踪和稀疏字典构造的杂波谱恢复算法matlab仿真
算法·matlab·omp·正交匹配追踪·稀疏字典构造·杂波谱恢复
云青黛1 小时前
ReAct(推理与行动)框架
python·算法
野犬寒鸦1 小时前
从零起步学习计算机操作系统:I/O篇
服务器·开发语言·网络·后端·面试
姓刘的哦2 小时前
Qt实现蚂蚁线
开发语言·qt
布局呆星2 小时前
Python 文件操作教程
开发语言·python
im_AMBER2 小时前
Leetcode 142 将有序数组转换为二叉搜索树 | 排序链表
算法·leetcode
码农三叔2 小时前
(10-5-01)大模型时代的人形机器人感知:基于RoboBrain大模型的人形机器人通用智能感知系统(1)构建模型
人工智能·算法·机器人·人形机器人
scott1985122 小时前
扩散模型之(十三)条件生成 Conditioned Generation
人工智能·算法·生成式
Elnaij2 小时前
从C++开始的编程生活(23)——哈希表
开发语言·c++