【哈希】用哈希桶封装unordered_map unordered_set

news2024/11/17 3:36:15
图片名称
🎉博主首页: 有趣的中国人

🎉专栏首页: C++进阶

🎉其它专栏: C++初阶 | Linux | 初阶数据结构

在这里插入图片描述

小伙伴们大家好,本片文章将会讲解 用哈希桶封装 unordered_map & unordered_set 的相关内容。

如果看到最后您觉得这篇文章写得不错,有所收获,麻烦点赞👍、收藏🌟、留下评论📝。您的支持是我最大的动力,让我们一起努力,共同成长!

🎉系列文章: 1. 闭散列的线性探测实现哈希表

🎉系列文章: 2. 开散列的哈希桶实现哈希表

文章目录

  • `0. 前言`
  • `1. K模型和KV模型模板参数传递`
    • ==<font color = blue><b>🎧1.0 相关解释🎧==
    • ==<font color = blue><b>🎧1.1 模板参数传递思路🎧==
    • ==<font color = blue><b>🎧1.2 模板参数传递图解🎧==
  • `2. 哈希表中函数的修改`
    • ==<font color = blue><b>🎧2.1 Insert函数修改思路🎧==
    • ==<font color = blue><b>🎧2.2 Insert函数修改后的代码🎧==
    • ==<font color = blue><b>🎧2.3 Find & Erase 函数的修改🎧==
    • ==<font color = blue><b>🎧2.4 Find & Erase 修改后的代码🎧==
  • `3. 哈希表迭代器的实现`
    • ==<font color = blue><b> 🎧3.1 operator++() 的实现🎧==
    • ==<font color = blue><b> 🎧3.2 operator*() & operator->() 的实现🎧==
    • ==<font color = blue><b> 🎧3.3 operator!=()的实现🎧==
    • ==<font color = blue><b> 🎧3.4 Begin() & End()的实现==
  • `4. const迭代器的实现`
  • `4. unordered_map的operator[]的实现`
    • ==<font color = blue><b> 🎧4.1 Find()的修改==
    • ==<font color = blue><b> 🎧4.2 Insert()的修改==
    • ==<font color = blue><b> 🎧4.3 operator[]的实现==
  • `5. 哈希桶封装的完整代码`



0. 前言


在之前的文章中我们详细描述了如何用 开放寻址法(闭散列)的线性探测 和 开散列的哈希桶 的方法来实现哈希表。此篇文章我们将用 哈希桶 来实现 unordered_map & unordered_set 的封装。




1. K模型和KV模型模板参数传递


🎧1.0 相关解释🎧


由于unordered_mapunordered_set的分别是 KV 类型和 K 类型,存储的数据类型是不相同的, 但是底层的哈希桶只有一份,这个时候我们得想到用模板的方法来解决此问题。(这块类似于红黑树那的封装)

🎧1.1 模板参数传递思路🎧


  1. 我们用哈希表来实现unordered_mapunordered_set,因此他们两个的成员变量就是用哈希桶实现出的哈希表的对象;
  2. 哈希表的前两个模板参数是 KVKey: 关键字Value: 值),但是我们不能这样传递,我们可以将第二个参数改为TType: 类型):
    • 这个T是我们在上一层:unordered_mapunordered_set进行传递的;
    • 如果是unordered_map传递的就是KV类型的pair
    • 如果是unordered_set传递的就是K类型。
  3. 由于哈希表中还封装了哈希节点,这个哈希节点的模板参数一开始传递的也是两个: KVKey: 关键字Value: 值),我们此时就将两个模板参数换为一个TType: 类型)。

🎧1.2 模板参数传递图解🎧


unordered_map模板参数的传递图示:

在这里插入图片描述

unordered_set模板参数的传递图示:

在这里插入图片描述



2. 哈希表中函数的修改


由于传递的模板参数改变了,所以我们对应的函数实现也要发生改变。


🎧2.1 Insert函数修改思路🎧


Inert函数的修改

  1. 起初给Insert()函数传递的参数类型是const pair<K, V>& kv, 现在传递的参数类型是const T& data
  2. unordered_set调用Insert(),传入的参数是 K K K 类型( T T T 会被实例化成 K K K),当unordered_map调用Insert(),传入的参数类型是 K V KV KV 类型( T T T 会被实例化成pair<K, V>);
  3. 那么问题就来了,对于 K K K 类型插入数据时要对key使用哈希函数,对于 K V KV KV 类型的pair,比对pair的第一个参数使用哈希函数,因此我们要用仿函数来取出对应的值

仿函数逻辑

  1. 我们要在哈希表的模板参数中 增加一个仿函数的模板参数 KeyOfT,并在两个容器的成员变量中传入对应的仿函数。
  2. 对于unordered_map的仿函数,我们要的就是key,因此直接返回 K K K 类型的变量即可;
  3. 对于unordered_map的仿函数,我们要的是pair的第一个参数,所以要返回传入的参数的第一个参数。

关于哈希函数

之前的文章讲过,对于不同类型的数据,我们要采用相应的哈希函数,使其在整数范围内有唯一的映射。尤其是对于字符串类型string,我们经常使用,还采用了模板的特化。

这一操作(模板特化、模板缺省参数的传递)我们是在哈希表的实现中完成的。

但是由于我们要对哈希表进行封装,对外暴露的就是unordered_setunordered_map,要把以上的操作放在此层。

🎧2.2 Insert函数修改后的代码🎧


unordered_set的仿函数代码:

template<class K>
struct setKeyOfT
{
	const K& operator()(const K& key)
	{
		return key;
	}
};

unordered_map的仿函数代码:

template<class K, class V>
struct mapKeyOfT
{
	const K& operator()(const pair<K, V>& kv)
	{
		return kv.first;
	}
};

Insert的修改代码:

bool Insert(const T& data)
{
	// 定义哈希函数对象
	HashFunc hf;
	// 定义取值的对象
	KeyOfT kot;
	if (Find(kot(data)))
	{
		return false;
	}
	// 判断负载因子扩容
	// 负载因子为1扩容
	if (_n == _tables.size())
	{
		vector<Node*> newtable;
		size_t newsize = 2 * _tables.size();
		newtable.resize(newsize, nullptr);
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			Node* cur = _tables[i];
			Node* next = nullptr;
			while (cur)
			{
				next = cur->_next;
				// 先取出对应的Key值,然后用哈希函数映射到相应的整数
				size_t hashi = hf(kot(cur->_data)) % newtable.size();
				cur->_next = newtable[hashi];
				newtable[hashi] = cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(newtable);
	}
	// 先取出对应的Key值,然后用哈希函数映射到相应的整数
	size_t hashi = hf(kot(data)) % _tables.size();
	Node* newnode = new Node(data);
	// 头插
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;
	return true;
}

🎧2.3 Find & Erase 函数的修改🎧


Find & Erase 函数的修改

  1. 由于Find函数和Erase函数传递参数的时候就是Key,因此在函数内部的代码逻辑无需进行修改。
  2. 这同样体现了哈希表的第一个模板参数 K K K 的作用,如果只有第二个模板参数,那么将无法进行查询。

🎧2.4 Find & Erase 修改后的代码🎧


Find代码:

Node* Find(const K& key)
{
	HashFunc hf;
	size_t hashi = hf(key) % _tables.size();

	Node* cur = _tables[hashi];
	while (cur)
	{
		if (kot(cur->_data) == key)
		{
			return cur;
		}
		else
		{
			cur = cur->_next;
		}
	}
	return nullptr;
}

Erase代码:

bool Erase(const K& key)
{
	HashFunc hf;
	size_t hashi = hf(key) % _tables.size();
	Node* cur = _tables[hashi];
	Node* prev = nullptr;
	while (cur)
	{
		if (kot(cur->_data) == key)
		{
			if (prev == nullptr)
			{
				_tables[hashi] = cur->_next;
			}
			else
			{
				prev->_next = cur->_next;
			}
			delete cur;
			--_n;
			return true;
		}
		else
		{
			prev = cur;
			cur = cur->_next;
		}
	}
	return false;
}


3. 哈希表迭代器的实现


如果用 原生指针的解引用、加加、箭头等 操作无法满足哈希表的相关操作,因此要对 节点指针进行封装,并采用运算符重载 来实现迭代器的相关操作。

🎧3.1 operator++() 的实现🎧


operator++() 实现思路

  1. 假设当前所在的节点为cur,当前所在的哈希桶为i
  2. 如果当前节点不为空,加加到下一个节点;
  3. 如果当前节点为空,那么就要去下一个不为空的桶中寻找,问题就来了,如何寻找下一个桶呢?
    • 因为要找到下一个桶,并且要访问桶中的元素,因此要把 哈希表的指针传到迭代器的类中
    • 之后,判断下个位置的指针是否为空,如果不为空,就让_node == _node->next
    • 如果为空,就寻找下一个不为空的桶:
      • 先利用当前指针计算出当前位置所在的桶;
      • 向后寻找不为空的桶,如果找到了,就让cur = _tables[i],直到走到空为止,不然继续找下一个不为空的节点;
      • 如果一直到 i = _tables.size(),还没有找到不为空的节点,证明已经访问完毕,那么_node = nullptr
  4. 最后返回此迭代器类型的对象 return *this

operator()++代码:

__HashIterator<K, T, KeyOfT, HashFunc> operator++()
{
	KeyOfT kot;
	Node* cur = _node;
	// 判断下个位置的指针是否为空,如果不为空,就让`_node == _node->next`
	if (_node->_next)
	{
		_node = _node->_next;
	}
	else
	{
		// 先利用当前指针计算出当前位置所在的桶
		size_t hashi = kot(cur->_data) % _pht->_tables.size();
		// 向后寻找不为空的桶,如果找到了,
		// 就让`cur = _tables[i]`,直到走到空为止,不然继续找下一个不为空的节点;
		++hashi;
		for (; hashi < _pht->_tables.size(); ++hashi)
		{
			if (_pht->_tables[hashi])
			{
				cur = _pht->_tables[hashi];
				break;
			}
		}
		// 出循环有两种情况,一种是找到了不为空的节点,一种是直到走到最后都没有找到不为空的节点,两种情况分别判断以下。
		// 如果走到最后还未找到,就让_node = nullptr;
		if (hashi == _pht->_tables.size())
		{
			_node = nullptr;
		}
		// 如果找到不为空的节点就让_node = cur;
		else
		{
			_node = cur;
		}
	}
	return *this;
}

🎧3.2 operator*() & operator->() 的实现🎧


operator*() 实现思路

  • 返回_node->_data即可。

operator->() 实现思路

  • 返回&_node->_data即可。
    • 这里在unordered_set底层调用的时候实际上是用了两次->
    • 一次是调用operator->()的运算符重载,访问到了pair的地址;
    • 然后再用->pair进行解引用访问到它的firstsecond
    • 但是编译器做了优化直接写一个->即可。

operator*() & operator->()的代码:

T& operator*()
{
	return _node->_data;
}

T* operator->()
{
	return &_node->_data;
}

🎧3.3 operator!=()的实现🎧


operator!=() 实现思路

  • 判断两个_node的地址是否相同即可:_node != h._node

operator!=() 的代码:

bool operator!=(const __HashIterator<K, T, KeyOfT, HashFunc>& h)
{
	return _node != h._node;
}

🎧3.4 Begin() & End()的实现


Begin()的实现思路

  1. 开始节点就是第一个不为空的桶中存放的节点:
    • 我们先定义一个节点cur,让他向后寻找到第一个不为空的位置。
    • 找到了返回迭代器类型的数据,由于迭代器的构造函数是两个,要传两个参数,第一个是cur指针,第二个是this指针:return Iterator(cur, this)
      • 因为迭代器中需要的是哈希表类型的指针,而this就是能代表此哈希表类型的指针,所以用this即可。
  2. 如果找不到为空的节点返回迭代器构造的nullptrreturn Iterator(nullptr, this)

End()的实现思路

  • 返回迭代器构造的nullptrreturn Iterator(nullptr, this)

Begin() & End()的代码:

Iterator Begin()
{
	for (size_t i = 0; i < _tables.size(); ++i)
	{
		Node* cur = _tables[i];
		if (cur)
		{
			return Iterator(cur, this);
		}
	}
	return End();
}

Iterator End()
{
	return Iterator(nullptr, this);
}


4. const迭代器的实现


const迭代器也是借用了模板的功能,只是解引用和箭头的返回值不同。


const迭代器的实现思路:

  1. 首先要给迭代器的模板加两个参数RefPtr,代表传入的T类型的引用和指针;
  2. 改变operator*() & operator->()的返回值,分别是:RefPtr
  3. 在哈希表类中增加const类型的 Begin() & End()
  4. 这里有三个问题:
    • __HashIterator中有一个HashTable的对象,所以要在__HashIterator前先声明一下HashTable,不然会报错。
    • 由于_tablesHashTable类中的private成员变量,因此要在HashTable增加__HashIterator的友元声明。
    • 🔎这两个问题都可以用内部类来解决,因为内部类是是外部类的友元,但是C++不太喜欢用内部类,博主这里就不用内部类实现了,而且内部类实现的方法也相对简单。🔍
    • 由于迭代器中有HashTable*类型的指针,而普通迭代器的指针类型就是HashTable*,而const迭代器的指针类型是const HashTable*,我们知道权限只能缩小,不能放大,因此要把__HashIterator中的HashTable*的成员变量一直修改为const HashTable*

完整的迭代器的相关代码:

// 先声明
template<class K, class T, class KeyOfT, class HashFunc>
class HashTable;

template<class K, class T, class KeyOfT, class HashFunc, class Ref, class Ptr>
class __HashIterator
{
public:
	typedef HashNode<T> Node;
	typedef __HashIterator<K, T, KeyOfT, HashFunc, Ref, Ptr> Self;
	__HashIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
		:_node(node)
		,_pht(pht)
	{}

	Ref operator*()
	{
		return _node->_data;
	}

	Ptr operator->()
	{
		return &_node->_data;
	}

	Self operator++()
	{
		KeyOfT kot;
		Node* cur = _node;
		if (_node->_next)
		{
			_node = _node->_next;
		}
		else
		{
			size_t hashi = kot(cur->_data) % _pht->_tables.size();
			++hashi;
			for (; hashi < _pht->_tables.size(); ++hashi)
			{
				if (_pht->_tables[hashi])
				{
					cur = _pht->_tables[hashi];
					break;
				}
			}
			if (hashi == _pht->_tables.size())
			{
				_node = nullptr;
			}
			else
			{
				_node = cur;
			}
		}
		return *this;
	}

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


private:
	Node* _node;
	const HashTable<K, T, KeyOfT, HashFunc>* _pht;
};

template<class K, class T, class KeyOfT, class HashFunc>
class HashTable
{
public:
	typedef typename __HashIterator<K, T, KeyOfT, HashFunc, T&, T*> Iterator;
	typedef typename __HashIterator<K, T, KeyOfT, HashFunc, const T&, const T*> Const_Iterator;
	typedef HashNode<T> Node;
	// 友元声明
	friend __HashIterator<K, T, KeyOfT, HashFunc, T&, T*>;
	friend __HashIterator<K, T, KeyOfT, HashFunc, const T&, const T*>;
	Const_Iterator Begin() const
	{
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			Node* cur = _tables[i];
			if (cur)
			{
				return Const_Iterator(cur, this);
			}
		}
		return End();
	}

	Const_Iterator End() const
	{
		return Const_Iterator(nullptr, this);
	}


	Iterator Begin()
	{
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			Node* cur = _tables[i];
			if (cur)
			{
				return Iterator(cur, this);
			}
		}
		return End();
	}

	Iterator End()
	{
		return Iterator(nullptr, this);
	}
private:
	vector<Node*> _tables;
	size_t _n = 0;
};



4. unordered_map的operator[]的实现


这里 o p e r a t o r [ ] operator[ ] operator[] 的用法和红黑树那里的相同:

1. 首先不管是否存在,都执行 插入的逻辑

  • 如果存在,则返回一个 pair 类型(first:对应值位置的迭代器,secondbool 类型,是否插入成功,此处为 false);
  • 如果不存在,依然返回一个 pair类型(first:新插入位置的迭代器,secondbool 类型,是否插入成功,此处 true)。

2. 取出 pairfirst,也就是迭代器,根据迭代器找到它的 second,也就是 value,返回它的 value

🎧4.1 Find()的修改


Find()的修改逻辑:

  • Find() 原本返回的是节点类型的指针,现在要返回迭代器类型:
    • 迭代器类型,第一个参数还是节点类型的指针,第二个参数是this指针 。

Find() 修改后的代码:

Iterator Find(const K& key)
{
	HashFunc hf;
	size_t hashi = hf(key) % _tables.size();

	Node* cur = _tables[hashi];
	while (cur)
	{
		if (kot(cur->_data) == key)
		{
			return Iterator(cur,this);
		}
		else
		{
			cur = cur->_next;
		}
	}
	return Iterator(nullptr, this);
}

🎧4.2 Insert()的修改


Insert()的修改逻辑:

  1. 当对应的key值能用Find()函数找到的时候,则返回pair类型,first是对应节点的迭代器,secondfalse
  2. key找不到进行插入操作时,依然返回pair类型,first是插入节点的迭代器,secondtrue

Insert 修改后的代码:

pair<Iterator, bool> Insert(const T& data)
{
	HashFunc hf;
	KeyOfT kot;
	Iterator it = Find(kot(data));
	if (it != End())
	{
		return make_pair(it, false);
	}
	// 判断负载因子扩容
	// 负载因子为1扩容
	if (_n == _tables.size())
	{
		vector<Node*> newtable;
		size_t newsize = 2 * _tables.size();
		newtable.resize(newsize, nullptr);
		for (size_t i = 0; i < _tables.size(); ++i)
		{
			Node* cur = _tables[i];
			Node* next = nullptr;
			while (cur)
			{
				next = cur->_next;
				size_t hashi = hf(kot(cur->_data)) % newtable.size();
				cur->_next = newtable[hashi];
				newtable[hashi] = cur;
				cur = next;
			}
			_tables[i] = nullptr;
		}
		_tables.swap(newtable);
	}
	size_t hashi = hf(kot(data)) % _tables.size();
	Node* newnode = new Node(data);
	// 头插
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;
	// 先用当前节点的指针和this指针构造迭代器类,
	// 再用迭代器和bool类型构造pair
	return make_pair(Iterator(_tables[hashi], this), true);
}

🎧4.3 operator[]的实现



operator[]的实现思路:

  1. 对于传入的key值进行对应的插入操作,因为插入返回类型是pair,取它的first,也就是迭代器类型,取变量名为ret
  2. retsecond,也就是value的值即可,实现思路较为简单。

operator[]的实现代码:

V& operator[](const K& k)
{
	iterator ret = _ht.Insert(make_pair(k, V())).first;
	return ret.operator->()->second;
}


5. 哈希桶封装的完整代码



🎧有需要的小伙伴自取哈,博主已经检测过了,无bug🎧

🎨博主gitee链接: Jason-of-carriben 哈希桶封装的完整代码

在这里插入图片描述

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

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

相关文章

pdf拆分成有图和无图的pdf(方便打印)

pdf拆分成有图和无图的pdf(方便打印) 原因 打印图片要彩印&#xff0c;每次都要手动弄&#xff0c;打印的时候很麻烦&#xff1b; 随着打印次数的增加&#xff0c;时间就越来越多 为解决此问题&#xff0c;使用python写一个exe解决这个问题 历程 找一个python的GUI界面找到 t…

USART串口外设

USART介绍 USART&#xff1a;另外我们经常还会遇到串口&#xff0c;叫UART&#xff0c;少了个S&#xff0c;就是通用异步收发器&#xff0c;一般我们串口很少使用这个同步功能&#xff0c;所以USART和UART使用起来&#xff0c;也没有什么区别。 其实这个STM32的USART同步模式&a…

MySQL 索引的使用

本篇主要介绍MySQL中索引使用的相关内容。 目录 一、最左前缀法则 二、索引失效的场景 索引列运算 字符串无引号 模糊查询 or连接条件 数据分布 一、最左前缀法则 当我们在使用多个字段构成的索引时&#xff08;联合索引&#xff09;&#xff0c;需要考虑最左前缀法则…

基于物理的分析模型,用于具有场板结构的GaN HEMT的输入、输出及反向电容

Physics-Based Analytical Model for Input, Output, and Reverse Capacitance of a GaN HEMT With the Field-Plate Structure&#xff08;TPE 17年&#xff09; 摘要 该论文提出了一种分析模型&#xff0c;用于描述带有场板结构的常开型AlGaN/GaN高电子迁移率晶体管&#x…

多屏多机同控!天途首发瑶光智控地面站

瑶光智控地面站全新发布&#xff01;高性能处理器&#xff0c;高亮三屏显示。内置天途云控系统&#xff0c;融合图传、控制、存储和数据处理等功能与一体&#xff0c;强大算力&#xff0c;高度集成无人机、无人船、无人车和机械狗等多种无人装备进行云控云算。 内置4G公网通讯模…

RabbitMQ-发布/订阅模式

1、发布/订阅模式介绍 在普通的生产者、消费者模式&#xff0c;rabbitmq会将消息依次传递给每一个消费者&#xff0c;一个worker一个&#xff0c;平均分配&#xff0c;这就是Round-robin调度方式&#xff0c;为了实现更加复杂的调度&#xff0c;我们就需要使用发布/订阅的方式…

现货白银的交易时间有多连贯?

国际市场上的现货白银优势很多&#xff0c;它除了具备国内同类型品种所不具备的数十倍资金杠杆外&#xff0c;也基本上实现了全天24小时不间断的交易时间&#xff0c;所以投资者可以在全天候连贯的行情中&#xff0c;寻找属于自己的交易获利机会。 但对于内地的投资者来说&…

【香橙派 AIpro】新手保姆级开箱教程:Linux镜像+vscode远程连接

香橙派 AIpro 开发板 AI 应用部署测评 写在最前面一、开发板概述官方资料试用印象适用场景 二、详细开发前准备步骤1. 环境准备2. 环境搭建3. vscode安装ssh插件4. 香橙派 AIpro 添加连接配置5. 连接香橙派 AIpro6. SSH配置 二、详细开发步骤1. 登录 juypter lab2. 样例运行3. …

HQChart使用教程100-uniapp如何在vue3运行微信小程序

HQChart使用教程100-uniapp如何在vue3运行微信小程序 症状原因分析解决思路解决步骤1. 修改vender.js2. 修改HQChartControl.js 完整实例HQChart代码地址 症状 HQChart插件在uniappvue3的项目编译成小程序以后&#xff0c; 运行会报错&#xff0c;见下图。 原因分析 查了下…

从了解到掌握 Spark 计算框架(二)RDD

文章目录 RDD 概述RDD 组成RDD 的作用RDD 算子分类RDD 的创建1.从外部数据源读取2.从已有的集合或数组创建3.从已有的 RDD 进行转换 RDD 常用算子大全转换算子行动算子 RDD 算子综合练习RDD 依赖关系窄依赖宽依赖宽窄依赖算子区分 RDD 血统信息血统信息的作用血统信息的组成代码…

【C语言回顾】预处理

前言1. 简单概要2. 预处理命令讲解结语 上期回顾: 【C语言回顾】编译和链接 个人主页&#xff1a;C_GUIQU 归属专栏&#xff1a;【C语言学习】 前言 各位小伙伴大家好&#xff01;上期小编给大家讲解了C语言中的编译和链接&#xff0c;接下来我们讲解一下预处理&#xff01; …

k8s自定义资源你会创建吗

创建自定义资源定义 CustomResourceDefinition 当你创建新的 CustomResourceDefinition&#xff08;CRD&#xff09;时&#xff0c;Kubernetes API 服务器会为你所 指定的每一个版本生成一个 RESTful 的 资源路径。CRD 可以是名字空间作用域的&#xff0c;也可以是集群作用域的…

接口测试工具:Postman的下载安装及使用

1 Postman 介绍 1.1 Postman 是什么 Postman 是一款功能超级强大的用于发送 HTTP 请求的 测试工具 做 WEB 页面开发和测试的人员常用工具 创建和发送任何的 HTTP 请求(Get/Post/Put/Delete...) 1.2 Postman 相关资源 1.2.1 官方网站&#xff1a;https://www.postman.com/ …

算法(七)插入排序

文章目录 插入排序简介代码实现 插入排序简介 插入排序&#xff08;insertion sort)是从第一个元素开始&#xff0c;该元素就认为已经被排序过了。然后取出下一个元素&#xff0c;从该元素的前一个索引下标开始往前扫描&#xff0c;比该值大的元素往后移动。直到遇到比它小的元…

【Uniapp小程序】自定义导航栏uni-nav-bar滚动渐变色

效果图 新建activityScrollTop.js作为mixins export default {data() {return {navBgColor: "rgba(0,0,0,0)", // 初始背景颜色为完全透明navTextColor: "rgba(0,0,0,1)", // 初始文字颜色};},onPageScroll(e) {// 设置背景const newAlpha Math.min((e.s…

elasticsearch7.15实现用户输入自动补全

Elasticsearch Completion Suggester&#xff08;补全建议&#xff09; Elasticsearch7.15安装 官方文档 补全建议器提供了根据输入自动补全/搜索的功能。这是一个导航功能&#xff0c;引导用户在输入时找到相关结果&#xff0c;提高搜索精度。 理想情况下&#xff0c;自动补…

手机站怎么推广

随着手机的普及和移动互联网的快速发展&#xff0c;越来越多的人开始使用手机进行在线购物、社交娱乐、阅读资讯等&#xff0c;同时也催生了越来越多的手机站的出现。但是&#xff0c;在海量的手机站中&#xff0c;要让自己的手机站脱颖而出&#xff0c;吸引更多用户访问和使用…

β-烟酰胺单核苷酸(NMN)功能不断得到验证 市场规模呈增长态势

β-烟酰胺单核苷酸&#xff08;NMN&#xff09;功能不断得到验证 市场规模呈增长态势 β-烟酰胺单核苷酸&#xff08;β-Nicotinamide mononucleotide&#xff0c;NMN&#xff09;是一种生物活性分子&#xff0c;是一种辅酶Ⅰ&#xff08;NAD&#xff09;的前体&#xff0c;也是…

Python魔法之旅-魔法方法(04)

目录 一、概述 1、定义 2、作用 二、主要应用场景 1、构造和析构 2、操作符重载 3、字符串和表示 4、容器管理 5、可调用对象 6、上下文管理 7、属性访问和描述符 8、迭代器和生成器 9、数值类型 10、复制和序列化 11、自定义元类行为 12、自定义类行为 13、类…

Idea的相关操作

1、关闭自动更新 点击左上角File->Setting&#xff0c;进入配置页面&#xff0c;点击Appearance & Behavior > System Settings > Updates&#xff0c;取消勾选更新选项&#xff0c;如图&#xff1b; 2、代码提示忽略大小写 点击左上角File->Setting&#xf…