list【2】模拟实现(含迭代器实现超详解哦)

news2024/11/26 22:43:19

模拟实现list

  • 引言(实现概述)
  • list迭代器实现
    • 默认成员函数
    • operator* 与 operator->
    • operator++ 与 operator--
    • operator== 与 operator!=
    • 迭代器实现概览
  • list主要接口实现
    • 默认成员函数
      • 构造函数
      • 析构函数
      • 赋值重载
    • 迭代器
    • 容量
    • 元素访问
    • 数据修改
      • insert
      • erase
      • push_back 与 push_front
      • pop_back 与 pop_front
      • clear
      • swap
  • 源码概览
  • 总结

引言(实现概述)

在前面,我们介绍了list的使用:
戳我看list的介绍与使用详解哦

在本篇文章中将重点介绍list的接口实现,通过模拟实现可以更深入的理解与使用list
在这里插入图片描述
我们模拟实现的 list 底层是一个带头双向循环链表

在实现list时,我们首先需要一个结构体以表示链表中结点的结构list_node,大致包括数据与指向前后结点的指针

template<class T>
struct list_node  //结点类
{
	list_node<T>* _prev;
	list_node<T>* _next;
	T _date;

	list_node(const T& date = T()) //匿名对象
		: _prev(nullptr)
		, _next(nullptr)
		, _date(date)
	{}
};

在有了结点之后,还需要一个 list类,包括双向链表的头结点指针_pHead,链表中的元素个数_size

template<class T>
class list  //带头双向循环链表
{
	typedef list_node<T> Node;
private:
	Node* _pHead;
	size_t _size;
};

大致结构如下:
在这里插入图片描述
关于带头双向循环链表的实现可以参考之前的C语言版本实现,在本篇文章中结构将不是最重点的内容:戳我看C语言实现带头双向循环链表详解哦

与vector相同,list是一个类模板,其声明与定义不能分离。我们将模拟实现的list放在我们创建的命名空间内,以防止与库发生命名冲突。
在list的模拟实现中,我们只实现一些主要的接口,包括默认成员函数、迭代器、容量、元素访问与数据修改

list迭代器实现

与vector不同,list的迭代器是指针的封装,通过运算符重载来实现原生指针的功能
我们可以通过封装结点的指针,即list_node<T>*,来实现迭代器:

默认成员函数

对于默认成员函数,其实我们只需要实现构造函数即可,这个__list_iterator类的属性只有一个结构体指针,并不存在类中申请的资源,所以编译器生成的析构函数与赋值运算符重载就够用了,不需要再实现了。

  1. 默认构造
    对于默认构造函数,我们可以使用缺省参数,即参数类型为结构体指针,缺省值为nullptr
    直接在初始化列表中用参数初始化类属性即可:
	__list_iterator(Node* pNode = nullptr)
		:_pNode(pNode)
	{}
  1. 拷贝构造
    对于拷贝构造,参数必须是类类型的引用,可以加上const修饰(我们可以在类中将类类型__list_iterator<T, Ref, Ptr>重命名为self,以方便书写)
    在函数中直接赋值即可:
	__list_iterator(const self& it)
	{
		_pNode = it._pNode;
	}

operator* 与 operator->

迭代器使用时应该是与指针基本类似的,所以也需要重载*->

  1. 重载 *
    指针当然可以进行解引用的操作,指向容器中元素的指针。对这个指针进行解引用操作结果就应该是该指针指向的元素。对于list的迭代器而言,解引用该迭代器的结果就应该是结点中的_data元素的引用,类型为&T
    (我们可以为模板参数加上一个参数,即Ref,它表示T&
	Ref operator*()
	{
		return _pNode->_date;
	}
  1. 重载->
    T是自定义类型时,其指针还可以通过->直接访问到T类型对象_data中的元素,起指针作用的迭代器自然也需要实现这个功能
    但是对于这个运算符重载而言,它并不知道要返回T类型对象中的什么元素,所以这个operator->函数可以直接返回该迭代器对应的结点中的_data元素的指针,然后在调用的时候再通过->来访问其中元素:it->->a;(通过迭代器it访问T类型对象中的a元素)。
    但是,这样的形式访问又与指针的用法不一致,所以这里有一个特殊的规定,即规定需要使用it->a;的方式通过迭代器访问T类型对象中的元素,以获得与原生指针相同的使用方式
    (我们可以为模板参数加上一个参数,即Ptr,它表示T*
	Ptr operator->()
	{
		return &(_pNode->_date);
	}

operator++ 与 operator–

  1. 重载 ++
    ++操作即实现迭代器的指向向后移动一个元素,list的迭代器底层是一个结构体指针,所以只需要将当前_pNode->_next 赋值给_pNode即可
    ++分为前置++与后置++,在区别这两个的实现时,前面类和对象时已经详细介绍过了,即给后置++增加一个参数int,但是不传参,只用于区分前置与后置。
    另外后置++需要创建临时对象,在*this++后必须要返回临时对象而非引用:
	self& operator++()
	{
		_pNode = _pNode->_next;
		return *this;
	}
	self operator++(int)
	{
		self temp(*this);
		_pNode = _pNode->_next;
		return temp;
	}
  1. 重载 --
    重载--与前面的++类似,即_pNode->_prev 赋值给_pNode即可
	self& operator--()
	{
		_pNode = _pNode->_prev;
		return *this;
	}
	self operator--(int)
	{
		self temp(*this);
		_pNode = _pNode->_prev;
		return temp;
	}

operator== 与 operator!=

对于==!= 重载,即判断两个迭代器对象的属性是否相等即可

	bool operator==(const self& it)
	{
		return _pNode == it._pNode;
	}
	bool operator!=(const self& it)
	{
		return _pNode != it._pNode;
	}

迭代器实现概览

	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* pNode = nullptr)
			:_pNode(pNode)
		{}
		__list_iterator(const self& it)
		{
			_pNode = it._pNode;
		}


		Ref operator*()
		{
			return _pNode->_date;
		}
		Ptr operator->()
		{
			return &(_pNode->_date);
		}


		self& operator++()
		{
			_pNode = _pNode->_next;
			return *this;
		}
		self operator++(int)
		{
			self temp(*this);
			_pNode = _pNode->_next;
			return temp;
		}
		self& operator--()
		{
			_pNode = _pNode->_prev;
			return *this;
		}
		self operator--(int)
		{
			self temp(*this);
			_pNode = _pNode->_prev;
			return temp;
		}


		bool operator==(const self& it)
		{
			return _pNode == it._pNode;
		}
		bool operator!=(const self& it)
		{
			return _pNode != it._pNode;
		}
	};

list主要接口实现

在实现list之前,我们可以对一些较为麻烦的类型进行重命名以方便书写:

typedef list_node<T> Node;
typedef __list_iterator<T, T&, T*> iterator;
typedef __list_iterator<T, const T&, const T*> const_iterator;

默认成员函数

构造函数

实现构造函数时,我们需要实现无参构造、n个指定元素构造、迭代器区间构造以及拷贝构造

无参构造
由于我们模拟实现的list底层为带头双向循环链表,所以在无参构造时虽然没有在list中放入元素,但是还使需要先放入一个空结点作为头节点
创建头节点的操作在任何构造函数中都要先进行,所以我们将其封装为一个init_empty_list函数。这个初始化空list的函数需要new一个结点,然后使节点中的_prev_next都指向它自身,最后将_size赋值为0:

	void init_empty_list()
	{			
		_pHead = new Node();
		_pHead->_prev = _pHead;
		_pHead->_next = _pHead;
		_size = 0;
	}
	
	list()
	{
		init_empty_list();
	}

n个指定元素构造:
使用n个指定元素构造有两个参数,第一个就是int,第二个是const T&,缺省值为T()
函数中,首先调用init_empty_list构建一个头结点;
再循环,使用push_bake尾插n个指定元素value即可(push_back后面会实现):

	list(int n, const T& value = T())
	{
		init_empty_list();
		while (n--)
		{
			push_back(value);
		}
	}

迭代器区间构造
是用迭代器区间构造函数是一个函数模板,可以使用任何容器的迭代器区间来构造list
函数中,首先调用init_empty_list构建一个头结点;
然后再循环,使用push_bake尾插first迭代器的解引用出的元素,当firstlast相等时出循环:

	template <class Iterator>
	list(Iterator first, Iterator last)
	{
		init_empty_list();
		while (first != last)
		{
			push_back(*first);
			++first;
		}
	}

拷贝构造:
拷贝构造函数的参数是一个const list<T>&
实现时,首先调用init_empty_list构建一个头结点;
然后范围for,使用push_bake将l中的元素逐一尾插到list中:

	list(const list<T>& l)
	{
		init_empty_list();
		for (auto el : l)
		{
			push_back(el);
		}
	}

析构函数

析构函数需要实现释放list中的资源。
首先复用clear,清空list中的元素。clear中会实现释放结点中的资源,后面部分会实现;
delete _pHead;,释放头节点中的资源,它会调用结点结构体的析构函数

	~list()
	{
		clear();
		delete _pHead;
	}

赋值重载

对于赋值运算符重载,我们直接使用新写法,即先使用参数l创建一个临时对象;
然后使用swap将临时对象与*this交换(后面会实现swap函数);
最后返回*this即可,创建的临时对象就会在函数栈帧销毁时自动释放

	list<T>& operator=(const list<T>& l)  //list& operator=(const list l) 对于赋值重载,这样也可
	{
		list<T> temp(l);
		swap(temp);
		return *this;
	}

迭代器

在前面已经实现了list的迭代器,它是结点指针的封装

这里暂时只实现beginend,关于反向迭代器的实现在后面会详细介绍。
begin返回首元素的地址,即头结点的下一个结点的地址
end返回尾元素下一个位置的地址,即头节点的地址,他们分别重载有const版本:

	iterator begin()
	{
		return iterator(_pHead->_next);
	}
	iterator end()
	{
		return iterator(_pHead);
	}
	
	const_iterator begin() const
	{
		return const_iterator(_pHead->_next);
	}
	const_iterator end() const
	{
		return const_iterator(_pHead);
	}

容量

在容器部分,由于list并没有容量的概念,所以我们只需要实现sizeempty即可;
在这里插入图片描述
我们在list的属性中,我们设置了_size,在插入元素时_size++,删除元素时_size--,所以这里只需要返回_size的值即可;
_size == 0时,list为空,empty返回true,否者返回false

	size_t size() const
	{
		return _size;
	}
	bool empty() const
	{
		if (_size == 0)
			return true;
		else
			return false;
	}

元素访问

由于list在任意位置访问元素的成本较高,就没有提供operator[]的接口,所以我们只需要实现frontback即可。分别返回首尾的元素,有普通对象与const对象两个重载版本:
在这里插入图片描述
在实现时,我们可以借助list的属性_pHead,即头结点的指针来访问首尾元素
我们模拟实现list的底层是带头双向循环链表,所以list中的第一个元素就是_pHead->_next指向的结点中的元素;list中的_pHead->_prev指向的结点中的元素

front只需要返回 _pHead->_next->_date 即可;
back返回_pHead->_prev->_date即可,返回值类型为T&,const版本就返回const T&即可:

	T& front()
	{
		return _pHead->_next->_date;
	}
	const T& front()const
	{
		return _pHead->_next->_date;
	}
	
	T& back()
	{
		return _pHead->_prev->_date;
	}
	const T& back()const
	{
		return _pHead->_prev->_date;
	}

数据修改

在这里插入图片描述

insert

list 的结构使在任意位置插入数据的效率是较高的,只需要创建结点,再链接到pos位置前即可

在实现insert时,首先new一个结点,类型为Node,并用val初始化(这个Node类型是前面重命名后的类型);
这时我们需要记录pos位置的结点指针为curpos位置前面的结点指针为prev,以方便后续链接;
然后将pos结点的前一个结点与新结点链接,即newnodeprev链接;
再将pos结点与新结点链接,即newnodecur链接;

最后,更新_size,并返回新结点的迭代器:

	// 在pos位置前插入值为val的节点
	iterator insert(iterator pos, const T& val)
	{
		Node* newnode = new Node(val);

		Node* cur = pos._pNode;
		Node* prev = cur->_prev;

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

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

		++_size;

		return iterator(newnode);
	}

erase

erase实现时,只需要释放pos位置的结点,并链接剩余的结点即可:

首先assert判断list是否为空;
这时我们需要记录pos位置的结点指针为curpos位置前面的结点指针为prevpos的后一个结点指针为next,以方便后续链接;
然后直接链接pos位置的前一个结点与后一个结点,即链接prevnext即可;

最后,释放cur指向的结点,更新_size,并返回next

	// 删除pos位置的节点,返回该节点的下一个位置
	iterator erase(iterator pos)
	{
		assert(!empty());
		Node* cur = pos._pNode;
		Node* prev = cur->_prev;
		Node* next = cur->_next;

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

		delete cur;
		--_size;
		return iterator(next);
	}

push_back 与 push_front

对于头插与尾插的实现,复用insert即可:

push_front,即在首结点的前面一个位置插入一个元素,即begin()迭代器位置插入
push_back,即在尾结点的后一个位置插入一个元素,即end()位置插入

	void push_back(const T& val)
	{
		insert(end(), val);
	}
	void push_front(const T& val)
	{
		insert(begin(), val);
	}

pop_back 与 pop_front

对于头删尾删的实现,复用erase即可

pop_front,即删除头节点,即 erase删除begin()位置的结点 即可;
pop_back,删除尾结点,即 erase删除end()前面一个结点即可,但是由于list迭代器不支持-操作,所以这里传参为--end()

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

clear

clear用于清理list中的所有元素,可以直接复用erase来实现清理

我们可以通过遍历迭代器的方式逐一释放结点:
it的初始值为begin(),循环逐一erase删除,当it等于end()的时候终止循环,就可以实现删除所有结点并保留头节点;
另外,由于erase删除某节点后,会返回删除节点的下一个位置,所以只要把返回值载赋值给it就实现了迭代器的向后移动

	void clear()
	{
		iterator it = begin();
		while (it != end())
		{
			it = erase(it);//erase返回删除的结点的下一个位置的迭代器
		}
	}

swap

实现list的交换函数时,只需要使用库swap交换list的属性即可,即交换_pHead_size

	void swap(list<T>& l)
	{
		std::swap(_pHead, l._pHead);
		std::swap(_size, l._size);
	}

源码概览

(关于反向迭代器的实现在后面会详细介绍,现在可以暂时忽略)

#include<iostream>
#include<cassert>
#include"my_reverse_iterator.h"

namespace qqq
{
	template<class T>
	struct list_node
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _date;

		list_node(const T& date = T()) //匿名对象
			: _prev(nullptr)
			, _next(nullptr)
			, _date(date)
		{}
	};

	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* pNode = nullptr)
			:_pNode(pNode)
		{}
		__list_iterator(const self& it)
		{
			_pNode = it._pNode;
		}


		Ref operator*()
		{
			return _pNode->_date;
		}
		Ptr operator->()
		{
			return &(_pNode->_date);
		}


		self& operator++()
		{
			_pNode = _pNode->_next;
			return *this;
		}
		self operator++(int)
		{
			self temp(*this);
			_pNode = _pNode->_next;
			return temp;
		}
		self& operator--()
		{
			_pNode = _pNode->_prev;
			return *this;
		}
		self operator--(int)
		{
			self temp(*this);
			_pNode = _pNode->_prev;
			return temp;
		}


		bool operator==(const self& it)
		{
			return _pNode == it._pNode;
		}
		bool operator!=(const self& it)
		{
			return _pNode != it._pNode;
		}
	};

	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;
		typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
		typedef ReverseIterator<const_iterator, const T&, const T*> const_reverse_iterator;

	public:
		/ constructor and destructor 
		void init_empty_list()
		{			
			_pHead = new Node();
			_pHead->_prev = _pHead;
			_pHead->_next = _pHead;

			_size = 0;
		}
		list()
		{
			init_empty_list();
		}
		list(int n, const T& value = T())
		{
			init_empty_list();

			while (n--)
			{
				push_back(value);
			}
		}
		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			init_empty_list();
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		list(const list<T>& l)
		{
			init_empty_list();
			for (auto el : l)
			{
				push_back(el);
			}
		}
		list<T>& operator=(const list<T>& l)  //list& operator=(const list l) 对于赋值重载,这样也可
		{
			list<T> temp(l);
			swap(temp);
			return *this;
		}
		~list()
		{
			clear();
			delete _pHead;
		}


		// List Iterator///
	
		iterator begin()
		{
			return iterator(_pHead->_next);
		}
		iterator end()
		{
			return iterator(_pHead);
		}
		const_iterator begin() const
		{
			return const_iterator(_pHead->_next);
		}
		const_iterator end() const
		{
			return const_iterator(_pHead);
		}
		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}
		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}


		/List Capacity//
		size_t size() const
		{
			return _size;
		}
		bool empty() const
		{
			if (_size == 0)
				return true;
			else
				return false;
		}

		///List Access///
		T& front()
		{
			return _pHead->_next->_date;
		}
		const T& front()const
		{
			return _pHead->_next->_date;
		}
		T& back()
		{
			return _pHead->_prev->_date;
		}
		const T& back()const
		{
			return _pHead->_prev->_date;
		}

		List Modify//
		void push_back(const T& val)
		{
			insert(end(), val);
		}
		void pop_back()
		{
			erase(--end());
		}
		void push_front(const T& val)
		{
			insert(begin(), val);
		}
		void pop_front()
		{
			erase(begin());
		}
		// 在pos位置前插入值为val的节点
		iterator insert(iterator pos, const T& val)
		{
			Node* newnode = new Node(val);

			Node* cur = pos._pNode;
			Node* prev = cur->_prev;

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

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

			++_size;

			return iterator(newnode);
		}
		// 删除pos位置的节点,返回该节点的下一个位置
		iterator erase(iterator pos)
		{
			assert(!empty());
			Node* cur = pos._pNode;
			Node* prev = cur->_prev;
			Node* next = cur->_next;

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

			delete cur;
			--_size;
			return iterator(next);
		}
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);//erase返回删除的结点的下一个位置的迭代器
			}
		}
		void swap(list<T>& l)
		{
			std::swap(_pHead, l._pHead);
			std::swap(_size, l._size);
		}

	private:
		Node* _pHead;
		size_t _size;
	};
};

总结

到此,关于list的模拟实现就到此结束了
模拟实现容器并不是为了造一个更好的轮子,而是为了更好的理解与使用容器

如果大家认为我对某一部分没有介绍清楚或者某一部分出了问题,欢迎大家在评论区提出

如果本文对你有帮助,希望一键三连哦

希望与大家共同进步哦

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/969332.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数学建模--二维插值函数模型的Python实现

目录 1.算法实现步骤 2.算法核心代码 3.算法效果展示 1.算法实现步骤 #二维插值函数的展示通过Axes3D函数来进行实现 #我们需要绘制出20*20的插值效果和500*500的插值效果,进行比较. 具体步骤如下所示: 1.将x-y分为20*20并且绘制网格图 2.进行20*20的插值计算并且绘制可视化图…

使用SimPowerSystems并网光伏阵列研究(Simulink实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

9.4.tensorRT高级(4)封装系列-使用pybind11为python开发扩展模块

目录 前言1. pybind112. 补充知识2.1 pybind11 介绍 总结 前言 杜老师推出的 tensorRT从零起步高性能部署 课程&#xff0c;之前有看过一遍&#xff0c;但是没有做笔记&#xff0c;很多东西也忘了。这次重新撸一遍&#xff0c;顺便记记笔记。 本次课程学习 tensorRT 高级-使用p…

《网络是怎样连接的》(六)

本文主要取材于 《网络是怎样连接的》 第六章。 目录 6.1 服务器概览 6.2 服务器的接收操作 6.3 Web服务器程序解释请求消息并作出响应 6.4 浏览器接收响应消息并显示内容 简述&#xff1a;本文主要内容是解释 网络包到达服务器之后&#xff0c;如何给客户端响应的。 服务…

电商实战项目(java)知识点整理(持续更新)《苍穹外卖》

一、重要知识点精讲 1.1 nginx反向代理 1. nginx反向代理好处&#xff1a; 1. 提高访问速度&#xff08;可以进行缓存&#xff0c;如果访问相同资源可以直接响应数据&#xff09; 2. 可以进行负载均衡&#xff08;如果没有nginx前端只能固定地访问后端某一台服务器&#xf…

Linux系统编程—socket网络编程

Linux系统编程—socket网络编程 理论概念1. TCP与UDP对比端口号作用 socket开发过程服务端1. socket 创建套接字2. bind 绑定IP端口3. listen 监听客户端4. accept 接收客户端5. read / write 数据传输 客户端1. socket 创建套接字2. connect 连接服务3. read / write 数据传输…

合宙Air724UG LuatOS-Air LVGL API控件--下拉框 (Dropdown)

下拉框 (Dropdown) 在显示选项过多时&#xff0c;可以通过下拉框收起多余选项。只为用户展示列表中的一项。 示例代码 -- 回调函数 event_handler function(obj, event)if (event lvgl.EVENT_VALUE_CHANGED) thenprint("Option:", lvgl.dropdown_get_symbol(obj)…

稀疏数组的实现

文章目录 目录 文章目录 前言 一 什么是稀疏数组? 二 稀疏数组怎么存储数据? 三 稀疏数组的实现 总结 前言 大家好,好久不见了,这篇博客是数据结构的第一篇文章,望大家多多支持! 一 什么是稀疏数组? 稀疏数组&#xff08;Sparse Array&#xff09;是一种数据结构&a…

Elastic-job分布式调度系统

一、定时任务实现方式 1、Thread方式 final int timeInterval 1000;Thread thread new Thread(new Runnable() {Overridepublic void run() {while (true){try {//每一秒执行一次Thread.sleep(timeInterval);System.out.println("run...");} catch (InterruptedE…

数据结构和算法(1):开始

算法概述 所谓算法&#xff0c;即特定计算模型下&#xff0c;旨在解决特定问题的指令序列 输入 待处理的信息&#xff08;问题&#xff09; 输出 经处理的信息&#xff08;答案&#xff09; 正确性 的确可以解决指定的问题 确定性 任一算法都可以描述为一个由基本操作组成的序…

SpringBoot核心原理与实践

第一章、SpringBoot简介 1、入门案例 2、官网创建压缩包程序 注意使用的版本pom文件中java --> 1.8、 springboot --> 2.5.0 3、SpringBoot快速启动 运行程序--找引导类 换技术、加技术--加starter 第二章、基础配置 1、配置文件格式 《1、端口号配置》 《2、将目录文…

React原理 - React Hooks

目录 扩展学习资料 React Hooks 编写函数组件 Hooks使命 Hooks解决了什么问题 Hooks原理 useState源码解析 mountState源码解析 Hooks应用 Hooks 实践 倒计时组件 练习 扩展学习资料 名称 链接 React Hooks 官方文档 Introducing Hooks – React useEffect 完整…

PYTHON知识点学习-列表和元组

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由 Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

把一般数据转换成因子数据格式,做单因子、债券对历史数据回测+获取curl命令+垃圾数据转换成标准行情数据(bardata)

下载curl软件&#xff0c;地址&#xff1a; curl for Windows for 64-bit下载好后解压到文件夹&#xff0c;将里面的bin文件添加到环境变量中&#xff0c;bon文件地址为&#xff1a;C:\Users\59980\curl-8.2.1_7-win64-mingw\bin 打开cmd&#xff0c;输入curl --help,出现下…

软考:中级软件设计师:程序语言基础:表达式,标准分类,法律法规,程序语言特点,函数传值传址

软考&#xff1a;中级软件设计师:程序语言基础&#xff1a;表达式 提示&#xff1a;系列被面试官问的问题&#xff0c;我自己当时不会&#xff0c;所以下来自己复盘一下&#xff0c;认真学习和总结&#xff0c;以应对未来更多的可能性 关于互联网大厂的笔试面试&#xff0c;都…

ssm民宿管理系统源码和论文

ssm民宿管理系统源码和论文110 开发工具&#xff1a;idea 数据库mysql5.7 数据库链接工具&#xff1a;navcat,小海豚等 技术&#xff1a;ssm 摘 要 现代经济快节奏发展以及不断完善升级的信息化技术&#xff0c;让传统数据信息的管理升级为软件存储&#xff0c;归纳&…

SSM整合~

构建并配置项目&#xff1a; 第一步&#xff1a;创建maven项目 第二步&#xff1a;配置pom.xml文件 设置打包方式&#xff1a; <packaging>war</packaging>设置版本号为自定义属性&#xff1a; <properties><!--将版本号通过自定义属性配置--><…

跨站请求伪造(CSRF)攻击与防御原理

跨站请求伪造&#xff08;CSRF&#xff09; 1.1 CSRF原理 1.1.1 基本概念 跨站请求伪造&#xff08;Cross Site Request Forgery&#xff0c;CSRF&#xff09;是一种攻击&#xff0c;它强制浏览器客户端用户在当前对其进行身份验证后的Web 应用程序上执行非本意操作的攻击&a…

差异化竞争阵地的所在【周技术进阶】-从BS 项目C#最基础截取字符串方法开始

效果 代码 using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks;namespace ConsoleAppNumberOneHelloWorld {class Program{static void Main(string[] args){Console.WriteLine("hello world&#xf…

TCP机制之确认应答及超时重传

TCP因为其可靠传输的特性被广泛使用,这篇博客将详细介绍一下TCP协议是如何保证它的可靠性的呢?这得主要依赖于其确认应答及超时重传机制,同时三次握手四次挥手也起到了少部分不作用,但是主要还是由确认应答和超时重传来决定的;注意:这里的可靠传输并不是说100%能把数据发送给接…