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

news2024/9/21 15:45:15

在这里插入图片描述

👦个人主页:@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;
}

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

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

相关文章

Minio如何禁止桶遍历

说明&#xff1a;Minio是文件资源服务器&#xff0c;相当于免费的OSS&#xff0c;安装参考&#xff1a;Minio使用及整合起步依赖&#xff1b; 如果你的桶&#xff08;Bucket&#xff09;权限设置的是“Private”&#xff0c;那么可能会有数据泄露的风险。像别有用心的用户&…

BIO到NIO、多路复用器, 从理论到实践, 结合实际案例对比各自效率与特点(上)

文章目录 文章引入IO模型及概念梳理BIO简单介绍代码样例压测结果 NIO(单线程模型)简单介绍与BIO的比较代码样例压测结果 多路复用器问题引入 文章引入 如果你对BIO、NIO、多路复用器有些许疑惑, 那么这篇文章就是肯定需要看的, 本文将主要从概念, 代码实现、发展历程的角度去突…

基于FPGA的Lorenz混沌系统verilog开发,含testbench和matlab辅助测试程序

目录 1.算法运行效果图预览 2.算法运行软件版本 3.部分核心程序 4.算法理论概述 5.算法完整程序工程 1.算法运行效果图预览 将vivado的仿真结果导入到matlab显示三维混沌效果&#xff1a; 2.算法运行软件版本 vivado2019.2 matlab2022a 3.部分核心程序 testbench如下所…

怎么对App进行功能测试

测试人员常被看作是bug的寻找者&#xff0c;但你曾想过他们实际是如何开展测试的吗&#xff1f;你是否好奇他们究竟都做些什么&#xff0c;以及他们如何在一个典型的技术项目中体现价值&#xff1f;本文将带你经历测试人员的思维过程&#xff0c;探讨他们测试app时的各种考虑. …

MyBatis-Plus框架技术总结

MybatisPlus 1、概述 MybatisPlus是一款Mybatis增强工具&#xff0c;用于简化开发&#xff0c;提高效率。 它在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高效率而生。 ​ 官网&#xff1a; https://mp.baomidou.com/ 2、快速入门 2.0、准备工作 ①准…

PROFIBUS主站转MODBUS TCP网关

1.产品功能 YC-DPM-TCP网关在Profibus总线侧实现主站功能&#xff0c;在以太网侧实现ModbusTcp服务器功能。可将Profibus DP从站接入到ModbusTcp网络&#xff1b;通过增加DP/PA耦合器&#xff0c;也可将Profibus PA从站接入ModbusTcp网络。YC-DPM-TCP网关最多支持125个Profibu…

wireshark 流量抓包例题重现

目录 要求 黑客攻击的第一个受害主机的网卡IP地址黑客对URL的哪一个参数实施了SQL注入第一个受害主机网站数据库的表前缀 第一个受害主机网站数据库的名字 要求 &#xff08;1&#xff09;黑客攻击的第一个受害主机的IP地址 &#xff08;2&#xff09;黑客对URL的某一参数实…

在服务器上搭建Jenkins

目录 1.服务器要求 2.官方文档 3.在服务器上下载Jenkins 3.1 下载war包 3.2 将war包上传到服务器的一个目录下 3.3 启动jenkins 3.3.1 jdk版本升级 1&#xff09;下载jdk17 2&#xff09;解压到当前文件夹 3&#xff09;配置路径 4.jenkins配置 4.1 填写初始密码&a…

直线模组在搬运行业的应用

近几年&#xff0c;国内直线模组的研发发展非常快&#xff0c;直线模组应用的范围也在一直在扩大&#xff0c;发展到今天&#xff0c;已经被广泛应用到各种设备中&#xff0c;尤其是在搬运行业中&#xff0c;是搬运行业中必不可少的传动元件之一。 直线模组在搬运中的应用&…

算法笔记(一):时间复杂度

省略的技巧 已知 f ( n ) f(n) f(n) 来说&#xff0c;求 g ( n ) g(n) g(n) 表达式中相乘的常量&#xff0c;可以省略&#xff0c;如 f ( n ) 100 ∗ n 2 f(n) 100*n^2 f(n)100∗n2 中的 100 100 100 多项式中数量规模更小&#xff08;低次项&#xff09;的表达式&#…

vue create -p dcloudio/uni-preset-vue my-project创建文件报错443

因为使用vue3viteuniappvant4报错&#xff0c;uniapp暂不支持vant4&#xff0c;所以所用vue2uniappvant2 下载uni-preset-vue-master 放到E:\Auniapp\uni-preset-vue-master 在终端命令行创建uniapp vue create -p E:\Auniapp\uni-preset-vue-master my-project

mysql profiling profiles profile

要想优化一条 Query&#xff0c;我们就需要清楚的知道这条 Query 的性能瓶颈到底在哪里&#xff0c;是消耗的 CPU计算太多&#xff0c;还是需要的的 IO 操作太多&#xff1f;要想能够清楚的了解这些信息&#xff0c;在 MySQL 5.0 和 MySQL 5.1正式版中已经可以非常容易做到了&a…

云计算中的数据安全与隐私保护策略

文章目录 1. 云计算中的数据安全挑战1.1 数据泄露和数据风险1.2 多租户环境下的隔离问题 2. 隐私保护策略2.1 数据加密2.2 访问控制和身份验证 3. 应对方法与技术3.1 零知识证明&#xff08;Zero-Knowledge Proofs&#xff09;3.2 同态加密&#xff08;Homomorphic Encryption&…

cublas_v2.h没有那个文件和目录,解决

我的是orin&#xff0c;使用的cuda11.4&#xff0c;后来发现通过sudo jetson_release看到的CUDA是没有安装的。 定位到问题是&#xff1a; 使用ls /usr/local/ -lha查看软连接&#xff0c;如下&#xff1a; 能够发现cuda这个软连接是有问题的&#xff0c;他链接的是cuda10.2 …

几种在JavaScript中创建对象的方式!

聚沙成塔每天进步一点点 ⭐ 专栏简介⭐ 字面量方式⭐ 构造函数方式⭐ Object.create()方式⭐ 工厂函数方式⭐ ES6类方式⭐ 写在最后 ⭐ 专栏简介 前端入门之旅&#xff1a;探索Web开发的奇妙世界 记得点击上方或者右侧链接订阅本专栏哦 几何带你启航前端之旅 欢迎来到前端入门…

Python分享之redis(2)

Hash 操作 redis中的Hash 在内存中类似于一个name对应一个dic来存储 hset(name, key, value) #name对应的hash中设置一个键值对&#xff08;不存在&#xff0c;则创建&#xff0c;否则&#xff0c;修改&#xff09; r.hset("dic_name","a1","aa&quo…

java八股文面试[多线程]——sleep wait join yield

sleep和wait有什么区别 sleep 方法和 wait 方法都是用来将线程进入阻塞状态的&#xff0c;并且 sleep 和 wait 方法都可以响应 interrupt 中断&#xff0c;也就是线程在休眠的过程中&#xff0c;如果收到中断信号&#xff0c;都可以进行响应并中断&#xff0c;且都可以抛出 In…

ServiceManger Binder的处理流程

陌生知识点如下&#xff1a; BinderProxy&#xff1a;是将Native层的BpBinder对象进行封装后传给Java层使用的Binder对象android_util_binder: Binder在JNI层的相关注册&#xff0c;处理&#xff0c;转换封装接口BpBinder:Binder驱动在Native层的封装。IPCThreadState&#xf…

3.BGP状态机和路由注入方式

BGP状态机 BGP路由的生成 不同于IGP路由协议,BGP自身并不会发现并计算产生路由,BGP将GP路由表中的路由注入到BGP路由表中,并通过Update报文传递给BGP对等体。 BGP注入路由的方式有两种: Networkimport-route与IGP协议相同,BGP支持根据已有的路由条目进行聚合,生成聚合路由…

【Linux】多线程概念线程控制

文章目录 多线程概念Linux下进程和线程的关系pid本质上是轻量级进程id&#xff0c;换句话说&#xff0c;就是线程IDLinux内核是如何创建一个线程的线程的共享和独有线程的优缺点 线程控制POSIX线程库线程创建线程终止线程等待线程分离 多线程概念 Linux下进程和线程的关系 在…