list的使用,及部分功能的模拟实现(C++)

目录(文章中"节点"和"结点"是同一个意思)

1. list的介绍及使用

1.1 list的介绍

1.2 list的使用

1.2.1 list的构造

 1.2.2 list iterator的使用

 1.2.3 list capacity

1.2.4 list element access

 1.2.5 list modifiers

 1.2.6 list的迭代器失效

 2. list的模拟实现

2.1 list_node结点

 2.2 list_iterator迭代器

 2.2.1 运算符重载!=

2.2.2  运算符重载==

 2.2.3 运算符重载前置++

 2.2.4 运算符重载后置++

 2.2.5 运算符重载前置--

 2.2.6 运算符重载后置--

 2.2.7 运算符重载*

 2.2.8 运算符重载->

 2.3 list类

2.3.1 构造函数

2.3.1.1默认构造函数list

 2.3.1.2 拷贝构造

2.3.1.3  list(std::initializer_list lt)

 2.3.2 析构函数

2.3.3 赋值运算符重载

 2.3.4 front和back

2.3.5 size和begin和end

2.3.6 insert插入和erase删除

2.3.6.1 insert插入

 2.3.6.2 erase删除

2.3.7 头插push_front,头删pop_front,尾插push_back,尾删pop_back

2.4 整体代码

1. list的介绍及使用

1.1 list的介绍

std::list是 C++ 标准模板库(STL)中的一个容器类,底层实现为双向链表,适用于需要频繁插入和删除操作的场景。

1.2 list的使用

以下为list中一些常见的重要接口。

1.2.1 list的构造

构造函数( (constructor))接口说明
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

使用演示:

void test1()
{
	list<int> l1(10, 5);
	list<int> l2;
	list<int> l3(l1);
	list<int> l4(l1.begin(),l1.end());
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto& e : l2)
	{
		cout << e << " ";
	}
	cout << endl;
	for (auto& e : l3)
	{
		cout << e << " ";
	}
	cout << endl;	
	for (auto& e : l4)
	{
		cout << e << " ";
	}
	cout << endl;
}

结果:

 1.2.2 list iterator的使用

这里可以暂时将迭代器理解成一个指针,该指针指向list中的某个节点。(其实底层实现时,迭代器是一个封装的对象

函数声明接口说明
begin + end返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin + rend返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位 置的reverse_iterator,即begin位置

1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动

2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

使用演示:

void test2()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	list<int>::reverse_iterator rit = l.rbegin();
	while (rit != l.rend())
	{
		cout << *rit << " ";
		rit++;
	}
	cout << endl;
	list<int>::iterator it = l.begin();
	while (it != l.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}

结果:

 1.2.3 list capacity

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数

使用演示:

void test3()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	if(!l.empty())
	{
		cout << "该链表非空" << endl;
	}
	cout << "有效节点个数:" << l.size() << endl;
}

结果:

1.2.4 list element access

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

 使用演示:

void test4()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	cout << l.front() << endl;
	cout << l.back() << endl;
}

结果:

 1.2.5 list modifiers

函数声明接口说明
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中的有效元素

使用演示:

push_front和pop_front使用:

void test5()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	for (auto& e : l)
	{
		cout << e << " ";
	}
	cout << endl;
	l.push_front(10);
	cout << "头插后:";
	for (auto& e : l)
	{
		cout << e << " ";
	}
	cout << endl;
	l.pop_front();
	cout << "头删后:";
	for (auto& e : l)
	{
		cout << e << " ";
	}
	cout << endl;
}

结果:

 push_back和pop_back:

void test6()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	for (auto& e : l)
	{
		cout << e << " ";
	}
	cout << endl;
	l.push_back(6);
	cout << "尾插后:";
	for (auto& e : l)
	{
		cout << e << " ";
	}
	cout << endl;
	l.pop_back();
	cout << "尾删后:";
	for (auto& e : l)
	{
		cout << e << " ";
	}
	cout << endl;
}

结果:

 insert和erase:

void test7()
{
	list<int> l;
	l.push_back(1);
	l.push_back(2);
	l.push_back(3);
	l.push_back(4);
	l.push_back(5);
	for (auto& e : l)
	{
		cout << e << " ";
	}
	cout << endl;
	l.insert(++l.begin(), 30);
	cout << "插入后:";
	for (auto& e : l)
	{
		cout << e << " ";
	}
	cout << endl;
	l.erase(--l.end());
	cout << "删除后:";
	for (auto& e : l)
	{
		cout << e << " ";
	}
	cout << endl;
}

结果:

swap:

void test8()
{
	list<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.push_back(5);
	list<int> l2(5, 5);
	cout << "交换前" << endl;
	cout << "l1:";
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "l2:";
	for (auto& e : l2)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "交换后" << endl;
	l1.swap(l2);
	cout << "l1:";
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << "l2:";
	for (auto& e : l2)
	{
		cout << e << " ";
	}
	cout << endl;
}

 结果:

clear:

void test9()
{
	list<int> l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.push_back(5);
	cout << "清空前" << endl;
	cout << "有效元素个数:" << l1.size() << endl;
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	l1.clear();
	cout << "清空后" << endl;
	cout << "有效元素个数:" << l1.size() << endl;
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
}

 结果:

 1.2.6 list的迭代器失效

小编前面文章里面“vector迭代器失效”,提到只要使用erase或者insert之后迭代器就直接失效,但是对于list有所不同,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。

 举例(删除所有结点):

void test10()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给其赋值
		l.erase(it);
		++it;
	}
}

结果:

可以看到这里报段错误,迭代器失效了。

改进后:

void test10()
{
	int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
	list<int> l(array, array + sizeof(array) / sizeof(array[0]));
	auto it = l.begin();
	while (it != l.end())
	{
		it=l.erase(it);
	}
}

因为erase函数删除后,会返回删除结点的下一个节点迭代器,it接收返回值,相当于更新了迭代器。

 2. list的模拟实现

这里利用双向带头链表实现,因为链表的物理空间并不是连续的,所以这里的迭代器不能使用指针,这里对迭代器进行了封装,实现一个对象。还有这里为了和原本库里list区分,将自己写的list封装在命名空间里(作者的是iu)

2.1 list_node结点

template<class T>
struct list_node
{
	list_node<T>* _prev;
	list_node<T>* _next;
	T _value;

	list_node(const T& x = T())
		:_prev(nullptr)
		,_next(nullptr)
		,_value(x)
	{}
};

双向带头链表,所以成员有前驱结点,和后继结点,和元素对象。

 2.2 list_iterator迭代器

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和PTR?其实是解决,范围for迭代器访问时,会有const修饰的对象,如果直接确定的写,只有普通对象可以,所以这里多给两个参数模版,让编译器自己去判断是const修饰的对象,还是普通对象。

 2.2.1 运算符重载!=

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

2.2.2  运算符重载==

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

 2.2.3 运算符重载前置++

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

结点的指向直接向后移动一位。

 2.2.4 运算符重载后置++

Self operator++(int)
{
	Self tmp(*this);
	_node = _node->_next;
	return tmp;
}

创建一个临时对象,节点的指向向后移动一位,返回临时对象。

 2.2.5 运算符重载前置--

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

结点的指向直接向前移动一位。

 2.2.6 运算符重载后置--

Self operator++(int)
{
	Self tmp(*this);
	_node = _node->_next;
	return tmp;
}

创建一个临时对象,节点的指向向前移动一位,返回临时对象。

 2.2.7 运算符重载*

REF operator*()
{
	return _node->_value;
}

解引用,就是返回对象的值。

 2.2.8 运算符重载->

PTR operator->()
{
	return &_node->_value;
}

这里为什么是取值的地址?通过下面的例子来讲解:

void test2()
{
	struct A
	{
		A(int a = 0,int b=0)
			:_a(a)
			,_b(b)
		{}
		int _a;
		int _b;
	};
	iu::list<A>l2;
	l2.push_back({1,1});
	l2.push_back({2,2});
	l2.push_back({3,3});
	l2.push_back({4,4});
	iu::list<A>::iterator it = l2.begin();
	while (it != l2.end())
	{
		cout << (*it)._a << ":" << it->_b << endl;
		it++;
	}
}

这里可以利用解引用在去访问,A类中的成员,这是一种访问方式;而这个it->b是如何访问的?前面实现的是返回A的地址,拿到A对象的地址和成员_b之间也没有运算符,那有怎么样才能访问到_b呢?其实这里省略了一个->,这里其实应该是it->->_b,所以先拿到A对象的地址,再利用结构体->这种方式。解引用访问里面的成员。

结果:

 2.3 list类

	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;

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

成员有头结点,和元素个数。

2.3.1 构造函数

2.3.1.1默认构造函数list
void empty_init()
{
	_head = new Node;
	_head->_prev = _head;
	_head->_next = _head;
	_size = 0;
}

list()
{
	empty_init();
}

这里利用empty_init函数,进行初始化,是为了方便后面几个构造函数,先初始化,再尾插的操作。

 2.3.1.2 拷贝构造
list(const list<T>& lt)
{
	empty_init();
	for (auto& e : lt)
	{
		push_back(e);
	}
}

初始化,再遍历一遍lt,进行尾插依次插入。

2.3.1.3  list(std::initializer_list<T> lt)
list(std::initializer_list<T> lt)
{
	empty_init();

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

和上面同理。

这里举一个例子:

void test8()
{
	iu::list<int>l1 = { 1,2,3,4,5,6,7 };
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
}

 结果:

这个initializer_list<T>类型是一个类似于数组的类型,可以理解成一种类似数组初始化的方式。

 2.3.2 析构函数

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

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

先在clear函数里面将数据全部清除,再将头结点释放掉,置为空。

2.3.3 赋值运算符重载

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

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

 利用std中的交换函数,将临时对象与this指向的对象进行交换,临时对象出了作用域会自然销毁,也不会影响实参,这样就相当于实现了赋值操作。

 2.3.4 front和back

T& front()
{
	return _head->_next->_value;
}

const T& front()const
{
	return _head->_next->_value;
}
T& back()
{
	return _head->_prev->_value;
}
const T& back()const
{
	return _head->_prev->_value;
}

这里利用的是双向带头链表,所以头部front元素就是头结点的下一个,尾结点back就是头结点的前一个。这里还重载了const修饰的对象,当对象元素是不可改变的const时,也可以使用。

2.3.5 size和begin和end

size_t size()
{
	return _size;
}

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

iterator end()
{
	return _head;
}

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

const_iterator end() const
{
	return _head;
}		

size函数,直接返回成员_size大小即可。

begin()返回头结点的下一个结点的迭代器,end()返回最后一个元素的下一个结点的迭代器,所以就是头结点head,这里也是重载了const修饰的对象,当对象元素是不可改变的const时,也可以使用。

2.3.6 insert插入和erase删除

2.3.6.1 insert插入
void insert(iterator pos,const T& x)
{
	Node* newnode = new Node(x);

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

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

	++_size;
}

首先创建一个新的结点,再进行插入操作,插入时,先找到pos位置的结点,和pos位置的前一个结点,再改变前后指针指向即可,最后_size再加1。

 2.3.6.2 erase删除
iterator erase(iterator pos)
{
	assert(pos != end());

	Node* del = pos._node;
	Node* prev = del->_prev;
	Node* next = del->_next;

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

	delete del;

	--_size;
    return iterator(next);
}

首先找到pos位置的结点,再确定pos前一个结点,和后一个结点的位置,最后改变指针指向,再删除pos位置的节点,_size再减1,最后还要返回删除节点的下一个位置的结点,返回匿名对象即可。

2.3.7 头插push_front,头删pop_front,尾插push_back,尾删pop_back

void push_back(const T& x)
{
	insert(end(), x);
}
void push_front(const T& x)
{
	insert(begin(), x);
}
void pop_back()
{
	erase(--end());
}

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

这里直接复用insert和erase函数实现,只是要注意尾结点的位置是end()的前一个位置。

 例子:

void test4()
{
	iu::list<int>l1;
	l1.push_back(1);
	l1.push_back(2);
	l1.push_back(3);
	l1.push_back(4);
	l1.insert(l1.begin(), 10);
	l1.insert(l1.begin(), 20);
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
	l1.pop_back();
	l1.pop_back();
	l1.pop_front();
	l1.pop_front();
	for (auto& e : l1)
	{
		cout << e << " ";
	}
	cout << endl;
}

结果:

2.4 整体代码

#include<assert.h>
#include<algorithm>
#include <initializer_list>

namespace iu
{
	template<class T>
	struct list_node
	{
		list_node<T>* _prev;
		list_node<T>* _next;
		T _value;

		list_node(const T& x = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_value(x)
		{}
	};

	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)
		{}

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

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

		REF operator*()
		{
			return _node->_value;
		}

		PTR operator->()
		{
			return &_node->_value;
		}

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

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

		Self operator++(int)
		{
			Self tmp(*this);
			_node = _node->_next;
			return tmp;
		}

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

	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;

		void empty_init()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
			_size = 0;
		}

		list()
		{
			empty_init();
		}

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

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

		list(std::initializer_list<T> lt)
		{
			empty_init();

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

		list(const list<T>& lt)//拷贝构造
		{
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

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

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

		T& front()
		{
			return _head->_next->_value;
		}

		const T& front()const
		{
			return _head->_next->_value;
		}
		T& back()
		{
			return _head->_prev->_value;
		}
		const T& back()const
		{
			return _head->_prev->_value;
		}
		size_t size()
		{
			return _size;
		}

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

		iterator end()
		{
			return _head;
		}

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

		const_iterator end() const
		{
			return _head;
		}

		void push_back(const T& x)
		{
			//Node* newnode = new Node(x);
			//Node* tail = _head->_prev;
			//newnode->_prev = tail;
			//tail->_next = newnode;
			//newnode->_next = _head;
			//_head->_prev = newnode;
			insert(end(), x);
		}

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

		void insert(iterator pos,const T& x)
		{
			Node* newnode = new Node(x);

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

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

			++_size;
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

			Node* del = pos._node;
			Node* prev = del->_prev;
			Node* next = del->_next;

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

			delete del;

			--_size;
			return iterator(next);
		}

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

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


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


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

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

相关文章

StarRocks BE源码编译、CLion高亮跳转方法

阅读SR BE源码时&#xff0c;很多类的引用位置爆红找不到&#xff0c;或无法跳转过去&#xff0c;而自己的Linux机器往往缺乏各种C依赖库&#xff0c;配置安装比较麻烦&#xff0c;因此总体的思路是通过CLion远程连接SR社区已经安装完各种依赖库的Docker容器&#xff0c;进行编…

Axure PR 9 旋转效果 设计交互

大家好&#xff0c;我是大明同学。 这期内容&#xff0c;我们将学习Axure中的旋转效果设计与交互技巧。 旋转 创建旋转效果所需的元件 1.打开一个新的 RP 文件并在画布上打开 Page 1。 2.在元件库中拖出一个按钮元件。 创建交互 创建按钮交互状态 1.选中按钮元件&#xf…

Java - 引用类型:强引用、软引用、弱引用和虚引用详解

文章目录 概述1. 强引用&#xff08;Strong Reference&#xff09;1.1 什么是强引用&#xff1f;1.2 强引用的特点1.3 强引用的使用场景1.4 强引用的注意事项 2. 软引用&#xff08;Soft Reference&#xff09;2.1 什么是软引用&#xff1f;2.2 软引用的特点2.3 软引用的使用场…

S4 HANA给科目分配允许记账的税码

本文主要介绍在S4 HANA OP中给科目分配允许记账的税码相关设置。具体请参照如下内容&#xff1a; 1. 给科目分配允许记账的税码 以上配置定义了总账科目可以使用什么税码进行记账。通常在科目主数据中会明确总账科目的“Tax Category”来请明确总账科目可以使用什么类型的税码…

xss-labs靶场

xss-labs靶场 xss攻击类型 反射型xss 即攻击者将恶意脚本嵌入到url或者表单中&#xff0c;当用户访问特定的url或者提交表单时&#xff08;用户端请求时)&#xff0c;恶意脚本会执行 攻击需要用户点击恶意链接或访问包含恶意参数的url触发 存储型xss 即攻击者将恶意脚本提交…

CVE-2024-23897-Jenkins任意文件读取漏洞复现

content Jenkins是什么CVE-2024-23897总结修复建议 Jenkins是什么 Jenkins是一人基于Java开发的、可扩展的持续集成引擎&#xff0c;用于持续、自动地构建/测试软件项目&#xff0c;可以监控一些定时执行的任务。 官网文档&#xff1a; Jenkins是一款开源 CI&CD 软件&…

解析 Oracle 中的 ALL_SYNONYMS 和 ALL_VIEWS 视图:查找同义词与视图的基础操作

目录 前言1. ALL_SYNONYMS 视图2. ALL_VIEWS 视图3. 扩展 前言 &#x1f91f; 找工作&#xff0c;来万码优才&#xff1a;&#x1f449; #小程序://万码优才/r6rqmzDaXpYkJZF 1. ALL_SYNONYMS 视图 在 Oracle 数据库中&#xff0c;同义词&#xff08;Synonym&#xff09;是对数…

30.Word:设计并制作新年贺卡以及标签【30】

目录 NO1.2 NO3邮件合并-信函 NO4邮件合并-标签​ NO1.2 另存为/F12&#xff1a;考生文件夹&#xff1a;Word.docx布局→页面设置对话框→页边距&#xff1a;上下左右→纸张&#xff1a;宽度/高度&#xff08;先调页边距&#x1f197;&#xff09;设计→页面颜色→填充效果→…

Unity实现按键设置功能代码

一、前言 最近在学习unity2D&#xff0c;想做一个横版过关游戏&#xff0c;需要按键设置功能&#xff0c;让用户可以自定义方向键与攻击键等。 自己写了一个&#xff0c;总结如下。 二、界面效果图 这个是一个csv文件&#xff0c;准备第一列是中文按键说明&#xff0c;第二列…

一个简单的自适应html5导航模板

一个简单的 HTML 导航模板示例&#xff0c;它包含基本的导航栏结构&#xff0c;同时使用了 CSS 进行样式美化&#xff0c;让导航栏看起来更美观。另外&#xff0c;还添加了一些 JavaScript 代码&#xff0c;用于在移动端实现导航菜单的展开和收起功能。 PHP <!DOCTYPE htm…

TensorFlow 示例摄氏度到华氏度的转换(一)

TensorFlow 实现神经网络模型来进行摄氏度到华氏度的转换&#xff0c;可以将其作为一个回归问题来处理。我们可以通过神经网络来拟合这个简单的转换公式。 1. 数据准备与预处理 2. 构建模型 3. 编译模型 4. 训练模型 5. 评估模型 6. 模型应用与预测 7. 保存与加载模型 …

2.1.3 相机图像信号处理的基本流程

文章目录 ISP基本流程ISP各基本流程职责 ISP基本流程 图像信号处理将传感器采集到的Bayer阵列数据转换成符合人眼观感的图像数据。ISP(Image Signal Processing)图像信号处理基本流程包括坏点校正&#xff08;DPC, Defect Pixel Correction&#xff09;&#xff0c;黑电平校正&…

51单片机CLD1602显示万年历+闹钟+农历+整点报时

1. 硬件设计 硬件是我自己设计的一个通用的51单片机开发平台&#xff0c;可以根据需要自行焊接模块&#xff0c;这是用立创EDA画的一个双层PCB板&#xff0c;所以模块都是插针式&#xff0c;不是表贴的。电路原理图在文末的链接里&#xff0c;PCB图暂时不选择开源。 B站上传的…

颠覆AI界限!o3-mini与DeepSeek V3的巅峰对决

性能之战&#xff1a;谁才是AI推理的王者&#xff1f; 在AI技术飞速发展的今天&#xff0c;OpenAI最新发布的o3-mini模型如同一颗新星&#xff0c;闪耀在AIGC的天空中。它不仅带来了惊人的性能提升&#xff0c;还具备了多项用户友好的功能。与此同时&#xff0c;DeepSeek V3也…

Spring Boot项目如何使用MyBatis实现分页查询

写在前面&#xff1a;大家好&#xff01;我是晴空๓。如果博客中有不足或者的错误的地方欢迎在评论区或者私信我指正&#xff0c;感谢大家的不吝赐教。我的唯一博客更新地址是&#xff1a;https://ac-fun.blog.csdn.net/。非常感谢大家的支持。一起加油&#xff0c;冲鸭&#x…

PDCA 循环法

目录 循环周而复始大环套小环阶梯式上升 如何从 0 开始搭建 PDCA应用场景示例一、健身 APP 拉新活动策划第一阶段&#xff1a;制定详细的活动计划与方案第二阶段&#xff1a;活动执行阶段第三阶段&#xff1a;活动效果监控阶段第四阶段&#xff1a;活动复盘阶段 二、员工培训三…

深入理解Spring事务管理

一、事务基础概念 1.1 什么是事务&#xff1f; 事务&#xff08;Transaction&#xff09;是数据库操作的最小工作单元&#xff0c;具有ACID四大特性&#xff1a; 原子性&#xff08;Atomicity&#xff09;&#xff1a;事务中的操作要么全部成功&#xff0c;要么全部失败 一致…

【Linux-网络】初识计算机网络 Socket套接字 TCP/UDP协议(包含Socket编程实战)

&#x1f3ac; 个人主页&#xff1a;谁在夜里看海. &#x1f4d6; 个人专栏&#xff1a;《C系列》《Linux系列》《算法系列》 ⛰️ 道阻且长&#xff0c;行则将至 目录 &#x1f4da;一、初识计算机网络 &#x1f4d6; 背景 &#x1f4d6; 网络协议 &#x1f516;OSI七层…

2 MapReduce

2 MapReduce 1. MapReduce 介绍1.1 MapReduce 设计构思 2. MapReduce 编程规范3. Mapper以及Reducer抽象类介绍1.Mapper抽象类的基本介绍2.Reducer抽象类基本介绍 4. WordCount示例编写5. MapReduce程序运行模式6. MapReduce的运行机制详解6.1 MapTask 工作机制6.2 ReduceTask …

【Rust自学】15.5. Rc<T>:引用计数智能指针与共享所有权

喜欢的话别忘了点赞、收藏加关注哦&#xff0c;对接下来的教程有兴趣的可以关注专栏。谢谢喵&#xff01;(&#xff65;ω&#xff65;) 15.5.1. 什么是Rc<T> 所有权在大部分情况下都是清晰的。对于一个给定的值&#xff0c;程序员可以准确地推断出哪个变量拥有它。 …