【C++初阶】模拟实现list

在这里插入图片描述

👦个人主页:@Weraphael
✍🏻作者简介:目前学习C++和算法
✈️专栏:C++航路
🐋 希望大家多多支持,咱一起进步!😁
如果文章对你有帮助的话
欢迎 评论💬 点赞👍🏻 收藏 📂 加关注✨


目录

  • 一、简单剖析list源码
  • 二、准备工作
  • 三、模拟实现list常见操作
      • 3.1 默认构造函数
      • 3.2 push_back - 尾插
      • 3.3 迭代器(重点)
      • 3.4 const的迭代器(重点)
      • 3.5 insert - 插入
      • 3.6 erase - 删除
      • 3.7 头插 - push_front
      • 3.8 尾删 - pop_back
      • 3.9 头删 - pop_front
      • 3.10 个数 - size
      • 3.11 析构
      • 3.12 清空 - clear
      • 3.13 拷贝构造
      • 3.14 交换
      • 3.15 赋值运算符重载
  • 四、源码

一、简单剖析list源码

在模拟vector容量讲过,要想快速了解STL源码,首先要看成员变量

在这里插入图片描述

node从名字上猜测是一个节点,其类型是list_node。然后我发现list_node也是重命名出来的:

在这里插入图片描述

__list_node<T>又是什么东西呢?如下所示:

在这里插入图片描述

显然这是一个双向链表,并且__list_node是用来定义结点的

在这里插入图片描述

接下来就应该分析构造函数

在这里插入图片描述

get_node从名字上是得到结点,那么应该是开辟空间的。我们可以简单看看:

在这里插入图片描述

空间配置器讲起来有点麻烦,直接使用newdelete也是够用的

然后nodenextprev都指向自己。因此list的底层是一个带头(哨兵位)双向循环链表,因此list的成员变量应该是哨兵位结点。

大致结构我们已经知道了,不妨再来看看插入操作:

在这里插入图片描述

这和以往学习过的双向循环链表很相似,无非就是创造新的结点,然后再把它们链接起来。

大致内容已经了解了,直接开始实现吧~

二、准备工作

为了方便管理代码,分两个文件来写:

  • Test.cpp - 测试代码逻辑
  • list.h - 模拟实现list
    在这里插入图片描述

三、模拟实现list常见操作

3.1 默认构造函数

namespace wj
{
	template<class T>
	struct list_node // 定义结点
	{
		list_node<T>* _next; 
		list_node<T>* _prev;
		T _val;
	};

	template<class T>
	class list
	{
	public:
		list()
		{
			// 为哨兵位头结点开空间
			_head = new list_node<T>;
			// 自己指向自己
			_head->_prev = _head;
			_head->_next = _head;
		}
	private:
		list_node<T> _head; // 哨兵位(不存储有效数据)
	};
}

定义结点的成员变量最好是公有的,方便类外可以随时访问。注意:此处的struct可不是C语言的结构体,在C++中已经升级成了类,并且默认成员都是公有的。当然使用class也是没问题的,只是要加上public

以上代码还能简化,我们知道类模板和普通类是不同的,普通类的类名即是类型,而类模板的类名是类名<T>。而有许多人会很容易忘记加上<T>,因此我们可以对list_node<T>进行重命名typedef

namespace wj
{
	template<class T>
	struct list_node // 定义结点
	{
		list_node<T>* _next; 
		list_node<T>* _prev;
		T _val;
	};

	template<class T>
	class list
	{
		typedef list_node<T> Node;
	public:
		list()
		{
			// 为哨兵位头结点开空间
			_head = new Node;
			// 自己指向自己
			_head->_prev = _head;
			_head->_next = _head;
		}
	private:
		list_node<T> _head; // 哨兵位(不存储有效数据)
	};
}
  • 为了防止与库的list冲突,要重新写一个命名空间域wj
  • typedef在类中是有讲究的。如果typedef放在public段中,则可以在类外部使用;而如果放在private段中,则只能在类内使用。注意:上述代码是只能在类中使用!

3.2 push_back - 尾插

void push_back(const T& val)
{
	//1. 找尾(哨兵位的prev)
	Node* tail = _head->_prev;
	// 2. 开辟一个新节点
	Node* newnode = new Node(val); 
	// 3. 链接 _head tail newnode
	tail->_next = newnode;
	newnode->_prev = tail;
	newnode->_next = _head;
	_head->_prev = newnode;
}

尾插就容易多了,下面有图帮助大家理解:

在这里插入图片描述

注意:new对于自定义类型除了开空间,还会调用构造函数。初始化_val

struct list_node // 结点的定义
{
	list_node<T>* _next;
	list_node<T>* _prev;
	T _val; 

	list_node(const T& val = T())
		:_next(nullptr)
		, _prev(nullptr)
		, _val(val)
	{}
};

缺省值给T()相信看过模拟实现vector都不陌生。不能直接给0,这样就写死能,只能int类型适用,对于string就不行了。因此可以给个匿名对象,它会调用T类型的默认构造。内置类型也是有默认构造的:

在这里插入图片描述

3.3 迭代器(重点)

能否定义类似像vector的迭代器?如下所示:

typedef Node* iterator;

答案当然不行!list不能像vector一样以原生指针(普通指针)作为迭代器。

vector类似于数组,数据在内存中是连续存储的。对迭代器(指针)++,就可以跳过一个对象的大小,并且解引用也能得到对应的数据;然而,list的节点不能保证一定在内存空间中连续存在,导致++/--不一定能找到下一个节点,并且对其解引用得到的是结点而不是有效数据。

那问题来了,如何定义list的迭代器呢?

我们可以封装一个类,然后用重载运算符去改变指针的行为。为什么可以这样呢?原因是:内置类型的++是行为规定的,但是自定义类型的++是自己说的算。可以联想以往实现的日期类->点击跳转

auto it = l.begin();
while (it != l.end())
{
	cout << *it << ' ';
	++it;
}

我们可以对照以上代码一步一步实现迭代器

  • begin() + end()

在这个类中,只需要一个结点类的指针成员变量,用于指向list某一个结点, 在一开始定义迭代器时,需要一个构造函数,用于迭代器的初始化。注意:beginend需要定义在list类中,因为它们本身就是list内置的接口函数

// 封装一个类实现迭代器
template<class T>
struct __list_iterator 
{
	typedef list_node<T> Node;
	Node* _node; //指向某个节点的指针

	// 迭代器的初始化
	__list_iterator(Node* node) 
		:_node(node)
	{}
};

template<class T>
class list
{
	typedef list_node<T> Node;
public:
	typedef __list_iterator<T> iterator; 

	iterator begin()
	{
		return _head->_next;
		// return iterator(_head->_next);
	}

	iterator end()
	{
		return _head;
		//return iterator(_head);
	}
private:
	Node* _head;
};

这里还有一个知识点,beginend返回类型为迭代器,怎么能返回结点的指针呢?— 这是因为单参数的构造函数支持隐式类型转换。

  • !=== *++--

封装一个类,然后用重载运算符去改变指针的行为

// 封装一个类实现迭代器
template<class T>
struct __list_iterator 
{
	typedef list_node<T> Node;
	typedef __list_iterator<T> self;
	
	Node* _node; //指向某个节点的指针

	__list_iterator(Node* node) // 迭代器的初始化
		:_node(node)
	{}
/
	// 用结点的指针比
	bool operator!=(const self& it) const
	{
		return _node != it._node;
	}

	bool operator==(const self& it) const
	{
		return _node == it._node;
	}
/
	T& operator*()
	{
		// 出了作用域,结点还在,引用返回
		return _node->_val;
	}
/
	// 迭代器++返回的还是迭代器
	self& operator++() //前置
	{
		_node = _node->_next;
		return *this;
	}

	self& operator--() // 前置
	{
		_node = _node->_prev;
		return *this;
	}

	self operator--(int) // 后置
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	self operator++(int) // 后置
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}
};

前置++后置++会发生一个问题:函数名会相同。因此,C++规定:后置(++/--)重载时多增加一个int类型的参数,但调用函数时该参数不用传递。

3.4 const的迭代器(重点)

现在又有一个问题,const的迭代器也能否像类似于vector一样设计?如下所示:

在这里插入图片描述

答案当然是不可以的!这是因为 const迭代器要求的是迭代器指向的内容不可以被修改,而对一个类加上一个const,这是让这个类对象无法被修改啊。也就是类的成员变量都不可以被修改,这样一来,这个迭代器里面的指针无法移动了。(const的迭代器指针是可以移动的,但是指向的内容不可被修改)

那么const的迭代器该如何设计呢?我们知道,list迭代器输出数据是依靠解引用的,因此可以在返回值加上const

template<class T>
struct __list_iterator
{
	typedef list_node<T> Node;
	typedef __list_iterator<T> self

	Node* _node; //指向某个节点的指针

	__list_iterator(Node* node) // 迭代器的初始化
		:_node(node)
	{}
	
	// 用结点的指针比
	bool operator!=(const self& it) const
	{
		return _node != it._node;
	}

	bool operator==(const self& it) const
	{
		return _node == it._node;
	}
	
	T& operator*()
	{
		// 出了作用域,结点还在,引用返回
		return _node->_val;
	}
	
	// 迭代器++返回的还是迭代器
	self& operator++() //前置
	{
		_node = _node->_next;
		return *this;
	}

	self& operator--() // 前置
	{
		_node = _node->_prev;
		return *this;
	}

	self operator--(int) // 后置
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	self operator++(int) // 后置
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}
};

template<class T>
struct __list_iterator
{
	typedef list_node<T> Node;
	typedef __list_iterator<T> self;

	Node* _node; //指向某个节点的指针

	__list_iterator(Node* node) // 迭代器的初始化
		:_node(node)
	{}

	// 用结点的指针比
	bool operator!=(const self& it) const
	{
		return _node != it._node;
	}

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

	const T& operator*()
	{
		// 出了作用域,结点还在,引用返回
		return _node->_val;
	}

	// 迭代器++返回的还是迭代器
	self& operator++() //前置
	{
		_node = _node->_next;
		return *this;
	}

	self& operator--() // 前置
	{
		_node = _node->_prev;
		return *this;
	}

	self operator--(int) // 后置
	{
		self tmp(*this);
		_node = _node->_prev;
		return tmp;
	}

	self operator++(int) // 后置
	{
		self tmp(*this);
		_node = _node->_next;
		return tmp;
	}
};

但以上代码显得有点冗余,只有两个函数的返回值不一样,其它都是一样的。那还有什么别的设计方法呢?

注意:上面两个函数只要返回值的类型不一样,因此可以通过一个类型来控制返回值 -> 即增加一个模板参数(库里也是这么实现的~)

// 封装一个类实现迭代器
template<class T, class Ref> // 增加一个模板参数
struct __list_iterator
{
	typedef list_node<T> Node;
	typedef __list_iterator<T, Ref> self;

	Node* _node; //指向某个节点的指针

	__list_iterator(Node* node) // 迭代器的初始化
		:_node(node)
	{}
	
	Ref operator*()
	{
		return _node->_val;
	}
}

template<class T>
class list
{
	typedef list_node<T> Node;
public:
	typedef __list_iterator<T, T&> iterator;
	typedef __list_iterator<T, const T&> const_iterator;

	iterator begin()
	{
		return _head->_next;
	}

	const_iterator end() const
	{
		return _head;
	}

	const_iterator begin() const
	{
		return _head->_next;
	}

	iterator end()
	{
		return _head;
	}
private:
	list_node<T> _head; // 哨兵位(不存储有效数据)
};

补充:除了重载*运算符,当然也要重载->操作符

T* operator->() 
{
	return &_node->_val;
}

那什么时候会用到->操作符呢?下面有个例子:

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

struct A
{
	A(int a = 0)
		:_a(a)
	{}
	int _a;
};
int main()
{
	wj::list<A> lt;
	lt.push_back(A(1));
	lt.push_back(A(2));
	lt.push_back(A(3));
	lt.push_back(A(4));
	lt.push_back(A(5));

	wj::list<A>::iterator it = lt.begin();
	while (it != lt.end())
	{
		cout << it->_a << " ";
		it++;
	}
	cout << endl;
}

【输出结果】

在这里插入图片描述

有没有发现operator->非常怪,首先我们这个运算符重载返回的是什么呢?是T*,也就是A*,也就是说它还需要一次->才能打印_a。严格来说,it->->_a,才是符合语法的。那么这里为什么还能编译通过呢?因为运算符重载要求可读性,那么编译器特殊处理,省略了一个->

但是以上代码还是不够完善,由于->只针对普通对象,如果是const对象,其返回值应该是const T*,这个问题就和运算符重载*类似了,再增加一个模板参数,因此完整代码如下:

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->_val;// 出了作用域,结点还在,要加&
	}

	ptr operator->() 
	{
		return &_node->_val;
	}
}

template<class T> // 为list提供
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; 

	iterator begin()
	{
		// return iterator(_head->_next);
		return _head->_next;
	}

	iterator end()
	{
		// return iterator(_head);
		return _head;
	}
private:
	Node* _head; // 哨兵位(不存储有效数据)
};

3.5 insert - 插入

iterator insert(iterator pos, const T& x)
{
	// pos 不需要检查  
	// 假设在node前插入
	// head newnode node tail
	// 步骤如下
	
	// 1. 开辟新的结点
	Node* newnode = new Node(x);
	// 2. 找到要删除的结点node
	Node* cur = pos._node;
	// 3. 以及node的前一个节点
	Node* prev = cur->_prev;
	// 4. 链接
	prev->_next = newnode;
	newnode->_next = cur;
	cur->_prev = newnode;
	newnode->_prev = prev;

	return newnode;// 返回新插入元素的位置
}

在这里插入图片描述

3.6 erase - 删除

iterator erase(iterator pos)
{
	// 检查pos的有效性
	assert(pos != end());
	
	// 1.分别找到pos的前一个节点和后一个节点
	Node* cur = pos._node;
	Node* prev = cur->_prev;
	Node* next = cur->_next;
	// 2, 链接
	prev->_next = next;
	next->_prev = prev;
	// 3. 删除
	delete cur;
	
	// 注意:list的erase会有迭代器失效问题
	// 返回删除元素的下一个位置
	return next;
}

在这里插入图片描述

3.7 头插 - push_front

复用insert

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

3.8 尾删 - pop_back

复用erase

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

3.9 头删 - pop_front

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

3.10 个数 - size

遍历即可

size_t size()
{
	size_t count = 0;
	iterator it = begin();
	while (it != end())
	{
		++count;
		++it;
	}
	return count;
}

或者还可以在成员变量中定义size_t _size,每次插入数据++,以及删除数据--即可

3.11 析构

~list()
{
	clear();
	delete _head;
	_head = nullptr;
}

3.12 清空 - clear

void clear()
{
	iterator it = begin();
	while (it != end())
	{
		it = erase(it);
	}
}

3.13 拷贝构造

list(const list<T>& it)
{
	_head = new Node;
	_head->_prev = _head;
	_head->_next = _head;

	for (auto& e : it)
	{
		push_back(e);
	}
}

3.14 交换

void swap(list<T> it)
{
	std::swap(_head, it._head);
	std::swap(this->size(), it._size());
}

3.15 赋值运算符重载

list<T>& operator=(const list<T> it)
{
	swap(it);
	return *this;
}

四、源码

#pragma once
#include <assert.h>

namespace wj
{
	template<class T> 
	struct list_node 
	{
		list_node<T>* _next;
		list_node<T>* _prev;
		T _val; 

		list_node(const T& val = T())
			:_next(nullptr)
			, _prev(nullptr)
			, _val(val)
		{}
	};

	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->_val;
		}

		ptr operator->() 
		{
			return &_node->_val;
		}

		self& operator++()
		{
			_node = _node->_next;
			return *this;
		}

		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}

		self& operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}

		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;
			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_iterator<T, T&, T*> iterator; 
		typedef __list_iterator<T, const T&, const T*> const_iterator; 

		iterator begin()
		{
			// return iterator(_head->_next);
			return _head->_next;
		}

		iterator end()
		{
			// return iterator(_head);
			return _head;
		}

		const_iterator begin() const
		{
			//return _head->_next;
			return const_iterator(_head->_next);
		}

		const_iterator end() const
		{
			return _head;
			//return const_iterator(_head);
		}

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

			_size = 0;
		}

		list(const list<T>& it)
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
			_size = 0;

			for (auto& x : it)
			{
				push_back(x);
			}
		}

		void push_back(const T& val)
		{
			Node* tail = _head->_prev;
			Node* newnode = new Node(val);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
			_size++;
		}

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

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

			_size++;
			return newnode;
		}

		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;
			_size--;

			return next;
		}

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

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

		void pop_front()
		{
			erase(begin());
		}
		
		size_t size()
		{
			/*size_t count = 0;
			iterator it = begin();
			while (it != end())
			{
				++count;
				++it;
			}
			return count;*/
			return _size;
		}

		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
			_size = 0;
		}

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

		list<T>& operator=(const list<T> it)
		{
			swap(it);
			return *this;
		}

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

测试代码

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

int main()
{
	// 默认构造
	wj::list<int> ll;
	// 尾插测试
	ll.push_back(1);
	ll.push_back(2);
	ll.push_back(3);
	ll.push_back(4);

	// 迭代器测试
	wj::list<int>::iterator it = ll.begin();
	while (it != ll.end())
	{
		cout << *it << ' ';
		it++;
	}
	cout << endl;
	
	// 范围for(底层迭代器)
	for (auto& x : ll)
	{
		cout << x << ' ';
	}
	cout << endl;

	// insert测试
	// 在3的前面插入30
	it = ll.begin();
	for (int i = 0; i < 2; i++)
	{
		it++;
	}
	ll.insert(it, 30);
	for (auto& x : ll)
	{
		cout << x << ' ';
	}
	cout << endl;

	//  erase测试
	it = ll.begin();
	// 删除30
	for (int i = 0; i < 2; i++)
	{
		it++;
	}
	ll.erase(it);
	for (auto x : ll)
	{
		cout << x << ' ';
	}
	cout << endl;

	// 头插测试
	// 头插100
	ll.push_front(100);
	for (auto x : ll)
	{
		cout << x << ' ';
	}
	cout << endl;
	
	// 尾删测试
	ll.pop_back(); // 100 1 2 3
	for (auto x : ll)
	{
		cout << x << ' ';
	}
	cout << endl;

	// 头删测试
	ll.pop_front(); // 1 2 3
	for (auto x : ll)
	{
		cout << x << ' ';
	}
	cout << endl;

	// size测试
	cout << "个数为:" << ll.size() << endl; // 3

	// 清空
	ll.clear();
	for (auto x : ll)
	{
		cout << x << ' '; // 无输出
	}
	cout << endl;

	// 拷贝构造
	ll.push_back(1);
	ll.push_back(2);
	ll.push_back(3);
	ll.push_back(4);
	ll.push_back(5);

	wj::list<int> lll(ll);
	for (auto x : lll)
	{
		cout << x << ' '; // 1 2 3 4 5
	}
	cout << endl;

	// 赋值运算符重载
	wj::list<char> a;
	a.push_back('a');
	wj::list<char> b;
	b.push_back('b');
	b.push_back('b');
	b.push_back('b');

	a = b;

	for (auto x : a)
	{
		cout << x << ' ';
	}
	cout << endl;

	// 交换
	wj::list<char> c;
	a.push_back('c');
	wj::list<char> d;
	b.push_back('d');
	b.push_back('d');
	b.push_back('d');
	d.swap(c);
	for (auto x : c)
	{
		cout << x << ' ';
	}
	cout << endl;

	for (auto x : d)
	{
		cout << x << ' ';
	}
	cout << endl;

	return 0;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Docker安装Jenkins实操记录

前置条件&#xff1a; 1、安装了docker 2、安装了java&#xff08;没有安装情况下&#xff0c;可运行&#xff1a;yum install -y java-1.8.0-openjdk-devel.x86_64&#xff09; 一、拉取镜像 1、docker pull jenkins/jenkins 2、mkdir -p /usr/local/jenkins 3、chmod 777 …

Ubuntu搭建CT_ICP里程计的环境暨CT-ICP部署

CT-ICP部署以及运行复现过程 0.下载资源&#xff0c;并按照github原网址的过程进行。1.查看所需要的各个部分的版本。2.安装clang编译器3.进行超级构建3.1标准进行3.2构建过程中遇到的问题 4.构建并安装CT-ICP库4.1标准进行4.2遇到的问题及解决办法 5.构建 CT-ICP 的 ROS 包装5…

python+mysql+前后端分离国内职位数据分析(源码+文档+指导)

系统阐述的是使用国内python职位数据分析系统的设计与实现&#xff0c;对于Python、B/S结构、MySql进行了较为深入的学习与应用。主要针对系统的设计&#xff0c;描述&#xff0c;实现和分析与测试方面来表明开发的过程。开发中使用了 Flask框架和MySql数据库技术搭建系统的整体…

深度学习算法模型转成算能科技平台xx.bmodel模型的方法步骤

目录 1 docker镜像下载 2 SDK下载 3 下载sophon-demo 4 修改docker镜像的脚本 5 创建个文件夹 6.source 7.转模型 1 docker镜像下载 可以在dockerhub看到镜像的相关信息 https://hub.docker.com/r/sophgo/tpuc_dev/tags 用下面的命令下载 docker pull sophgo/tpuc_d…

Vue2向Vue3过度Vuex状态管理工具快速入门

目录 1 Vuex概述1.是什么2.使用场景3.优势4.注意&#xff1a; 2 需求: 多组件共享数据1.创建项目2.创建三个组件, 目录如下3.源代码如下 3 vuex 的使用 - 创建仓库1.安装 vuex2.新建 store/index.js 专门存放 vuex3.创建仓库 store/index.js4 在 main.js 中导入挂载到 Vue 实例…

【微服务】04-Polly实现失败重试和限流熔断

文章目录 1. Polly实现失败重试1.1 Polly组件包1.2 Polly的能力1.3 Polly使用步骤1.4 适合失败重试的场景1.5 最佳实践 2.Polly实现熔断限流避免雪崩效应2.1 策略类型2.2 组合策略 1. Polly实现失败重试 1.1 Polly组件包 PollyPolly.Extensions.HttpMicrosoft.Extensions.Htt…

SMC_Interpolator2Dir反向插补运动

附加函数是&#xff1a; SMC_Interpolator2Dir_SlowTask 函数的位置&#xff1a; 输入&#xff1a; 运行 bExecute 【BOOL】 路径包 poqDataIn 指向SMC_OUTQUEUE的指针 停止 bSlow_Stop 停止BOOL 急停 bEmergency_Stop 紧急停止BOOL 单…

1. HBase中文学习手册之揭开HBase的神秘面纱

揭开Hbase的神秘面纱 1.1 欢迎使用 Apache Hbase1.1.1 什么是 Hbase?1.1.2 Hbase的前世今生1.1.3 HBase的技术选型&#xff1f;1.1.3.1 不适合使用 HBase的场景1.1.3.2 适合使用 HBase的场景 1.1.4 HBase的特点1.1.4.1 HBase的优点1.1.4.2 HBase的缺点 1.1.5 HBase设计架构 1.…

[JavaWeb]【九】web后端开发-SpringBootWeb案例(菜单)

目录 一、准备工作 1.1 需求 1.2 环境搭建 1.2.1 准备数据库&表 1.2.2 创建springboot工程 1.2.3 配置application.properties & 准备对应实体类 1.2.3.1 application.properties 1.2.3.2 实体类 1.2.3.2.1 Emp类 1.2.3.2.2 Dept类 1.2.4 准备对应的Mapper、…

Yolo系列-yolov2

YOLO-V2 更快&#xff01;更强&#xff01; YOLO-V2-BatchNormalization BatchNormalization&#xff08;批归一化&#xff09;是一个常用的深度神经网络优化技术&#xff0c;它可以将输入数据进行归一化处理&#xff0c;使得神经网络更容易进行学习。在YOLOv2中&#xff0c;B…

wxpython + cef 是优秀的 WebView 组件

CEF 即 (Chromium Embedded Framework)&#xff1b;cef 是优秀的 WebView 组件。 pip install wxpython4.2 wxPython-4.2.0-cp37-cp37m-win_amd64.whl (18.0 MB) Successfully installed wxpython-4.2.0 pip install cefpython3 cefpython3-66.1-py2.py3-none-win_amd64.whl …

C语言基础之——指针(上)

前言&#xff1a;小伙伴们又见面啦&#xff01;本期内容&#xff0c;博主将展开讲解有关C语言中指针的上半部分基础知识&#xff0c;一起学习起来叭&#xff01;&#xff01;&#xff01; 目录 一.什么是指针 二.指针类型 1.指针的解引用 2.指针-整数 三.野指针 1.野指针…

Qt --- QTimer

在Qt开发界面的时候&#xff0c;非常多的时候都得使用定时器&#xff0c;定时器具体可以干什么呢&#xff1f;比如&#xff1a;控制时钟、定时改变样式、改变进度等。。。说到这里&#xff0c;经常使用QQ&#xff0c;而不同的时段都会显示不同的背景&#xff0c;我认为如果用Qt…

文本编辑器Vim常用操作和技巧

文章目录 1. Vim常用操作1.1 Vim简介1.2 Vim工作模式1.3 插入命令1.4 定位命令1.5 删除命令1.6 复制和剪切命令1.7 替换和取消命令1.8 搜索和搜索替换命令1.9 保存和退出命令 2. Vim使用技巧 1. Vim常用操作 1.1 Vim简介 Vim是一个功能强大的全屏幕文本编辑器&#xff0c;是L…

【网络】数据链路层——MAC帧协议 | ARP协议

&#x1f431;作者&#xff1a;一只大喵咪1201 &#x1f431;专栏&#xff1a;《网络》 &#x1f525;格言&#xff1a;你只管努力&#xff0c;剩下的交给时间&#xff01; 来到数据链路层后&#xff0c;完整的数据被叫做数据帧&#xff0c;习惯上称之为MAC帧。 MAC帧协议 | A…

不用循环数组,js+html实现贪吃蛇

功能描述&#xff1a;每走10步随机改变一个方方向&#xff0c;当键盘按下方向键 w,s,a,d时&#xff0c;使用键盘方向控制蛇的移动&#xff0c;蛇头每撞到一次自身时改变屏幕颜色&#xff0c;蛇头碰到边界时从另一边回来。 实现思路&#xff1a;用个30大小的数组存放每个结点&a…

Open3D 点云均值滤波

目录 一、算法原理1、均值滤波2、参考文献二、代码实现三、结果展示本文由CSDN点云侠原创,原文链接。如果你不是在点云侠的博客中看到该文章,那么此处便是不要脸的爬虫。 一、算法原理 1、均值滤波 对待处理的当前采样点,选择一个模板,该模板由其邻近的若干个数据点组成,…

tomcat高可用和nginx高可用

tomcat高可用和nginx高可用 小白教程&#xff0c;一看就会&#xff0c;一做就成。 1.什么是高可用&#xff1f; 高可用HA&#xff08;High Availability&#xff09;是分布式系统架构设计中必须考虑的因素之一&#xff0c;它通常是指&#xff0c;通过设计减少系统不能提供服务…

自平衡性:保持数据结构稳定的关键

自平衡性是一种重要的数据结构属性&#xff0c;它确保在执行插入、删除等操作后&#xff0c;数据结构能够自动进行调整&#xff0c;以保持整体的平衡状态。平衡的数据结构可以提供更快的操作性能&#xff0c;避免极端情况下的低效操作&#xff0c;同时保持树或其他结构的整体稳…

深度学习2.神经网络、机器学习、人工智能

目录 深度学习、神经网络、机器学习、人工智能的关系 大白话解释深度学习 传统机器学习 VS 深度学习 深度学习的优缺点 4种典型的深度学习算法 卷积神经网络 – CNN 循环神经网络 – RNN 生成对抗网络 – GANs 深度强化学习 – RL 总结 深度学习 深度学习、神经网络…