由浅到深-模拟实现list_模拟list实现添加一个值到list底部

先自我介绍一下,小编浙江大学毕业,去过华为、字节跳动等大厂,目前阿里P7

深知大多数程序员,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年最新大数据全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友。
img
img
img
img
img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,涵盖了95%以上大数据知识点,真正体系化!

由于文件比较多,这里只是将部分目录截图出来,全套包含大厂面经、学习笔记、源码讲义、实战项目、大纲路线、讲解视频,并且后续会持续更新

如果你需要这些资料,可以添加V获取:vip204888 (备注大数据)
img

正文

前言

作者:小蜗牛向前冲

名言:我可以接受失败,但我不能接受放弃

如果觉的博主的文章还不错的话,还请********************************************************************************************************************************************************************************************************************************************************点赞,收藏,关注👀支持博主。如果发现有问题的地方欢迎❀大家在评论区指正。

目录

一 、见见STL中的list

1、list的介绍

2、list的常见接口

二、list的模拟实现

1、list框架搭建

2、模拟实现list迭代器

3、list整体实现

三、list和vector的对比

1、对比二者的优缺点

2、list和vector的排序效率


本期学习目标:认识STL中的list,模拟实现list,对list的迭代器深入理解,对比list和vector。

一 、见见STL中的list

1、list的介绍

下面我们了看看cpulcpul官网中的介绍:

文档介绍:

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向 其前一个元素和后一个元素。
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效。
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list 的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间 开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这 可能是一个重要的因素)。

从上面的介绍中我们初步认识到了list的是带头双向链表,对于要掌握的数据结构之一,下面我们一起来回忆一下他的增删查改操作。

2、list的常见接口

list的有很多接口,下面我们主要介绍几个重点接口:

list的构造

因为list在C++中是用类来封装的,他也就有自己的构造函数,但由于list初始化的场景非常多,所以他有多个构造函数,下面的在模拟实现的时候可以细细体会,下面我们先见见有哪些构造函数:

构造函数(Construct)接口说明
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list

list modifiers

为来对list进行修改,也提供了一些修改的接口:

函数声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

二、list的模拟实现

为了更好的理解list的底层实现,下面将大家一起去模拟实现list。

1、list框架搭建

我们要模式实现list,而list是个带头双向链表,那么我们首先搭建一个list_node的类模板

	struct list_node
	{
		list_node<T>*  _next;//指向后一个节点
		list_node<T>*  _prev;//指向前一个节点
		T _data;//节点中的数据
		list_node(const T& x)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{}
	};

这里我们要注意的是我们不仅仅定义了节点的指向,我们还应该对节点进行初始化。

有了节点,那么我们就应该定义list类的主体,他的私有变量应该要有指向list_node的指针head,和记录链表个数的size,为了方便定义,这里我们可以直接对list_node的变量名重定义。

	template<class T>
	class list
	{
		typedef list_node<T> node;
	public:
        //各种成员函数
	private:
		node* _head;
		size_t _size;
	};

下面我们就要实现各种成员函数就可以了,但是在实现成员函数之前,我们要先实现list的迭代器。

2、模拟实现list迭代器

我们在模式实现vector的迭代器的时候,认为迭代器就是一个指针。那么我们这里也可以把list的迭代器当作指针实现吗?这里显然是不可以的,为什么这么说呢?

当一个指针++他跳过的是他的一个类型的大小,但是list节点并不是挨个存储的他节点的空间是随机的,节点间是依靠节点中存放对方的地址指向对方的。

其实不仅仅++操作不满足,还有许多操作都是不满足的,如–操作。

我们又该如何解决这个问题呢?

其实我们可以用一个类模板,包含迭代器功能的成员函数,就可以解决。当我们调用迭代器时其实就是调用类模板中的成员函数。

但是这里要注意一个细节:由于成员函数他的返回值可能存在类型的差异,比如:*解引用的时候,返回_pnode->_data,但是->的时候是&_pode->_data;

这样类模板的参数就不仅仅是一个模板参数,而要三个模板参数才能解决。

//定义迭代器
	template <class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> Self;
		node* _pnode;
		//初始化
		__list_iterator(node* p)
			:_pnode(p)
		{}

		Ptr operator->()
		{
			return &_pnode->_data;
		}
		Ref operator*()
		{
			return _pnode->_data;
		}
		Self& operator++()
		{
			_pnode = _pnode->_next;
			return *this;
		}
		Self operator++(int)
		{
			Self tmp(*this);
			_pnode = _pnode->_next;
			return tmp;
		}
		Self& operator--()
		{
			_pnode = _pnode->prev;
			return *this;
		}
		Self operator--(int)
		{
			Self tmp(*this);
			_pnode = _pnode->_prev;
			return tmp;
		}
		bool operator!=(const Self it)const
		{
			return _pnode != it._pnode;
		}
		bool operator==(const Self& it)const
		{
			return _pnode == it._pnode;
		}
	};

其实不少同学可能会困惑,为什么要在迭代器中重载出->,这个不是我们在用结构体或者类中指针成员才用到的吗?

我们要明白list节点中可能存放的不是数据,也可能是存放指针一个结构体的指针。

下面我们来看代码理解:

	struct Pos
	{
		int _row;
		int _col;

		Pos(int row = 0, int col = 0)
			:_row(row)
			, _col(col)
		{}
	};

	void print_list(const list<Pos>& lt)
	{
		list<Pos>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			//it->_row++;

			cout << it->_row << ":" << it->_col << endl;

			++it;
		}
		cout << endl;
	}
	void test3()
	{
		list<Pos> lt;
		Pos p1(1, 1);
		lt.push_back(p1);
		lt.push_back(p1);
		lt.push_back(p1);
		lt.push_back(Pos(2, 2));
		lt.push_back(Pos(3, 3));

		// int* p  -> *p
		// Pos* p  -> p->
		list<Pos>::iterator it = lt.begin();
		//list<Pos>::iterator it2 = it;
		while (it != lt.end())
		{
			it->_row++;

			//cout << (&(*it))->_row << ":" << (*it)._col << endl;
			cout << it->_row << ":" << it->_col << endl;
			//cout << it.operator->()->_row << ":" << it->_col << endl;

			++it;
		}
		cout << endl;

		print_list(lt);
	}

这里我们定义了一个Pos的类,他的功能就是记录row 和col,在定义一个函数print_list打印list中的做标,下面在我们的测试函数在插入一些数据。如果是在测试函数体内打印lt本来是非常复杂的如果没有重载迭代器的->.

这里理解: (&(*it))->_row?----->简单的来是就是要拿到这个it节点中的数据

如果我们要拿到Pos中的数据就只要用Pos创建一个变量p,p->row,就能拿到类中的数据,但是现在我们只有一个指向链表节点的迭代器,也就是只要我们*解引用it就能拿到节点中的数据,但是节点中的数据是一个类的,要能到类Pos的数据就要拿到类的地址,并用->指向结构体中变量的数据。

听起来是不是好晕,所以为了简化操作我们就在迭代器的类中封装了->.

	Ptr operator->()
		{
			return &_pnode->_data;//&这里是取地址,也就是说返回的指针
		}

迭代器失效问题

我们都知道迭代器是用类封装好的里面有功能各异的成员函数,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代 器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响

3、list整体实现

这里我们在整体实现的时候仍然采取分文件的做法,test.cpp用来包含所要的头文件,list.h用来实现list的主体内容。

test.cpp

#define  _CRT_SECURE_NO_WARNINGS

#include<iostream>
#include<assert.h>
using namespace std;
#include"list.h"

int main()
{
	pjb::test1();
	return 0;
}

list.h

#pragma once//防止头文件被多次包含


namespace pjb
{
	template<class T>
	struct list_node
	{
		list_node*  _next;
		list_node*  _prev;
		T _data;
		list_node(const T& x)
			:_next(nullptr)
			, _prev(nullptr)
			, _data(x)
		{}
	};
	//定义迭代器
	template <class T,class Ref,class Ptr>
	struct __list_iterator
	{
		typedef list_node<T> node;
		typedef __list_iterator<T, Ref, Ptr> Self;
		node* _pnode;
		//初始化
		__list_iterator(node* p)
			:_pnode(p)
		{}

		Ptr operator->()
		{
			return &_pnode->_data;
		}
		Ref operator*()
		{
			return _pnode->_data;
		}
		Self& operator++()
		{


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
![img](https://img-blog.csdnimg.cn/img_convert/bd30adff29f9f026aa2a8147ebcbfc9a.png)

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

		//初始化
		__list_iterator(node* p)
			:_pnode(p)
		{}

		Ptr operator->()
		{
			return &_pnode->_data;
		}
		Ref operator*()
		{
			return _pnode->_data;
		}
		Self& operator++()
		{


**网上学习资料一大堆,但如果学到的知识不成体系,遇到问题时只是浅尝辄止,不再深入研究,那么很难做到真正的技术提升。**

**需要这份系统化的资料的朋友,可以添加V获取:vip204888 (备注大数据)**
[外链图片转存中...(img-vF5VgGhF-1713418255835)]

**一个人可以走的很快,但一群人才能走的更远!不论你是正从事IT行业的老鸟或是对IT行业感兴趣的新人,都欢迎加入我们的的圈子(技术交流、学习资源、职场吐槽、大厂内推、面试辅导),让我们一起学习成长!**

  • 3
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
实现滚动到底部加载更多,可以借助 Vue 自带的 `scroll` 事件和一些计算属性。具体实现步骤如下: 1. 在需要滚动的容器上监听 `scroll` 事件。 ```html <template> <div ref="container" @scroll="handleScroll"> <!-- 列表内容 --> </div> </template> ``` 2. 在 `handleScroll` 方法中,判断容器滚动到了底部,如果是则触发加载更多的方法。 ```js methods: { handleScroll() { const container = this.$refs.container // 容器滚动到底部 if (container.scrollHeight - container.scrollTop === container.clientHeight) { this.loadMore() } }, loadMore() { // 加载更多数据 } } ``` 3. 计算当前是否有更多数据需要加载,用于显示加载更多按钮或者提示信息。 ```js computed: { hasMore() { return this.list.length < this.total } } ``` 4. 在模板中根据 `hasMore` 的值来显示加载更多按钮或者提示信息。 ```html <template> <div ref="container" @scroll="handleScroll"> <!-- 列表内容 --> <div v-if="hasMore" class="load-more" @click="loadMore">加载更多</div> <div v-else class="no-more">没有更多数据了</div> </div> </template> ``` 完整代码示例: ```html <template> <div ref="container" @scroll="handleScroll"> <ul> <li v-for="(item, index) in list" :key="index">{{ item }}</li> </ul> <div v-if="hasMore" class="load-more" @click="loadMore">加载更多</div> <div v-else class="no-more">没有更多数据了</div> </div> </template> <script> export default { data() { return { list: [], // 列表数据 total: 0, // 总共的数据量 pageSize: 10, // 每页数据量 currentPage: 1 // 当前页数 } }, computed: { hasMore() { return this.list.length < this.total } }, methods: { handleScroll() { const container = this.$refs.container // 容器滚动到底部 if (container.scrollHeight - container.scrollTop === container.clientHeight) { this.loadMore() } }, loadMore() { // 模拟加载更多数据 setTimeout(() => { const start = this.pageSize * (this.currentPage - 1) const end = start + this.pageSize this.list = this.list.concat(Array.from({ length: this.pageSize }).map((_, i) => `Item ${start + i + 1}`)) this.currentPage++ }, 500) } } } </script> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值