【C++】认识 list(初步认识+模拟实现)

继vector的学习后,我们开始下一个模版的学习---------List。废话不多说,发车!


C++双向链表(list)

1.初步认识

std::list 是 C++ STL 标准库提供的双向循环链表容器。它是序列式容器,底层用双向链表实现,每个元素都有前驱指针和后继指针。

核心特点(面试必背)

  1. 底层结构:双向循环链表
  2. 访问方式:不支持随机访问(不能直接用 [].at()
  3. 插入 / 删除:任意位置 O (1) 常数时间(最核心优势
  4. 迭代器:插入删除不会失效(除了被删除元素的迭代器)
  5. 内存:非连续内存,每个节点额外存两个指针(内存开销比 vector 大)
  6. 适用场景:频繁在中间插入、删除元素
特性 std::vector std::list std::deque
底层结构 连续动态数组 双向链表 双端数组
随机访问 [] ✅ 支持,O (1) ❌ 不支持 ✅ 支持,O (1)
头部插入 / 删除 ❌ 极慢,O (n) ✅ 极快,O (1) ✅ 很快,O (1)
中间插入 / 删除 ❌ 慢 ✅ 极快,O (1) ❌ 慢
尾部插入 / 删除 ✅ 快 ✅ 快 ✅ 快
内存占用 连续,可能有浪费 分散,无浪费 分段连续
迭代器失效 扩容 / 插入易失效 极稳定,仅删除节点失效 插入删除可能失效
适用场景 查找多、增删少 任意位置频繁增删 双端频繁增删

2.底层实现

1. 节点结构定义

复制代码
template<class T>
struct list_node
{
    T _data;                // 存储数据
    list_node<T>* _next;    // 指向下一个节点的指针
    list_node<T>* _prev;    // 指向上一个节点的指针
    
    // 构造函数,参数有默认值
    list_node(const T& x = T())
        :_data(x)
        ,_next(nullptr)
        ,_prev(nullptr)
    { }
};

功能说明:

  • 定义了链表的节点结构,包含三个成员

  • _data:存储用户数据

  • _next:指向后继节点的指针

  • _prev:指向前驱节点的指针

  • 构造函数允许创建带有默认值的节点


2. 迭代器实现

复制代码
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 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)const
    {
        return _node != it._node;
    }

    // 相等比较运算符
    bool operator==(const Self& it)const
    {
        return _node == it._node;
    }
};

未优化写法:

功能说明:

  • 实现了双向链表的迭代器,支持前向和后向遍历

  • 通过模板参数RefPtr区分const和非const迭代器

  • 重载了*->++--!===等运算符

  • 前置和后置自增/自减运算符实现遍历功能

扩展:


3. 链表类主框架

复制代码
template<class T>
class list
{
    typedef list_node<T> Node;
public:
    // 迭代器类型定义
    typedef __list_iterator<T, T&, T*> iterator;
    typedef __list_iterator<T, const T&, const T*> const_iterator;

    // 私有成员
private:
    Node* _head;       // 哨兵节点(头节点)
    size_t _size = 0;  // 链表大小
};

功能说明:

  • 定义了iterator和const_iterator类型别名

  • 使用哨兵节点_head简化链表操作

  • _size记录链表当前元素数量


4. 迭代器接口

复制代码
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);
}

功能说明:

  • begin()返回指向第一个元素的迭代器

  • end()返回指向最后一个元素之后的迭代器(哨兵节点)

  • 哨兵节点形成循环,end()指向_head

  • 提供const版本用于只读访问


5. 构造函数和初始化

复制代码
// 初始化空链表
void empty_init()
{
    _head = new Node;
    _head->_next = _head;
    _head->_prev = _head;
}

// 默认构造函数
list()
{
    empty_init();
}

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

// initializer_list构造函数
list(initializer_list<T> il)
{
    empty_init();
    for (const auto& e : il)
    {
        push_back(e);
    }
}

功能说明:

  • empty_init():创建哨兵节点并使其自环

  • 默认构造函数:创建空链表

  • 拷贝构造函数:深拷贝另一个链表

  • initializer_list构造函数:用初始化列表构造链表


6.内存管理和运算符重载

复制代码
// 交换两个链表
void swap(list<T>& it)
{
    std::swap(_head, it._head);
    std::swap(_size, it._size);
}

// 赋值运算符(拷贝并交换技法)
list<T>& operator=(list<T> lt)
{
    swap(lt);
    return *this;
}

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

// 清空链表
void clear()
{
    iterator it = begin();
    while (it != end())
    {
        it = erase(it);
    }
}

功能说明:

  • swap():交换两个链表的内部指针

  • 赋值运算符:使用拷贝并交换(copy-and-swap)技法

  • 析构函数:释放所有节点内存

  • clear():删除所有数据节点,保留哨兵节点


7.核心操作函数

复制代码
// 在指定位置插入元素
iterator insert(iterator pos, const T& val)
{
    Node* cur = pos._node;
    Node* newnode = new Node(val);
    Node* prev = cur->_prev;

    // 调整指针连接
    prev->_next = newnode;
    newnode->_next = cur;
    cur->_prev = newnode;
    newnode->_prev = prev;

    ++_size;
    return iterator(newnode);
}

// 删除指定位置的元素
iterator erase(iterator pos)
{
    Node* cur = pos._node;
    Node* prev = cur->_prev;
    Node* next = cur->_next;

    // 调整指针,跳过当前节点
    prev->_next = next;
    next->_prev = prev;
    delete cur;

    --_size;
    return next;  // 返回下一个节点的迭代器
}

功能说明:

  • insert():在指定位置前插入新节点,返回指向新节点的迭代器

  • erase():删除指定节点,返回指向下一个节点的迭代器

  • 操作包括调整四个指针(前驱、后继节点的next/prev指针)


8.便捷接口函数

复制代码
// 尾部插入
void push_back(const T& x)
{
    insert(end(), x);
}

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

// 尾部删除
void pop_back()
{
    erase(--end());
}

// 头部删除
void pop_front()
{
    erase(begin());
}

// 获取元素数量
size_t size() const
{
    return _size;
}

功能说明:

  • 提供了标准容器接口:push_backpush_frontpop_backpop_front

  • size()返回当前元素数量

  • 这些函数都基于insert()erase()实现

9.print函数(用于输出调试)

复制代码
template<class T>
	void print(const list<T>& lt)
	{
		//typename list<T>::const_iterator it = lt.begin();
		auto it = lt.begin();
		while (it != lt.end())
		{
			//*it += 1;
			cout << *it << " ";
			++it;
		}

		cout << endl;
	}

为什么要加 typename

在模板还没实例化时,编译器不知道 T 是什么,所以它不敢确定:

  • list<T>::const_iterator 是一个类型(迭代器类)?
  • 还是一个静态成员变量?

编译器默认会把它当成变量 / 值,而不是类型,于是编译报错。

typename 的作用:

typename 就是告诉编译器:

"别猜了!我保证 list<T>::const_iterator 是一个类型(type),不是变量!"


源码:

复制代码
#pragma once
namespace wxx
{
	template<class T>
	struct list_node
	{
		T _data;
		list_node<T>* _next;
		list_node<T>* _prev;
		list_node(const T& x=T())
			:_data(x)
			,_next(nullptr)
			,_prev(nullptr)
		{ }
	};
	template<class T,class Ref, class Ptr>
	struct __list_lterator
	{
		typedef list_node<T> Node;
		typedef __list_lterator<T, Ref, Ptr> Self;
		Node* _node;

		__list_lterator(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 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)const
		{
			return _node != it._node;
		}

		bool operator==(const Self& it)const
		{
			return _node == it._node;
		}
	};
		template<class T>
		class list
		{
			typedef list_node<T> Node;
		public:
			typedef __list_lterator<T, T&, T*> iterator;
			typedef __list_lterator<T, const T&, const T*> const_iterator;

			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 empty_init()
			{
				_head = new Node;
				_head->_next = _head;
				_head->_prev = _head;
			}

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

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

				return *this;
			}
			/*list<T>& operator=(list<T> it)
			{
				if (this != &it)
				{
					clear();
					for (const auto& e : it)
					{
						push_back(e);
					}
				}
			 }*/
			~list()
			{
				clear();
				delete _head;
				_head = nullptr;
			}

			void clear()
			{
				iterator it = begin();
				while (it != end())
				{
					it = erase(it);
				}
			}
			void push_back(const T& x)
			{
				insert(end(), x);
			}

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

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

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

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

				prev->_next = newnode;
				newnode->_next = cur;
				cur->_prev = newnode;
				newnode->_prev = prev;

				++_size;

				return iterator(newnode);
			}
			iterator erase(iterator pos)
			{
				Node* cur = pos._node;
				Node* prev = cur->_prev;
				Node* next = cur->_next;

				prev->_next = next;
				next->_prev = prev;
				delete cur;

				--_size;

				//return iterator(next);
				return next;
			}
			size_t size() const
			{
				return _size;
			}
		private:
			Node* _head;
			size_t _size = 0;
		};
}

list模拟实现 · 1f734b4 · 加油少年/CCCCC - Gitee.comhttps://gitee.com/wxx547803_0/ccccc/commit/1f734b49fe4e3db73357b7e44683035aab519c0a


相关推荐
曹牧1 小时前
Java:数据载体
java·开发语言
赏金术士1 小时前
Kotlin 从入门到进阶 之基础语法模块(一)
开发语言·微信·kotlin
格林威1 小时前
Baumer工业相机堡盟相机Chunk功能全解析:如何在图像中嵌入时间戳、编码器值等元数据?
开发语言·人工智能·数码相机·机器学习·计算机视觉·视觉检测·机器视觉
南宫萧幕1 小时前
锂电池二阶 RC 模型仿真实战:从理论解析到 Simulink 闭环搭建全流程
开发语言·人工智能·算法·机器学习
Hical_W1 小时前
Hical 踩坑实录五部曲(一):Boost.Asio 协程开发的 N 个坑
网络·c++·开源
水木流年追梦1 小时前
【python因果库实战29】LaLonde 数据集2
开发语言·python·数据挖掘·langchain·机器人
不会编程的懒洋洋1 小时前
WPF 性能优化+异步+渲染
开发语言·笔记·性能优化·c#·wpf·图形渲染·线程
春夜喜雨1 小时前
类型定义的使用差异using/typedef/define/constexpr
c++·typedef·using·constexpr·类型定义·define·常量声明
故事和你911 小时前
洛谷-数据结构2-1-二叉堆与树状数组2
开发语言·javascript·数据结构·算法·ecmascript·动态规划·图论