【C++】STL六大组件之一——适配器(adapters)

news2024/12/26 9:21:45

目录

  • 1. 前言
  • 2. 初始适配器
    • 2.1 适配器的概念
    • 2.2 适配器的分类
  • 3. 容器适配器(container adapters)
    • 3.1 认识deque
      • 3.1.1 逻辑结构
      • 3.1.2 物理结构
      • 3.1.3 deque的迭代器
      • 3.1.4 选择deque做stack/queue底层容器的原因
    • 3.2 stack
    • 3.3 queue
    • 3.4 另一种容器适配器 —— 优先级队列
      • 3.4.1 认识priority_queue
      • 3.4.2 priority_queue的使用
      • 3.4.3 底层实现
  • 4. 仿函数适配器(function adapters)
    • 4.1 认识仿函数
    • 4.2 什么是仿函数适配器
  • 5. 迭代器适配器(iterator adapters)
    • 🔎 通过reverse_iterator深入理解迭代器适配器


1. 前言

开篇发问,什么是STL?

💭STL,全名标准模板库(Standard Template Library),是C++中的一个软件库,建立了数据结构和算法的一套标准,并降低了其间的耦合性,以达到提升各自的独立性、弹性和复用性的目的。

📝 STL提供了六大组件

  1. 容器(containers):如vector、list等数据结构,用以存放数据。
  2. 算法(algorithms):如常用的sort、swap算法。
  3. 迭代器(iterators):扮演容器和算法之间的胶合剂,是一种”泛型指针“。
  4. 空间配置器:负责空间配置与管理。
  5. 仿函数:行为类似函数的类。
  6. 适配器(adapters):一种用来修饰容器或仿函数或迭代器接口的东西

本文将着重介绍STL六大组件之一 —— 适配器




2. 初始适配器

2.1 适配器的概念

适配器(adapters)在STL组件的灵活组合运用功能上,扮演着轴承、转换器的角色。Adapters这个概念,事实上是一种设计模式(design pattern),在《Design Patterns》一书中对adapter样式的定义如下:将一个类(class)的接口转换成另一个类(class)的接口,使原本因接口不兼容而不能合作的两个类(classes)可以一起运作。

节选自《STL源码剖析》一书

💡 通俗理解,如果一个类(class A)(或是函数等)的成员、功能与另一个类(class B)类似,但是有一些不同之处,可以通过封装class B来实现class A,从而将class B的接口转化为class A 的接口。总而言之,就是把一个已经存在的东西,改成一个我们需要的东西。

2.2 适配器的分类

💨STL所提供的各种适配器,大致分为三类:

  1. 容器适配器(container adapters)
  2. 仿函数适配器(function adapters)
  3. 迭代器适配器(iterator adapters)

📝 接下来将逐个介绍




3. 容器适配器(container adapters)

💭 其实,STL给我们的stack、queue(栈和队列)并不是所谓的容器(如vector、list等),而是容器适配器。stack和queue封装其他容器,修饰其接口以满足自身逻辑结构的需求(stack先入后出、queue先入先出)。

💬下面抛出两个问题:

  • 所谓STL中的stack和queue封装其他容器,这个其他容器是什么呢?
    是另外一个线性序列容器,deque

  • 为什么要使用deque作为stack和queue的底层容器呢?
    想要回答这个问题,必须先简单认识deque的结构。

3.1 认识deque

3.1.1 逻辑结构

deque是一种双向开口的连续动态线性空间,又称双端队列 (这里的队列没有先进先出的原则)。所谓双向开口,就是可以在空间头尾两端分别做元素的插入和删除操作。

💭对比vector,虽然vector也是可以在头尾两端进行操作的连续线性空间,但是在其头部操作的效率非常低,因此无法被接受。

在这里插入图片描述

(deque的逻辑结构示意图)

3.1.2 物理结构

一些概念

  1. deque并不是真正的连续空间,而是由一段一段的定量连续空间构成的,这些片段式的小空间称为缓冲区
  2. 缓冲区由一个中控台控制着,并且各段小空间以及中控台会根据需要动态变化。缓冲区是deque存储数据的主体。
  3. 中控台是一个类似指针数组的空间,其中每个元素是一个指针,分别指向不同的缓冲区。

在这里插入图片描述

(deque的物理结构示意图)

  1. deque没有vector和array所谓容量(capacity)的概念,因为它是动态增长的。deque的插入、删除会关注操作位置所在缓冲区的元素个数。

💭拿尾插来说,若操作位置所在缓冲区尚未满载,则直接插入

在这里插入图片描述

💭若操作位置所在缓冲区已满,则要新开辟一个缓冲区,并链接到当前中控台位置的下一位置,再插入到新缓冲区的首位。

在这里插入图片描述

此外,尾删(pop_back)、头插(push_front)、头删(pop_front)也都会关注操作位置所在缓冲区的元素个数。做法与尾插相同,这里就不一一演示,

3.1.3 deque的迭代器

deque不是真正意义上的连续空间,而是分段式的空间,为了营造出整体连续的假象,满足随机访问的需求,STL为deque设计出一个复杂的迭代器。

🔎 deque的迭代器类包含四个指针,分别是:cur, first, last, node

在这里插入图片描述

(deque的迭代器示意图)

指针指向
cur此迭代器所指的当前元素
first当前元素所在缓冲区的头部
last当前元素所在缓冲区的尾部
node中控台内指向当前缓冲区的指针的位置
  • deque如何利用这样的迭代器来维护其“连续”的空间?

💭deque的类成员大致如下,用了两个迭代器控制空间的首尾。

template <class T>
class deque
{
	// ...
	iterator start;  // cur指向第一个结点
	iterator finish; // cur指向最后一个结点的下一个位置
	map_pointer map; // 指向中控台,实际是个二级指针T**(与迭代器中的node指针类型相同)
}

在这里插入图片描述

start、finish的指向

💡综上所述:

迭代器中各个指针各司其职,cur负责迭代器的首要工作,访问所指元素,first和last则在告诉迭代器访问的范围,当使用迭代器对deque做遍历操作时,只要迭代器指向还没脱离当前缓冲区,就支持随机访问,而当迭代器的cur和last指针相同时,若要继续访问下一个元素,则要跳到下一个缓冲区,恰好node就会帮我们找到下一个(或上一个)缓冲区。


3.1.4 选择deque做stack/queue底层容器的原因

💭 简单了解deque的结构后,我们就可以解答刚刚提出的问题:为什么要使用deque作为stack和queue的底层容器呢?

先谈谈deque相比vector、list的优缺点

  • deque的缺点
  1. 随机访问效率低。虽然deque支持随机访问,但是其效率远没有vector、array的效率高。因为其分段式的结构,使其在跨越不同缓冲区的时候会消耗时间,从而降低效率。
  2. 不适合遍历。受其结构特性影响,对deque遍历时,需要频繁检测迭代器是否到达某个缓冲区的边界,导致效率低下。
  • deque的优点
  1. 相比vector,deque减少了空间浪费,按需开辟空间。在数据量较少的情况下,deque的数据都集中在某几个缓冲区,此时可以基本忽略其缺点(随机访问效率低、不适合遍历),性能优于vector。而且,deque的头删、头插的效率也远高于vector,因为它不用挪动数据,最多只需再开一块空间。
  2. 相比list,deque支持随机访问。并且其底层是连续空间,空间利用率比较高,不需要存储额外字段

🔎为什么要使用deque作为stack和queue的底层容器呢?

  • stack和queue都是特殊的线性数据结构,只在端口处访问数据,stack先进先出,queue先进后出。都不支持遍历和随机读取,所以deque运用于stack和queue中,其缺点并不会体现出来。
  • 而且,deque支持头插尾插、头删尾删(且效率高于vector),提供“整体连续”的空间,提高了空间的使用率(vector有空间冗余问题,list需要存储额外字段)。
  • 所以使用deque作为stack和queue的底层容器可谓是“取其精华、去其糟粕”,规避了deque的缺点,利用了deque的优点。

💭弄清楚STL中stack和queue的底层容器是什么,接下来再介绍stack和queue

3.2 stack

🔎stack(栈)是一种容器适配器,逻辑结构是后入先出(LIFO),且只能访问栈顶元素。作为容器适配器,stack底层容器可以是任何支持push_back、pop_back、empty、back等操作的容器,但STL选择了deque,原因上文已解释。封装特定容器后,再提供一系列特殊的接口,以达到其后入先出的效果。

stack文档介绍

在这里插入图片描述

(stack的逻辑结构示意图)


💭 stack的结构和使用方法相信大家已经十分熟悉,这里就不过多解释,重点在于如何基于适配器的设计模式,模拟实现一个stack。

下面直接上代码:

namespace ckf // 命名空间封装,防止与标准库中的stack命名冲突
{
	template <class T,class Container = deque<T>> //增加一个模板参数,用于指定底层容器类型
	class stack
	{
		typedef T value_type;
		
	public:
		// 设计思路:将deque的尾部视为stack的栈顶即可,复用deque的接口,加以修饰成为stack的接口
		
		void push(value_type val) // 压栈操作
		{
			_st.push_back(val); // 复用deque的push_back
		}

		void pop() // 入栈操作
		{
			_st.pop_back(); // 复用deque的pop_back
		}

		value_type& top() // 取栈顶元素
		{
			return _st.back(); // 复用deque的back,取尾部元素
		}

		const value_type& top() const // 取栈顶元素(受const保护的)
		{
			return _st.back();
		}

		size_t size() const
		{
			return _st.size();
		}

		bool empty() const 
		{
			return _st.empty();
		}
		
	private:
		Container _st;
	};
}

💬 测试

void testStack()
{
	ckf::stack<int> st;
	st.push(1);
	st.push(2);
	st.push(3);
	st.push(4);
	st.push(5);
	st.pop();
	st.pop();

	while (!st.empty())
	{
		cout << st.top() << endl;
		st.pop();
	}
	cout << endl;
}

💡 符合预期

在这里插入图片描述


3.3 queue

🔎queue(队列)也是一种容器适配器,逻辑结构是先入先出(FIFO),可以访问队头元素和队尾元素。 同样使用deque作为底层容器,修饰deque接口成为自身的接口,以满足自身先入先出(队尾入、队头出)的逻辑。

queue文档介绍

在这里插入图片描述

(queue的逻辑结构示意图)

⭕ 基于适配器的设计模式,模拟实现一个queue

namespace ckf
{
	template <class T, class Container = deque<T>>
	class queue
	{
		typedef T value_type;
		
	public:
	
		void push(value_type val) // 入队
		{
			_q.push_back(val);
		}

		void pop() // 出队
		{
			_q.pop_front();
		}

		// 取队尾元素
		value_type& back()
		{
			return _q.back();
		}

		const value_type& back() const 
		{
			return _q.back();
		}
		
		// 取队头元素
		value_type& front()
		{
			return _q.front();
		}

		const value_type& front() const
		{
			return _q.front();
		}

		size_t size() const
		{
			return _q.size();
		}

		bool empty() const
		{
			return _q.empty();
		}
		
	private:
		Container _q;
	};
}

💬 测试

void testQueue()
{
	ckf::queue<int> q;
	q.push(1);
	q.push(2);
	q.push(3);
	q.push(4);
	q.push(5);
	q.pop();
	q.pop();

	while (!q.empty())
	{
		cout << q.front() << " ";
		q.pop();
	}
	cout << endl;
}

💡 符合预期

在这里插入图片描述


3.4 另一种容器适配器 —— 优先级队列

3.4.1 认识priority_queue

💭 在STL库中,除了stack和queue这两种容器适配器外,还存在另一种容器适配器 —— priority_queue,即优先级队列。

🔎priority_queue 是一种容器适配器,不同于队列,它并没有先入先出的逻辑结构,而是一种根据元素优先级决定排列顺序的结构,且只能读取其头部第一个数据 top,不能修改。STL中默认priority_queue第一个元素始终是最大元素。


3.4.2 priority_queue的使用

💭 priority_queue和queue在同一个头文件,使用时#include <queue>即可

  • 构造函数

在这里插入图片描述
有全缺省默认构造,也有迭代器范围构造,根据迭代器区间中的序列生成对应的 priority_queue。

  • 其他成员函数
成员函数功能
push压入一个新元素
pop弹出首元素
top读取首元素
empty判空
size获得长度

详见priority_queue文档介绍

⭕ 注意:priority_queue 的push和pop操作,在改变其序列后,会重新调整顺序,保证首元素一定是最大元素。

在这里插入图片描述

(优先级队列push数据前后的示意图)

在这里插入图片描述
(优先级队列pop数据前后的示意图)

3.4.3 底层实现

🔎事实上,priority_queue底层就是一个堆(默认是大堆),因为大堆堆顶元素始终是最大的,所以优先级队列首个元素始终是最大的。学习过堆的存储方式我们知道,堆用一个数组存储即可。因此,priority_queue的底层容器是一个vector,通过修饰vector的各种接口,实现堆的效果。

关于堆的原理和实现,我在《【数据结构】二叉树BinaryTree 》一文中已详细分析,这里不再过多赘述,直接利用堆的特性,模拟实现一个priority_queue。

namespace ckf
{
	// 优先级队列
	template <class T,class Container = vector<T>,class Compare = less<T>>
	class priority_queue
	{
	public:
		priority_queue() // 空构造
		{}

		template <class InputIterator>
		priority_queue(InputIterator first, InputIterator last) // 迭代器范围构造函数
			:_cont(first, last)
		{
			// make heap
			int begin_parent = (_cont.size() - 1 - 1) / 2;
			while (begin_parent >= 0)
			{
				adjust_down(begin_parent--);
			}
		}

		void adjust_up(int child) //向上调整算法
		{
			int parent = 0;
			while (child > 0) // parent >= 0 err!! 当child == 0 parent = (child - 1) / 2 = 0;
			{
				parent = (child - 1) / 2;
				if (_cmp(_cont[parent], _cont[child]))
				{
					swap(_cont[child], _cont[parent]);
					child = parent;
				}
				else
					break;
			}
		}

		void adjust_down(int parent) // 向下调整算法
		{
			int child = parent * 2 + 1;
			while (child < _cont.size())
			{
				if (child + 1 < _cont.size() && _cmp(_cont[child], _cont[child + 1])) // 找大孩子
				{
					++child;
				}

				if (_cmp(_cont[parent], _cont[child]))
				{
					swap(_cont[child], _cont[parent]);
					parent = child;
					child = parent * 2 + 1;
				}
				else
					break;
			}
		}

		void push(const T& val) // 入数据
		{
			_cont.push_back(val);

			adjust_up(_cont.size() - 1);
		}

		void pop() // 出数据
		{
			swap(_cont.front(), _cont.back());
			_cont.pop_back();

			adjust_down(0);
		}

		bool empty() const // 判空
		{
			return _cont.empty();
		}

		const T& top() const // 取堆顶元素,且不能修改(修改会破坏堆的结构)
		{
			return _cont[0];
		}

		size_t size() const
		{
			return _cont.size();
		}

	private:
		Container _cont;
		Compare _cmp = Compare(); // 仿函数
	};
}

🔎 模拟priority_queue的大致思路就是封装vector,并修饰vector的各个接口以实现堆的逻辑,这就是适配器的设计模式。
而从代码中我们看到,在对两个元素进行比较时,并没有直接用> <去比,而是用了一个 Compare类型的对象,这个对象是priority_queue的一个成员变量,这个对象我们称之为仿函数,为什么能用其进行元素比较呢?下面介绍。



4. 仿函数适配器(function adapters)

4.1 认识仿函数

💭 引入:很多算法、对象其实都有几个版本,举个例子,简单的排序算法,就有升序、降序两个版本,而上面我们模拟实现的priority_queue,默认底层是大堆,但也可以修改为小堆。先拿排序来说,若要控制一个排序算法是升序或是降序,应该怎么办呢?从C语言的角度,可以为该算法增加应该参数,这个参数是一个函数指针,我们传入不同的比较函数,就可以实现不同方式的比较,也就可以控制排序的是升降序。但是,在STL中,函数指针毕竟不能满足抽象性的要求,也无法与其他组件搭配使用。因此,STL提供了仿函数。

  • 概念
    🔎仿函数是STL中六大组件之一,又名函数对象,其本质实际就是一个类,是一个行为类似函数的类。 为了能够“行为类似函数”,仿函数中必须对函数调用操作符进行重载,即operator(),使我们能够像调用函数一样去调用这个仿函数。

下面简单写一个仿函数less,他的功能是判断左参数是否小于右参数。这是STL中提供的,我们简单模拟实现一下。

namespace ckf
{
	template <class T>
	class less
	{
		bool operator()(const T& x, const T& y) const // 重载()
		{
			return x < y;
		}
	};
}

💬 测试

void test()
{
	less<int> cmp;

	cout << cmp(1, 100) << endl;
	cout << less<int>()(1, 100) << endl;
}

仿函数的调用方式有两种,一是先创建一个仿函数对象,在调用,二是匿名对象调用。第二种方法较为常见。

⭕ 运行结果

在这里插入图片描述

STL中提供了许多的仿函数,定义在functional头文件中

详见<functional>的文档介绍

现在我们知道什么是仿函数了,也就知道了为什么priority_queue使用仿函数去进行元素大小的比较。若想让priority_queue的底层是小堆,则给模板参数Compare传入greater<T>(大于的比较)即可。

greater的文档介绍


4.2 什么是仿函数适配器

💭STL中提供了一系列仿函数适配器(functor adapters),这些适配操作包括连结(bind)、否定(negate)、组合(compose)以及对一般函数或成员函数的修饰。

  • 仿函数适配器可以封装、修饰一个仿函数,变成符合我们需要的仿函数

💭 例如,functional库里提供的仿函数适配器bind2nd,可以指定某个仿函数的第二个参数。例如,我们想要获得一个能够判断一个数是否小于10的仿函数,bind2nd<less<T>, 10> (这是一个仿函数) 便能满足需求。

详见bind2nd的文档介绍



5. 迭代器适配器(iterator adapters)

💭 STL还为迭代器提供了一系列适配器,包括:insert_iterator, reverse_iterator, istream_iterator等,定义在头文件<iterator>中。

🔎作用:修饰普通迭代器的接口,使其成为另一种迭代器,发挥不同的作用。

在这里插入图片描述

(STL提供的iterator adapters一览)

💭 学习vector、list时,调用rbegin(), rend()接口取出的反向迭代器,事实上只是一个迭代器适配器,就是这里的reverse_iterator。

reverse_iterator是一个类模板,给模板参数传入一个迭代器类,它就会将其封装为反向迭代器。

在这里插入图片描述

详见reverse_iterator的文档介绍


🔎 通过reverse_iterator深入理解迭代器适配器

  • 如何实现反向迭代器呢?

反向,无非就是迭代器的运动反向与原来的相反。即 ++----++。那么反向迭代器的前进操作,只需在 reverse_iterator的operator++中调用Iterator的operator– 即可,后退同理。

  • 容器之反向迭代器的始末位置稍有不同

学过vector、list我们知道,他们的begin接口取出的迭代器指向容器首位,end指向最后一个元素的下一个位置。如下图:

在这里插入图片描述

(vector的begin,end示意图)

STL中为了配合迭代器区间的“前闭后开”的习惯,规定容器的rbegin、rend指向如下:

在这里插入图片描述

因此,当调用 * 操作时,正向迭代器与反向迭代器的取值是不同的,反向迭代器会获取指向位置的前一个元素。

在这里插入图片描述

(红色箭头代表迭代器的实际指向)

💬 reverse_iterator的模拟实现:

namespace ckf
{
	template <class Iterator, class Ref, class Ptr>
	class reverse_IteratorAdapter
	{
		typedef reverse_IteratorAdapter<Iterator, Ref, Ptr> Self;

	public:
		reverse_IteratorAdapter()
		{}

		explicit reverse_IteratorAdapter(const Iterator& it) // 用正向迭代器构造反向迭代器
			:_it(it)
		{}

		reverse_IteratorAdapter(const Self& otherIter) // 拷贝构造反向迭代器
			:_it(otherIter._it)
		{}

		Self& operator=(const Self& otherIter) // 赋值重载
		{
			if (*this != otherIter)
			{
				_it = otherIter._it;
			}

			return *this;
		}

		Self& operator++() // 前置++
		{
			--_it;
			return *this;
		}

		Self operator++(int) // 后置++
		{
			Self tmp(_it);
			--_it;
			return tmp;
		}

		Self& operator--() // 前置--
		{
			++_it;
			return *this;
		}

		Self operator--(int) // 后置--
		{
			Self tmp(_it);
			++_it;
			return tmp;
		}

		Ref operator*()
		{
			// 访问前一个
			Iterator tmp = _it;
			return *(--tmp);
		}

		Ptr operator->()
		{
			// 重载->就是取地址
			return &(operator*());
		}

		bool operator==(const Self& otherIter) const
		{
			return _it == otherIter._it;
		}

		bool operator!=(const Self& otherIter) const
		{
			return _it != otherIter._it;
		}

	private:
		Iterator _it; // 底部封装了正向迭代器
	};

	// 容器(以vector为例)内部的类型定义和rbegin()、rend()接口

	template <class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		typedef reverse_IteratorAdapter<iterator, T&, T*> reverse_iterator;
		typedef reverse_IteratorAdapter<const_iterator, const T&, const T*> const_reverse_iterator;
		//...
		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		const_reverse_iterator rbegin() const // const的容器就取const的迭代器,为了保护容器内部数据
		{
			return const_reverse_iterator(end()); // 此处的end()为 const_iterator 类型,传入构造函数,构造函数的参数必须也是const_iterator类型才能接收
		}

		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

		const_reverse_iterator rend() const
		{
			return const_reverse_iterator(begin());
		}
		//...
	}
}

⭕ 测试

void test_reverse_iterator()
{
	int arr[] = { 1,2,3,4,5 };
	ckf::vector<int> v(arr, arr + sizeof(arr) / sizeof(arr[0]));
	ckf::vector<int>::reverse_iterator rit = v.rbegin();
	while (rit != v.rend())
	{
		cout << *rit << " ";
		++rit;
	}
	cout << endl;
	
	const ckf::vector<int> v2(arr, arr + sizeof(arr) / sizeof(arr[0]));
	ckf::vector<int>::const_reverse_iterator crit = v2.rbegin();
	cout << *(++crit) << endl;
}

💡 符合预期

在这里插入图片描述


完。

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

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

相关文章

阿里云计算巢 x GBase GCDW:自动化部署云原生数据仓库

近日&#xff0c;阿里云计算巢与天津南大通用数据技术股份有限公司&#xff08;以下简称&#xff1a;GBASE&#xff09;合作&#xff0c;双方融合各自技术优势&#xff0c;助力企业用户实现云上数据仓库的自动化部署&#xff0c;让用户在云端获取数据仓库服务“更简单”&#x…

【ESP32+freeRTOS学习笔记-(六)软件定时器】

目录1、软件定时器概念2、软件定时器的运行机制2.1 组成2.2 创建2.3 运行3、软件定时器的属性和状态3.1 定时器的周期3.2 定时器的类型3.3 定时器的状态4、软件定时器的回调函数原型5、定时器的使用5.1 创建定时器xTimeCreate()5.2 启动定时器xTimerStart()5.3 终止定时器xTime…

IPC进程间通信-system V 共享内存

&#x1f9f8;&#x1f9f8;&#x1f9f8;各位大佬大家好&#xff0c;我是猪皮兄弟&#x1f9f8;&#x1f9f8;&#x1f9f8; 文章目录一、共享内存原理二、共享内存的建立原理三、共享内存的创建四、共享内存的删除五、共享内存挂接到自己的地址空间六、从进程地址空间去掉与…

快过年了用Python抢红包

快过年了&#xff0c;刚刚收到了两个消息&#xff0c;一个好消息&#xff0c;一个坏消息。 先说好消息&#xff0c;好消息就是微信群里即将有人要发红包&#xff0c; 坏消息是我抢不上&#xff01; 难道就这么放弃了吗&#xff1f;那就只能试试能不能通过编程的方式实现自动化…

基于轻量级YOLOV5+BIFPN的苹果瑕疵检测识别分析系统

BIFPN是一种比较经典有效的特征融合手段&#xff0c;在很多检测模型中都有集成应用&#xff0c;实际表现也验证了BIFPN的有效性&#xff0c;这里并不是要探讨BIFPN的原理内容&#xff0c;而是想集成这项技术&#xff0c;提升原有模型的性能表现&#xff0c;在我之前的一些文章中…

排序算法之冒泡排序

一般学习过编程的人都知道&#xff0c;排序算法有很多种&#xff0c;包括直接选择排序、直接插入排序、计数排序、快速排序、归并排序、冒泡排序等&#xff0c;在我看来&#xff0c;以上六种排序算法是必须要掌握的&#xff0c;今天&#xff0c;我们先来讲解一下冒泡排序算法&a…

Java高手速成 | 新增类Record的工作实例

01、什么是Record? Record 是Java新增的库类&#xff0c;在Java 14和Java 15中以预览&#xff08;preview&#xff09;形式公布。Record类用来自动生成对定义数据进行创建、设置、访问以及比较等代码&#xff0c;所以又被称作数据类&#xff08;data class&#xff09;。在一…

初级开发者福音:手把手教你实现数字滚动效果~

文章目录一、前言二、背景知识三、实现方案Step 1&#xff1a;分析需求Step 2&#xff1a;实现单个数字的滚动效果Step 3&#xff1a;组件接口设计Step 4&#xff1a;完善组件一、前言 前端数字滚动显示的场景很多&#xff0c;比如抽奖的时候&#xff0c;营造一种马上公布中奖…

[MySQL从入门到实战环境部署](超详细版)

MySQL从入门到实战环境部署1.部署CentOS1.1部署CenOS虚拟机步骤&#xff08;1&#xff09;基于VirtualBox&#xff08;2&#xff09;下载CentOS1.2环境部署过程2.部署MySQL1.部署CentOS 1.1部署CenOS虚拟机步骤 &#xff08;1&#xff09;基于VirtualBox 下载网址&#xff1…

Docker Compose:Docker Compose部署nacos初始化MySQL

Docker Compose&#xff1a;Docker Compose部署nacos初始化MySQL找初始化sql文件nacos初始化mysql-schema.sql文件内容docker-compose.yml上传到挂载目录运行docker-compose.yml访问nacos找初始化sql文件 先去官网下载nacos安装包 官方github地址&#xff1a;https://github.…

Centos7安装opengauss

安装包下载地址&#xff1a;https://www.opengauss.org/zh/download/注&#xff1a;本文介绍的是轻量版安装先创建一个系统用户&#xff08;opengauss数据库不允许使用 root 用户安装&#xff09;创建用户useradd omm设置密码passwd omm将安装包拷贝并解压到用户家目录 ~/openG…

linux-云服务器数据盘挂载失败导致进入维护模式

已经在华为云、AWS上面吃过这个亏了&#xff0c;老这样可不好&#xff0c;心怦怦跳的。 华为云是由于服务器升级配置后重启&#xff0c;数据盘名称变化导致进入维护模式。AWS则是由于重启后没有挂载上数据盘&#xff0c;手动编辑/etc/fstab文件错误导致进入维护模式。 究其原…

2022年航空发动机行业研究报告

第一章 行业概况 航空发动机制造指主要用来产生拉力或推力使飞机前进的发动机设备。除了产生前进力外&#xff0c;还可以为飞机上的用电设备提供电力&#xff0c;为空调设备等用气设备提供气源。航空发动机制造产业链包括原材料研发、零部件生产制造、分系统和整机制造。 原材…

大智慧同花顺Level2行情数据有什么用

股市L2是大智慧Level2数据。由“上海证券交易所”最新推出的实时行情信息收费服务&#xff0c;主要提供在上海证券交易所上市交易的证券产品的实时交易数据。该行情速度比传统行情快3秒以上&#xff0c;同时包括十档行情、买卖队列、逐笔成交、总买总卖和统计信息等多种新式数据…

Fabric.js 拖放元素进画布

本文简介 点赞 关注 收藏 学会了 学习 Fabric.js&#xff0c;我的建议是看文档不如看 demo。 本文实现的功能&#xff1a;将元素拖进到画布中并生成对应的图形或图片。 效果如下图所示&#xff1a; 思路 要实现以上效果&#xff0c;需要考虑以下几点&#xff1a; 元素有…

婴儿游泳池行业市场经营管理及未来前景展望分析

2023-2029年中国婴儿游泳池行业市场经营管理及未来前景展望报告报告编号&#xff1a;1691316免费目录下载&#xff1a;http://www.cninfo360.com/yjbg/qthy/ly/20230109/1691316.html本报告著作权归博研咨询所有&#xff0c;未经书面许可&#xff0c;任何组织和个人不得以任何形…

PyQt6快速入门-事件处理

事件处理 文章目录 事件处理1、Qt事件介绍2、常用事件函数2.1 paintEvent事件2.2 鼠标事件2.3 窗口大小改变事件2.4 窗口隐藏/关闭/显示事件2.5 键盘按键事件3、事件拦截4、事件过滤器5、事件队列与事件处理1、Qt事件介绍 Qt GUI应用程序的核心是 QApplication 类。 每个GUI应…

Linux 文件 I/O

1.Linux 应用编程中最基础的知识&#xff0c;即文件 I/O&#xff08;Input、 Outout&#xff09; &#xff0c; 文件 I/O 指的是对文件的输入/输出操作&#xff0c;说白了就是对文件的读写操作&#xff1b; Linux 下一切皆文件&#xff0c;文件作为 Linux 系统设计思想的核心理…

java Lambda表达式引用类方法

Lambda表达式和方法引用是一对孪生兄弟 而引用类方法是Lambda支持的方法引用中的一种 引用类方法其实就是引用类的静态方法 直接上代码 首先 我们要创建一个包 包下创建一个接口 我这里叫subInterface 参考代码如下 public interface subInterface {int convelutl(String s…

【RabbitMQ】SpringBoot整合RabbitMQ

文章目录搭建初始环境引入依赖配置配置文件HelloWorld模型使用Work模型使用Fanout 广播模型Route 路由模型Topic 订阅模型(动态路由模型)搭建初始环境 引入依赖 <!--引入与rabbitmq集成依赖--> <dependency><groupId>org.springframework.boot</groupId…