C++——哈希(二)unordered_map和unordered_set的封装

前言

在上一篇文章中我们已经对闭散列的哈希表(线性探测法)和开散列的哈希表(哈希桶)进行了简单的模拟实现,由于是简单实现,功能简单、没有迭代器且不支持不同的类型(非泛型编程)。此时我们这篇文章主要是对上次开散列哈希表的完善并用其封装出unordered_map和unordered_set

上次哈希桶的代码

#pragma once
#include <vector>
//版本1
namespace HashBucket
{
	template <class K,class V>
	struct HashNode//表的节点(桶口)
	{
		HashNode<K,V>* _next;
		pair<K, V> _kv;
		HashNode(const pair<K,V>& kv)
			:_next(nullptr)
			,_kv(kv)
		{}
	};
	template<class K, class V>
	class HashTable
	{
	
		typedef HashNode<K,V> Node;
	public:
		HashTable()
		{
			_tables.resize(10, nullptr);
			_n=0;
		}
		Node* Find(const K& key)
		{
			size_t hashi = key % _tables.size();//找下标
			Node* cur = _tables[hashi];
			while (cur)//走链表
			{
				if (cur->_kv.first == key)//找到了
				{
					return cur;
				}
				cur = cur->_next;//往下走
			}
			//找不到
			return nullptr;
		}
 
		bool Insert(const pair<K, V>& kv)
		{
			//插入失败(找不到)
			if (Find(kv.first))
				return false;
			//扩容
			if (_n == _tables.size())//负载因子极限
			{
				vector< Node*> newtable(_tables.size() * 2, nullptr);
				//把旧桶的节点挂到新表新桶中
				for (size_t i=0;i<_tables.size();i++)
				{
					Node* cur = _tables[i];//遍历走一个个旧桶
					while (cur)
					{
						Node* next = cur->_next;//存一下next,待会可以往下走
						size_t hashi = cur->_kv.first % newtable.size();//节点挂到新桶的下标可能不一样
						//挂上新桶
						cur->_next = newtable[hashi];
						newtable[hashi] = cur;
						//往下走
						cur = next;
					}
					//把旧桶被移走的节点置空
					_tables[i] = nullptr;
				}
				_tables.swap(newtable);
			}
			size_t hashi = kv.first % _tables.size();//找到下标
			Node* newnode = new Node(kv);//开一个节点的空间
			//头插(入桶)
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			return true;
 
		}
		bool Erase(const K& key)
		{
			size_t hashi = key % _tables.size();
			Node* prev = nullptr;//保存遍历节点的上一个
			Node* cur = _tables[hashi];
			while (cur)	
			{
 
				if (cur->_kv.first == key)//找到
				{
					//删除
					if (prev)//节点有可能是头节点  prev非空,不是头节点
					{
						prev->_next = cur->_next;
					}
					else//要删除的节点是头节点
					{
						_tables[hashi] = cur->_next;
					}
					
					delete cur;
					_n--;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}
	 
	private:
		vector<Node*> _tables; // 指针数组
		size_t _n;
	};
 
}

1.哈希表的改造

哈希表里面不止能存数字,还可能存字符串之类的,当然我们上面的哈希表是存不了字符串的,如果硬用字符串那么会报错:string类不能取模 

1.1模板参数的改造

那么解决办法是什么呢?可以用仿函数去解决,也就是再加一层映射

若key不是整形先整成整形再与位置建立关系

template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}

		return hash;
	}
};

template<class K, class V,class Hash = HashFunc<K>>
class HashTable
{
    typedef HashNode<K,V> Node;
public:
    //...
private:
	vector<Node*> _tables; // 指针数组
	size_t _n;
};

1.2对不同的成员函数进行修改

存不同类型的值,对应不同的仿函数

然后哈希表中的成员函数大部分都要修改(凡是遇到求下标取模的),都要用仿函数去获取

比如:下面的Find

Node* Find(const K& key)
{
	Hash hs;
	size_t hashi = hs(key) % _tables.size();//找下标
	Node* cur = _tables[hashi];
	while (cur)//走链表
	{
		if (cur->_kv.first == key)//找到了
		{
			return cur;
		}
		cur = cur->_next;//往下走
	}
	//找不到
	return nullptr;
}

当然insert和erase也要

bool Insert(const pair<K, V>& kv)
{
	Hash hs;
	//插入失败(找不到)
	if (Find(kv.first))
		return false;
	//扩容
	if (_n == _tables.size())
	{
		vector< Node*> newtable(_tables.size() * 2, nullptr);
		//把旧桶的节点挂到新表新桶中
		for (size_t i=0;i<_tables.size();i++)
		{
			Node* cur = _tables[i];
			while (cur)
			{
				Node* next = cur->_next;
                //找到下标,取模要调仿函数
				size_t hashi = hs(cur->_kv.first) % newtable.size();
				//挂上新桶
				cur->_next = newtable[hashi];
				newtable[hashi] = cur;
				//往下走
				cur = next;
			}
			//把旧桶被移走的节点置空
			_tables[i] = nullptr;
		}
		_tables.swap(newtable);
	}
	size_t hashi = hs(kv.first) % _tables.size();//找到下标,取模要调仿函数
	Node* newnode = new Node(kv);
	//头插(入桶)
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;
	return true;

}

bool Erase(const K& key)
{
	Hash hs;
	size_t hashi = hs(key) % _tables.size();
	Node* prev = nullptr;//保存遍历节点的上一个
	Node* cur = _tables[hashi];
	while (cur)	
	{

		if (cur->_kv.first == key)//找到
		{
			//删除
			if (prev)//节点有可能是头节点  prev非空,不是头节点
			{
				prev->_next = cur->_next;
			}
			else//要删除的节点是头节点
			{
				_tables[hashi] = cur->_next;
			}
					
			delete cur;
			_n--;
			return true;
		}
		prev = cur;
		cur = cur->_next;
	}
	return false;
}

2.再次改造哈希表

把哈希表封装出unordered_map和unordered_set,这和之前红黑树封装出map和set类似

	template<class K, class T>
	class HashTable

对于unordered_map

template<class K, class V>
class myunorderedmap
{
public:
	//...
private:
	HashTable<K, pair<const K, V>> _ht;
};

对于unordered_set

template<class K>
class myunorderedset
{
public:
	//...
private:
	HashTable<K, const K> _ht;
};

泛型编程 T可以是K也可以是pair<K,V>,所以节点的模板参数和存储的数据类型都要修改

template <class T>
struct HashNode//表的节点(桶口)
{
	HashNode<T>* _next;
	T _data;
	HashNode(const T& data)
		:_next(nullptr)
		, _data(data)
	{}
};

当然别忘了上面的更改(仿函数),不过仿函数还是由unordered_map和unordered_set来决定比较好;后面用到的data(上面代码中的kv.first)可能是unordered_map的键值对(pair<K,V>)也可能是unordered_set的键(key),底层哈希表并不知道是哪个,所以还要用一个仿函数来解决这一问题

那么对于unordered_map增加MapKeyOfT

template<class K, class V, class Hash = HashFunc<K>>
class myunorderedmap
{
	struct MapKeyOfT
	{
		const K& operator()(const pair<K, V>& kv)
		{
			return kv.first;
		}
	};
public:
	//...
private:
	HashTable<K, pair<const K, V>,MapKeyOfT,Hash> _ht;
};

对于unordered_set增加SetKeyOfT

template<class K, class Hash = HashFunc<K>>
class myunorderedset
{
	struct SetKeyOfT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
public:
	//...
private:
	HashTable<K, const K,SetKeyOfT,Hash> _ht;
};

当然哈希表也要有对应的接收

template<class K, class T, class KeyOfT, class Hash>
class HashTable
{
    typedef HashNode<T> Node;
public:
    //...
private:
	vector<Node*> _tables; // 指针数组
	size_t _n;
};

这样一来,如果是unordered_map调用哈希表,就会用MapKeyOfT传给哈希表,如果是unordered_set调用哈希表,就用SetKeyOfT传给哈希表,那么哈希表就可以知道T是键还是键值对

既然这些增加了对应的仿函数和模板参数,和上文的仿函数类似,所有成员函数中需要用到key的都要用KeyOfT创建的对象去替换

KeyOfT kot;
kot(data);

find函数修改

Node* Find(const K& key)
{
	Hash hs;
	KeyOfT kot;
	size_t hashi = hs(key) % _tables.size();//找下标
	Node* cur = _tables[hashi];
	while (cur)//走链表
	{
		if (kot(cur->_data) == key)//找到了
		{
			return cur;
		}
		cur = cur->_next;//往下走
	}
	//找不到
	return nullptr;
}

 insert修改

bool Insert(const T& data)
{
	Hash hs;
	KeyOfT kot;
	//插入失败(找不到)
	if(Find(kot(data)))
		return false;
	//扩容
	if (_n == _tables.size())//负载因子极限
	{
		vector< Node*> newtable(_tables.size() * 2, nullptr);
		//把旧桶的节点挂到新表新桶中
		for (size_t i = 0; i < _tables.size(); i++)
		{
			Node* cur = _tables[i];//遍历走一个个旧桶
			while (cur)
			{
				Node* next = cur->_next;//存一下next,待会可以往下走
				size_t hashi = hs(kot(cur->_data)) % newtable.size();
				//挂上新桶
				cur->_next = newtable[hashi];
				newtable[hashi] = cur;
				//往下走
				cur = next;
			}
			//把旧桶被移走的节点置空
			_tables[i] = nullptr;
		}
		_tables.swap(newtable);
	}
	size_t hashi = hs(kot(data)) % _tables.size();//找到下标
	Node* newnode = new Node(data);//开一个节点的空间
	//头插(入桶)
	newnode->_next = _tables[hashi];
	_tables[hashi] = newnode;
	++_n;
	return true;

}

erase修改

bool Erase(const K& key)
{
	KeyOfT kot;
	Hash hs;
	size_t hashi = hs(key) % _tables.size();
	Node* prev = nullptr;
	Node* cur = _tables[hashi];
	while (cur)
	{
		if (kot(cur->_data) == key)
		{
			// 删除
			if (prev)
			{
				prev->_next = cur->_next;
			}
			else
			{
				_tables[hashi] = cur->_next;
			}

			delete cur;

			--_n;
			return true;
		}

		prev = cur;
		cur = cur->_next;
	}
	return false;
}

3.具体封装及其迭代器实现

3.1哈希表的构造与析构

构造还是之前的构造

至于析构,先遍历表(一个个桶往后走),再遍历表的过程中遍历桶

具体来说表走第一个桶,然后第一个桶往下走(遍历)删除节点,第一个桶删完,表往后走到第二个桶,第二个桶往下走删除节点,表往后走到第三个桶......

HashTable()//构造
{
	_tables.resize(10, nullptr);
	_n = 0;
}
~HashTable()//析构
{
	for (size_t i = 0; i < _tables.size(); i++)
	{
		Node* cur = _tables[i];
		while (cur)
		{
			Node* next = cur->_next;
			delete cur;
			_n--;
			cur = next;
		}
		_tables[i] = nullptr;
	}
}

3.2迭代器的实现

3.2.1迭代器的框架

template<class K, class T, class KeyOfT, class Hash>
struct HTIterator
{
	typedef HashNode<T> Node;
	typedef HashTable<K, T, KeyOfT, Hash> HT;
	typedef HTIterator<K, T, KeyOfT, Hash> Self;
public:
	Node* _node;
	HT* _ht;
}

这里要注意的是如果哈希表的实现是再迭代器的下面的话,迭代器的上面要对哈希表进行前置声明,不然编译器向上搜索不到哈希表的话会报错

加了前置声明的话就不会 

 3.2.2迭代器的构造

HTIterator(Node* node,HT* ht)//构造
	:_node(node)
	,_ht(ht)
{}

3.2.3重载*操作符和重载->操作符

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

3.2.4重载操作符==和!=

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

3.2.5重载操作符++

如果当前桶还没走完,那么就这个桶(链表)继续往下走

如果走完了找下一个非空的桶,找到了下一个非空的桶则走到它的第一个节点,没找到走到空

Self& operator++()
{
	if (_node->_next)//当前桶还未走完
	{
		_node = _node->_next;
	}
	else
	{
		Hash hs;
		KeyOfT kot;
		//当前桶走完了,找下一个桶
		size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();//计算当前在哪个位置(桶上)
		hashi++;
		while (hashi < _ht->_tables.size())
		{
			if (_ht->_tables[hashi])//找到了
			{
				_node = _ht->_tables[hashi];
				break;
			}
			hashi++;
		}
		if (hashi == _ht->_tables.size())//走到后面没有桶了
		{
			_node = nullptr;
		}
	}
	return *this;
}

4.源码

4.1unordered_map

#pragma once

#include"MyHash.h"

namespace HashBucket
{
	template<class K,class V, class Hash = HashFunc<K>>
	class myunorderedmap
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K,V>& kv)
			{
				return kv.first;
			}
		};
	public:
		typedef typename HashBucket::HashTable<K, pair<const K, V>, MapKeyOfT, Hash>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		bool Insert(const pair<K,V>& kv)
		{
			return _ht.Insert(kv);
		}
	private:
		HashBucket::HashTable<K,pair<const K,V>,MapKeyOfT, Hash> _ht;
	};
	void test_map1()
	{
		myunorderedmap<string, string> dict;
		dict.Insert(make_pair("banana", "香蕉"));
		dict.Insert(make_pair("strawberry", "草莓"));
		dict.Insert(make_pair("orange", "橙子"));
		dict.Insert(make_pair("watermelon", "西瓜"));
		dict.Insert(make_pair("banana", "香蕉"));
		dict.Insert(make_pair("strawberry", "草莓"));

		for (auto& e : dict)
		{
			cout << e.first << ":" << e.second<<endl;
		}
	}
}

4.2unordered_set

#pragma once

#include"MyHash.h"

namespace HashBucket
{
	template<class K,class Hash = HashFunc<K>>
	class myunorderedset
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
	public:
		typedef typename HashBucket::HashTable<K, const K, SetKeyOfT, Hash>::iterator iterator;

		iterator begin()
		{
			return _ht.begin();
		}
		iterator end()
		{
			return _ht.end();
		}
		bool insert(const K& key)
		{
			return _ht.Insert(key);
		}
	private:
		HashBucket::HashTable<K,const K,SetKeyOfT, Hash> _ht;
	};

	void test_set1()
	{
		myunorderedset<int> us;
		us.insert(19);
		us.insert(10);
		us.insert(11);
		us.insert(2);
		us.insert(9);
		us.insert(7);
		us.insert(3);
		us.insert(26);

		myunorderedset<int>::iterator it = us.begin();
		while(it!=us.end())
		{
			cout << *it << " ";
			++it;
		}
		cout << endl;
		for (auto& e : us)
		{
			cout << e << " ";
		}
		cout << endl;

	}
}

4.3Hash

#pragma once
#include<vector>
template<class K>
struct HashFunc
{
	size_t operator()(const K& key)
	{
		return (size_t)key;
	}
};

// 特化
template<>
struct HashFunc<string>
{
	size_t operator()(const string& s)
	{
		size_t hash = 0;
		for (auto e : s)
		{
			hash += e;
			hash *= 131;
		}

		return hash;
	}
};
namespace HashBucket
{
	template <class T>
	struct HashNode//表的节点(桶口)
	{
		HashNode<T>* _next;
		T _data;
		HashNode(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
	};
	//前置声明
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

	template<class K, class T, class KeyOfT, class Hash>
	struct HTIterator
	{
		typedef HashNode<T> Node;
		typedef HashTable<K, T, KeyOfT, Hash> HT;
		typedef HTIterator<K, T, KeyOfT, Hash> Self;
	public:
		Node* _node;
		HT* _ht;

		HTIterator(Node* node,HT* ht)//构造
			:_node(node)
			,_ht(ht)
		{}
		T& operator*()
		{
			return _node->_data;
		}
		T* operator->()
		{
			return &_node->_data;
		}
		Self& operator++()
		{
			if (_node->_next)//当前桶还未走完
			{
				_node = _node->_next;
			}
			else
			{
				Hash hs;
				KeyOfT kot;
				//当前桶走完了,找下一个桶
				size_t hashi = hs(kot(_node->_data)) % _ht->_tables.size();//计算当前在哪个位置(桶上)
				hashi++;
				while (hashi < _ht->_tables.size())
				{
					if (_ht->_tables[hashi])//找到了
					{
						_node = _ht->_tables[hashi];
						break;
					}
					hashi++;
				}
				if (hashi == _ht->_tables.size())//走到后面没有桶了
				{
					_node = nullptr;
				}
			}
			return *this;
		}

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




	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		template<class K, class T, class KeyOfT, class Hash>
		friend struct HTIterator;

		typedef HashNode<T> Node;
	public:
		typedef HTIterator<K, T, KeyOfT, Hash> iterator;
		iterator begin()
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				// 找到第一个桶的第一个节点
				if (_tables[i])
				{
					return iterator(_tables[i], this);
				}
			}

			return end();
		}
		iterator end()
		{
			return iterator(nullptr,this);
		}

		HashTable()//构造
		{
			_tables.resize(10, nullptr);
			_n = 0;
		}
		~HashTable()//析构
		{
			for (size_t i = 0; i < _tables.size(); i++)
			{
				Node* cur = _tables[i];
				while (cur)
				{
					Node* next = cur->_next;
					delete cur;
					_n--;
					cur = next;
				}
				_tables[i] = nullptr;
			}
		}
		bool Insert(const T& data)
		{
			Hash hs;
			KeyOfT kot;
			//插入失败(找不到)
			if(Find(kot(data)))
				return false;
			//扩容
			if (_n == _tables.size())//负载因子极限
			{
				vector< Node*> newtable(_tables.size() * 2, nullptr);
				//把旧桶的节点挂到新表新桶中
				for (size_t i = 0; i < _tables.size(); i++)
				{
					Node* cur = _tables[i];//遍历走一个个旧桶
					while (cur)
					{
						Node* next = cur->_next;//存一下next,待会可以往下走
						size_t hashi = hs(kot(cur->_data)) % newtable.size();//节点挂到新桶的下标可能不一样
						//挂上新桶
						cur->_next = newtable[hashi];
						newtable[hashi] = cur;
						//往下走
						cur = next;
					}
					//把旧桶被移走的节点置空
					_tables[i] = nullptr;
				}
				_tables.swap(newtable);
			}
			size_t hashi = hs(kot(data)) % _tables.size();//找到下标
			Node* newnode = new Node(data);//开一个节点的空间
			//头插(入桶)
			newnode->_next = _tables[hashi];
			_tables[hashi] = newnode;
			++_n;
			return true;

		}
		Node* Find(const K& key)
		{
			Hash hs;
			KeyOfT kot;
			size_t hashi = hs(key) % _tables.size();//找下标
			Node* cur = _tables[hashi];
			while (cur)//走链表
			{
				if (kot(cur->_data) == key)//找到了
				{
					return cur;
				}
				cur = cur->_next;//往下走
			}
			//找不到
			return nullptr;
		}

		bool Erase(const K& key)
		{
			KeyOfT kot;
			Hash hs;
			size_t hashi = hs(key) % _tables.size();
			Node* prev = nullptr;
			Node* cur = _tables[hashi];
			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					// 删除
					if (prev)
					{
						prev->_next = cur->_next;
					}
					else
					{
						_tables[hashi] = cur->_next;
					}

					delete cur;

					--_n;
					return true;
				}

				prev = cur;
				cur = cur->_next;
			}

			return false;
		}
	private:
		vector<Node*> _tables; // 指针数组
		size_t _n;
	};
}

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

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

相关文章

如何操作RAID 0阵列的扩容?

正文共&#xff1a;1888 字 23 图&#xff0c;预估阅读时间&#xff1a;2 分钟 RAID&#xff08;Redundant Array of Independent Disks&#xff09;即独立磁盘冗余阵列&#xff0c;通常简称为磁盘阵列&#xff0c;在高级磁盘阵列中&#xff0c;部分物理存储空间会用来记录保存…

前端学习<四>JavaScript基础——01-编程语言和JavaScript简介

计算机语言 概念 计算机语言&#xff1a;人与计算机之间通信的语言。它是人与计算机之间传递信息的媒介&#xff0c;它通过特定的语法规则和语义约定&#xff0c;将人类可理解的指令转化为计算机可以执行的机器指令。 计算机程序&#xff1a;就是计算机所执行的一系列的指令…

数据结构——二叉树链式结构

目录 前言 1. 二叉树的概念及结构 1.1概念 1.2 特殊的二叉树 1.3 二叉树的性质 1.4 二叉树的存储结构 2. 二叉树链式结构实现 2.1 手动创建二叉树 2.2 二叉树的遍历 2.2.1 前序、中序和后序遍历 2.2.2 层序遍历 2.3 节点个数以及高度 2.3.1 节点个数 2.3.2 求二叉…

爬虫实战三、PyCharm搭建Scrapy开发调试环境

#一、环境准备 Python开发环境以及Scrapy框架安装&#xff0c;参考&#xff1a;爬虫实战一、Scrapy开发环境&#xff08;Win10Anaconda&#xff09;搭建 PyCharm安装和破解&#xff0c;参考&#xff1a;爬虫实战二、2019年PyCharm安装&#xff08;激活到2100年&#xff09; …

关于 ulimit 的两个天坑

稍微有点 Linux 经验的人一定会遇到过 “Too many open files” 错误&#xff0c;这个错误本质是 ulimit 设置不合理导致的。关于 ulimit 设置&#xff0c;有哪些需要注意的点呢&#xff1f;本文给大家做一个介绍&#xff0c;希望对大家有所帮助。 如何确认 ulimit 设置生效了…

苍穹外卖面试题准备(持续更新

苍穹外卖面试题准备 讲讲 redis&#xff0c;它在你的项目中作用是什么&#xff1f;在你的项目中 redis 作为缓存, MySQL 的数据如何与 redis 进行同步呢&#xff1f;如何解决 Redis 的缓存穿透问题redis 的淘汰机制是怎么样的&#xff1f;redis 的 IO 多路复用是什么&#xff1…

大屏可视化项目示例--基于Vue3+vite2+echart+mock+axios+dataV

图例&#xff1a; 项目环境&#xff1a; Vite、Echarts、Npm、Node、axios、mock、vue3、dataV。 项目地址&#xff1a; IofTV-Screen-Vue3: &#x1f525;(IofTV-Screen Vue3版本)一个基于 vue3、vite、Echart 框架的物联网可视化&#xff08;大屏展示&#xff09;模板&…

docker导出导入镜像

docker导出镜像 查看要导出的镜像 docker images主要有两列 REPOSITORY TAG 导出命令 导出公式 docker save -o xxxx.tar REPOSITORY:TAG例子 docker save -o minio.tar minio/minio:latestminio/minio:latest可以使用image id代替&#xff0c;但是使用image id会导致导…

CSS之第一个CSS样式和CSS选择符

前端这些博客&#xff0c;我觉得都是固定的语法&#xff0c;故而不会以过多的文字进行描述&#xff0c;本系列博文均以实例和代码介绍的方式进行&#xff0c;主要按照代码进行。不会以过多的文字描述。 第一个CSS样式 <!DOCTYPE html> <html lang"en">…

百度地图 JavaScript API GL快速升级 和mapV的使用

一、百度地图 JavaScript API GL快速升级 JavaScript API GL升级指南 1、在原来的链接中添加type webgl 2、批量替换页面中的BMap直接替换为BMapGL 二、mapV的使用 MapVGL mapVGL的效率确实要快很多&#xff0c;一万个点基本实现秒现 1、加载mapvgl.min.js export const…

eclipse显示包层级设置

1、选择中右上角的倒三角 2、选中 Package Presentation -> Hierachical

【linux学习15】日志管理rsyslog、logrotate介绍

1.rsyslog 系统日志管理 什么程序产生什么日志&#xff0c;日志存放文件夹 rsyslogd&#xff1a;系统专职日志程序 httpd/nginx/mysql&#xff1a;各类应用程序&#xff0c;存放自己的日志查看rsyslogd程序 ps aux | grep rsyslogd//常见日志文件&#xff08;洗头膏、进程、应…

CTK插件框架学习-信号槽(05)

CTK插件框架学习-事件监听(04)https://mp.csdn.net/mp_blog/creation/editor/137171155 一、主要流程 信号发送者告诉服务要发送的信号信号发送者发送信号信号接收者告诉服务当触发某个订阅的主题时通知槽函数信号接收者处理槽函数信号槽参数类型必须为&#xff08;const ctk…

算法训练day57leetcode1143.最长公共子序列 1035.不相交的线 53最大子序和

part14 1143.最长公共子序列 1035.不相交的线 53最大子序和 动态规划 1143. 最长公共子序列 初始化动态规划数组 dp 动态规划数组 dp 是一个二维数组&#xff0c;其大小为 (text1.size() 1) x (text2.size() 1)&#xff0c;dp[i][j] 表示 text1 的前 i 个字符和 text2 的前…

对【AI技术创业】有哪些机会进行分析和引导

文章目录 方向一&#xff1a;行业解决方案,以下是一些常见的行业解决方案&#xff1a;方向二&#xff1a;智能产品和服务,以下是一些智能产品和服务的示例&#xff1a;方向三&#xff1a;教育和培训 1.智能客户服务&#xff1a; 利用自然语言处理&#xff08;NLP&#xff09;和…

通过SSH在苹果手机上查看系统文件:远程访问iOS文件系统的方法

​ 目录 引言 用户登录工具和连接设备 查看设备信息&#xff0c;电池信息 查看硬盘信息 硬件信息 查看 基带信息 销售信息 电脑可对手机应用程序批量操作 运行APP和查看APP日志 IPA包安装测试 注意事项 引言 苹果手机与安卓手机不同&#xff0c;无法直接访问系统文件…

【蓝牙协议栈】【BLE】【ATT】低功耗蓝牙之属性协议介绍

1. 精讲蓝牙协议栈&#xff08;Bluetooth Stack&#xff09;&#xff1a;SPP/A2DP/AVRCP/HFP/PBAP/IAP2/HID/MAP/OPP/PAN/GATTC/GATTS/HOGP等协议理论 2. 欢迎大家关注和订阅&#xff0c;【蓝牙协议栈】和【Android Bluetooth Stack】专栏会持续更新中.....敬请期待&#xff01…

zabbix 7.0 新增功能亮点(一)——T参数

概要&#xff1a; T参数是zabbix7.0新增的一项功能&#xff0c;它支持对配置文件进行可用性验证&#xff0c;即zabbix程序(server/proxy/agent等)修改配置文件后&#xff0c;支持-T或–test-config参数验证配置参数可用性。 T参数主要包含以下三个方面的应用场景&#xff1a; …

宁盾身份域管与Coremail邮件系统完成兼容互认证,持续深化信创布局

在信创国产化改造的背景下&#xff0c;企业邮箱的替换是许多党政、央国企、金融、制造企业面临的重要任务。为了满足企业对国产邮箱、OA等其他应用、终端实现统一身份认证&#xff0c;宁盾国产化身份域管与 Coremail XT 安全增强电子邮件系统 V5.0、V6.0 完成了产品兼容互认证&…

新能源汽车充电桩主板产业链解析

新能源汽车充电桩主控制板&#xff0c;简称汽车充电桩主板&#xff0c;是充电桩设施的核心部件&#xff0c;主要负责控制充电桩的整体运行和管理充电过程。了解汽车充电桩主板的整体产业链是非常重要的&#xff0c;这可以帮助您更好地了解供应链、采购渠道以及行业发展趋势。 产…