深入浅出 C++ STL list:从入门到精通

前言

在 C++ 编程中,选择合适的容器至关重要。今天我们要深入学习的 list 是 STL 中最常用的容器之一,它的底层实现是带头结点的双向循环链表 。本文将带你从零开始,彻底掌握 list 的使用、原理和注意事项。

一、list 是什么?

list 是 C++ 标准模板库(STL)中的序列式容器,它的底层是一个带头结点的双向循环链表

通俗理解

想象一列火车:

  • 每节车厢就是一个元素

  • 每节车厢都有前后两个挂钩,连接相邻车厢

  • 你可以在任意位置插入或拆除车厢,而不影响其他车厢的连接关系

这就是 list 的本质------非连续存储,通过指针连接

list 的特点

特点 说明
动态大小 元素个数可以动态变化
双向遍历 可以向前和向后遍历
常数时间插入/删除 在已知位置插入删除元素效率极高
不支持随机访问 不能使用 []at() 直接访问第 n 个元素

二、list 的完整使用指南

2.1 准备工作

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

2.2 list 的构造方式

cpp 复制代码
void test_constructor() {
    // 1. 默认构造:空链表
    list<int> l1;
    
    // 2. 构造包含 n 个值为 val 的元素的链表
    list<int> l2(5, 10);  // 5 个 10
    
    // 3. 拷贝构造
    list<int> l3(l2);
    
    // 4. 迭代器区间构造
    int arr[] = {1, 2, 3, 4, 5};
    list<int> l4(arr, arr + 5);
    
    // 5. 初始化列表构造(C++11)
    list<int> l5 = {1, 2, 3, 4, 5};
    
    // 打印验证
    cout << "l2: ";
    for (int x : l2) cout << x << " ";
    cout << endl;
}

2.3 迭代器的使用

迭代器可以理解为指向链表节点的指针,通过它可以遍历和访问元素。

cpp 复制代码
void test_iterator() {
    list<int> lst = {1, 2, 3, 4, 5};
    
    // 正向迭代器:从前往后
    cout << "正向遍历: ";
    for (auto it = lst.begin(); it != lst.end(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    // 反向迭代器:从后往前
    cout << "反向遍历: ";
    for (auto it = lst.rbegin(); it != lst.rend(); ++it) {
        cout << *it << " ";
    }
    cout << endl;
    
    // 范围 for(底层也是迭代器)
    cout << "范围 for: ";
    for (int x : lst) {
        cout << x << " ";
    }
    cout << endl;
}

2.4 容量相关操作

cpp 复制代码
void test_capacity() {
    list<int> lst = {1, 2, 3, 4, 5};
    
    cout << "是否为空: " << (lst.empty() ? "是" : "否") << endl;
    cout << "元素个数: " << lst.size() << endl;
}

2.5 元素访问

cpp 复制代码
void test_element_access() {
    list<int> lst = {10, 20, 30, 40, 50};
    
    cout << "第一个元素: " << lst.front() << endl;
    cout << "最后一个元素: " << lst.back() << endl;
    
    // 注意:没有 lst[2] 这种用法!list 不支持随机访问
}

2.6 修改操作(增删改)

这是 list 最强大的部分

cpp 复制代码
void test_modifiers() {
    list<int> lst;
    
    // 尾部插入
    lst.push_back(1);
    lst.push_back(2);
    lst.push_back(3);
    
    // 头部插入
    lst.push_front(0);
    lst.push_front(-1);
    
    cout << "插入后: ";
    for (int x : lst) cout << x << " ";
    cout << endl;  // -1 0 1 2 3
    
    // 在指定位置插入
    auto it = lst.begin();
    advance(it, 2);  // 移动到第 3 个位置
    lst.insert(it, 99);
    
    cout << "在位置2插入99: ";
    for (int x : lst) cout << x << " ";
    cout << endl;  // -1 0 99 1 2 3
    
    // 删除
    lst.pop_front();  // 删除头部
    lst.pop_back();   // 删除尾部
    
    cout << "删除首尾后: ";
    for (int x : lst) cout << x << " ";
    cout << endl;  // 0 99 1 2
    
    // 删除指定位置
    it = lst.begin();
    advance(it, 1);
    lst.erase(it);  // 删除 99
    
    cout << "删除第二个元素后: ";
    for (int x : lst) cout << x << " ";
    cout << endl;  // 0 1 2
    
    // 清空所有
    lst.clear();
    cout << "清空后 size = " << lst.size() << endl;
}

2.7 其他实用操作

cpp 复制代码
void test_other_operations() {
    list<int> lst1 = {1, 2, 3};
    list<int> lst2 = {4, 5, 6};
    
    // 交换两个链表
    lst1.swap(lst2);
    cout << "交换后 lst1: ";
    for (int x : lst1) cout << x << " ";
    cout << endl;
    
    // 反转链表
    lst1.reverse();
    cout << "反转后 lst1: ";
    for (int x : lst1) cout << x << " ";
    cout << endl;
    
    // 排序(list 有自己的 sort,不能用 algorithm 的 sort)
    list<int> lst3 = {5, 2, 8, 1, 9, 3};
    lst3.sort();
    cout << "排序后: ";
    for (int x : lst3) cout << x << " ";
    cout << endl;
    
    // 去重(需要先排序)
    list<int> lst4 = {1, 1, 2, 2, 3, 1, 1};
    lst4.sort();
    lst4.unique();
    cout << "去重后: ";
    for (int x : lst4) cout << x << " ";
    cout << endl;
}

三、迭代器失效问题(重要!)

什么是迭代器失效?

迭代器就像一张车票 ,指向链表中的某个节点。当你删除这个节点后,这张车票就无效了,不能再使用

核心规则

操作 迭代器是否失效
插入(insert) ❌ 不失效
删除(erase) ✅ 被删除节点的迭代器失效
头尾插入删除 同上
clear() ✅ 所有迭代器失效

错误示例

cpp 复制代码
void wrong_erase() {
    list<int> lst = {1, 2, 3, 4, 5};
    
    auto it = lst.begin();
    while (it != lst.end()) {
        lst.erase(it);  // it 指向的节点被删除,it 失效
        ++it;           // ❌ 错误!使用失效的迭代器
    }
}

正确写法 1:使用 erase 的返回值

cpp 复制代码
void correct_erase1() {
    list<int> lst = {1, 2, 3, 4, 5};
    
    auto it = lst.begin();
    while (it != lst.end()) {
        it = lst.erase(it);  // erase 返回下一个有效位置
    }
    
    cout << "删除后 size = " << lst.size() << endl;
}

正确写法 2:先获取下一个再删除

cpp 复制代码
void correct_erase2() {
    list<int> lst = {1, 2, 3, 4, 5};
    
    auto it = lst.begin();
    while (it != lst.end()) {
        lst.erase(it++);  // 先传 it,然后 it++,再删除原来的
    }
    
    cout << "删除后 size = " << lst.size() << endl;
}

四、list vs vector:如何选择?

这是面试必考实际开发必用的知识点。

对比维度 vector(动态数组) list(双向链表)
底层结构 连续内存空间 非连续,节点分散
随机访问 ✅ O(1) ❌ O(n)
头部插入/删除 ❌ O(n) ✅ O(1)
尾部插入/删除 ✅ O(1)(均摊) ✅ O(1)
中间插入/删除 ❌ O(n) ✅ O(1)
内存占用 紧凑,利用率高 额外存储前后指针
缓存友好 ✅ 高 ❌ 低
迭代器失效(插入) ✅ 可能失效(扩容) ❌ 不失效
迭代器失效(删除) ✅ 后续全部失效 ✅ 仅被删除的失效

// 场景1:需要频繁随机访问,选 vector

vector<int> scores; // 学生成绩,需要按索引访问

// 场景2:大量中间插入删除,选 list

list<int> taskQueue; // 任务队列,需要频繁在中间插入删除

// 场景3:不知道选什么,优先 vector

// vector 更简单,缓存友好,性能通常更好

六、常见陷阱与注意事项

1. 不要使用 algorithm 的 sort

cpp 复制代码
list<int> lst = {5, 2, 8, 1};

// ❌ 错误!list 的迭代器不是随机访问迭代器
// sort(lst.begin(), lst.end());

// ✅ 正确!使用 list 自己的 sort
lst.sort();

2. 不要用 [] 访问元素

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

// ❌ 编译错误
// cout << lst[1];

// ✅ 使用迭代器或范围 for
auto it = lst.begin();
advance(it, 1);
cout << *it;

3. 注意迭代器失效

cpp 复制代码
// ❌ 危险
auto it = lst.begin();
lst.erase(it);
cout << *it;  // it 已经失效!

// ✅ 安全
it = lst.erase(it);  // 更新迭代器

七、list模拟实现

cpp 复制代码
#include<iostream>
#include<assert.h>
namespace ty {
	template<class T>
	struct list_node {
	public:
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;

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

	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;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}
		// 前置 ++
		Self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		// 后置 ++
		Self operator++(int)
		{
			Self temp = *this;
			_node = _node->_next;
			return temp;
		}

		// 前置 --
		Self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		// 后置 --
		Self operator--(int)
		{
			Self temp = *this;
			_node = _node->_prev;
			return temp;
		}

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

	//template <class T>
	//struct list_const_iterator
	//{
	//	typedef list_node<T> Node;
	//	typedef list_const_iterator<T> Self;
	//	Node* _node;

	//	list_const_iterator(Node* node) :_node(node) {}

	//	const T& operator*()
	//	{
	//		return _node->data;
	//	}
	//	const T* operator->()
	//	{
	//		return &_node->data;
	//	}
	//	// 前置 ++
	//	Self& operator++()
	//	{
	//		_node = _node->_next;
	//		return *this;
	//	}

	//	// 后置 ++
	//	Self operator++(int)
	//	{
	//		Self temp = *this;
	//		_node = _node->_next;
	//		return temp;
	//	}

	//	// 前置 --
	//	Self& operator--()
	//	{
	//		_node = _node->_prev;
	//		return *this;
	//	}
	//	// 后置 --
	//	Self operator--(int)
	//	{
	//		Self temp = *this;
	//		_node = _node->_prev;
	//		return temp;
	//	}

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


	template<class T>
	class list {
	public:
		typedef list_node<T> Node;

		/*typedef list_iterator<T> iterator;
		typedef list_const_iterator<T> const_iterator;*/

		typedef list_iterator<T, T&, T*> iterator;
		typedef list_iterator<T, const T&, const T*> const_iterator;

		/*list() {
			_head = new Node(T());
			_head->_next = _head;
			_head->_prev = _head;
		}*/

		void empty_init()
		{
			_head = new Node(T());
			_head->_next = _head;
			_head->_prev = _head;
		}

		list()
		{
			empty_init();
		}



		list(const list<T>& other)
		{
			empty_init();
			for (auto& e : other)
			{
				push_back(e);
			}
		}

		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		list<T>& operator=(list<T> other)
		{
			swap(other);
			return *this;
		}

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

		}

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

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

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

		iterator end()
		{
			return iterator(_head);
		}

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

		const_iterator end()const
		{
			return const_iterator(_head);
		}


		void pop_back()
		{
			erase(--end());
		}

		void pop_front()
		{
			erase(begin());
		}

		// 修复完毕!
		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;
		}

		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos._node;
			Node* prev = cur->_prev;

			Node* newnode = new Node(x);
			newnode->_next = cur;
			newnode->_prev = prev;
			prev->_next = newnode;
			cur->_prev = newnode;
			++_size;
			return newnode;
		}

		size_t size() const {
			return _size;
		}

		bool empty() const {
			return _size == 0;
		}

	private:
		Node* _head;
		size_t _size = 0;
	};

	struct AA
	{
		int _a1;
		int _a2;

		// 必须加构造函数,否则 AA(1,2) 报错
		AA(int a1 = 0, int a2 = 0)
			:_a1(a1)
			, _a2(a2)
		{
		}
	};

	template <class Container>
	void print_container(const Container& v)
	{
		auto it = v.begin();
		while (it != v.end()) {
			std::cout << *it << " ";
			++it;
		}
		std::cout << std::endl;
	}


	void test_list1() {

		list<int> lt;
		lt.push_back(1);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(4);

		auto it = lt.begin();
		while (it != lt.end()) {
			std::cout << *it << " ";
			++it;
		}
		std::cout << std::endl;

		for (auto& x : lt)
		{
			std::cout << x << " ";
		}
		std::cout << std::endl;

		print_container(lt);

		it = lt.begin();
		while (it != lt.end())
		{
			if (*it % 2 == 0)
				it = lt.erase(it);
			else
			{
				it++;
			}
		}
		it = lt.begin();
		print_container(lt);



		/*	list<AA> lta;
			lta.push_back(AA(1, 2));
			lta.push_back(AA(3, 4));
			lta.push_back(AA(5, 6));

			auto ita = lta.begin();
			while (ita != lta.end()) {
				std::cout << (*ita)._a1 << " " << (*ita)._a2 << " ";
				std::cout<<ita.operator->()->_a1<<" "<<ita.operator->()->_a2<<std::endl;
				std::cout<<ita->_a1<<" "<<ita->_a2;
				++ita;
			}
			*/

	}
	void test3()
	{
		list<int> lt;
		lt.push_back(100);
		lt.push_back(2);
		lt.push_back(3);
		lt.push_back(784);
		list<int> lt1(lt);
		print_container(lt1);
		print_container(lt);

		list<int> lt2;
		lt2.push_back(100);
		lt2.push_back(255);
		lt2.push_back(355333);
		lt2.push_back(784);

		lt = lt2;
		print_container(lt);
	}

	void test4()
	{


	}



}

int main() {
	ty::test_list1();
	ty::test3();
	ty::test4();
	return 0;
}

讲解:

1. 链表节点结构 list_node
cpp 复制代码
template<class T>
struct list_node {
public:
    T _data;          // 存数据
    list_node<T>* _next; // 后指针
    list_node<T>* _prev; // 前指针

    list_node(const T& x)
        : _data(x)
        , _next(nullptr)
        , _prev(nullptr)
    {}
};
  • 这就是一个链表节点
  • 每个节点存一个数据 + 前后指针
  • 构造时初始化数据,指针置空
2. 迭代器 list_iterator

这是代码最难、最核心、最牛逼的地方。

cpp 复制代码
template <class T, class Ref, class Ptr>
struct list_iterator {
    typedef list_node<T> Node;
    Node* _node;        // 迭代器本质:包装一个节点指针

    list_iterator(Node* node) :_node(node) {}

operator*

cpp 复制代码
Ref operator*() {
    return _node->_data;
}

*it 拿到节点里的数据

operator->

cpp 复制代码
Ptr operator->() {
    return &_node->_data;
}

用于自定义类型,比如 it->_a1

为什么迭代器要写 3 个模板参数?

cpp 复制代码
template <class T, class Ref, class Ptr>
  • T:节点数据类型
  • Ref:引用类型(T&const T&
  • Ptr:指针类型(T*const T*

这样一份迭代器代码,自动生成两种迭代器

cpp 复制代码
typedef list_iterator<T, T&, T*>         iterator;        // 普通迭代器
typedef list_iterator<T, const T&, const T*> const_iterator; // const迭代器

八、总结

核心要点

  1. list 是双向循环链表,插入删除快,但不支持随机访问

  2. 迭代器就是指向节点的指针,删除时要小心失效问题

  3. 插入不会导致迭代器失效,删除只会导致被删节点的迭代器失效

  4. list 有自己的 sort 和 reverse,不能用 algorithm 的版本

  5. vector vs list:随机访问多选 vector,插入删除多选 list

相关推荐
饕餮争锋2 小时前
Bash 简介
开发语言·bash
爱吃烤鸡翅的酸菜鱼2 小时前
【Java】封装位运算通用工具类——用一个整数字段替代几十个布尔列,极致节省存储空间
java·开发语言·设计模式·工具类·位运算·合成复用原则
xinhuanjieyi2 小时前
php给30支NBA球队添加logo图标,做好对应关系
android·开发语言·php
菜菜小狗的学习笔记2 小时前
八股(三)Java并发
java·开发语言
史迪仔01122 小时前
[QML] 交互事件深度解析:鼠标、键盘、拖拽
前端·c++·qt
米啦啦.2 小时前
类继承、子类拷贝构造函数、赋值运算符重载函数、多继承(虚继承)
c++·多继承·类继承·赋值运算符重载
一晌小贪欢2 小时前
PyQt5 开发一个 PDF 批量合并工具
开发语言·qt·pdf
神仙别闹2 小时前
基于 MATLAB 实现的图像信号处理
开发语言·matlab·信号处理
swift192212 小时前
Qt多语言问题 —— 静态成员变量
开发语言·c++·qt