C++:利用哈希表对unordered系列容器模拟实现

news2024/11/19 8:39:26

文章目录

  • unordered容器使用
    • [在长度 2N 的数组中找出重复 N 次的元素](https://leetcode.cn/problems/n-repeated-element-in-size-2n-array/description/)
  • 底层结构
    • 初步改造哈希表
    • 基本逻辑的实现
  • 最终实现

本篇主要总结unordered系列容器和其底层结构

unordered容器使用

从使用的角度来讲,不管是unordered_map还是unordered_set,本质上就是把内容不再有序,而是采用无序的方式进行存储,对于其实现细节在后续会进行封装

在长度 2N 的数组中找出重复 N 次的元素

在这里插入图片描述
对于之前的方法,大多使用一种类似于计数排序的方法来解决问题

class Solution 
{
public:
    int repeatedNTimes(vector<int>& nums) 
    {
       int hash[10001] = {0};
        for(auto e : nums)
        {
            hash[e]++;
        }
        for(auto e : nums)
        {
            if(hash[e] != 1)
                return e;
        }
        return -1;
    }
};

而利用unordered_set,可以更方便的解决问题

class Solution {
public:
    int repeatedNTimes(vector<int>& nums) {
        unordered_set<int> found;
        for (int num: nums) {
            if (found.count(num)) {
                return num;
            }
            found.insert(num);
        }
        // 不可能的情况
        return -1;
    }
};

底层结构

unordered系列的关联式容器,在进行查找等的效率比较高,究其原因是因为使用了哈希的结构,那么下面就利用哈希表,来对这两个容器进行封装

首先搭建出主体框架

// set
#pragma once
#include "HashTable.h"
namespace myset
{
	template<class K>
	class unordered_set
	{
		struct SetKeyOfT
		{
			K& operator()(K& key)
			{
				return key;
			}
		};
	public:
		bool insert(const K& key)
		{
			return _table.insert(key);
		}
		void print()
		{
			_table.print();
		}
	private:
		opened_hashing::HashTable<K, K, SetKeyOfT> _table;
	};
}

// map
#pragma once

#include "HashTable.h"

namespace mymap
{
	template<class K, class V>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& key)
			{
				return key.first;
			}
		};
	public:
		bool insert(const pair<K, V>& key)
		{
			return _table.insert(key);
		}
		void print()
		{
			_table.print();
		}
	private:
		opened_hashing::HashTable<K, pair<K, V>, MapKeyOfT> _table;
	};
}

// 测试函数
// 最基础的测试
void test_set1()
{
	myset::unordered_set<int> st;
	st.insert(1);
	st.insert(2);
	st.insert(60);
	st.insert(50);
	st.print();
}

void test_map1()
{
	mymap::unordered_map<int, int> dict;
	dict.insert(make_pair(1, 1));
	dict.insert(make_pair(2, 2));
	dict.insert(make_pair(3, 3));
	dict.insert(make_pair(4, 4));
}

此时运行会报错
在这里插入图片描述
报错的原因是因为,对于map来说Value传递的是键值对,键值对不可以直接进行运算,因此要引入KeyOfT的概念,这里借助这个仿函数找到Key值

在这里插入图片描述
下面实现迭代器的内容:

对于哈希表来说,迭代器内部包含的就是一个一个节点的地址:

实现++

对于哈希表来说,实现++的逻辑可以看成,看当前节点的下一个还有没有值,如果没有就说明走到最后了,要找下一个哈希桶里面的内容,如果后面还有值,那就是下一个内容

那么如何记录所在的桶,可以通过对哈希表的位置进行定位来获取现在属于哪一桶,进而去寻找下一个桶

初步改造哈希表

#pragma once
#include <iostream>
#include <vector>
using namespace std;

namespace opened_hashing
{
	template<class T>
	struct _Convert
	{
		T& operator()(T& key)
		{
			return key;
		}
	};

	template<>
	struct _Convert<string>
	{
		size_t& operator()(const string& key)
		{
			size_t sum = 0;
			for (auto e : key)
			{
				sum += e * 31;
			}
			return sum;
		}
	};

	// 定义节点信息
	template<class T>
	struct Node
	{
		Node(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
		Node* _next;
		T _data;
	};

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

	template<class K, class T, class Ref, class Ptr, class KeyOfT>
	struct _iterator
	{
		typedef _iterator<K, T, T&, T*, KeyOfT> Self;
		typedef Node<T> Node;

		_iterator(Node* node, HashTable<K, T, KeyOfT>* pht, int hashi)
			:_node(node)
			, _pht(pht)
			, _hashi(hashi)
		{}

		Self operator++()
		{
			// 如果后面还有值,++就是到这个桶的下一个元素
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				++_hashi;
				while (_hashi < _pht->_table.size())
				{
					if (_pht->_table[_hashi])
					{
						_node = _pht->_table[_hashi];
						break;
					}

					++_hashi;
				}
				if (_hashi == _pht->_table.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}

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

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

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

		Node* _node;
		int _hashi; // 现在位于哪个桶
		HashTable<K, T, KeyOfT>* _pht; // 迭代器所在的哈希表
	};

	template<class K, class T, class KeyOfT>
	class HashTable
	{
		template<class K, class T, class Ref, class Ptr, class KeyOfT>
		friend struct _iterator;
		typedef Node<T> Node;
	public:
		typedef _iterator<K, T, T&, T*, KeyOfT> iterator;
		// 构造函数
		HashTable()
			:_n(0)
		{
			_table.resize(10);
		}

		// 析构函数
		~HashTable()
		{
			//cout << endl << "*******************" << endl;
			//cout << "destructor" << endl;
			for (int i = 0; i < _table.size(); i++)
			{
				//cout << "[" << i << "]->";
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					//cout << cur->_kv.first << " ";
					delete cur;
					cur = next;
				}
				//cout << endl;
				_table[i] = nullptr;
			}
		}

		// 迭代器
		iterator begin()
		{
			for (int i = 0; i < _table.size(); i++)
			{
				if (_table[i])
				{
					return iterator(_table[i], this, i);
				}
			}
			return end();
		}

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

		// 插入元素
		bool insert(const T& data)
		{
			KeyOfT kot;
			// 如果哈希表中有这个元素,就不插入了
			if (find(data))
			{
				return false;
			}

			// 扩容问题
			if (_n == _table.size())
			{
				HashTable newtable;
				int newsize = (int)_table.size() * 2;
				newtable._table.resize(newsize, nullptr);
				for (int i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						// 把哈希桶中的元素插入到新表中
						int newhashi = kot(cur->_data) % newsize;
						// 头插
						cur->_next = newtable._table[newhashi];
						newtable._table[newhashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newtable._table);
			}

			// 先找到在哈希表中的位置
			size_t hashi = kot(data) % _table.size();

			// 把节点插进去
			Node* newnode = new Node(data);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			_n++;

			return true;
		}

		Node* find(const T& Key)
		{
			KeyOfT kot;
			// 先找到它所在的桶
			int hashi = kot(Key) % _table.size();

			// 在它所在桶里面找数据
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_data == Key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

	private:
		vector<Node*> _table;
		size_t _n;
	};
}

基本逻辑的实现

// set
#pragma once
#include "HashTable.h"
namespace myset
{
	template<class K>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		typedef typename opened_hashing::HashTable<K, K, SetKeyOfT>::iterator iterator;
	public:
		iterator begin()
		{
			return _table.begin();
		}

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

		bool insert(const K& key)
		{
			return _table.insert(key);
		}
	private:
		opened_hashing::HashTable<K, K, SetKeyOfT> _table;
	};
}

// map
#pragma once

#include "HashTable.h"

namespace mymap
{
	template<class K, class V>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<K, V>& key)
			{
				return key.first;
			}
		};
		typedef typename opened_hashing::HashTable<K, pair<K, V>, MapKeyOfT>::iterator iterator;
	public:
		iterator begin()
		{
			return _table.begin();
		}

		iterator end()
		{
			return _table.end();
		}
		bool insert(const pair<K, V>& key)
		{
			return _table.insert(key);
		}

	private:
		opened_hashing::HashTable<K, pair<K, V>, MapKeyOfT> _table;
	};
}

现在的主体框架已经搭建出来了,用下面的测试用例已经可以实现迭代器遍历了,证明我们的基本框架逻辑已经完成了

// 最基础的测试
void test_set1()
{
	myset::unordered_set<int> st;
	st.insert(1);
	st.insert(2);
	st.insert(60);
	st.insert(50);
	auto it = st.begin();
	while (it != st.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	cout << endl;
	cout << endl;
}

void test_map1()
{
	mymap::unordered_map<int, int> dict;
	dict.insert(make_pair(1, 1));
	dict.insert(make_pair(10, 1));
	dict.insert(make_pair(220, 1));
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << (*it).first << ":" << (*it).second << endl;
		++it;
	}
	cout << endl;
}

但当遇到string类的内容就不可以继续进行了

// 带有string类的内容
void test_set2()
{
	myset::unordered_set<string> st;
	st.insert("apple");
	st.insert("banana");
	st.insert("pear");
	st.insert("cake");
	auto it = st.begin();
	while (it != st.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
	cout << endl;
	cout << endl;
}

void test_map2()
{
	mymap::unordered_map<string, string> dict;
	dict.insert(make_pair("排序", "sort"));
	dict.insert(make_pair("苹果", "apple"));
	dict.insert(make_pair("香蕉", "banana"));
	auto it = dict.begin();
	while (it != dict.end())
	{
		cout << (*it).first << ":" << (*it).second << endl;
		++it;
	}
	cout << endl;
}

在这里插入图片描述
还是前面出现过的原因,因为这里的kot取出来的是第一个元素,但是第一个元素是string,string是不能参与运算的,因此这里要再套一层仿函数来解决string类的问题

最终实现

改造后的哈希表

#pragma once
#include <iostream>
#include <vector>
using namespace std;

template <class T>
struct _HashT
{
	const T& operator()(const T& Key)
	{
		return Key;
	}
};

template<>
struct _HashT<string>
{
	size_t operator()(const string& Key)
	{
		size_t sum = 0;
		for (auto e : Key)
		{
			sum += e * 31;
		}
		return sum;
	}
};

namespace opened_hashing
{
	// 定义节点信息
	template<class T>
	struct Node
	{
		Node(const T& data)
			:_next(nullptr)
			, _data(data)
		{}
		Node* _next;
		T _data;
	};

	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 _iterator
	{
		typedef _iterator<K, T, Ref, Ptr, KeyOfT, Hash> Self;
		typedef Node<T> Node;

		_iterator(Node* node, HashTable<K, T, KeyOfT, Hash>* pht, int hashi)
			:_node(node)
			, _pht(pht)
			, _hashi(hashi)
		{}

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

		Self operator++()
		{
			// 如果后面还有值,++就是到这个桶的下一个元素
			if (_node->_next)
			{
				_node = _node->_next;
			}
			else
			{
				++_hashi;
				while (_hashi < _pht->_table.size())
				{
					if (_pht->_table[_hashi])
					{
						_node = _pht->_table[_hashi];
						break;
					}

					++_hashi;
				}
				if (_hashi == _pht->_table.size())
				{
					_node = nullptr;
				}
			}
			return *this;
		}

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

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

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

		Node* _node;
		int _hashi; // 现在位于哪个桶
		const HashTable<K, T, KeyOfT, Hash>* _pht; // 迭代器所在的哈希表
	};

	template<class K, class T, class KeyOfT, class Hash>
	class HashTable
	{
		template<class K, class T, class Ref, class Ptr, class KeyOfT, class Hash>
		friend struct _iterator;
		typedef Node<T> Node;
	public:
		typedef _iterator<K, T, T&, T*, KeyOfT, Hash> iterator;
		typedef _iterator<K, T, const T&, const T*, KeyOfT, Hash> const_iterator;
		// 构造函数
		HashTable()
			:_n(0)
		{
			_table.resize(10);
		}

		// 析构函数
		~HashTable()
		{
			//cout << endl << "*******************" << endl;
			//cout << "destructor" << endl;
			for (int i = 0; i < _table.size(); i++)
			{
				//cout << "[" << i << "]->";
				Node* cur = _table[i];
				while (cur)
				{
					Node* next = cur->_next;
					//cout << cur->_kv.first << " ";
					delete cur;
					cur = next;
				}
				//cout << endl;
				_table[i] = nullptr;
			}
		}

		// 迭代器
		iterator begin()
		{
			for (int i = 0; i < _table.size(); i++)
			{
				if (_table[i])
				{
					return iterator(_table[i], this, i);
				}
			}
			return end();
		}

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

		const_iterator begin() const
		{
			for (int i = 0; i < _table.size(); i++)
			{
				if (_table[i])
				{
					return const_iterator(_table[i], this, i);
				}
			}
			return end();
		}

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

		// 插入元素
		bool insert(const T& data)
		{
			KeyOfT kot;
			Hash ht;
			// 如果哈希表中有这个元素,就不插入了
			if (find(data))
			{
				return false;
			}

			// 扩容问题
			if (_n == _table.size())
			{
				HashTable newtable;
				int newsize = (int)_table.size() * 2;
				newtable._table.resize(newsize, nullptr);
				for (int i = 0; i < _table.size(); i++)
				{
					Node* cur = _table[i];
					while (cur)
					{
						Node* next = cur->_next;
						// 把哈希桶中的元素插入到新表中
						int newhashi = ht(kot(cur->_data)) % newsize;
						// 头插
						cur->_next = newtable._table[newhashi];
						newtable._table[newhashi] = cur;
						cur = next;
					}
					_table[i] = nullptr;
				}
				_table.swap(newtable._table);
			}

			// 先找到在哈希表中的位置
			size_t hashi = ht(kot(data)) % _table.size();

			// 把节点插进去
			Node* newnode = new Node(data);
			newnode->_next = _table[hashi];
			_table[hashi] = newnode;
			_n++;

			return true;
		}

		Node* find(const T& Key)
		{
			KeyOfT kot;
			Hash ht;
			// 先找到它所在的桶
			int hashi = ht(kot(Key)) % _table.size();

			// 在它所在桶里面找数据
			Node* cur = _table[hashi];
			while (cur)
			{
				if (cur->_data == Key)
				{
					return cur;
				}
				cur = cur->_next;
			}
			return nullptr;
		}

	private:
		vector<Node*> _table;
		size_t _n;
	};
}

封装后的哈希结构

// set
#pragma once
#include "HashTable.h"
namespace myset
{
	template<class K, class Hash = _HashT<K>>
	class unordered_set
	{
		struct SetKeyOfT
		{
			const K& operator()(const K& key)
			{
				return key;
			}
		};
		typedef typename opened_hashing::HashTable<K, K, SetKeyOfT, Hash>::const_iterator iterator;
		typedef typename opened_hashing::HashTable<K, K, SetKeyOfT, Hash>::const_iterator const_iterator;
	public:
		const_iterator begin() const
		{
			return _table.begin();
		}

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

		bool insert(const K& key)
		{
			return _table.insert(key);
		}
	private:
		opened_hashing::HashTable<K, K, SetKeyOfT, Hash> _table;
	};
}

// map
#pragma once

#include "HashTable.h"

namespace mymap
{
	template<class K, class V, class Hash = _HashT<K>>
	class unordered_map
	{
		struct MapKeyOfT
		{
			const K& operator()(const pair<const K, V>& key)
			{
				return key.first;
			}
		};
		typedef typename opened_hashing::HashTable<K, pair<const K, V>, MapKeyOfT, Hash >::iterator iterator;
		typedef typename opened_hashing::HashTable<K, pair<const K, V>, MapKeyOfT, Hash >::const_iterator const_iterator;
	public:
		iterator begin()
		{
			return _table.begin();
		}

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

		bool insert(const pair<const K, V>& key)
		{
			return _table.insert(key);
		}

	private:
		opened_hashing::HashTable<K, pair<const K, V>, MapKeyOfT, Hash> _table;
	};
}

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

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

相关文章

pytorch下载离线包的网址

下载地址&#xff1a;https://download.pytorch.org/whl/torch_stable.html 安装GPU版本需要安装&#xff1a;torch、torchvision、 注意版本需要对应上 格式&#xff1a;适用cuda版本&#xff0c;torch版本 或者 orchvision版本&#xff0c;cp38就是适用python 3.8版本 下…

Altium Designer学习笔记2

原理图的绘制 需要掌握的是系统自带原理图库元件的添加。

一次性能测试,为啥把我逼疯了?

最近&#xff0c;公司领导让我做下性能方面的竞品对比&#xff0c;作为一个性能测试小白的我&#xff0c;突然接到这样的任务&#xff0c;下意识发出大大的疑问。 整理好心情&#xff0c;内心想着“领导一定是为了考验我&#xff0c;才给我这个任务的”&#xff0c;开始了这一…

基于WEB的停车场管理系统的设计和实现【附源码】

基于WEB的停车场管理系统的设计和实现 摘 要 随着现代社会的快速发展&#xff0c;人民生活水平快速提高&#xff0c;汽车的数量飞速增加&#xff0c;与此同时停车问题也越来越受到人们的关注&#xff0c;为了实现对停车场进行有效的管理&#xff0c;结合一些停车场的模式和现状…

轻松驾驭Linux命令:账户查看、目录文件操作详解

&#x1f3a5; 屿小夏 &#xff1a; 个人主页 &#x1f525;个人专栏 &#xff1a; Linux系统操作 &#x1f304; 莫道桑榆晚&#xff0c;为霞尚满天&#xff01; 文章目录 &#x1f4d1;引言&#x1f324;️查看账户☁️whoami☁️who &#x1f324;️ls和目录文件的创建删除☁…

家庭教育专家:如何创建家庭自主学习环境?

经常听到一些父母这样抱怨&#xff1a;“明明和孩子说好就看20分钟电视&#xff0c;结果到了时间&#xff0c;他死活都不肯关。”“作业还没完成的情况下&#xff0c;孩子还一直抱着手机或者电子产品玩游戏。到了约定时间也不撒手&#xff0c;一直跟你讨价还价。” 其实&#…

0时区格林威治时间转换手机当地时间-Android

假设传入的是2023-11-01T12:59:10.420987这样的格式 要将格式为2023-11-01T12:59:10.420987的UTC时间字符串转换为Android设备本地时间&#xff0c;您可以使用java.time包中的类&#xff08;在API 26及以上版本中可用&#xff09;。如果您的应用需要支持较低版本的Android&…

用二维码进行人员管理,人员信息一目了然

对于人员实名管理、来访登记、安全教育等需求&#xff0c;可以在草料二维码上搭建人员信息管理系统。除了扫码查看个人信息、身份证件、资格证书、劳务合同等人员档案&#xff0c;还可以组合表单、状态等功能组件&#xff0c;在二维码上展示证件状态&#xff0c;更新人员的奖惩…

[java进阶]——泛型类、泛型方法、泛型接口、泛型的通配符

&#x1f308;键盘敲烂&#xff0c;年薪30万&#x1f308; 目录 泛型的基础知识&#xff1a; ♥A 泛型的好处&#xff1a; ♠A 泛型擦除&#xff1a; ♣A 泛型的小细节&#xff1a; 泛型的使用&#xff1a; ①泛型类&#xff1a; ②⭐泛型接口&#xff1a; ③泛型方法&…

2023年中国绕包线行业产量及发展前景分析:市场规模将持续上升[图]

绕包线用绝缘纸、玻璃丝、天然丝和合成丝等紧密绕包在铜线或者漆包线上&#xff0c;形成绝缘层。绕包线包括纸包线、玻璃丝包线、薄膜绕包线、潜油电机用特种绕包线、风力发电机用绕组线、丝包单线、丝包束线、换位导线。 绕包线种类 资料来源&#xff1a;共研产业咨询&#x…

串口工作流程硬核解析,没有比这更简单的了!

串口通信,就是我们常说的串口通讯,是一种短距离、点对点的数据传输方式。它基于串行通信协议,通过串口线连接设备进行数据交互。串口在很多硬件系统中广泛使用,是工控机、单片机、外设设备之间信息交换的重要接口。 那串口是怎么工作的呢?我们举个形象的例子。假设A和B是两台…

比赛倒计时4天,快来做做2023年小学生古诗文大会复赛在线模拟题

2023年第八届上海小学生古诗文大会复选&#xff08;复赛&#xff09;定于11月25日上午举办&#xff08;即本周六&#xff09;&#xff0c;具体安排和操作手册、注意事项请看我之前发布的文章&#xff1a;2023年11月25日小学生古诗文大会复选&#xff08;复赛&#xff09;答题操…

66从零开始学Java之集合中的Collection体系

作者&#xff1a;孙玉昌&#xff0c;昵称【一一哥】&#xff0c;另外【壹壹哥】也是我哦 千锋教育高级教研员、CSDN博客专家、万粉博主、阿里云专家博主、掘金优质作者 前言 截止到今天&#xff0c;我们《从零开始学Java系列》的文章已经要到一个新的阶段了。在此之前&#xf…

十六、RabbitMQ快速入门

目录 一、在centos上下载MQ镜像 二、安装运行容器 三、登录进入MQ 1、添加一个新的用户 2、新建虚拟机 3、 为用户分配权限 四、RabbitMQ的基本概念 RabbitMQ中的几个概念: 五、常见消息模型 六、简单的消息生产与消费 1、消费者类 2、生产者类 3、基本消息队列的消…

2023亚太杯数学建模思路 - 案例:感知机原理剖析及实现

文章目录 1 感知机的直观理解2 感知机的数学角度3 代码实现 4 建模资料 # 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 感知机的直观理解 感知机应该属于机器学习算法中最简单的一种算法&#xff0c;其…

给卖家的 5 个 TikTok 联盟营销创意

了解如何开始 TikTok 联盟营销不足以让您为 TikTok 商店实施最佳联盟计划。促进您的 TikTok 联盟营销工作。如下&#xff1a; 建立相关受众 为了确保您在 TikTok 联盟营销上的投资没有白费&#xff0c;清楚地了解您的目标受众至关重要。只有了解了这个平台的目标受众&#xf…

官宣!Sam Altman加入微软,OpenAI临时CEO曝光,回顾董事会‘’政变‘’始末

11月20日下午&#xff0c;微软首席执行官Satya Nadella在社交平台宣布&#xff0c;“微软仍然致力于与 OpenAI的合作伙伴关系。同时欢迎Sam Altman 和 Greg Brockman 及其团队加入微软&#xff0c;领导一个全新的AI研究团队”。 Sam第一时间对这个消息进行了确认。 此外&…

2023年中国聚氨酯树脂涂料需求量、市场规模及行业趋势分析[图]

聚氨酯是一种新兴的有机高分子材料&#xff0c;被誉为“第五大塑料”&#xff0c;因其卓越的性能而被广泛应用于国民经济众多领域。产品应用领域涉及轻工、化工、电子、纺织、医疗、建筑、建材、汽车、国防、航天、航空等。2022年中国聚氨酯产量已达1600万吨。 2012-2022年中国…

Flutter笔记:使用相机

Flutter笔记 使用相机 作者&#xff1a;李俊才 &#xff08;jcLee95&#xff09;&#xff1a;https://blog.csdn.net/qq_28550263 邮箱 &#xff1a;291148484163.com 本文地址&#xff1a;https://blog.csdn.net/qq_28550263/article/details/134493373 【简介】本文介绍在 Fl…

系列一、介绍

一、概述 官网&#xff1a; ThreadLocal用于提供线程内的局部变量&#xff0c;不同线程之间不会互相干扰&#xff0c;这种变量在线程的生命周期内起作用&#xff0c;减少同一个线程内多个函数或者组件之间一些公共变量传递的复杂度。 大白话&#xff1a; 线程并发&#xff1a;T…