用哈希表封装unordered_map(以及set)【C++】

news2025/1/17 21:49:00

目录

一,前言

二,封装层框架(哈希底层以哈希桶为例)

三,迭代器

1. operator++

2. operator[]

3. 仿函数优化

3. 解决unordered_set中Key可以修改的Bug

代码区

Hash_map_set.h

HashTable.h

下节预告:哈希应用!!

结语


一,前言

在学习封装unordered_map与unordered_set前,建议先学习如何封装map & set该篇文章用红黑树封装map&set【C++】-CSDN博客

这样更能理解其中的封装思想(两种封装方式,主题思路大体相似)

同时用哈希表封装 unordered_map和undordered_set,在封装之前,我们首先是要学会哈希表基本的精华,哈希实现建议大家先学习从底层认识哈希表【C++】-CSDN博客

二,封装层框架(哈希底层以哈希桶为例)

由于我们前面已经学习过map与set的封装,在学习unordered_map(set) 封装这里我们直接给出基本的框架代码啦! 

本质上:还是对哈希表进行封装,在unordered_map层面调用哈希底层。

用哈希表封装 unordered_map &  unordered_set,思想核心:就是让unordered_set如何适应unordered_map,看到这里的都是已经学习过红黑树封装map与set的童鞋了吧

namespace hash_map_set
{
	template <class K>
	class unordered_set
	{
	public:
		struct SetkeyofT   // 从T类型中提取Key
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

		bool insert(const K& data)
		{
			return table.insert(data);
		}

	private:
		hash_barrel::HashTable<K, K, SetkeyofT>  table;
	};

	template <class K, class V> 
	class unordered_map
	{
	public:

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

		bool insert(const pair<const K, V>& kv)
		{
			return table.insert(kv);
		}
	private:
		hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT>  table;
	};
}
// 哈希表底层部分变化,为了避免冗余,就只展现部分代码

template <class T>
	struct Node_Data
	{
		typedef Node_Data<T> Node_data;
		T _data;
		Node_data* _downstars = nullptr;

		Node_Data(const T& pa = T())
			:_data(pa)
		{}
	};

template <class K, class T, class KeyofT ,class Hashstr = Hashstr<K>>
	class HashTable
	{
	public:
		typedef Node_Data<T> Node_Data;

.......

三,迭代器

首先我们先完成 迭代器基本框架 和 基本“ * ”, “ & ”,“ ->”, " != "实现,代码其实比较简单,重要的是逻辑如何行云流水的形成,多写多看多思考吧!!

    template <class T>
	struct Node_Data
	{
		typedef Node_Data<T> Node_data;
		T _data;
		Node_data* _downstars = nullptr;

		Node_Data(const T& pa = T())
			:_data(pa)
		{}
	};

	template <class type>
	struct Hashstr
	{
		int operator()(const type& sd)
		{
			return sd;
		}
	};

	template<>
	struct Hashstr<string>
	{
		size_t operator()(const string& str)
		{
			size_t sum = 0;
			for (auto e : str)
			{
				sum += e;
				sum *= 31;   // 别问,问就是实验的结果
			}
			return sum;
		}
	};

	// 由于迭代器需要使用HashTable作为成员变量,因此需要前置声明
	template <class K, class T, class KeyofT, class Hashstr = Hashstr<K>>
	class HashTable;

	template <class K, class T, class KeyofT, class Ref, class Ptr, class Hash>
	struct HT_iterator
	{
		typedef Node_Data<T> Node_Data;
		typedef HashTable<K, T, KeyofT, Hash> HashTable;
		typedef HT_iterator<K, T, KeyofT, Ref, Ptr, Hash> HT_iter; // 正常迭代器

		Node_Data* _node;  // 当前位置
		HashTable* _ht;    // 当前哈希表
		size_t _bucket = -1;     // 当前桶

		HT_iterator(Node_Data* node, HashTable* ht)
			:_node(node)
			, _ht(ht)
		{
			Hash hashstr;
			KeyofT keyofT;
			if (node) // node为空的时候,我们会对nullptr进行访问,所以规避一下
			_bucket = hashstr(keyofT(_node->_data)) % _ht->Get_tables().size();
		}

		// HT_iterator(const HT_iterator)

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

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

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

1. operator++

思路:从第一个桶开始访问数据,访问完一个数据后,接着下一个桶,直至结束

思路比较简单,但迭代器设计,逻辑比较难处理。


        // 哈希迭代器只往前单向
		HT_iter operator++()
		{
			// 三种情况:
			// 1. 下一个为空,下一个桶存在
			// 2. 下一个为空,下一个不存在
			// 3. 下一个存在
			if (_node->_downstars)
			{ // 3.
				_node = _node->_downstars;
				return *this;
			}
			else
			{   // 1 , 2
				 寻找下一个不为空的桶
				++_bucket;
				while (_bucket < _ht->Get_tables().size())
				{
					_node = (_ht->Get_tables())[_bucket];
					if (_node)  // 找到后中断寻找
						break;
					++_bucket;
				}
				return *this;
			}
		}

2. operator[]

在实现operator[]时,我们需要先将insert,find函数进行优化:

让insert返回值从bool ——> 返回pair<iterator,  bool>;

find()从返回数据指针——>  返回迭代器;

// Hashtable——哈希类中
        T& operator[](const T& pa)
		{
			pair<HT_iterator, bool> it = insert(pa);
			return *(it.first);
		}

// 封装层 unordered_map
        V& operator[](const K& pa)
		{
			pair<HT_iterator, bool> ret = insert(make_pair(pa, V()));
			return ret.first->second;
		}
        
// unordered_set
        K& operator[](const K& pa)
		{
			return table[pa];
		}

3. 仿函数优化

在哈希实现过程中,我们实现了该仿函数,目的就是为了解决当 K不是int的时候(比如string类)无法转化为size_t类型,来寻找哈希地址。 

那么问题来了!!  我如果想将Day类型(处理年,月,日的类)作为K呢?? 请问这怎么将这个日期类转化为size_t???

为了方便分析,我将这个将其他数据转化为size_t的类叫做转化类 

我们会发现,封装在底层的中转化类(int与string类)在目前看来,应该将其放在unordered_map与unordered_set的封装层;同时在两个封装层添加一个新的参数,作为转化类的行参

这样做的好处是,在我们不知道K类型时,用户可以导入其自己设定的转化类,来实现各种类(各种类型)转化为size_t类型。

3. 解决unordered_set中Key可以修改的Bug

在前面的文章中(用红黑树封装map&set【C++】-CSDN博客),我们已经修复过一次了,这里就只展现修改位置了。

代码区

Hash_map_set.h

namespace hash_map_set
{

	template <class type>
	struct Hashstr
	{
		int operator()(const type& sd)
		{
			return sd;
		}
	};

	template<>
	struct Hashstr<string>
	{
		size_t operator()(const string& str)
		{
			size_t sum = 0;
			for (auto e : str)
			{
				sum += e;
				sum *= 31;   // sum为啥都要乘31这个数??1.减少数据聚集在一个桶中。2.31这个数是大量实验的最佳数
			}
			return sum;
		}
	};

	template <class K, class Hash = Hashstr<K>>
	class unordered_set
	{
	public:
		struct SetkeyofT   // 从T类型中提取Key
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};

		// 关于为什么要这样操作?原因:底层(HT_iterator)实例化后,再取得模板,而不是自己设定
		typedef typename hash_barrel::HashTable<K, K, SetkeyofT, Hash>::const_HT_iterator  HT_iterator;
		typedef typename hash_barrel::HashTable<K, K, SetkeyofT, Hash>::const_HT_iterator const_HT_iterator;

		const_HT_iterator begin() const
		{
			return table.begin();
		}

		const_HT_iterator end() const 
		{
			return table.end();
		}

		HT_iterator begin()
		{
			return table.begin();
		}

		HT_iterator end()
		{
			return table.end();
		}

		pair<HT_iterator, bool> insert(const K& data)
		{
			return table.insert(data);
		}

		K& operator[](const K& pa)
		{
			return table[pa];
		}

		HT_iterator find(const K& pa)
		{
			return table.find(pa);
		}

		bool erase(const K& pa)
		{
			return table.erase(pa);
		}

	private:
		hash_barrel::HashTable<K, K, SetkeyofT, Hash>  table;
	};

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

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

		typedef typename hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash>::HT_iterator  HT_iterator;
		typedef typename hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash>::const_HT_iterator  const_HT_iterator;

		const_HT_iterator begin() const 
		{
			return table.begin();
		}

		const_HT_iterator end() const 
		{
			return table.end();
		}

		HT_iterator begin()
		{
			return table.begin();
		}

		HT_iterator end()
		{
			return table.end();
		}

		pair<HT_iterator, bool> insert(const pair<const K, V>& kv)
		{
			return table.insert(kv);
		}

		V& operator[](const K& pa)
		{
			pair<HT_iterator, bool> ret = insert(make_pair(pa, V()));
			return ret.first->second;
		}

		HT_iterator find(const K& pa)
		{
			return table.find(pa);
		}

		bool erase(const K& pa)
		{
			return table.erase(pa);
		}

	private:
		hash_barrel::HashTable<K, pair<const K, V>, MapkeyofT, Hash>  table;
	};
}

HashTable.h

namespace hash_barrel
{
	template <class T>
	struct Node_Data
	{
		typedef Node_Data<T> Node_data;
		T _data;
		Node_data* _downstars = nullptr;

		Node_Data(const T& pa = T())
			:_data(pa)
		{}
	};

	// 由于迭代器需要使用HashTable作为成员变量,因此需要前置声明
	template <class K, class T, class KeyofT, class Hashstr>
	class HashTable;

	template <class K, class T, class KeyofT, class Ref, class Ptr, class Hash>
	struct HT_iterator
	{
		typedef Node_Data<T> Node_Data;
		typedef HashTable<K, T, KeyofT, Hash> HashTable;
		typedef HT_iterator<K, T, KeyofT, Ref, Ptr, Hash> HT_iter; // 正常迭代器
		typedef HT_iterator<K, T, KeyofT, T&, T*, Hash> iterator; 

		Node_Data* _node;  // 当前位置
		const HashTable* _ht;    // 当前哈希表,添加const原因:1.迭代器不会修改哈希表 2.const迭代器传入的是const的哈希表,所以需要适配
		size_t _bucket = -1;     // 当前桶

		HT_iterator(Node_Data* node, const HashTable* ht)
			:_node(node)
			, _ht(ht)
		{
			Hash hashstr;
			KeyofT keyofT;
			if (node) // node为空的时候,我们会对nullptr进行访问,所以规避一下
				_bucket = hashstr(keyofT(_node->_data)) % _ht->Get_tables().size();
		}

		// 1. 当对象是普通迭代器时,这是拷贝构造
		// 2. 当对象是const迭代器时,是利用普通迭代器构造const迭代器的构造函数
		HT_iterator(const iterator& it)
			:_node(it._node)
			,_ht(it._ht)
			,_bucket(it._bucket)
		{}

		// HT_iterator(const HT_iterator)

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

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

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

		// 哈希迭代器只往前单向
		HT_iter operator++()
		{
			// 三种情况:
			// 1. 下一个为空,下一个桶存在
			// 2. 下一个为空,下一个不存在
			// 3. 下一个存在
			if (_node->_downstars)
			{ // 3.
				_node = _node->_downstars;
				return *this;
			}
			else
			{   // 1 , 2
				 寻找下一个不为空的桶
				++_bucket;
				while (_bucket < _ht->Get_tables().size())
				{
					_node = (_ht->Get_tables())[_bucket];
					if (_node)
						break;
					++_bucket;
				}
				return *this;
			}
		}
	};



	template <class K, class T, class KeyofT ,class Hash>
	class HashTable
	{
	public:
		typedef Node_Data<T> Node_Data;
		typedef HT_iterator<K, T, KeyofT, const T&, const T*, Hash>  const_HT_iterator;  // const迭代器
		typedef HT_iterator<K, T, KeyofT, T&, T*, Hash> HT_iterator;    // 普通迭代器
		
		~HashTable()
		{
			for (auto bucket : _tables)
			{
				Node_Data* cur = bucket;
				while (cur)
				{
					Node_Data* next = cur->_downstars;
					delete (cur);
					cur = next;
				}
			}
		}

		const_HT_iterator begin() const 
		{
			size_t i = 0;
			Node_Data* cur = nullptr;
			for (; i < _tables.size(); i++)
			{
				cur = _tables[i];

				if (cur)
					break;
			}

			return const_HT_iterator(cur, this);  // this就是哈希表对象
		}

		const_HT_iterator end() const 
		{
			return const_HT_iterator(nullptr, this);
		}

		HT_iterator begin()
		{
			size_t i = 0;
			Node_Data* cur = nullptr;
			for (; i < _tables.size(); i++)
			{
				cur = _tables[i];

				if (cur)
					break;
			}
			return HT_iterator(cur, this);  // this就是哈希表对象
		}

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

		// 在实现 operator[]时,底层用到insert(),没找到,插入新值返回迭代器;找到直接返回迭代器
		pair<HT_iterator,bool> insert(const T& pa)
		{
			KeyofT keyofT;
			HT_iterator ret = _find(keyofT(pa));
			if (ret != end())
				return make_pair(ret, true);  

			// 考虑扩容:负载因子为1,2,3都可以
			Hash hashstr;
			if (_tables.size() == 0 || _n * 10 / _tables.size() >= 10)
			{
				size_t  new_size = _tables.size() == 0 ? 10 : _tables.size() * 2;
			     // 开始扩容	
				vector<Node_Data*> new_tables;
				new_tables.resize(new_size);
				for (auto& data : _tables)
				{
					// 处理桶内的数据,重新插入新节点
					Node_Data* cur = data;
					while (cur)
					{
						Node_Data* room = cur->_downstars;
						size_t new_hashi = hashstr(keyofT(cur->_data)) % new_tables.size();
						// 处理结点关系
						Node_Data* tmp = new_tables[new_hashi];
						new_tables[new_hashi] = cur;
						cur->_downstars = tmp;
						cur = room;
					}
				}
				_tables.swap(new_tables);
			}

			size_t hashi = hashstr(keyofT(pa)) % _tables.size();
			Node_Data* new_node = new Node_Data(pa);
			new_node->_downstars = _tables[hashi];
			_tables[hashi] = new_node;
			_n++;
			return make_pair(HT_iterator(new_node, this), true);
		}

		HT_iterator find(const K& order)
		{
			return _find(order);
		}

		bool erase(const K& order)
		{
			Hash hashstr;
			HT_iterator cur = find(order);
			if (!(cur._node))
			{
				cout << "擦除失败: 不存在" << endl;
				return false;
			}
			size_t index = hashstr(order) % _tables.size();
			Node_Data* tmp = _tables[index];

			while (tmp)
			{
				if (cur._node == tmp)
				{
					break;
				}
				else if (cur._node == tmp->_downstars)
				{
					break;
				}
				tmp = tmp->_downstars;
			}
			// 开始处理节点
			if (tmp == _tables[index])  // 如果擦除的是头,那要置空的包括指针数组
			{
				_tables[index] = cur._node->_downstars;
			}
			else
			{
				tmp->_downstars = cur._node->_downstars;
			}
			
			delete (cur._node);
			cout << "擦除成功" << endl;
			return true;
		}

		T& operator[](const T& pa)
		{
			pair<HT_iterator, bool> it = insert(pa);
			return *(it.first);
		}

		size_t Get_n()
		{return _n; }

		 vector<Node_Data*>& Get_tables()
		{
			return _tables;
		}

		 // 需要为const 对象进行重载
		 const vector<Node_Data*>& Get_tables() const 
		 {
			 return _tables;
		 }

	private:
		// Node_Data* _find(const K& order)
		HT_iterator _find(const K& order)
		{
			if (!_tables.size())
				return end();

			Hash hashstr;
			KeyofT keyofT;
			size_t hashi = hashstr(order) % _tables.size();
			auto cur = _tables[hashi];
			while (cur)
			{
				if (keyofT(cur->_data) == order)
					return HT_iterator(cur, this);
				cur = cur->_downstars;
			}
			return end();
		}

		vector<Node_Data*> _tables;  // 存放各个哈希地址的第一个结点地址的指针数组
		size_t _n = 0;                  // 哈希桶中,数据个数
	};
}

下节预告:哈希应用!!

结语

   本小节就到这里了,感谢小伙伴的浏览,如果有什么建议,欢迎在评论区评论,如果给小伙伴带来一些收获请留下你的小赞,你的点赞和关注将会成为博主创作的动力

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

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

相关文章

Notion汉化

Notion真无语&#xff0c;汉化版都没有。真的无力吐槽。 2023.11.7汉化经历 教程链接&#xff1a;github Reamd7/notion-zh_CN at 2.4.20-handmade (github.com) 网页版&#xff1a; 油猴下载插件。 Notion中文汉化 浏览器插件下载 windows&#xff1a; github realse 这…

Ubuntu22.04 部署Mqtt服务器

1、打开Download EMQX 下载mqtt服务器版本 2、Download the EMQX repository curl -s https://assets.emqx.com/scripts/install-emqx-deb.sh | sudo bash 3.Install EMQX sudo apt-get install emqx 4.Run EMQX sudo systemctl start emqx

Js:获取最近6个月的月份(包含本月、不包含本月)

一、需求 获取最近6个月的月份&#xff08;不包含本月&#xff09;&#xff0c;比如现在是11月份&#xff0c;则需要获取到的月份是&#xff1a;10、9、8、7、6、5将月份从小到大排列 二、解决 1、获取最近的6个月份&#xff08;不包含本月&#xff09; var monthALL[]; …

在3+1的方向上展开结构加法4a3+4a14

4a3 4a14 - - 1 - - - - - - - - - - - - - 1 1 1 - 1 1 - 1 - - 1 - - - 要求得到的图片只能有4个点&#xff0c;并且需要最大限度的保留4a3和4a14两张图片的内在结构特征。 4个点的结构总可以认为是3个点的结构1合成的 - - 1 - - …

Java内存模型(JMM) ----多线程/并发编程

在介绍 Java 内存模型之前&#xff0c;先来看一下到底什么是计算机内存模型。 计算机结构简介 冯诺依曼&#xff0c;提出计算机由五大组成部分&#xff0c;输入设备&#xff0c;输出设备存储器&#xff0c;控制器&#xff0c;运算器 CPU 中央处理器&#xff0c;是计算机的…

完整时间线!李开复Yi大模型套壳争议;第二届AI故事大赛;AI算命GPTs;LLM应用全栈开发笔记;GPT-5提上日程 | ShowMeAI日报

&#x1f440;日报&周刊合集 | &#x1f3a1;生产力工具与行业应用大全 | &#x1f9e1; 点赞关注评论拜托啦&#xff01; &#x1f440; 李开复「零一万物」大模型陷套壳争议&#xff0c;事件时间线完整梳理 https://huggingface.co/01-ai/Yi-34B/discussions/11#65531458…

2022CCPC绵阳 ACGHM

Dashboard - 2022 China Collegiate Programming Contest (CCPC) Mianyang Onsite - Codeforces C.Catch You Catch Me 题意 思路 首先注意到贡献可以按深度统计&#xff0c;对于每个深度dep&#xff0c;贡献是在dep深度中属于的子树种类数&#xff0c;如果在该深度中子树存在…

结合scss实现黑白主题切换

是看了袁老师的视频后&#xff0c;自己做了一下练习。原视频地址&#xff1a; b站地址https://www.bilibili.com/video/BV15z4y1N7jB/?spm_id_from333.1007.top_right_bar_window_history.content.click&vd_sourcec6cf63302f28d94ebc02cbedcecc57ea首先创建一个全局的scs…

VSCode配置ESP-IDF

参考其他 文章即可 如果编译时遇到问题&#xff0c;就去找环境变量&#xff0c;多半是环境变量没有配置好。根据自己安装的idf的目录重新配置 环境变量. 如果电脑上有python环境&#xff0c;但是编译时出现找不到python解释器&#xff0c;需要执行下面命令&#xff0c;另外重…

MATLAB 模糊设计器 构建 模糊系统

系列文章目录 文章目录 系列文章目录前言一、创建 FIS 结构二、定义输入变量三、定义输出变量四、定义成员函数五、定义规则库六、设计分析七、存储和修改设计八、导出 FIS总结 前言 本例演示如何使用 Fuzzy Logic Designer 应用程序交互式创建 1 型 Mamdani 模糊推理系统&…

【自动化测试】Appium环境搭建与配置-详细步骤,一篇带你打通...

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Node.js环境搭…

easyExcle单元格合并

自定义单元格合并策略&#xff1a; /*** 自定义单元格合并策略** create: 2023-11-15 13:41**/ Data NoArgsConstructor AllArgsConstructor Slf4j public class EasyExcelCustomMergeStrategy implements RowWriteHandler {/*** 总数*/private Integer totalNum;//合并行计数…

【ARM Trace32(劳特巴赫) 使用介绍 5 -- Trace32 scan dump 详细介绍】

文章目录 1.1 JTAG 测试逻辑架构1.2 D型扫描触发器1.2.1 全扫描介绍1.3 IR 寄存器1.4 TDR(Test data registers)1.4.1 TDR 的实现1.4.1.1 Bypass Register1.4.1.2 Boundary-scan register1.5 Scan Dump1.5.1 soft fusion1.1 JTAG 测试逻辑架构 图 1-1 片上测试逻辑概念图 如前面…

我这些年对于自动化测试的理解

&#x1f4e2;专注于分享软件测试干货内容&#xff0c;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01;&#x1f4e2;交流讨论&#xff1a;欢迎加入我们一起学习&#xff01;&#x1f4e2;资源分享&#xff1a;耗时200小时精选的「软件测试」资…

DTW(Dynamic Time Warping)算法学习应用实践与效率对比分析

DTW&#xff08;Dynamic Time Warping&#xff09;算法是一种用于度量两个时间序列之间的相似性的方法。它的构建原理如下&#xff1a; 基本思想&#xff1a;DTW算法通过计算两个时间序列之间的最小距离&#xff0c;来度量它们的相似性。与欧氏距离等传统距离度量方法不同&…

MariaDB安装配置、使用、授权、增删改查以及数据库备份与恢复

目录 1 MariaDB安装 1.1 MariaDB源配置 1.2 清空缓存 1.3 安装MariaDB 2 MariaDB的基本配置 2.1 启动MariaDB 2.2 MariaDB进程查看 2.3 MariaDB数据库初始化 2.3.1 数据库初始化 2.3.2 初始化测试登录 3 MariaDB的使用 3.1 查看数据库 3.2 修改密码 3.3 创建数据库test 3…

conda环境下Tesseract:Failed loading language ‘eng‘问题解决

1 问题描述 使用Tesseract进行ocr文字识别&#xff0c;运行识别代码&#xff0c;报错如下&#xff1a; C:\Users\lishu\anaconda3\envs\pt2\python.exe D:/code/ptcontainer/opencv/car_reg.py Traceback (most recent call last): File "D:\code\ptcontainer\opencv\…

C#WPF文本转语音实例

本文介绍C#WPF文本转语音实例 实现方法:使用类库(SpeechSynthesizer )实现的。 一、首先是安装程序包。 二、创建项目 需要添加引用using System.Speech.Synthesis; UI界面 <Windowx:Class="TextToSpeechDemo.MainWindow"xmlns="http://schemas.micr…

著名的《NP问题》是个啥概念?

一、说明 关于复杂问题&#xff0c;始终是计算机科学挡在路前的一块巨石。所谓一个问题有解&#xff0c;但需要秒完成&#xff0c;这相当于说&#xff0c;此类问题无解。还有一类问题是说不清楚到底有没有一个具体解法&#xff0c;该解法能在多项式时间复杂函数上完成&#xff…

mybatis-plus3.5.3.1 支持不同数据源sql适配

mybatis-plus3.5.3.1 支持不同数据源sql适配 背景 最近公司要求支持国产数据库达梦&#xff0c;人大金仓&#xff0c;高斯等数据库&#xff0c;这些数据库与mysql的语法有一些差异&#xff0c;需要做一些兼容操作。 解决问题 1.不同数据库分页不同 2.支持通过参数控制执行…