unordered_map与unordered_set的实现(含迭代器)

unordered_map与unordered_set的实现


文章目录

  • unordered_map与unordered_set的实现
  • 前言
  • 一、问题一
    • HashTable.h
  • 二、问题二&问题三
    • 1.封装时如何取出key
    • 2.不同类型key如何建立对应关系
  • 三、问题四&问题五
    • 问题四
    • 问题五
  • 四、实现代码
    • MyUnorderedSet.h
    • MyUnorderedMap.h
    • HashTable.h


前言

在C++11中新增了两个很好用的容器,分别是unordered_map与unordered_set,和map和set有不同之处

map与set的底层实现是平衡树——红黑树,并且采取中序遍历时默认有序

而本文的unordered_map与unordered_set底层实现是哈希思想(拉链法),并且他的存储方式不支持排序,所以是unordered

两者的查找效率综合比较,会发现unordered_map与unordered_set的效率综合会更高,所以在C++11中支持了unordered_map与unordered_set

本文中是用哈希桶的拉链法来封装unordered_map与unordered_set
这里的封装与红黑树封装map和set的封装相似,但此处会更难

具体通过解决问题来实现

  1. HashTable的迭代器实现
  2. 封装时HashTable如何取出map的key和set的key
  3. 取出key后如何针对不同类型key建立映射关系
  4. 如何解决Set中key不能被修改,Map中key不能被修改,value能被修改的问题
  5. Insert插入返回值问题以及Map[]的重载实现

关于哈希思想及其具体实现细节看我的上篇文章:数据结构之哈希表


一、问题一

这里就一步步,先实现iterator再实现const_iterator版本了,而是直接放出能适配iterator与const_iterator的版本,本质上就是用类模板泛型编程,需要什么就调用什么,是什么类型就返回什么类型的迭代器

这里尤为注意的一点是,这里的迭代器是单向迭代器,只支持++,而由于底层是哈希表的拉链法实现的,是数组与链表结合的方式
在实现运算符重载++时,本质上就是在逐个遍历哈希桶,而当前桶走完的时候,需要进入下一个桶,那么如何判断当前桶的位置,以及如何找到下一个桶,就需要把这个数组或者整个哈希表传过来,这里我们做的是把整个哈希表传过来

注意:其实将数组传过来会更简单些,传哈希表会有一些问题

  1. 我们将哈希表传过来,是可能要访问哈希表内的私有变量来获得下一个桶,而直接在_HTIterator这个类内使用哈希表内的私有变量是不可取的,所以需要在哈希表内声明友元
  2. 此处还涉及编译问题,由于编译器是从上往下编译代码,我们将迭代器写在哈希表代码的上面,而迭代器中有哈希表,这里编译器并不认识哈希表,因为哈希表的定义还未出现,所以还需要哈希表对应的类的声明
	template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
	friend struct _HTIterator;
	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

至于class KeyOfT 与 class Hash这两个类模板的作用,则在下文中解答

HashTable.h

namespace hash_bucket
{
	template<class T>
	struct HashNode
	{
		HashNode(const T& data)
			:_data(data)
			,_next(nullptr)
		{}

		T _data;
		HashNode* _next;
	};


	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

	template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
	struct _HTIterator
	{
		typedef HashNode<T> Node;
		typedef _HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;

		typedef _HTIterator<K, T, T&, T*, KeyOfT, Hash> iterator;


		_HTIterator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht)
			:_node(node)
			,_pht(pht)
		{}

		_HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht)
			:_node(node)
			, _pht(pht)
		{}
		

		_HTIterator(const iterator& x)
			:_node(x._node)
			, _pht(x._pht)
		{}


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

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



		Self operator++()
		{
			Hash hf;
			KeyOfT kot;

			size_t hashi = hf(kot(_node->_data)) % _pht->_t.size();

			if (_node->_next != nullptr)
			{
				_node = _node->_next;
			}
			else
			{
				hashi++;

				while (hashi < _pht->_t.size())
				{
					if (_pht->_t[hashi])
					{
						_node = _pht->_t[hashi];
						break;
					}

					hashi++;
				}

			}
			
			if (hashi == _pht->_t.size())
			{
				_node = nullptr;
			}

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

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

		const HashTable<K, T, KeyOfT, Hash>* _pht;
		Node* _node;
	};



	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		typedef HashNode<T> Node;

		template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct _HTIterator;

	public:

		HashTable(size_t n = 10)
		{
			_t.resize(n);
		}


		typedef _HTIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
		typedef _HTIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;


		iterator begin()
		{
			size_t hashi = 0;

			while (hashi < _t.size())
			{
				if (_t[hashi])
				{
					break;
				}

				hashi++;
			}

			if (hashi == _t.size())
			{
				return iterator(nullptr, this);
			}

			return iterator(_t[hashi], this);
		}

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



		const_iterator begin()const
		{
			size_t hashi = 0;

			while (hashi < _t.size())
			{
				if (_t[hashi])
				{
					break;
				}

				hashi++;
			}

			if (hashi == _t.size())
			{
				return iterator(nullptr, this);
			}

			return iterator(_t[hashi], this);
		}

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



		pair<iterator,bool> Insert(const T& data)
		{
			KeyOfT kot;
			Hash hf;

			iterator ret = Find(kot(data));
			if (ret != end())
			{
				return make_pair(ret, false);
			}
	
			//扩容
			if (_n / _t.size() == 1)
			{
				size_t newsize = _t.size() * 2;

				HashTable newtable(newsize);

				for (int i = 0; i < _n; i++)
				{
					Node* cur = _t[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hf(kot(cur->_data)) % newsize;

						cur->_next = newtable._t[hashi];
						newtable._t[hashi] = cur;

						cur = next;
					}

					_t[i] = nullptr;
				}

				swap(_t, newtable._t);
			}


			size_t  hashi = hf(kot(data)) % _t.size();

			Node* newnode = new Node(data);

			newnode->_next = _t[hashi];
			_t[hashi] = newnode;

			_n++;

			return make_pair(iterator(newnode, this), true);
		}

		iterator Find(const K& key)
		{
			Hash hf;
			KeyOfT kot;
			size_t hashi = hf(key) % _t.size();

			Node* cur = _t[hashi];

			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}
				
				cur = cur->_next;
			}

			return iterator(nullptr, this);
		}

		bool Erase(const K& key)
		{
			Hash hf;
			KeyOfT kot;
			size_t hashi = hf(key) % _t.size();

			Node* cur = _t[hashi];
			Node* prev = nullptr;

			if (kot(cur->_data) == key)
			{
				_t[hashi] = cur->_next;
				delete cur;
				return true;
			}

			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					prev->_next = cur->_next;
					delete cur;
					_n--;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}


	private:
		vector<HashNode<T>*> _t;
		size_t _n = 0;
	};
}

二、问题二&问题三

1.封装时如何取出key

首先解释一下问题,我们的目的是将unordered_map与unordered_set用哈希表封装实现,map中存的是pair,set中存的是key,而如何用一份哈希表适配两种结构呢

在封装的时候解决这个问题,在unordered_map与unordered_set中写一个内部类,这个类之中实现了一个仿函数,用来返回key,并且将其传给哈希表内

	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
	public:

		struct KeyOfT
		{
			const K& operator()(const pair<K, V>& kv)
			{
				return  kv.first;
			}
		};
	private:
		hash_bucket::HashTable<K, pair<const K, V>, KeyOfT, Hash> _ht;
	};

template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
	public:
		struct KeyOfT
		{
			const K& operator()(const K& key)
			{
				return  key;
			}
		};

	private:
		hash_bucket::HashTable<K, K, KeyOfT, Hash> _ht;
	};

2.不同类型key如何建立对应关系

本文的拉链法中,使用的哈希函数是除留余数法,如果key是int类型的话那正好,可以直接用key除,但如果key是string或者自定义类型,那么就不能够直接除了,则需要将其转换成int类型,另外一个模板参数Hash,则是将其转换方式传给哈希表

如果是内置类型的float,double之类的,我们可以直接强转成size_t返回
如果是string类型,由于string比较常用,我们可以为string搞个特化,默认支持string

上面两个都是默认支持的,用默认缺省值就行,不需要手动传Hash

而如果是自定义类型,则需要使用者通过接口手动传Hash,因为默认的缺省值用不了

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

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

		return hash;
	}
};

三、问题四&问题五

问题四与问题五与set与map用红黑树封装的问题相同

问题四

set的iterator和const_iterator都是红黑树的const_iterator复用而来
map中的iterator是红黑树的iterator复用而来,const_iterator是红黑树的const_iterator复用而来
既然set中的迭代器都是const_iterator所以key自然不能被修改

	typedef typename hash_bucket::HashTable<K, K, KeyOfT, Hash>::const_iterator iterator;
	typedef typename hash_bucket::HashTable<K, K, KeyOfT, Hash>::const_iterator const_iterator;

map解决key不能被修改,value能被修改的原理也很简单,就是在实例化的时候,声明第二个模板参数——在map中也就是pair,pair的first是const类型

	private:
		hash_bucket::HashTable<K, pair<const K, V>, KeyOfT, Hash> _ht;

问题五

unordered_map与unordered_set与map和set的红黑树封装相同,insert的返回值都是一个键值对
在这里插入图片描述

first是一个迭代器,second是一个bool类型

基于此性质,引出了map的计数功能,可以通过insert返回的迭代器查看是否有key值,如果不存在则插入,将value值赋值为1,如果key已经存在,则通过insert返回的迭代器将value++,以此实现计数功能,所以map实现了operator[],用来计数

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

四、实现代码

MyUnorderedSet.h

#include "HashTable.h"

namespace Tlzns
{
	template<class K, class Hash = HashFunc<K>>
	class unordered_set
	{
	public:
		struct KeyOfT
		{
			const K& operator()(const K& key)
			{
				return  key;
			}
		};



		typedef typename hash_bucket::HashTable<K, K, KeyOfT, Hash>::const_iterator iterator;
		typedef typename hash_bucket::HashTable<K, K, KeyOfT, Hash>::const_iterator const_iterator;



		iterator begin()const
		{
			return _ht.begin();
		}

		iterator end()const
		{
			return _ht.end();
		}


		pair<iterator, bool> Insert(const K& key)
		{
			auto ret = _ht.Insert(key);
			return pair<iterator, bool>(iterator(ret.first._node, ret.first._pht), ret.second);
		}

		bool Erase(const K& key)
		{
			return _ht.Erase(key);
		}

		iterator Find(const K& key)
		{
			return _ht.Find(key);
		}

	private:
		hash_bucket::HashTable<K, K, KeyOfT, Hash> _ht;
	};


	void test_set()
	{
		unordered_set<int> us;
		us.Insert(5);
		us.Insert(15);
		us.Insert(52);
		us.Insert(3);

		unordered_set<int>::iterator it = us.begin();
		while (it != us.end())
		{
			//*it += 5;
			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : us)
		{
			cout << e << " ";
		}
		cout << endl;
	}
}

MyUnorderedMap.h

#include "HashTable.h"

namespace Tlzns
{
	template<class K, class V, class Hash = HashFunc<K>>
	class unordered_map
	{
	public:

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



		typedef typename hash_bucket::HashTable<K, pair<const K, V>, KeyOfT, Hash>::iterator iterator;
		typedef typename hash_bucket::HashTable<K, pair<const K, V>, KeyOfT, Hash>::const_iterator const_iterator;


		iterator begin()
		{
			return _ht.begin();
		}

		iterator end()
		{
			return _ht.end();
		}

		const_iterator begin()const
		{
			return _ht.begin();
		}

		const_iterator end()const
		{
			return _ht.end();
		}


		pair<iterator, bool> Insert(const pair<K, V>& kv)
		{
			return _ht.Insert(kv);
		}
		
		bool Erase(const K& key)
		{
			return _ht.Erase(key);
		}

		iterator Find(const K& key)
		{
			return _ht.Find(key);
		}

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


	private:
		hash_bucket::HashTable<K, pair<const K, V>, KeyOfT, Hash> _ht;
	};

	void test_map()
	{
		unordered_map<string, string> dict;
		dict.Insert(make_pair("sort", ""));
		dict.Insert(make_pair("string", ""));
		dict.Insert(make_pair("insert", ""));

		unordered_map<string, string>::const_iterator it = dict.begin();

		for (auto& kv : dict)
		{
			//kv.first += 'x';
			kv.second += 'x';

			cout << kv.first << ":" << kv.second << endl;
		}
		cout << endl;

		string arr[] = { "香蕉", "甜瓜","苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜", "苹果", "香蕉", "苹果", "香蕉" };
		unordered_map<string, int> count_map;
		for (auto& e : arr)
		{
			count_map[e]++;
		}

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

HashTable.h

#include <iostream>
#include <vector>

using namespace std;

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

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

		return hash;
	}
};

namespace hash_bucket
{
	template<class T>
	struct HashNode
	{
		HashNode(const T& data)
			:_data(data)
			,_next(nullptr)
		{}

		T _data;
		HashNode* _next;
	};


	template<class K, class T, class KeyOfT, class Hash>
	class HashTable;

	template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
	struct _HTIterator
	{
		typedef HashNode<T> Node;
		typedef _HTIterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;

		typedef _HTIterator<K, T, T&, T*, KeyOfT, Hash> iterator;


		_HTIterator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht)
			:_node(node)
			,_pht(pht)
		{}

		_HTIterator(Node* node, const HashTable<K, T, KeyOfT, Hash>* pht)
			:_node(node)
			, _pht(pht)
		{}
		

		_HTIterator(const iterator& x)
			:_node(x._node)
			, _pht(x._pht)
		{}


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

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



		Self operator++()
		{
			Hash hf;
			KeyOfT kot;

			size_t hashi = hf(kot(_node->_data)) % _pht->_t.size();

			if (_node->_next != nullptr)
			{
				_node = _node->_next;
			}
			else
			{
				hashi++;

				while (hashi < _pht->_t.size())
				{
					if (_pht->_t[hashi])
					{
						_node = _pht->_t[hashi];
						break;
					}

					hashi++;
				}

			}
			
			if (hashi == _pht->_t.size())
			{
				_node = nullptr;
			}

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

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

		const HashTable<K, T, KeyOfT, Hash>* _pht;
		Node* _node;
	};



	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		typedef HashNode<T> Node;

		template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct _HTIterator;

	public:

		HashTable(size_t n = 10)
		{
			_t.resize(n);
		}


		typedef _HTIterator<K, T, T&, T*, KeyOfT, Hash> iterator;
		typedef _HTIterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;


		iterator begin()
		{
			size_t hashi = 0;

			while (hashi < _t.size())
			{
				if (_t[hashi])
				{
					break;
				}

				hashi++;
			}

			if (hashi == _t.size())
			{
				return iterator(nullptr, this);
			}

			return iterator(_t[hashi], this);
		}

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



		const_iterator begin()const
		{
			size_t hashi = 0;

			while (hashi < _t.size())
			{
				if (_t[hashi])
				{
					break;
				}

				hashi++;
			}

			if (hashi == _t.size())
			{
				return iterator(nullptr, this);
			}

			return iterator(_t[hashi], this);
		}

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



		pair<iterator,bool> Insert(const T& data)
		{
			KeyOfT kot;
			Hash hf;

			iterator ret = Find(kot(data));
			if (ret != end())
			{
				return make_pair(ret, false);
			}
	
			//扩容
			if (_n / _t.size() == 1)
			{
				size_t newsize = _t.size() * 2;

				HashTable newtable(newsize);

				for (int i = 0; i < _n; i++)
				{
					Node* cur = _t[i];
					while (cur)
					{
						Node* next = cur->_next;

						size_t hashi = hf(kot(cur->_data)) % newsize;

						cur->_next = newtable._t[hashi];
						newtable._t[hashi] = cur;

						cur = next;
					}

					_t[i] = nullptr;
				}

				swap(_t, newtable._t);
			}


			size_t  hashi = hf(kot(data)) % _t.size();

			Node* newnode = new Node(data);

			newnode->_next = _t[hashi];
			_t[hashi] = newnode;

			_n++;

			return make_pair(iterator(newnode, this), true);
		}

		iterator Find(const K& key)
		{
			Hash hf;
			KeyOfT kot;
			size_t hashi = hf(key) % _t.size();

			Node* cur = _t[hashi];

			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					return iterator(cur, this);
				}
				
				cur = cur->_next;
			}

			return iterator(nullptr, this);
		}

		bool Erase(const K& key)
		{
			Hash hf;
			KeyOfT kot;
			size_t hashi = hf(key) % _t.size();

			Node* cur = _t[hashi];
			Node* prev = nullptr;

			if (kot(cur->_data) == key)
			{
				_t[hashi] = cur->_next;
				delete cur;
				return true;
			}

			while (cur)
			{
				if (kot(cur->_data) == key)
				{
					prev->_next = cur->_next;
					delete cur;
					_n--;
					return true;
				}
				prev = cur;
				cur = cur->_next;
			}
			return false;
		}

	private:
		vector<HashNode<T>*> _t;
		size_t _n = 0;
	};
}

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

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

相关文章

文案二次创作软件,文案二次创作的软件

文案创作成为品牌传播和营销不可或缺的一环。对于许多从业者而言&#xff0c;文案创作常常是一项既耗时又耗力的工作。为了解决这一文案创作的难题&#xff0c;市场上涌现出了众多的智能文案生成工具。我们通过对这些工具的介绍和分析&#xff0c;希望能够为你提供一些在文案创…

Kafka 架构深度解析:生产者(Producer)和消费者(Consumer)

Apache Kafka 作为分布式流处理平台&#xff0c;其架构中的生产者和消费者是核心组件&#xff0c;负责实现高效的消息生产和消费。本文将深入剖析 Kafka 架构中生产者和消费者的工作原理、核心概念以及高级功能。 Kafka 生产者&#xff08;Producer&#xff09; 1 发送消息到…

【C++】异常处理 ⑦ ( 异常类的继承层次结构 | 抛出 / 捕获 多个类型异常对象 | 抛出子类异常对象 / 捕获并处理 父类异常对象 )

文章目录 一、抛出 / 捕获 多个类型异常对象1、抛出 / 捕获 多个类型异常对象2、操作弊端3、完整代码示例 二、异常类的继承层次结构1、抛出子类异常对象 / 捕获并处理 父类异常对象2、完整代码示例 - 抛出子类异常对象 / 捕获并处理 父类异常对象 自定义的 异常类 , 可能存在 …

prometheus部署及与grafana结合应用

一、prometheus 介绍 prometheus server 是 Prometheus组件中的核心部分&#xff0c;负责实现对监控数据的获取&#xff0c;存储以及查询。它会定期从静态配置的监控目标或者基于服务发现自动配置的自标中进行拉取数据&#xff0c;当新拉取到的数据大于配置的内存缓存区时&…

Python 入门

目录 编译性语言 解释型语言 Python语言介绍 Python安装 配置环境变量 Pycharm安装 Pycharm基本使用 编译性语言 解释型语言 计算机是不能理解高级语言的&#xff0c;更不能直接执行高级语言&#xff0c;它只能直接理解机器语言&#xff0c;所以使用任何高级语言编写的程…

vue3项目脚手架如何使用swiper, ‘vue-awesome-swiper‘报错解决(简单示例)

目录 前言 使用方法 效果图 1.下载swiper 2. 写入需要的页面 3.在对应页面引入组件 4.推荐页完整代码 前言 Vue3和Vue2在使用swiper时是有差别的&#xff0c;Vue3引入swiper需要注意Vu3的版本和swiper的版本&#xff0c;如果不匹配通常会报错 如下&#xff1a;当引用版本过…

canvas基础:绘制虚线

canvas实例应用100 专栏提供canvas的基础知识&#xff0c;高级动画&#xff0c;相关应用扩展等信息。 canvas作为html的一部分&#xff0c;是图像图标地图可视化的一个重要的基础&#xff0c;学好了canvas&#xff0c;在其他的一些应用上将会起到非常重要的帮助。 文章目录 示例…

【深度学习实验】图像处理(四):PIL——自定义图像数据增强操作(图像合成;图像融合(高斯掩码))

文章目录 一、实验介绍二、实验环境1. 配置虚拟环境2. 库版本介绍 三、实验内容0. 导入必要的库1. PIL基础操作2~4. 随机遮挡、随机擦除、线性混合5. 图像合成5.1 原理5.2 实现5.3 效果展示 6. 图像融合6.1 原理6.2 实现6.3 效果展示 一、实验介绍 在深度学习任务中&#xff0c…

【数据库原理】函数依赖、三范式、视图、事务、数据库设计(概念、逻辑、物理结构设计,实施)、数据流图、数据字典、存储过程、触发器、备份与还原【我统统拿下!】

函数依赖 函数依赖是关系数据库中的重要概念&#xff0c;用于描述关系中属性之间的依赖关系。 在关系数据库中&#xff0c;如果关系 R 中的某个属性或属性组的值能够唯一确定关系中其他属性的值&#xff0c;那么我们就说这个属性或属性组对其他属性具有函数依赖关系。 举个例…

“此应用专为旧版android打造,因此可能无法运行”,问题解决方案

当用户在Android P系统上打开某些应用程序时&#xff0c;可能会弹出一个对话框&#xff0c;提示内容为&#xff1a;“此应用专为旧版Android打造&#xff0c;可能无法正常运行。请尝试检查更新或与开发者联系”。 随着Android平台的发展&#xff0c;每个新版本通常都会引入新的…

人工智能发展史

人工智能&#xff08;AI&#xff09;的发展史是一段跨越数十年的旅程&#xff0c;涵盖了从早期理论探索到现代技术革新的广泛内容。人工智能的发展历程展示了从最初的概念探索到现代技术突破的演变。尽管经历了多次起伏&#xff0c;但AI领域持续进步&#xff0c;不断拓展其应用…

springBoot整合task

springBoot整合task 文章目录 springBoot整合task开开关设置任务&#xff0c;并设置执行周期定时任务的相关配置 开开关 设置任务&#xff0c;并设置执行周期 Component public class MyBean {Scheduled(cron "0/1 * * * * ?")public void print(){System.out.prin…

Maven的安装与配置本地仓库,镜像源,环境变量详细步骤

参考视频&#xff1a; 黑马程序员2023新版JavaWeb开发教程&#xff0c;实现javaweb企业开发全流程 【小飞非系列】最新Maven实战教程-项目实战构建利器 文章目录 一.下载Maven安装包二.配置Maven的本地仓库(本机仓库)三.配置镜像源&#xff08;加速jar包的下载)四.配置Maven的环…

【Java 基础】17 集合

文章目录 1.基本概念2.核心接口3.常见实现1&#xff09;List 接口的实现类ArrayListLinkedListVector 2&#xff09;Set 接口的实现类HashSetLinkedHashSetTreeSet 3&#xff09;Queue 接口的实现**类**ArrayQueue 4&#xff09;Map 接口的实现类HashMapLinkedHashMapTreeMap 4…

监控之Spring Boot Admin

目录 一、Spring Boot Admin 简介 官方网址 简介 二、Spring Boot Admin的使用 启动SBA server 微服务端配置SBA server地址 查看监控信息 一、Spring Boot Admin 简介 官方网址 GitHup网址&#xff1a;GitHub - codecentric/spring-boot-admin: Admin UI for administ…

ChaoJi充电连接装置典型试验案例分析 GB/T 20234.1充电连接装置型式试验变化分析

GB/T 20234.1充电连接装置典型试验变化分析 1、ChaoJi充电连接装置典型试验案例分析 1.1、大功率直流充电接口 1.2、枪线尺寸、重量、面积数据对比 1.3、枪线温升对比试验 1.4、chaoji 枪线温升试验 1.5、chaoji枪线防护等级试验 1.6、GB/T 20234.4项目列表 1.7、小结 ✓ 通…

关于媒体查询不能生效的原因

问题 今天写媒体查询&#xff0c;遇到了个问题&#xff0c;卡了很久&#xff0c;引入三个样式&#xff1a;mainPageCommon.css、mainPageBig.css、mainPageSmall.css。其中的两个样式可以生效&#xff0c;但是小尺寸的媒体查询不能生效&#xff0c;这里很奇怪&#xff01;&…

Cairo编程语言

文章目录 Cairo编程语言一、背景二、什么是Cairo工作原理 三、Solidity 和 Cairo 的区别四、开发 Starknet 智能合约的工具链protostarstarknet-foundryScarb安装Scarb 安装starknet-foundry通过snfoundryupStarknet Foundry的snforge 命令行工具如何创建新项目、编译和测试snc…

Linux驱动开发学习笔记2《LED驱动开发试验》

目录 一、Linux下LED灯驱动原理 1.地址映射 二、硬件原理图分析 三、实验程序编写 1.LED 灯驱动程序编写 2.编写测试APP 四、运行测试 1.编译驱动程序和测试APP &#xff08;1&#xff09;编译驱动程序 &#xff08;2&#xff09;编译测试APP 2.运行测试 一、Linux下…

jsp高校教师调课管理系统Myeclipse开发mysql数据库web结构java编程计算机网页项目

一、源码特点 JSP 高校教师调课管理系统是一套完善的java web信息管理系统&#xff0c;对理解JSP java编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为 TOMCAT7.0,Myeclipse8.5开发&#xff0c;数据库为Mysq…