C++-----list

news2024/11/24 18:54:18

本期我们来讲解list,有了string和vector的基础,我们学习起来会快很多

目录

list介绍

​编辑

list常用接口 

 insert

erase

reverse

sort 

merge

 unique

remove

splice

 模拟实现

基础框架

构造函数

 push_back

迭代器

 常见问题

const迭代器

insert

erase

push和pop

size

析构和clear 

拷贝构造

赋值

全部代码


本期内容需要比较扎实的基础

list介绍

 1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代。

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

 简单来说,list就是带头双向循环列表

我们先简单看看如何使用 

list常用接口 

list的迭代器啦,构造函数,析构函数这些跟我们前面学的都是一样的,大家稍微看看文档即可,这里就不细讲了

不过list不一样的是它有头插头删,尾插尾删,这些和我们以前用C语言实现的是一样的

 insert

 我们从vector开始,insert里的参数都不是pos,而是迭代器了

我们之前使用vector时是可以使用迭代器+一个数的,而list是不行的,因为vector是数组,是连续的空间,而list是链表 ,是一个个节点

 我们想在5的位置插入,是需要手动让迭代器走的,然后再插入

我们上面插入了1,2,3,4,5,如果我们想在数字3前面插入数据,是需要使用find的,但是我们看文档,list是没有提供find的,我们在vector里说过,find属于算法,通过迭代器来和算法联系起来,我们通过统一的方法,而不用关注容器底层是如何实现的

 我们来看find的查找,它的循环条件是first!=last,而不是小于,我们用while来进行遍历,也使用的是begin!=end,而不是小于,因为后面节点的地址不一定比前面小

通过迭代器,不止是vector,list,即使是树也是可以查找的,因为都是迭代器,区间范围是左闭右开

比如我们在3前面插入30

并且由于是带头双向循环的原因,insert是不存在迭代器失效问题的

erase

虽然insert没有迭代器失效问题,但是erase是有的

 

节点都不存在了

 insert以后,it不失效,erase以后,it会失效

如果我们要删掉所有的偶数呢?

是需要使用返回值的,和vector是一样的

 返回的是刚刚被删除的元素的后一个

reverse

 reverse是链表的逆置,其实有点多余了,因为算法库里也有

意义其实不大

sort 

sort在库里面也有,那是否也是多余的呢?

这里编译报错了

 sort编译报错了,原因是sort底层是快排,快排是需要三叔数取中的,会取到这个数的位置,链表是不适应这个场景的

  我们仔细看算法库里sort,find,reverse这些,都是模板,我们仔细看参数名字,sort的是random,reverse的是bidirection,一个是随机的意思,一个是双向的意思,这些都是暗示,这些名字都不是随便起的

这里就和迭代器有关了,迭代器分为单向迭代器,双向迭代器和随机迭代器,单向迭代器只能++,双向可以++也可以- -,而随机不仅可以++和- -,还可以+和-

单链表的迭代器就是单向迭代器,双向链表,也就是list就是双向迭代器,还有map和set也是,vector和string是随机迭代器

find的迭代器是input,是所有迭代器都可以使用,这里涉及到继承,我们后面会讲 

那我们该如何知道一个容器的迭代器属于什么呢?

其实文档里都有说明 ,比如list的迭代器是双向的

但迭代器的使用不是定死的,比如string,vector都可以使用reverse,因为随机迭代器可以看作特殊的双向迭代器,是可以兼容的,比如双向可以使用单向的

我们再回过头来看list的sort还有意义吗?有一点,但不多

如果要排序的话,我们应该使用vector,而不是list,在500w的数据量时,vector能比list快10倍

list的排序底层使用的是归并排序,所以list的sort的意义就是方便

我们要排序的话

  可以这样做,把list的数据拷贝到vector,然后再拷贝回来 

所以各位以后在排序时,尤其是数据量大时,不要用list的sort

merge

merge是两个链表的归并

不过归并前先排序一下

 unique

unique去重的意思,不过去重前也是需要先排序的,不然效率太低了

remove

 remove就是find加erase

 

找不到的话就什么也不做,找到就删除 

splice

 splice是转移,可以将a链表的节点拿下来转移到b链表

我们看第一个接口,是把x链表的所有值转移到当前链表(position)之前,第二个是转移x链表的i,第三个是转移一个区间

这里是调用第一个接口 ,是全部转移,此时mylist2是空的

这里调用第二个接口,我们把第二个链表的第二个数转移到第一个链表 

这是第三个接口 ,我们把第二个链表的第二个位置开始全部转移

还可以自己转移自己

我们把第二个位置转移到第一个位置的前面

 

还有不要有重叠,像这样就死循环了

 模拟实现

下面我们来进行模拟实现

首先我们先看看源代码

我们看到有一个void*类型

我们还找到了链表

还找到了成员变量

它的本质是这样的

我们还看到了无参的构造函数 ,初始化了一个节点

getnode是哨兵位节点

putnode下面我们发现了熟人,construct 

 这些我们了解一下即可

我们还可以找到pushback ,我们发现它调用了insert

我们就不再深入,各位感兴趣的话可以自己再看看,下面我们进入正题

基础框架

 我们使用struct定义节点,使用class也可以,不过需要使用public,节点不是公有的使用起来会非常麻烦

补全一下,最后写成这样,看框住的地方,有人可能直接在private里写list_node*  _head,这样是错误的,因为 忘记了<T>

构造函数

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

 除了list里要写构造函数,节点里也要写一个,因为我们在插入节点时是需要创建的,而创建时调用构造函数即可

 push_back

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

其实写起来和我们以前用C实现的是一样的

迭代器

首先,大家想一想我们的迭代器用Node*可以吗?

大家想一想,数组是连续的,我们解引用后就可以直接用,++就可以到下一个位置,但是链表不行,它不是连续的,即使当前是连续的,我们插入一个值,也会变成不连续的 

 我们当时日期对象++是如何到下一天的?是调用了一个函数,是需要运算符重载

 我们看库里面,是这样实现的 ,我们要++的话,就是让node=node->next

深入看库对于现在的我们来说还是有点难度的,我们就不再往下看

所以我们最后迭代器是这样一个东西 

现在需要写begin和end

我们遍历是这样写的

begin是第一个数据,end是最后一个数据的下一个位置 

 我们看库里面,库里面的node是我们的head,end返回了node,begin返回了node的next,和我们是一样的

迭代器是自定义类型,是节点指针,但因为底层原因,不是连续的,我们不能用原生的,不像之前的vector等等,C++可以用一个类来封装内置类型,然后重载运算符,比如++就是调operator++,就变成我们来控制了,就像C里面指针是不符合我们行为的,我们可以把它写的和指针一样,但是底层不一样

就像这样,再想想我们要写list的遍历,也是这样写,但是*it看起来一样,实际不一样,一个数组,一个链表,甚至一棵树,遍历都可以这样写,封装和运算符重载的力量是非常强大的

我们从应用的角度来实现,即从遍历这里开始写怎么写迭代器

我们补全一下__list_iterator,再看 begin,这里为什么可以这样返回呢?因为单参数的构造函数支持隐式类型转换,就像const char*可以转换为string一样

还可以这样写,本质是一样的

 我们再想想解引用该怎么办

转换为调用operator*,返回节点里的值,因为出了作用域节点还在,val也还在,所以我们返回引用 

同样的,我们还要写++,我们让node=node->next即可

 

我们还要写不等于

此时我们进行测试发现报错了,报错的是!=

原因是在我们的循环条件里,去调用operator!= ,然后我们的operator!=的参数我们给的是引用,调用了end,end返回的是iterator,是传值返回,传值返回的是head的拷贝,是临时对象,具有常性,所以我们在!=要加const

也就是这样

此时就没有问题了 

我们上面实现了++,我们++有前置++,还有后置++,下面是后置++的实现

 同样的,我们实现了不等于,也要实现等于

 常见问题

下面再解释一下第一次学习的同学一些常见问题

为什么两个typedef不在一块,iterator是对外用的,所以放在public里,而Node是对内用的,我们不希望别人访问我们的节点,别人使用迭代器就可以了,所以不在一块 

还有为什么我们起名是list_node,而不是node,迭代器也是,因为除了list,还有vector,树等等各种结构,他们都在std这个命名空间里面

就像我写的这个namespace bai,官方的std,它所有的都在这里面,所以不这样取名会存在命名冲突问题 

还有一个疑惑,迭代器的本质是通过自定义类型封装,改变了原生指针的行为,达到我们的目的

再看一个问题,这里调用begin,begin的值是如何给it的? 是拷贝构造,这里不是赋值

我们现在的迭代器没有写拷贝构造,默认生成的拷贝构造对内置类型完成值拷贝,也就是浅拷贝,但是没有问题,我们要的就是浅拷贝

 begin返回这个位置的迭代器,给it,我们就期望it里面的节点指针也是指向这个位置

但是两个对象指向同一个节点,为什么这里没有崩溃?

崩溃的核心原因在于析构函数多次释放,但是迭代器对象我们没写析构函数,原因是节点不是属于迭代器的,不需要迭代器来进行释放,我们只是借助迭代器来访问容器,不论数组,链表,树,我们都可以用同样的方式来访问容器,而不是管理容器,节点是由链表释放的

const迭代器

大家先想想这样设计const迭代器对吗?

 迭代器模拟的是指针,指针有两种const指针

 const迭代器模拟的是第一种指针的行为,第二个指针是不能++的,const在*后面,修饰的是指针本身,而第一个是修饰指向的内容不能修改,const迭代器是期望指向的内容不能修改

此时我们再看我们上面设计的,是不行的,我们模拟的是上面的ptr2,这样写出来的是const迭代器本身不能修改,我们就不能遍历,不能++了,因为++是非const,const是不能调用非const的,同样我们也不能把++变为const,因为++里要改变成员变量

我们怎么写才能控制指向的内容不能修改?

我们控制解引用即可 ,这样返回的就是const引用

此时就会有人把上面迭代器整个拷贝一份,然后改改名什么的,那样设计太冗余了

我们再看库里面,它加了两个模板参数 

 我们先在自己的迭代器这里加一个Ref,然后修改operator*的返回值类型

接着我们在list里就可以typedef const迭代器了 

其实这样写也相当于我们写了两个类,大家想一想,vector<int>和vector<double>是两个类,是两个相似的类,有编译器通过模板生成

而我们这样写,通过模板参数,给T引用的时候,operator*返回的就是T引用,给const T引用时返回的就是const T引用,但是他们两个是两个类,给不同的模板参数就是不同的类

这样写的话,我们还要修改一些东西

我们要在迭代器类里写这样一个,我们习惯叫做self,就是自己的意思 

接着我们把这些修改为self即可

还有别忘记写const的begin和end

 我们继续看源码发现,除了重载operator*之外,还重装了这样一个,并且库里面是三个模板参数,我们现在只有两个 

我们知道,指针除了*解引用,还有->解引用,*是取指针指向的数据,指向的对象,那->呢?

如果是一个结构体的指针,就需要->,迭代器是模拟指针的行为,什么时候模拟结构体指针呢?

 operator->默认返回的是T*

 我们先看这样一段代码,我们下面*it解引用是什么? 这段代码为什么不能运行?

因为*it是取里面的数据T,T是A,而A是自定义类型,这里是需要重载流插入的,这是第一种方法,如果我们不想重载,A里的成员变量a1,a2都不是私有的

 我们是可以这样写的 

此时迭代器是模拟的指向结构体的指针,如果这里类型是int,我们直接解引用,如果是自定义类型,是需要用箭头的

是可以这样写的 

但是这里有个非常诡异的问题

 首先,我们把operator->屏蔽掉的话这里是会报错的 

 但是,这个箭头调用是非常奇怪的,operator*是没问题的

如果是*it,这里就是it.operator*() ,然后operator*返回A&,A.a1,A.a2是没有问题的

而箭头是it.operator->(),返回的是A*,A*怎么去访问呢?

所以这里严格来说严格是it->->_a1,才是符合语法的,是两个箭头,第一个箭头的运算符重载,调用operator->返回A*,A*再加一个箭头才能访问

因为运算符重载要求可读性,所以编译器在这里是特殊处理的,省略了一个箭头

如果是const迭代器,operator->应该返回const T*

 所以我们需要加第三个模板参数Ptr

然后用Ptr代替T* 

普通迭代器传T*,const传const T*

 我们上面实现的思路大家也发现了,我们要从核心的东西开始看,如果一上来我们就看到迭代器有三个参数,人都是懵的,是看不明白的,所以看不懂的东西我们可以先暂时放下,先看后面的,看它的核心,看它的功能,慢慢就可以知道了,我们看不懂的原因是当前站的高度太低了,很多东西是需要看好几遍的

另外这些不修改的我们最好再加上const 

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

再补充一下-- 

insert

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

insert非常简单,这里是可以体先出为什么我们节点使用的是结构体,而不是类,如果是类的话,会变得麻烦一点,我们是和库保持一致的,库里面也使用的是结构体,而且我们不用担心有人会写it._node这种代码,因为官方只规定了迭代器可以++这些,而没有规定底层,我们不确定这里是_node,还是Node,或者是_Node,即使我们去查了源码,也只是针对当前平台,换一个平台可能就不同了

库里面和我们是差不多的 

erase

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

erase是需要先检查一下的,end是哨兵位,不能删除,另外,我们可以发现,这里是存在迭代器失效问题的,所以我们需要返回下一个位置,另外我们上面还看到了insert其实也有返回值,我们一块补充一下

        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;
			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;
			return next;
		}

push和pop

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

这些都没什么说的,有了insert和insert,我们都是可以复用的

size

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

我们可以这样遍历一遍,如果感觉这样不好,也可以加一个成员变量_size

 这样写我们要修改一下insert和erase

++一下即可,其他地方不用变 ,同理,erase里--一下即可

还有初始化也要修改一下

析构和clear 

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

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

clear和析构是不一样的,clear是清理数据,清理还要释放空间,不过哨兵位是不清的,所以我们需要接受一下,it刚好接受下一个位置,最后处理一下size即可,析构直接复用即可

  我们简单测试一下,没有问题 

拷贝构造

        list(const list<T>& lt)//拷贝构造
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
			_size = 0;
			for (auto& e : lt)
			{
				push_back(e);
			}
		}

我们把数据一个一个的放进去即可

这里是有点冗余,我们提取一下公共部分,库里面也是这样写的 

赋值

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

和vector一样,我们直接用现代写法

测试一下也没有问题 

 

仔细看我们和库里面的区别,库里面最开始是list&,我们是list<T>&

 我们写的是类型,它写的是类名

 对于拷贝构造和赋值,写类名是允许的,不过这样写有点降低可读性,我们这里不推荐和库一样

类模板在类里面使用,既可以写类名,也可以写类型,虽然语法上允许,不过我们最好保持统一写类型

全部代码

#include<iostream>
#include<list>
#include<algorithm>
#include<assert.h>
using namespace std;
namespace bai
{
	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++(int)
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		self operator--(int)
		{
			self tmp(*this);
			_node = _node->_prev;
			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;

		//typedef const __list_iterator<T> const_iterator;//错误的

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

		const_iterator end() const
		{
			return _head;
			//return const_iterator(_head);
		}
		void empty_init()
		{
			_head = new Node;
			_head->_prev = _head;
			_head->_next = _head;
			_size = 0;
		}
		list()
		{
			empty_init();
		}
		//list(const list& lt)//拷贝构造
		list(const list<T>& lt)//拷贝构造
		{
			empty_init();
			for (auto& e : lt)
			{
				push_back(e);
			}
		}
		void swap(list<T>& lt)
		{
			std::swap(_head,lt._head);
			std::swap(_size, lt._size);
		}
		
		//list& operator=(list lt)//赋值
		list<T>& operator=(list<T> lt)//赋值
		{
			swap(lt);
			return *this;
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}

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

		void push_back(const T& x)
		{
			/*Node* tail = _head->_prev;
			Node* newnode = new Node(x);
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;*/
			insert(end(),x);
		}
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		void pop_back()
		{
			erase(--end());
		}
		void pop_front()
		{
			erase(begin());
		}
		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;
		}
		size_t size()
		{
			/*size_t sz = 0;
			iterator it = begin();
			while (it != end())
			{
				++sz;
				++it;
			}
			return sz;*/
			return _size;
		}
	private:
		Node* _head;
		size_t _size;
	};
}

以上即为本期全部内容,希望大家可以有所收获

如有错误,还请指正

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

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

相关文章

BEVPoolv2 A Cutting-edge Implementation of BEVDet Toward Deployment 论文学习

Github Repo: https://github.com/HuangJunJie2017/BEVDet/tree/dev2.0 Arxiv Paper: https://arxiv.org/abs/2211.17111 1. 解决了什么问题&#xff1f; 多相机 3D 目标检测是自动驾驶领域的基本任务&#xff0c;受到学术界和工业界的大量关注。Lift-Splat-Shoot view trans…

Linux基础IO(一)

Linux基础IO 文章目录 Linux基础IOC语言中的文件操作c语言文件打开方式C语言文件操作函数 系统文件操作stdin/stdout/stderropeanclosewriteread 文件描述符重定向什么是重定向dup2 C语言中的文件操作 我们通过两个代码复习一下C语言中的文件操作&#xff1a; int main() {FI…

uniapp-开发APP使用自定义插件

uniapp-开发APP使用自定义插件 需求背景&#xff1a; 项目组开发了一个APP需要使用到打印机的功能、所以需要通过打印机厂商提供的jdk包结合自己的业务融合到uniapp 中。 首先你需要一个懂开发android开发的同事、让他帮忙配合写一些调用方法&#xff08;调用打印机提供的一些…

态势标绘专题介绍

介绍 这个专栏是专门针对基于Cesium来实现态势标绘的专题专栏,专栏主要实现了30余种态势几何形状的标绘和编辑、文本的标绘和编辑、图片的标绘和编辑以及简单模型的标绘,同时支持标绘结果的导出以及导入。包括最终编写成的一个完整的Vue3.2+TS+Cesium1.107.2的标绘组件。专栏…

【STL】list用法试做_底层实现

目录 一&#xff0c;list 使用 1. list 文档介绍 2. 常见接口 1. list中的sort 2. list sort 与 vector sort效率对比 3. 关于迭代器失效 4. clear 二&#xff0c;list 实现 1.框架搭建 2. 迭代器类——核心框架 3. operator-> 实现 4. const——迭代…

c++11 标准模板(STL)(std::basic_filebuf)(二)

定义于头文件 <fstream> template< class CharT, class Traits std::char_traits<CharT> > class basic_filebuf : public std::basic_streambuf<CharT, Traits> std::basic_filebuf 是关联字符序列为文件的 std::basic_streambuf 。输入序…

基于AOP实现登录日志和操作日志(新手入门版)

基于AOP实现登录日志和操作日志 目录结构代码PostMan测试代码控制台查看输出解析成JSON如果你觉得对你有帮助的话&#xff0c;请点赞收藏 目录结构 代码 package com.demo.mymaintest.constants;import java.lang.annotation.Documented; import java.lang.annotation.ElementT…

在Debian 12 上安装 PHP 5.6, 7.4

环境&#xff1a;Debian 12 Debian 12 默认的PHP版本为 8.2 如果直接安装php7.4就出现下面的报错&#xff1a; sudo apt-get install libapache2-mod-php7.4 php7.4 php7.4-gd php7.4-opcache php7.4-mbstring php7.4-xml php7.4-json php7.4-zip php7.4-curl php7.4-imap p…

【人工智能】神经网络、前向传播、反向传播、梯度下降、局部最小值、多层前馈网络、缓解过拟合的策略

神经网络、前向传播、反向传播 文章目录 神经网络、前向传播、反向传播前向传播反向传播梯度下降局部最小值多层前馈网络表示能力多层前馈网络局限缓解过拟合的策略前向传播是指将输入数据从输入层开始经过一系列的权重矩阵和激活函数的计算后,最终得到输出结果的过程。在前向…

HarmonyOS/OpenHarmony应用开发-Stage模型UIAbility组件使用(六)

UIAbility组件间交互&#xff08;设备内&#xff09; UIAbility是系统调度的最小单元。在设备内的功能模块之间跳转时&#xff0c;会涉及到启动特定的UIAbility&#xff0c;该UIAbility可以是应用内的其他UIAbility&#xff0c;也可以是其他应用的UIAbility&#xff08;例如启…

unity 调用C++追踪物体后的位姿通过 dll,左右手坐标转化2

外参矩阵转四元数,左右手坐标系转化1_天人合一peng的博客-CSDN博客 在之前1的基础上更新 通过定位已经求得了物体的4*4的位姿矩阵&#xff0c;将其变化为四元数并从opengl的右手坐标转化为unity的左手坐标系。 body2world_pose().matrix()为计算得到的4*4的位姿矩阵 例 body…

Go语言之结构体

在实际开发中&#xff0c;我们可以将一组类型不同的、但是用来描述同一件事物的变量放到结构体中。例如&#xff0c;在校学生有姓名、年龄、身高、成绩等属性&#xff0c;学了结构体后&#xff0c;我们就不需要再定义多个变量了&#xff0c;将它们都放到结构体中即可。 在Go语言…

基于linux下的高并发服务器开发(第三章)- 3.7 线程属性

int pthread_attr_init(pthread_attr_t *attr);- 初始化线程属性变量int pthread_attr_destroy(pthread_attr_t *attr);- 释放线程属性的资源int pthread_attr_getdetachstate(const pthread_attr_t *attr, int *detachstate);- 获取线程分离的状态属性int pthread_attr_setdet…

【Redis】所以延迟双删有啥用

文章目录 1、何为延时双删2、常用缓存策略2.1、介绍2.2、先删缓存后更库2.3、先更库后删缓存2.4、使用场景 3、延时双删实现4、为什么要使用延时双删5、方案选择6、延时双删真的完美吗7、如何确定延时的时间 1、何为延时双删 延迟双删&#xff08;Delay Double Delete&#xf…

Jupyter 安装、简单操作及工作路径更换

一、Jupyter下载安装 pip install jupyterAnaconda是Python另一个非常流行的发行版&#xff0c;它之后有着自己的叫做“conda”的安装工具。用户可以使用它来安装很多第三方包。然而&#xff0c;Anaconda会预装很多包&#xff0c;包括了Jupyter Notebook,所以若已经安装了Anac…

VUE3 语法教程

vue3 起步 刚开始学习 Vue&#xff0c;我们不推荐使用 vue-cli 命令行工具来创建项目&#xff0c;更简单的方式是直接在页面引入 vue.global.js 文件来测试学习。 Vue3 中的应用是通过使用 createApp 函数来创建的&#xff0c;语法格式如下&#xff1a; const app Vue.crea…

cmder 使用简介

文章目录 1. cmder 简介2. 下载地址3. 安装4. 配置环境变量5. 添加 cmder 到右键菜单6. 解决中文乱码问题 1. cmder 简介 cmder 是一个增强型命令行工具&#xff0c;不仅可以使用 windows 下的所有命令&#xff0c;更爽的是可以使用 linux的命令, shell 命令。 2. 下载地址 …

机器学习深度学习——预备知识(下)

机器学习&&深度学习——预备知识&#xff08;下&#xff09; 4 微积分4.1 导数和微分4.2 偏导数4.3 梯度4.4 链式法则 5 自动微分5.1 简单例子5.2 非标量变量的反向传播5.3 分离计算5.4 Python控制流的梯度计算 6 概率6.1 基本概率论6.1.1 概率论公理 6.2 处理多个随机…

5.1 Bootstrap 插件概览

文章目录 Bootstrap 插件概览data 属性编程方式的 API避免命名空间冲突事件 Bootstrap 插件概览 在前面 布局组件 章节中所讨论到的组件仅仅是个开始。Bootstrap 自带 12 种 jQuery 插件&#xff0c;扩展了功能&#xff0c;可以给站点添加更多的互动。即使您不是一名高级的 Jav…

超参数优化 - 贝叶斯优化的实现

目录 1. 基于Bayes_opt实现GP优化 1.1 定义目标函数 1.2 定义参数空间 1.3 定义优化目标函数的具体流程 4. 定义验证函数&#xff08;非必须&#xff09; 5. 执行实际优化流程 2. 基于HyperOpt实现TPE优化 2.1 定义目标函数 2.2 定义参数空间 2.3 定义优化目标函数的…