当前位置: 首页 > article >正文

【C++】STL——list底层实现

目录

💕1.list的三个类介绍

💕2.list——节点类 (ListNode)

💕3.list——链表类 (List)

💕4.list——迭代器类(重点思考)(ListIterator)

💕5. list遍历(迭代器,const迭代器)

💕6.整体代码 

💕7.测试用例

💕8.完结 


一个人的坚持到底有多难 

    声明:此文内容基于此文章->:【C++】STL——list的使用

💕1.list的三个类介绍

在list中存在着三个类,而我们使用的list就是这三个类相辅相成形成的功能

它们分别是节点类,链表类,迭代器类


节点类用来代表每一个节点的内容

迭代器类用来实现链表的遍历,成员为单个节点的地址

而链表类就是用来实现各种链表功能,成员为头结点


💕2.list——节点类 (ListNode)

节点类是每一个节点的内容,因此需要有前一个节点的地址,后一个节点的地址,以及数据

但因为它的内容需要频繁运用,因此最好用struct,struct中所有都为public,因此很方便,当然class也没有问题,只不过需要显示public

因此需要这样写->:

template<class T>
struct ListNode
{
	ListNode* prev;
	ListNode* next;
	T data;
	//1.T& x可以让形参不开辟新的空间,时间和空间上都有节省
	//2.const保护引用不被修改
	//3.T()是因为节点内容不一定是什么类型,而T()可以更好地初始化
	//例:int(),自动初始化为0,double(),string()
	ListNode(const T& x = T())
		:data(x)
		,prev(nullptr)
		,next(nullptr)
	{

	}
};

💕3.list——链表类 (List)

链表类的实现主要是具体功能,而我们知道链表是有一个头结点的,因此可以在链表类中实现

	template<class T>
	class List
	{
		typedef ListNode<T> Node;//将节点类重命名为Node
		//创建头节点,让其指向自己
		List()
		{
			phead = new Node();
			phead->next = phead;
			phead->prev = phead;
		}
	private:
		Node* phead;
	};

💕4.list——迭代器类(重点思考)(ListIterator)

迭代器类需要实现的是链表的遍历,想要实现遍历,必不可少的就是begin()函数,正常来说,begin()函数的返回类型应该是节点的地址,但是仔细思考一下,如果返回的是节点的地址,那这个节点的地址++后,不一定是下一个节点的地址,因此,begiin()函数不应该使用节点的地址作为返回值


新的思路:我们可以利用对象进行遍历,什么意思?

我们在进行遍历时,每一次begin()返回的是一个对象,通过访问这个对象中的节点地址,加上运算符重载,来进行遍历,因为利用这个对象中的节点地址,就进而可以通过这个节点地址访问到节点的数值


因此可以这样写->:

	template<class T>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		Node* node;//单个节点的地址
	};

为什么是结构体?先不要在意,请看后面

💕5. list遍历(迭代器,const迭代器)

我们的思路是,通过对象进行遍历,因此我们需要实现对象的++,--运算符重载,来一个一个的遍历每一个节点对象


	template<class T>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T> Self;
		ListIterator(Node* x)
		{
			node = x;
		}
		//前置++,作用是遍历,不可以返回节点地址,因为无法进行下一次++,不一定会指向下一个节点
		//利用对象进行遍历,因此返回的应该是一个对象,迭代器类的对象,因为要用迭代器类中的单个节点的地址来遍历
		Self& operator++()
		{
			node = node->next;
			return *this;
		}
		Self& operator--()
		{
			node = node->prev;
			return *this;
		}
		//后置--
		//后置--需要利用一个临时变量来表示原来的值,首先传参传给来的是对象的地址,即this
		Self operator--(int)
		{
			//Self* tep = this;//这种写法不可取,因为两块地址一样,任意一个修改全修改了所以没区别
			Self tep(*this);//拷贝构造函数创建临时对象,但是需要注意区分,这个对象的地址和*this的地址是不一样的,但是它们里面的node地址是一样的
			node = node->prev;
			return tep;
			//后置的返回值不可以用引用,因为临时变量tep出了函数就销毁了,用引用会导致空引用
		}
		Self operator++(int)
		{
			//Self* tep = this;//这种写法不可取,因为两块地址一样,任意一个修改全修改了所以没区别
			Self tep(*this);//拷贝构造函数创建临时对象,但是需要注意区分,这个对象的地址和*this的地址是不一样的,但是它们里面的node地址是一样的
			node = node->next;
			return tep;
		}
		T& operator*()
		{
			return this->node->data;
		}
		bool operator!=(const Self& it)
		{
			return this->node != it.node;
		}
		bool operator==(const Self& it)
		{
			return this->node == it.node;
		}
		Node* node;//单个节点的地址
	};


接下来是begin函数与end函数,写在List类中

		iterator begin()
		{
			//phead->next的类型是Node*类型,而返回类型需要是iterator类型,也就是对象
			//利用匿名对象,将phead->next传给ListIterator的构造函数,使它变为一个匿名对象,再return回去
			return iterator(phead->next);
		}
		iterator end()
		{
			return iterator(phead);
		}

如何实现const迭代器?

我们可以新建一个const的迭代器类,通过const修饰constIterator类中的成员变量,进而实现const迭代器的效果


具体实现如下->:
 

// 新增 const 迭代器
template<class T>
struct ListConstIterator
{
	typedef const ListNode<T> Node;
	typedef ListConstIterator<T> Self;
	ListConstIterator(Node* x)
		:node(x)
	{}

	// 前置++
	Self& operator++()
	{
		node = node->next;
		return *this;
	}

	Self& operator--()
	{
		node = node->prev;
		return *this;
	}

	// 后置++
	Self operator++(int)
	{
		Self tep(*this);
		node = node->next;
		return tep;
	}

	Self operator--(int)
	{
		Self tep(*this);
		node = node->prev;
		return tep;
	}

	const T& operator*() const
	{
		return node->data;
	}

	bool operator!=(const Self& it) const
	{
		return node != it.node;
	}

	bool operator==(const Self& it) const
	{
		return node == it.node;
	}

	//把节点定义为const类,因为const迭代器要实现的是const T* ,限制的是解引用
	const Node* node;
};
template<class T>
class List
{
	
public:
	typedef ListNode<T> Node;//将节点类重命名为Node
	typedef ListIterator<T> iterator;//将迭代器类重命名为iterator
	typedef ListConstIterator<T> const_iterator; // 新增 const_iterator
	//创建头节点,让其指向自己
	const_iterator begin() const
	{
		return const_iterator(phead->next);
	}

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

至此难点就全部完成了,剩下的就只剩拷贝构造等,看看整体代码即可

💕6.整体代码 

#pragma
#include<assert.h>
namespace yzq
{
	template<class T>
	struct ListNode
	{
		ListNode* prev;
		ListNode* next;
		T data;
		//1.T& x可以让形参不开辟新的空间,时间和空间上都有节省
		//2.const保护引用不被修改
		//3.T()是因为节点内容不一定是什么类型,而T()可以更好地初始化
		//例:int(),自动初始化为0,double(),string()
		ListNode(const T& x = T())
			:data(x)
			,prev(nullptr)
			,next(nullptr)
		{

		}
	};

	template<class T>
	struct ListIterator
	{
		typedef ListNode<T> Node;
		typedef ListIterator<T> Self;
		ListIterator(Node* x)
		{
			node = x;
		}
		//前置++,作用是遍历,不可以返回节点地址,因为无法进行下一次++,不一定会指向下一个节点
		//利用对象进行遍历,因此返回的应该是一个对象,迭代器类的对象,因为要用迭代器类中的单个节点的地址来遍历
		Self& operator++()
		{
			node = node->next;
			return *this;
		}
		Self& operator--()
		{
			node = node->prev;
			return *this;
		}
		//后置--
		//后置--需要利用一个临时变量来表示原来的值,首先传参传给来的是对象的地址,即this
		Self operator--(int)
		{
			//Self* tep = this;//这种写法不可取,因为两块地址一样,任意一个修改全修改了所以没区别
			Self tep(*this);//拷贝构造函数创建临时对象,但是需要注意区分,这个对象的地址和*this的地址是不一样的,但是它们里面的node地址是一样的
			node = node->prev;
			return tep;
			//后置的返回值不可以用引用,因为临时变量tep出了函数就销毁了,用引用会导致空引用
		}
		Self operator++(int)
		{
			//Self* tep = this;//这种写法不可取,因为两块地址一样,任意一个修改全修改了所以没区别
			Self tep(*this);//拷贝构造函数创建临时对象,但是需要注意区分,这个对象的地址和*this的地址是不一样的,但是它们里面的node地址是一样的
			node = node->next;
			return tep;
		}
		T& operator*()
		{
			return this->node->data;
		}
		bool operator!=(const Self& it)
		{
			return this->node != it.node;
		}
		bool operator==(const Self& it)
		{
			return this->node == it.node;
		}
		Node* node;//单个节点的地址
	};

	// 新增 const 迭代器
	template<class T>
	struct ListConstIterator
	{
		typedef const ListNode<T> Node;
		typedef ListConstIterator<T> Self;
		ListConstIterator(Node* x)
			:node(x)
		{}

		// 前置++
		Self& operator++()
		{
			node = node->next;
			return *this;
		}

		Self& operator--()
		{
			node = node->prev;
			return *this;
		}

		// 后置++
		Self operator++(int)
		{
			Self tep(*this);
			node = node->next;
			return tep;
		}

		Self operator--(int)
		{
			Self tep(*this);
			node = node->prev;
			return tep;
		}

		const T& operator*() const
		{
			return node->data;
		}

		bool operator!=(const Self& it) const
		{
			return node != it.node;
		}

		bool operator==(const Self& it) const
		{
			return node == it.node;
		}

		//把节点定义为const类,因为const迭代器要实现的是const T* ,限制的是解引用
		const Node* node;
	};

	template<class T>
	class List
	{
		
	public:
		typedef ListNode<T> Node;//将节点类重命名为Node
		typedef ListIterator<T> iterator;//将迭代器类重命名为iterator
		typedef ListConstIterator<T> const_iterator; // 新增 const_iterator
		//创建头节点,让其指向自己
		const_iterator begin() const
		{
			return const_iterator(phead->next);
		}

		const_iterator end() const
		{
			return const_iterator(phead);
		}
		List()
		{
			phead = new Node();
			phead->next = phead;
			phead->prev = phead;
		}
		//拷贝构造函数,可以开辟新空间然后直接尾插
		//因为l2是const类型的,所以auto时需要const类型的迭代器
		//遍历 const 对象需要 const 迭代器
		List(const List<T>& l2)
		{
			phead = new Node();
			phead->next = phead;
			phead->prev = phead;

			for (const auto& e : l2)
			{
				push_back(e);
			}
		}
		//赋值运算符重载
		//直接更改头结点,后面的访问就全更改了
		List<T>& operator=(List<T> lt)
		{
			swap(phead, lt.phead);

			return *this;
		}
		//析构函数
		~List()
		{
			clear();
			delete phead;
			phead = nullptr;
		}
		//全部遍历一遍s释放
		void clear()
		{
			auto it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}
		//头删
		void pop_back()
		{
			erase(--end());
		}
		//头插
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		//头删
		void pop_front()
		{
			erase(begin());
		}
		void push_back(const T& x)
		{
			Node* newnode = new Node(x);
			Node* tail = phead->prev;

			tail->next = newnode;
			newnode->prev = tail;
			newnode->next = phead;
			phead->prev = newnode;
		}
		iterator begin()
		{
			//phead->next的类型是Node*类型,而返回类型需要是iterator类型,也就是对象
			//利用匿名对象,将phead->next传给ListIterator的构造函数,使它变为一个匿名对象,再return回去
			return iterator(phead->next);
		}
		iterator end()
		{
			return iterator(phead);
		}
		iterator insert(iterator pos, const T& x)
		{
			Node* cur = pos.node;
			Node* newnode = new Node(x);
			Node* prev = cur->prev;

			// prev  newnode  cur
			prev->next = newnode;
			newnode->prev = prev;
			newnode->next = cur;
			cur->prev = newnode;

			return iterator(newnode);
		}

		// erase 后 pos失效了,pos指向节点被释放了
		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* cur = pos.node;
			Node* prev = cur->prev;
			Node* next = cur->next;

			prev->next = next;
			next->prev = prev;

			delete cur;

			return iterator(next);
		}
	private:
		Node* phead;
	};
}

💕7.测试用例

#define _CRT_SECURE_NO_WARNINGS 
#include <iostream>
using namespace std;
#include"list.h"
int main() {
    yzq::List<int> l1;
    l1.push_back(2);
    l1.push_back(3);
    
    l1.insert(l1.begin(), 5);
    l1.erase(l1.begin());
    yzq::List<int> l2(l1);
     //遍历输出: 2 3
   for (auto e : l2) {
        std::cout << e << " ";
    }
    return 0;
}

💕8.完结 


http://www.kler.cn/a/534099.html

相关文章:

  • 【初阶数据结构和算法】八大排序算法之插入排序(直接插入排序、希尔排序及其对比)
  • [c语言日寄]赋值操作对内存的影响
  • 【数据分析】豆瓣电影Top250的数据分析与Web网页可视化(numpy+pandas+matplotlib+flask)
  • 机器学习--2.多元线性回归
  • 2025 持续防范 GitHub 投毒,通过 Sharp4SuoExplorer 分析 Visual Studio 隐藏文件
  • 哈夫曼树原理及其C语言实现
  • Java基础进阶
  • vue 学习笔记 - 2、简单的一个例子
  • vscode修改自定义模板
  • DeepSeek图解,10页小册子,PDF开放下载!
  • STM32-启动文件
  • Java进阶文件输入输出实操(图片拷贝)
  • 安装mindspore_rl踩坑
  • 【深度学习】Java DL4J基于 RNN 构建智能停车管理模型
  • 华为OD最新机试真题-狼羊过河-Java-OD统一考试(E卷)
  • 大语言模型极速部署:Ollama 、 One-API、OpenWebUi 完美搭建教程
  • 大语言模型的「幻觉」(Hallucination)是指模型在生成内容时
  • 玩转goroutine:Golang中对goroutine的应用
  • js的 encodeURI() encodeURIComponent() decodeURI() decodeURIComponent() 笔记250205
  • 解决python写入csv时如000111样式的字符串前面的0被忽略掉的问题
  • DeepSeek-R1:开源机器人智能控制系统的革命性突破
  • Linux中安装rabbitMQ
  • 【含文档+PPT+源码】Python爬虫人口老龄化大数据分析平台的设计与实现
  • .net framework 4.5 的项目,用Mono 部署在linux
  • 【算法篇】选择排序
  • Mysql:数据库