详解c++---优先级队列和仿函数

news2024/11/16 9:46:26

目录标题

  • 什么是仿函数
  • 如何定义一个仿函数
  • 什么是优先级队列
  • 优先级队列的使用
  • 模拟实现priority_queue
    • 准备工作
    • top函数的实现
    • size函数的实现
    • empty函数的实现
    • adjustup函数的实现
    • push函数的实现
    • pop函数的实现
    • adjustdown函数的实现
    • 构造函数的实现

什么是仿函数

首先仿函数就是一个类,在类里面我们一般可以对各种操作符进行重载比如说对操作符+进行重载使得一个日期类对象加上一个整数可以得到一个新的日期类对象,还可以对操作符++或者–进行重载使得不同的迭代器能够指向新的位置,而()也是一个操作符,这个操作符的功能就是调用函数并传递参数,那这个我们能对()这个操作符进行重载吗?答案是可以的,我们把一个类对象里面重载一个()操作符的类称之为仿函数,然后我们就可以创建类对象,然后通过这个对象来调用仿函数里面的内容。

如何定义一个仿函数

c++中提供了两个仿函数,分别为great和less,这两个仿函数接收两个参数功能就是比较两个数据的大小,如果左边的数据比右边的数据大的话greater仿函数就返回真,如果左边的数据比右边的数据小的话less仿函数就返回真,那这里我们就来模拟实现一下这两个仿函数,首先得创建一个类,并且类的名字就叫greater:

namespace YCF
{
	class greater
	{

	};
}

再在类里面对操作符()进行重载,因为greater仿函数返回的是true或者false,所以重载函数的返回值就是bool,因为这里要接收两个相同且任意类型的参数,所以这里得添加一个类模板,在函数体里面就直接return两个数据相比较的结果就行,那这里的代码就如下:

	template<class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x > y;
		}
	};

greater是返回x>y的结果,那么less就是返回x<y的结果,所以这里简单的将代码进行一下修改就可以得到less仿函数的模拟实现,那这里的代码如下:

	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};

实现完这两个仿函数之后就可以用这两个仿函数来创建函数对象,并对这个对象进行传参然后就可以实现运算符重载里面的功能,比如说判断打印两个数据中较大的数据就可以这么来实现:

void test2()
{
	YCF::greater<int> greaterfunc;
	int a, b;
	cin >> a >> b;
	if (greaterfunc(a, b))
	{
		cout << "较大的数据为:" << a << endl;
	}
	else
	{
		cout << "较大的数据为:" << b << endl;

	}
}

这段代码打印的结果如下:
在这里插入图片描述
那么这就是仿函数的功能,他跟c语言中的函数指针非常的相似,比如说上面的功能如果使用函数指针的话代码就是这样:

bool func_greater(const int x, const int y)
{
	return x > y;
}
typedef bool(*func)(const int x, const int y);
void test3()
{
	int a, b;
	func p=func_greater;
	cin >> a >> b;
	if (p(a, b))
	{
		cout << "较大的数据为:" << a << endl;
	}
	else
	{
		cout << "较大的数据为:" << b << endl;
	}
}

这段代码运行的结果如下:
在这里插入图片描述
那么这就是函数指针实现的代码,大家能够很明显的发现函数指针实现的代码就很麻烦不容易看懂,所以就有了仿函数来代替函数指针,而且仿函数能够添加模板和成员变量进去,能够实现比函数指针更多的功能,比如说我要比较两个浮点数的大小,两个日期类的大小就可以直接通过模板实例化来得到想要的东西,那么这就是仿函数的作用希望大家能够理解。

什么是优先级队列

在之前的学习中,大家已经知道了队列对数据处理的方式是先入队列的数据先出数据,后入队列的数据后出队列,比如说下面的代码:

void test1()
{
	queue<int> q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	cout << "出队列的顺序为:";
	while (!q.empty())
	{
		cout << q.front() << " ";
		q.pop();
	}
}

这段代码的运行结果如下:
在这里插入图片描述
queue对数据处理的方式是先入队列的元素先出队列,而身为队列的亲兄弟那优先级队列对数据处理的方式又是什么呢?答案是优先级高的数据优先出队列,比如说把数据的大小作为优先级,那么5的优先级就会比3的优先级要高,在入队列的时候尽管5比3后入队列但是在出队列的时候依然是5先出队列,这里的优先级不仅仅可以是谁的数据大,还可以是谁的数据小,如果把这个作为优先级的话那3的优先级就会比5高,3就会比5先出队列,既然可以将数据的大小作为优先级的话,那我们也可以把日期的远近作为优先级,比如说我们可以创建一个日期类,把谁的日期更加古老作为优先级,那么Data(2018,6,17)的优先级就会比Data(2022,5,2)的优先级要高,在出队列的时候就是Data(2018,6,17)这个数据先出队列,那么这就是优先级队列的用法以及意义,优先级队列的名字叫做 priority_queue,这个容器有如下几个函数:
在这里插入图片描述
接下来我们就来看看优先级队列的使用,以及这几个函数的意义。

优先级队列的使用

在这里插入图片描述
priority_queue容器有三个模板参数,第一个参数表明容器所容纳的参数类型,第二个参数表明priority_queue底层使用的什么来容纳数据,这里给了一个缺省参数vector说明priority_queue在默认情况下是使用vector来存储数据,第三个参数则是一个仿函,c++提供了两个仿函数:less和greater,priority_queue默认使用less来作为底层的比较逻辑,所以在不传递仿函数的前提下priority_queue创建的是大堆,如果你想要创建小堆的话就得传递一个greater过去,那么这就是priority_queue的三个模板参数,我们再来看看这个容器所包含的函数:
在这里插入图片描述

push函数的介绍

在这里插入图片描述
这个函数的作用就是往容器里面插入一个数据,比如说下面的代码:

void test4()
{
	priority_queue<int, vector<int>, greater<int>> p;
	p.push(5);
	p.push(3);
	p.push(2);
	p.push(6);
	p.push(1);
}

通过调试便可以看到对象p中含有我们刚刚插入进去的数据:
在这里插入图片描述

empty函数的介绍

在这里插入图片描述
该函数的作用就是检查当前容器是否为空?如果不为空的话就返回false,如果为空的话empty函数就返回true,比如说下面的代码:

void test5()
{
	priority_queue<int> p;
	if (p.empty())
	{
		cout << "当前容器为空" << endl;
	}
	else
	{
		cout << "当前容器不为空" << endl;
	}
	cout << "插入一些数据" << endl;
	p.push(1);
	p.push(2);
	if (p.empty())
	{
		cout << "当前容器为空" << endl;
	}
	else
	{
		cout << "当前容器不为空" << endl;
	}
}

这段代码运行的结果如下:
在这里插入图片描述
那么这就是empty函数的作用。

top函数的介绍

在这里插入图片描述
因为priority_queue容器的底层是一个堆,所以堆顶元素是所有元素中优先级最高的,而top函数的作用就是取堆顶元素,比如说下面的代码

void test6()
{
	priority_queue<int> big_p;
	priority_queue<int, vector<int>, greater<int>> small_p;
	big_p.push(1);
	small_p.push(1);
	big_p.push(3);
	small_p.push(3);
	big_p.push(5);
	small_p.push(5);
	cout << "big_p的堆顶元素为:" << big_p.top() << endl;
	cout << "small_p的堆顶元素为:" << small_p.top() << endl;
}

这两个对象当中含有相同的数据内容但是一个是大堆一个是小堆,所以堆顶的元素都不一样,大堆的堆顶元素是所有元素中的最大的,而小堆的堆顶元素是所有元素中最小的,那么这段代码的运行结果如下:
在这里插入图片描述

pop函数的介绍
pop函数的官方介绍如下:

在这里插入图片描述
pop函数的作用就是删除堆顶的元素也就是删除优先级最高的元素,pop函数执行完之后堆顶元素就会变成优先级第二高的元素比如说下面的代码:

void test7()
{
	priority_queue<int> big_p;
	big_p.push(1);
	big_p.push(2);
	big_p.push(3);
	big_p.push(4);
	cout << "此时容器中的元素为:1 2 3 4" << endl;
	cout << "堆顶的元素为:" << big_p.top() << endl;
	cout << "删除堆顶的元素" << endl;
	big_p.pop();
	cout << "此时堆顶的元素为:" << big_p.top() << endl;
}

代码的运行结果为:
在这里插入图片描述
size函数的介绍
首先来看看size函数的官方介绍:
在这里插入图片描述
这个函数的作用就是告诉你容器里面元素的个数,如果容器里面含有三个元素的话这个函数就会返回3,比如说下面的代码:

void test8()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	priority_queue<int> p(v.begin(),v.end());
	cout << "容器里面的数据据个数为:" << p.size() << endl;
}

这段代码的运行结果如下:
在这里插入图片描述

constructor函数介绍
在这里插入图片描述
第一个是默认构造函数,第二个就是利用任意类型的迭代器区间来构造priority_queue,比如说采用vector的迭代器区间或者list的迭代器区间来构造priority_queue,比如说下面的代码:

void test8()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	priority_queue<int> p(v.begin(),v.end());
}

通过调试便可以看到容器里面的内容跟迭代器区间里面的内容一摸一样:
在这里插入图片描述
那么这就是构造函数的作用。看到这里想必大家应该知道priority_queue的基本用法,那么接下来我们就来尝试模拟实现这个容器。

模拟实现priority_queue

准备工作

首先得创建一个类,并给这个类取名字为priority_queue,因为这个类要容纳各种各样的数据,所以得添加一个模板,又因为该容器采用的是适配器模式,所以我们得在模板中再添加一个参数用于表示底层容乃数据的容器,又因为仿函数有很多种,所以还得添加一个参数用于接收各种各样的仿函数,那这里的代码就如下:

namespace YCF
{
	template<class T>
	class less
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x < y;
		}
	};
	template<class T>
	class greater
	{
	public:
		bool operator()(const T& x, const T& y)
		{
			return x > y;
		}
	};
	template<class T,class continer=vector<T>,class compare=less<T> >
	class priority_queue
	{
	public:


	private:
		continer con;
	};
}

top函数的实现

因为这里采用的是容器适配器模式,所以我们可以直接使用方括号重载调用下标为0的元素来实现该函数,那么这里的代码如下:

		const T& top()
		{
			return con[0];
		}

size函数的实现

这个也是同样的道理调用底层容器的size函数来实现该函数,那这里的代码就如下:

		size_t size()
		{
			return con.size();
		}

empty函数的实现

这个也是同样的代码可以调用底层容器的empty函数来实现该函数,那这里的代码就如下:

		bool empty()
		{
			return con.empty();
		}

adjustup函数的实现

adjustup函数的功就是将新插入进行来的数据进行向上调整,虽然堆的底层看上去是vector,各种数据没有任何的联系,但事实上每个数据都是有联系的,比如说vector中的元素为:1 2 3 4 5 6 7 8 9,但是通过一些联系,我们可以将这一行元素想象成为这样:
在这里插入图片描述
下标为i的节点的父节点的下标为(i-1)/2,根据这个关系我们就可以找到每个节点的父节点,我们把上面的树状结构称为堆,堆又分为大堆和小堆,大堆的每个节点的值都要比子节点的值要大,而小堆的每个节点的值都要比子节点要小,所以当我们往容器的尾部插入一个数据时,这个数据可能会破坏原来的树状结构使得不再成为大堆或者小堆,比如说尾插一个0到容器的尾部,这个时候树状结构就变成了这样:
在这里插入图片描述

不符合小堆的规律所以就得对值为0的节点进行向上调整,首先值为0的节点下标为9,那么他的父节点的下标就为(9-1)/2等于4,将子节点的值与父节点进行比较,因为这是小堆并且子节点的值要比父节点的小,所以我们得把子节点的值和父节点的值进行一下交换,那这时树状图就变成了这样:
在这里插入图片描述
交换完之后,调整的任务依然没有结束,因为这时新插入的节点下标变成了4他有了一个新父亲,父亲节点的下标为1,所以还得进行一次比较又因为子节点的值比父节点的值要小,所以还得进行一下交换,那这时树状图变成了这样;
在这里插入图片描述
同样的道理这里依然会再进行一次比较,因为父节点比子节点大,所以这里又会进行一次交换,那这时树状图的形状就成为了这样:
在这里插入图片描述
那这时就不会再进行比较因为新插入的节点已经来到了根节点的位置他没有父节点了所以就不用再进行比较,这里只展示了一种情况,还有一种就是当子节点的优先级比父节点低时就不用进行比较(就是大堆时,子节点的值比父节点的值要小,小堆时子节点的值要比大堆大),有了上面的过程想必大家应该能够理解调整的过程,那这里我们就来实现一个adjustup函数,首先这个函数得接收一个参数用于告诉我们要调整的参数的下标为多少,并且函数的返回值为void,那这里函数的声明如下:

		void adjustup(size_t child)
		{

		}

因为模板中有个参数为仿函数,所以首先得通过这个仿函数创建出来一个仿函数对象用于以后的比较,然后再创建一个名为_child的变量并使用函数的参数child对其进行初始化,因为这里需要多次进行调整,所以我们创建一个while循环,每一次循环都将子节点的值与父节点进行比较,如果比较的结果为真那么就对数据进行交换并修改迟child的值,如果比较的结果为假,那么就使用break语句跳出循环,这时数据的调整就完成了,那么这里的代码就如下:

		void adjustup(size_t child)
		{
			size_t _child = child;
			compare com;
			while ()
			{
				size_t _parent = (_child - 1) / 2;
				if (com())
				{
					swap(con[_parent], con[_child]);
					_child = _parent; 
				}
				else
				{
					break;
				}
			}
		}

这里我们就得思考两个问题,第一个就是循环什么时候结束,答案很简单新插入的节点调到了正确的位置时就结束,这个正确的位置分为两种情况一个是根节点,另外一个是子节点的优先级比父节点的优先级低的位置,第二个情况通过if else语句的break实现了,那么第一种情况就是当子节点的下标小于等于0时就结束循环,好解决完循环的问题之后就来看看第二个问题,com仿函数对象的参数怎么传,库中的priority_queue传递less函数时建立的是一个大堆,当less的第一个参数小于第二个参数时这个仿函数对象就会返回true,所以当父节点的值小于子节点的值时就交换两个节点的值,所以com仿函数对象的第一个参数就是parent,第二个参数就是child,那么这里代码就如下

void adjustup(size_t child)
{
	size_t _child = child;
	compare com;
	while (_child>0)
	{
		size_t _parent = (_child - 1) / 2;
		if (com(con[_parent],con[_child]))
		{
			swap(con[_parent], con[_child]);
			_child = _parent; 
		}
	     else
		{
			break;
		}
	}
}

push函数的实现

有了adjustup函数,push函数的实现就非常的简单,首先调用底层容器的push_back函数将数据尾插到容器的尾部,然后再调用adjustup函数对其进行向上调整便可以实现数据插入的功能,那么这里的代码实现如下:

void push(const T& x)
{
	con.push_back(x);
	adjustup(con.size() - 1);
}

有了push函数我们就可以对上面写的函数来进行一下验证看看函数的实现是否是正确的,那这里测试的代码就如下:

void test9()
{
	YCF::priority_queue<int> q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	q.push(5);
	if (!q.empty())
	{
		cout << "此时的容器不为空" << endl;
	}
	cout << "容器的顶部数据为:" << q.top() << endl;
	cout << "容器的数据个数为:" << q.size() << endl;
}

这段代码的运行结果如下:
在这里插入图片描述
那么这里的运行结果就是正确的,说明我们上面的代码实现的是正确的。

pop函数的实现

pop函数的功能就是删除堆顶的元素,但是这里的删除并不是简单的删除,因为我们得保证删除数据之后还能维持大堆或者小堆属性,那么这里就得采用特殊的删除策略,首先得将堆顶的元素和最后一个元素进行交换,然后再删除容器中的最后一个元素,比如说下面的图片:
在这里插入图片描述
将堆顶的元素和队尾的元素进行交换:
在这里插入图片描述
再将队尾的元素进行删除
在这里插入图片描述
经过上面的操作我们已将之前头顶的数据给删除了,但是我们还得做一个操作就是将当前头顶的元素向下调整使得他在一个正确的位置这样才能成为堆。那么这里就得实现一个adjustdown函数,那么pop函数的完整代码就如下:

void pop()
{
	swap(con[0], con[con.size() - 1]);
	con.pop_back;
	adjustdown(0);
}

接下来我们就要看看adjustdown函数是如何来实现的。

adjustdown函数的实现

经过一些操作数据的形状就变成了这样:
在这里插入图片描述
我们要对堆顶的元素进行向下调整,第一步就是将该节点的两个子节点进行比较,得到优先级较高的节点,然后再有优先级较高的节点和该节点进行比较,看谁的优先级更高,比如说上面的图片,由于这里建立的是小堆所以字节2的优先级要比子节点3的优先级要高,所以拿2和9进行比较,因为子节点2 的优先级又比根节点9的优先级要高所以这里得将2和9的位置进行一下替换,那么图片就变成了下面的样子:
在这里插入图片描述
同样的道理9又会和新的两个两个子节点进行比较这样一直不停的比较替换比较下去,知道9没有根节点为止或者9的两个子节点的优先级都比它小为止,那么接下我们就实现一下这个代码,首先来实现一下这个函数的声明,这个函数有一个参数表明要向下调整的元素下标为多少,然后这个函数的返回值为void,那么这里的代码就如下:

void adjustdown(size_t size)
{

}	

然后创建两个变量一个称为parent一个称为child,将参数size的值初始化给parent,那child的值该是多少呢?那么这里我们采用这样的一个方法,首先默认child的值是左孩子,然后让左孩子和右孩子进行比较,如果右孩子存在且右孩子的优先级比左孩子大的话我们就让child指向右孩子,那么这里的代码就如下:

		void adjustdown(size_t size)
		{
			size_t _parent = size;
			size_t _child = _parent * 2 + 1;
			if ((_child + 1 < con.size() - 1) && com(con[_child], con[_child + 1]))
			{
				_child += 1;
			}
			
		}

然后我们就要比较parent和child的优先级,因为这里的比较不只比较一次,所以我们得创建一个while循环,在循环体里面如果子节点的优先级大于父节点就使用swap函数交换两个节点的值并更新parent和child的值,相反则使用break函数结束循环,当child的值大于容器数据个数的时候则结束循环,那么完整的代码就如下:

void adjustdown(size_t size)
		{
			compare com;
			size_t _parent = size;
			size_t _child = _parent * 2 + 1;
			while (_child < con.size())
			{
				if ((_child + 1 < con.size()) && com(con[_child], con[_child + 1]))
				{
					_child += 1;
				}
				if (com(con[_parent], con[_child]))
				{
					swap(con[_parent], con[_child]);
					_parent = _child;
					_child = _parent * 2 + 1;
				}
				else
				{
					break;
				}
			}
		}

我们可以通过下面的代码来测试一下:

void test9()
{
	YCF::priority_queue<int> q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	q.push(5);
	if (!q.empty())
	{
		cout << "此时的容器不为空" << endl;
	}
	cout << "容器的顶部数据为:" << q.top() << endl;
	cout << "容器的数据个数为:" << q.size() << endl;
	q.pop();
	cout << "删除堆顶的数据" << endl;
	cout << "容器的顶部数据为:" << q.top() << endl;
	cout << "容器的数据个数为:" << q.size() << endl;
	q.pop();
	cout << "删除堆顶的数据" << endl;
	cout << "容器的顶部数据为:" << q.top() << endl;
	cout << "容器的数据个数为:" << q.size() << endl;
}

代码的运行结果如下:
在这里插入图片描述
那么这就说明我们的代码实现是正确的。

构造函数的实现

本篇文章的最后再来看看该容器的构造函数如何来实现,一个是默认构造函数,一个是迭代器区间的构造函数,因为该容器的底层采用其他的容器来记录数据,所以在默认构造函数里面我们就什么事情也用干,编译器自己会调用底层容器的默认构造来初始化数据,那么这个构造函数的代码如下:

priority_queue()
{

}

另外一个构造函数就是迭代器区间构造函数,这个构造函数得接收两个迭代器,因为迭代器的形式各种各样,所以得对这个函数添加一个模板,那么这里的代码如下:

template<class iterator>
priority_queue(iterator begin, iterator end)
{

}

在函数体里面我们就可以创建一个while循环,在循环里面不停的使用push函数将输出插入到优先级队列里面,循环结束的条件就是当begin等于end的时候结束循环,那么这里的代码就如下:

		template<class iterator>
		priority_queue(iterator begin, iterator end)
		{
			while (begin != end)
			{
				push(*begin);
				++begin;
			}
		}

那这里我们可以写一段代码来测试一下上面的函数正确性:

void test10()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	YCF::priority_queue<int> q(v.begin(), v.end());
}

这里采用的是自己写的构造函数,我们可以通过调试来看看容器里面的内容:
在这里插入图片描述
数据的顺序为5 4 2 1 3 ,我们将前面的命名空间去掉,使用库中的优先级队列再查看里面的内容就会发现好像内容不大一样:
在这里插入图片描述
库中的数据顺序为 5 4 3 1 2 那这是为什么呢?难道我们之前写的代码是错误的吗?答案是库中采用向下调整的方法来实现的构造函数,向下调整的代码如下:

		template<class iterator>
		priority_queue(iterator begin, iterator end)
			:con(begin,end)
		{
			for (int i = (con.size() - 1) / 2; i >= 0; --i)
			{
				adjustdown(i);
			}
		}

那么这就是向下调整的代码,使用这种方式初始化代码得到的结果就和库中的一摸一样。那么以上就是本篇文章的全部内容希望大家能够理解。

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

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

相关文章

若依任意文件下载(CVE-2023-27025)

若依它就是一个开源项目,任何公司的各种大的项目必然需要一个后台权限管理系统,这是必然的,但是如果不想投入太多人力物力去开发,又恰好有现成且比较好用的别人已经完成的项目直接供我们来使用 。 1、使用、减少工作量 2、学习优秀的开源项目底层的编程思想,设计思路,提…

UDP通讯(服务器/客户端)

前言&#xff1a;UDP通讯实现比较简单&#xff0c;单某些情况下也会使用&#xff0c;建议先看一下说明&#xff0c;然后运行代码感受一下。 UDP服务器 传输层主要应用的协议模型有两种&#xff0c;一种是TCP协议&#xff0c;另外一种则是UDP协议。TCP协议在网络通信中占主导地…

Golang-如何判断一个 interface{} 的值是否为 nil ?

引用 起初我会下意识的回答&#xff0c;直接 v nil 进行判断不就好了吗&#xff1f; 然后翻阅了很多资料终于大致搞定里面的道道. 例子 请看下面这段代码&#xff0c;可以先猜测一下输出的结果。 package mainimport ("fmt" )func main() {var a *string nilv…

网安笔记 08 key management

Key Management —— 不考 网络加密方法 1.1 链路加密 特点&#xff1a; 两个相邻点之间数据进行加密保护 不同节点对密码机和Key不一定同中间节点上&#xff0c;先解密后加密报文报头可一起加密节点内部&#xff0c;消息以明文存在密钥分配困难保密及需求数量大 缺点&…

day36_jdbc

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、JDBC 二、登录 三、ORM 零、 复习昨日 sql语言&#xff1a;DDL DML DQL DCL create table 表名(id int primary key auto_increment,sname varchar(2…

【SAP Abap】X-DOC:SMW0 - Excel 导入模板的上传和下载

X-DOC&#xff1a;SMW0 - Excel 导入模板的上传和下载 1、实现效果2、模板上传3、下载功能代码 做批导程序&#xff0c;离不开 Excel 导入模板&#xff0c;为了方便用户&#xff0c;一般会将模板文档整合到导入功能的界面上&#xff0c;方便用户获取模板。 1、实现效果 2、模板…

两个form表单的数据存入同一个数据库表,使用使用json操作

项目场景&#xff1a; 两个form表单的数据存入同一个数据库表 问题描述 1.两个from表单数据一起传到后端但是数据解析和xml文件的sql获取不到报错 2.数据接受到了但是提示数据类型不兼容 3.使用RequestBody注解报错Content type application/x-www-form-urlencoded;charsetUT…

深入解析linux IO Block layer

早期的 Block 框架是单队列&#xff08;single-queue&#xff09;架构&#xff0c;适用于“硬件单队列”的存储设备&#xff08;比如机械磁盘&#xff09;&#xff0c;随着存储器件技术的发展&#xff0c;支持“硬件多队列”的存储器件越来越常见&#xff08;比如 NVMe SSD&…

makefile 变量的替换,嵌套引用,命令行变量

文章目录 前言一、变量替换&#xff1a;1. 变量值的替换。2. 变量的模式替换。3. 规则中的模式替换。 二、变量的嵌套使用三、命令行变量四、override ,define 关键字总结 前言 一、变量替换&#xff1a; 1. 变量值的替换。 使用 指定的字符&#xff08;串&#xff09;替换变…

ctfshow web入门 ssrf web351-355

1.web351 尝试访问本机的flag.php payload: urlhttp://localhost/flag.php urlhttp://127.0.0.1/flag.php2.web352 必须要用http或https&#xff0c;ip没有过滤因为匹配时没加变量&#xff0c;恒为真 payload: urlhttp://127.0.0.1/flag.php urlhttp://localhost/flag.php3.…

智能美妆镜兴起,如何升级更精细、智能的化妆体验!

经常化妆的小姐姐&#xff0c;会发现化妆除了要有好皮肤、一堆化妆品之外&#xff0c;化妆镜的作用也尤其重要&#xff01;爱拍照的小姐姐们都知道&#xff0c;自拍的效果好不好&#xff0c;和背景、灯光有着很大的关系&#xff0c;其中灯光的冷调或者暖调&#xff0c;也是影响…

Linux网络——Shell编程之数组

Linux网络——Shell编程之数组 一、概念二、数组的定义三、Shell数组操作1. 获取数组的所有元素的列表2. 获取数组的所有元素下标3.取数组的元素个数4. 获取数组的某个元素的值5.删除数组某个元素6.删除数组7.数组切片8.数组字符替换9.数组追加元素 四、数组在函数的传参 一、概…

阿里云Intel(R) Xeon(R) Platinum处理器2.5 GHz主频

阿里云服务器CPU处理器Intel(R) Xeon(R) Platinum&#xff0c;2.5 GHz主频&#xff0c;3.2 GHz睿频&#xff0c;测试的云服务器ECS为通用算力型u1实例ecs.u1-c1m2.large&#xff0c;如下图&#xff1a; 阿里云服务器CPU处理器Intel(R) Xeon(R) Platinum 目前使用这款CPU处理器…

c++类和对象重要巩固练习-------日期类对象

这是对于类和对象的练习篇&#xff0c;用c来模拟完成日期计算器。 这其中需要我们完成&#xff1a; 日期 - 天数后 得到的新日期 日期 - 日期 得到相差的天数比较日期的大小等 .....具体如下头文件类中的成员函数 #pragma once #include<iostream> using namespace std…

【UE】简单实现屏幕UI定位

【UE】简单实现屏幕UI定位三维坐标方法 实现效果 屏幕空间定位 场景空间定位 一般实现兴趣点&#xff08;POI&#xff09;有两种实现方法&#xff0c;场景空间UI定位和屏幕空间UI定位。 场景空间定位&#xff1a;UI类似实例模型&#xff0c;位置和尺寸是相对于场景不变。大多…

Laravel 知识整合 1 路由

Laravel是一种基于php的MVC web开发框架 php artisan make:controller NAME//创建一个名字为name的controller路由&#xff1a;routes/web.php Route::get(/students,StudentControllerindex); 控制器&#xff1a;app/HTTP/Controllers&#xff08;可以直接用命令行生成&…

SSM框架学习-依赖注入、自动装配、暴力反射

1. 依赖注入 1.1 引用类型 注入引用类型 一种是默认按照类型装配 一种是如果有相同类型的bean采用按bean名称装配 但是在写的过程中Qualifier前面必须写上Autowired 也就是必须依赖Autowired //Autowired&#xff1a;注入引用类型&#xff0c;自动装配模式&#xff0c;默认按类…

一场面试下来,狂飙 Framework底层原理

近期有位Android 开发的朋友在吐槽说&#xff1a;现在的招聘信息网站是假的吧&#xff0c;我都怀疑一些公司的人事在冲他们的KPI&#xff0c;三个月了&#xff0c;简历投了上百份&#xff0c;结果收到回应的才个位数&#xff0c;去面试还一套Framework底层原理面试题狂炸…… 面…

178_技巧_Power BI 动态排名多项展示

178_技巧_Power BI 动态排名多项展示 一、背景 在 Power BI 中做排名矩阵时&#xff0c;我们经常遇到同一维度下&#xff0c;多项展示排名的问题。类似这样的排名矩阵&#xff0c;排名的名次不会太多&#xff0c;但是同一维度下会有多项同时展示排名&#xff0c;并且还要满足…

树结构 数据可视化

这边有个 树结构数据&#xff0c;想做一个可视化展示的需求&#xff1b;首先想到的是 python pyecharts, 一直传说 python 做数据可视化 一把梭&#xff1b; pyecharts 在线实例展示 pyecharts 官网 当看到上面的 示例 后&#xff0c;这不就我想找的嘛&#xff01;哈哈 随后 找…