STL-- C++ list类 模拟实现

哨兵节点、双向循环、迭代器封装、深拷贝 一个个坑踩过来。这篇文章记录我的实现思路和踩坑点(代码注释中详细标注),方便自己以后复习,也希望能帮到正在学容器底层的你。

一、节点结构 ListNode<T>

双向链表的基石,包含数据 _data、前驱指针 _prev、后继指针 _next。构造函数支持默认值。

cpp 复制代码
template <class T>
struct ListNode
{
	ListNode(const T& data = T())
		:_data(data)
		,_prev(nullptr)
		,_next(nullptr)
	{	}
	T _data;
	ListNode<T>* _prev;
	ListNode<T>* _next;
};//节点创建

二、迭代器 ListIterator<T, Ref, Ptr>

模拟指针,支持:

  • operator*() → 返回元素引用(Ref

  • operator->() → 返回元素指针(Ptr

  • ++ / --(前置/后置) → 移动迭代器

  • == / != → 比较迭代器是否指向同一节点

三个模板参数分工:

  • T :数据类型

  • RefT&const T&(控制读写)

  • PtrT*const T*(配合 ->

cpp 复制代码
template <class T,class Ref, class Ptr>
struct ListIterator //制作迭代器就要知道 迭代器在list中发挥的什么作用 分别实现什么功能
{
	typedef ListNode<T> Node;
	typedef ListIterator<T, Ref,Ptr> Self;//其实是代表的两种 list 非const 和const 的种类 、然后分别进入两种模板构造
	Node* _node;//这里创建一个节点---目的是让这个迭代器 指向这个位置

	ListIterator(Node* node)//既然用迭代器的话 后面就会接内容 否则就是随机迭代器
		:_node(node)
	{   }
	Ref operator* ()//返回的是一个可修改值或者 不可修改 别搞错了
	{
		return _node->_data;
	}
	Ptr operator->()//当链表元素是自定义类或结构体,并且需要访问该元素的成员时,就可以自然写出 it->member
	{//自定义类型也有可能是 const对象 所以再搞一个模板用来区分它
		return &(_node->_data);
	}
	Self& operator++()
	{
		_node = _node->_next;
		return *this;//先++后放回
	}
	Self operator++(int)//后置++
	{
		Self temp(_node);//先放回后++
		_node = _node->_next;
		return temp;
	}
	Self& operator--()
	{
		_node = _node->_prev;
		return *this;//先--后放回
	}
	Self operator--(int)
	{
		Self temp(_node);//先放回后++
		_node = _node->_prev;
		return temp;
	}
	bool operator!=(const Self& it) const
	{
		return _node != it._node;
	}
	bool operator==(const Self& it) const
	{
		return _node == it._node;
	}
};

三、链表容器 list<T>

1. 构造 / 析构 / 赋值
函数 说明
list() 创建空链表(带哨兵头节点)
list(const list& x) 深拷贝构造
list(initializer_list<T>) 支持 {1,2,3} 花括号初始化
operator= 拷贝赋值(需修正参数为传值)
~list() 释放所有节点(含哨兵)
cpp 复制代码
void swap(list<T>& x)
{
	std::swap(_head,x._head);
	std::swap(_size,x._size);
}

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

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

list(const list<T>& x)
{
	empty_init();
	for (auto e : x)//x是被拷贝的list push_back是插入准备要构造的 主宾要弄清楚
	{
		push_back(e);
	}
}
list& operator= (list<T> x)
{
	swap(x);
	return *this;
}

list(initializer_list<T> x)//用花括号初始化的那个迭代器 记得学习思路
{
	empty_init();
	for (const T&e : x)//用auto验证一下
	{
		push_back(e);
	}
}

~list()
{
	clear();
	delete _head;
	_head = nullptr;
}
2. 迭代器接口
  • begin() / end() 普通版本

  • begin() const / end() const 常量版本

cpp 复制代码
iterator begin()
{
	return iterator(_head->_next);
}
const_iterator begin() const
{
	return const_iterator(_head->_next);
}

iterator end()
{
	return iterator(_head);
}
const_iterator end() const
{
	return const_iterator(_head);
}
3. 容量
  • empty() :判空

  • size() :返回元素个数(通过 _size 维护)

cpp 复制代码
	bool empty() const
	{
		return _size == 0;
	}
		
    size_t size() const
		{
			//size_t n = 0;
			//iterator it = begin();
			//while (it != end())
			//{
			//	n++;
			//	it++;
			//}
			//return n;//由于每次添加新元素都会重新计算size
			// 那么不如直接在插入数据时进行size的操作 这样就能直接返回size的值
			return _size;
		}
4. 元素访问(通过迭代器间接实现)
cpp 复制代码
Ref operator* ()//返回的是一个可修改值或者 不可修改 别搞错了
{
	return _node->_data;
}
Ptr operator->()//当链表元素是自定义类或结构体,并且需要访问该元素的成员时,就可以自然写出 it->member
{//自定义类型也有可能是 const对象 所以再搞一个模板用来区分它
	return &(_node->_data);
}
5. 修改操作
函数 说明
push_front / pop_front 头部插入/删除(复用 insert / erase
push_back / pop_back 尾部插入/删除(复用 insert / erase
insert(iterator pos, const T& val) 在 pos 之前插入新节点,返回新节点迭代器
erase(iterator pos) 删除 pos 指向的节点,返回下一个迭代器
clear() 删除所有有效节点(哨兵保留)
swap(list& x) 交换两个链表的头指针和大小
cpp 复制代码
	void push_front(const T& data)
	{
/*		Node* New_node = new Node(data);
		Node* prev = _head->_next;

		prev->_prev = New_node;
		New_node->_next = prev;
		New_node->_prev = _head;
		_head->_next = New_node;
		_size++;*/
		insert(begin(), data);
	}
	void pop_front()
	{
		//assert(!empty());

		//Node* next = _head->_next->_next;//留的就是这个next 删的就是这个_head->_next

		//_head->_next = next;
		//next->_prev = _head;
		//_head->_next->_prev = nullptr;
		//_size--;
		erase(begin());
	}
	void push_back(const T& data)
	{
		//Node* New_node = new Node(data);
		//Node* tail = _head->_prev;//这个尾设置的很巧 无论是否有 有效节点 都能做到

		//tail->_next = New_node;
		//New_node->_prev = tail;
		//New_node->_next = _head;
		//_head->_prev = New_node;
		//_size++;
		insert(end(), data);
	}
	void pop_back()
	{
		//assert(!empty());
		//Node* prev = _head->_prev->_prev;//留的就是这个prev 删的就是这个_head->_prev

		//_head->_prev->_next = nullptr;
		//_head->_prev = prev;
		//prev->_next = _head;
		//_size--;
		erase(--end());//end()本身是最后一个有效位置位置的下一个 所以传这个迭代器时候要先-- 
	}
	iterator insert(iterator pos, const T& val)
	{
		Node* newnode = new Node(val);
		Node* prev = pos._node->_prev;

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

		newnode->_next = pos._node;
		pos._node->_prev = newnode;

		_size++;
		return iterator(newnode);
	}
	void clear()
	{
		iterator it = begin();
		while (it!=end())
		{
			it = erase(it);//删除就是指向下一个位置
		}
	}
	iterator erase(iterator pos)
	{
		assert(!empty());
		Node* prev = pos._node->_prev;
		Node* next = pos._node->_next;
		prev->_next = next;
		next->_prev = prev;
		delete pos._node;
		_size--;
		return next;//本应如此
	}
6. 辅助函数
  • empty_init() :初始化哨兵节点,让 _next_prev 都指向自身(循环空链表)
cpp 复制代码
	void empty_init()
	{
		_head = new Node;
		_head->_next = _head;
		_head->_prev = _head;
	}
7 成员
cpp 复制代码
	class list
	{
		typedef ListNode<T> Node;
	public://迭代器的相关内容
		typedef ListIterator<T,T&,T*> iterator;
		typedef ListIterator<T,const T&,const T*> const_iterator;//我该如何让他一个人承担两个角色
	public://各种函数
        .........
    private:
		Node* _head;
		size_t _size = 0;
	};

常见易错点

  • 哨兵头节点 :不存储数据,_next 指向第一个有效节点,_prev 指向最后一个有效节点。空链表时头节点的 _next_prev 都指向自己。

  • 迭代器失效erase 返回下一个有效迭代器,insert 返回新节点迭代器。

  • 深拷贝 :遍历原链表,逐个 push_back 值,不共享节点。

  • 析构顺序 :先 clear() 删除有效节点,再 delete _head 释放哨兵。

手写一个 list 比想象中麻烦,但收获也远超预期:指针操作、模板参数设计、const 迭代器、异常安全......每解决一个 bug,理解就深一层。

我的gitee仓库

https://gitee.com/jiangmingpeng0716/c-learning-process

相关推荐
BestOrNothing_20151 小时前
C++零基础到工程实战(5.2.5):函数默认参数和函数重载
c++·函数重载·函数默认参数·nullptr·函数声明与定义
JSON_L1 小时前
PHP 高精度计算完全指南:彻底解决浮点数精度丢失
开发语言·php
江屿风1 小时前
C++OJ题经验总结(竞赛)3
开发语言·c++·笔记·算法
NiceCloud喜云1 小时前
Anthropic 发布 Project Glasswing:未公开模型 Mythos 已挖出 10000+ 漏洞,含 OpenBSD 27 年老 bug
android·java·数据库·c++·python·docker·bug
guygg881 小时前
用 MATLAB 实现步进电机控制的仿真方案
开发语言·matlab
码农的小菜园1 小时前
Java创建单例
java·开发语言·单例模式
yuan199971 小时前
基于物理光学(波动光学)模型的 MATLAB 程序
开发语言·matlab
香蕉鼠片1 小时前
八股C++(二)
开发语言·c++
影寂ldy2 小时前
C#数组的高级方法
开发语言·c#