unordered_map 与 unordered_set 的模拟实现

news2024/9/22 11:40:49

unordered_map 与 unordred_set 的模拟实现与 map 与 set 的模拟实现差不多。map 与 set 的模拟实现中,底层的数据结构是红黑树。unordered_map 与 unordered_set 的底层数据结构是哈希表。因此,在模拟实现 unordered_map 与 unordred_set 之前你必须确保你已经熟练的掌握哈希表。如果你还对哈希表感到陌生,你可以参考我之前写过的这两篇文章:
哈希表开散列的实现
我们这里实现的 unordered_mapunordered_set 是以开散列实现的哈希表作为底层数据结构的哦!

修改存储的数据

在 unordered_map 与 unordred_set 的使用部分,我们知道:他俩存储的数据类型是不一样的,为了让哈希表能同时适配出 unordered_mapunordered_set 我们就需要对 HashNode 进行一定程度的修改。原理比较简单哈,因为他们俩的数据类型不一样,我们只需要将他俩存储的数据类型模板化即可。通过传入不同的数据类型,实例化出 HashNode 存储不同元素的哈希表。

  • 当这个模板参数传入 pair 那么是 unordered_map
  • 当这个模板参传入的不是一个 pair 那么就是一个 unordered_set
template<class T>
struct HashNode
{
   T _data;
   HashNode<T>* _next;

   HashNode(const T& data)
   	:_data(data)
   	, _next(nullptr)
   {}
};

我们将原本两个模板参数改为了一个,传入什么类型,那么 HashNode 就存储这个类型。

key 的获取

对于 unnordered_set 来说,他的 key 值就是模板参数 T。但是对于 unordered_map 来说,他的 key 值却是:T.first。因为 unordered_map 存储的是一个 pair 嘛。
怎么解决呢?处理思路和模拟实现 mapset 是一样的。通过仿函数来实现。
因此在 HashTable 中又要增加一个模板参数,不妨叫做:KeyOfT。在这个仿函数中,我们会根据传入的数据类型获取他的 key 值。

  • 对于 unordered_map 来说仿函数会返回传入参数 first。因为它存储的是 pair 嘛。key 值就是他的 first。
  • 对于 unordered_set 来说,仿函数直接将传入参数本身返回。因为他存储的数据就是 key 值本身嘛!
//这是 unordered_set.h 的代码
#pragma once
#include"Hash.h"

template<class K>
class unordered_set
{
public:
    struct SetKeyOfT
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
private:
    HashTable<K, K, SetKeyOfT> _ht;
};

在上面的代码中,我们定义了一个结构体 SetOfT 在这个结构体中,我们重载了圆括号运算符。然后将这个类型传入 HashTable 用来获取 unorered_set 的 key 值。你可能会问:为什么还要传入一个 K 值来实例化哈希表呢?不着急等会儿马上为你解答。

//这是 unordered_map.h 中的代码
#pragma once
#include "Hash.h"

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

同样的,我们定义了一个结构体:MapOfT 在这个结构体结构体中,重载了圆括号运算符,然后将这个结构体类型传入 HashTable 中用来获取 unordered_map 的 key 值。


我们修改了 HashNode 的存储数据, HashTable 的模板参数。那么 HashTable 中的代码也需要做相应的修改。
我们先来看 HashTable 的模板参数:
在这里插入图片描述

  • K:有了这个模板参数,HashTable 的函数参数的类型能更加好写:
    在这里插入图片描述
    在这里插入图片描述
    上面的两个成员函数中,我们都需要用到 key 的类型来定义函数的形参。如果 HashTable 的模板参数不加上 K,我们就很难获取 unordered_mapunoredred_set 的 key 值的类型。这便是为啥要在 unordered_map.hunordered_set 中实例化哈希表的时候传入 key 值的类型。是不是非常精妙。

  • T:存储的数据类型,unordered_map 就是 pair<K, V>unordered_set 就是:K。

  • KeyOfT:用该类型实例化出来对象,调用 operator(),仿函数嘛!获取 key 值。

  • HashFunc:这个模板参数可以处理字符串作为 key 值的情况。详细的逻辑在之前的文章:哈希表闭散列实现中讲过。


我们再来看 HashTable 中代码的修改:

  • insert 函数的参数不再是 const pair<K, V>& kv ,而是 const T& data
  • 所有的获取 key 值的地方,都要使用仿函数来获取。

普通迭代器

不用说,哈希表的迭代器肯定不是原生指针。因为每一个哈希表节点数据并不是连续的物理空间。
我们就要思考如何封装哈希表的迭代器:

  • 首先,类中肯定包含 HashNode 的指针。
  • 其次,在对迭代器进行加加或者减减运算时,可能会跨越不同的哈西桶,这个时候就比较难办了!不妨先想想我们应该如何解决。
    在这里插入图片描述
    • 假设我们当前迭代器指向的 HashNode 的值为 44,那么当这个迭代器加加之后,我们就要跨越当前的哈希桶,找到下一个有效的 HashNode
    • 再假设我们当前迭代器指向的 HashNode 的值为 7,那么当这个迭代器减减之后,我们也要跨越当前哈希桶,向前找到一个有效的 HashNode
  • 刚才谈到,我们迭代器加加或者减减之后可能跨越哈希桶。那么是不是要在迭代器中封装当前的哈希桶在数组中的下标呢?仔细一想其实是不需要的,因为我们可以通过当前迭代器,拿到存储数据的 key 值,经过除留余数法获得其在数组中的下标。
    但是另外的一个问题就暴露出来了:跨越哈希桶的时候,迭代器中拿不到哈希表的 _table 哇,我们就无法向前或者向后寻找一个有效的 HashNode。因此,在迭代器中我们可以封装一个 HashTable 的指针,在跨越哈希桶的时候就能顺利地向前或者向后查找啦!(当然实现的方法有很多,这取决于你的想象力啦!比如:你可以将 _table 传过来)
template <class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
struct __HashIterator
{
	typedef HashNode<T> Node;
	typedef __HashIterator<K, T, KeyOfT, HashFunc> self;
	
	//构造函数
	__HashIterator(Node* node, HashTable<K, T, KeyOfT, HashFunc>* pht)
	{
		_node = node;
		_pht = pht;
	}

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

operator++()

  • 如果当前迭代器的 _node->_next 不为 nullptr,说明当前节点的 _next 是一个有效的节点。我们直接修改 _node_node->_next 即可。
  • 如果当前迭代器的 _node->_next 为空,那么,我们就需要寻找下一个位置。
    • 首先我们需要计算当前迭代器的 _nodeHashTable 的哪个下标。
    • 然后从计算出来的下标的下一个位置开始,查找下一个不为空的哈希桶。
    • 如果找不到不为空的哈希桶,说明当前迭代器的节点已经是哈希表的最后一个有效元素了。我们可以令 _nodenullptr 充当我们 end 迭代器。
self operator++()
{
	if(_node->_next) //当前的哈希桶还有节点
	{
		_node = _node->_next;
		return *this;
	}
	else
	{
		KeyOfT kot;
		HashFunc hf;
		size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
		++hashi;
		while(hashi < _pht->_table.size())
		{
			if(_pht->_table[hashi])
			{
				_node = _pht->_table[hashi];
				return *this;
			}
			else
			{
				++hashi;
			}
		}

		_node = nullptr;
		return *this;
	}
}

operator!=()

这个函数简单,直接用节点的指针比较就可以啦!

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

operator*() 与 operator->()

这两个函数我们也写了很多遍了。分别返回数据域和数据域的指针就行。

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

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

begin() 和 end()

这两个函数是在 HashTable 的类中写,千万不敢写迷糊了。

  • begin():我们只需要遍历 _table 找到第一个不为空的哈希桶。返回这个哈希桶的第一个节点就行。如果找不到,就用 nullptr 作为迭代器构造函数的第一个参数返回。
  • end():直接用 nullptr 作为迭代器的第一个参数即可。
    __HashIterator 中,构造函数的第二个参数是哈希表的指针,begin 与 end 返回时第二个参数应该怎么传递呢?是不是就是 this 指针哇!这就是为什么我们将哈希表的指针作为迭代器成员的原因,因为简单嘛!如果是 _table 也行,总归要麻烦点。
iterator begin()
{
	for(size_t i = 0; i < _table.size(); i++)
	{
		Node* cur = _table[i];
		if(cur) return iterator(cur, this);
	}
	return iterator(nullptr, this);
}

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

你编译一下代码,发现编译不通过:原因就是在迭代器中你要用哈希表,在哈希表中你又要用迭代器。无论将哪一个类放在前面都行不通。因此需要加前置声明。

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

unorered_mapunordered_set 添加 begin() 与 end()

这两个函数都比较简单哈!我们已经在 HashTable 中实现了 begin 与 end 函数。因此只需要在这两个容器中分别调用 begin 与 end 函数即可。

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

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

上面的代码在 unordred_mapunordered_set 中都搞一份就行啦!

代码写到这里我们就可以使用范围 for 遍历我们自己实现的 unordred_mapunordered_set 了。但是我们运行代码之后又报了一个错误。说是:_table 不可访问。在迭代器中我们封转了哈希表的指针。但是因为 _table 是哈希表中的私有成员,外部当然不能访问啦!
在这里插入图片描述
这里就有两种比较靠谱的解决方式:

  • 写一个函数,返回 HashTable 的 _table 成员。
  • 用友元,谁是谁的友元呢?当然是 __HashIteratorHashTable 的友元啦!
template <class K, class T, class KeyOfT, class HashFunc>
friend struct __HashIterator;

上面的代码在 visual studio 中运行是没有问题的,但是在 vscode 中就不能运行。报错的提示是:嵌套作用域不能使用相同的模板参数。如果你遇到了这样的报错提示,那么随便改一下模板参数的名称即可!

const 迭代器

首先我们要明确普通迭代器与 const 迭代器的区别,在之前封转 list,map,set 我们就已经知道,其实就是 begin 与 end 的返回值类型不一样嘛!处理方式都是一样的:将不一样的地方参数化即可!因此我们又要向 __HashIterator 中加入模板参数啦!

template <class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
struct __HashIterator
  • Ref:表示返回引用,如果是普通迭代器传入的 Ref 就是 T&,返回的也就是 T& 啦!如果是 const 迭代器,传入的 Ref 就是 const T&,返回的自然也是 const T& 啦!
  • Ptr:表示返回指针。如果是普通迭代器传入的 Ptr就是 T*,返回的也就是 T* 啦!如果是 const 迭代器,传入的 Ptr就是 const T*,返回的自然也是 const T* 啦!

因此:在 HashTable 中就要传入不同的参数类型,来定义出普通迭代器和 const 迭代器。

typedef __HashIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;
typedef __HashIterator<K, T, const T&, const T*, KeyOfT, HashFunc> const_iterator;

实现了 cosnt_iterator 之后呢?我们就需要为 HashTable 添加 const 迭代器的版本,也是非常简单呢!添加完成了之后,我们再为 unordred_mapunordered_set 添加 cosnt 迭代器。其中 unordered_set 的 key 值不允许修改,因此 unordered_set 的 iterator 与 const_iterator都是 const_iterator。
map 解决 key 不能被修改的方法就是:在 pair 中为 K 加上 const。
但是这么修改之后报错:
在这里插入图片描述

invalid conversion from ‘const HashTable<int, int, unordered_set::SetKeyOfT, DefaultHashFunc >’ to ‘HashTable<int, int, unordered_set::SetKeyOfT, DefaultHashFunc >’ [-fpermissive]

这是什么原因呢?begin() const 中的 const 修饰的是 this 指向的内容。因此,这里的 this 的完整类型为:const HashTable<K, T, KeyOfT, HashFunc> * 但是呢?迭代器的构造函数的第二个参数是:HashTable<K, T, KeyOfT, HashFunc>* pht
非 const 是无法转化为 const 类型的,因此会报错,解决办法就是在构造函数的参数加上 cosnt 就行。并且将 __HashIterator 的哈希表指针的成员改为 const。因为迭代器中不会通过哈希表的指针修改哈希表的内容。因此这么写完全没有问题呢!

修改 Find 返回值

在库函数中 find 的返回值是 iterator。因此我们也需要修改。修改的方法很简单,只需在返回的地方处理一下就可以啦!
在返回的地方调用构造函数就可以啦!

iterator Find(const K &key)
{
	HashFunc hf;
	KeyOfT kot;
	size_t hashi = hf(key) % _table.size();
	Node *cur = _table[hashi];
	while (cur)
	{
		if (kot(cur->_data) == key)
		{
			return iterator(cur, this);
		}

		cur = cur->_next;
	}

	return iterator(nullptr, this);
}

修改 insert 函数的返回值

首先我们得知道库函数中 Insert 的返回值:pair<iterator, bool>

  • 在判断哈希表中是否已经存在这个元素的时候,需要用 iterator 接收 Find 函数的返回值。通过这个返回值与 end 做比较,如果不等于 end 说明哈希表中已经存在这个元素了,我们返回这个迭代器,和 false 构造出来的 pair 即可。
  • 如果是新插入,就用新插入的节点构造一个迭代器对象和 true 一起打包成 pair 返回即可。
    在这里插入图片描述
    在这里插入图片描述
    好的,我们现在已经修改完成了 HashTable 中的 Insert 函数。之后呢,我们顺利的修改了 unordered_mapunordered_set 中的 insert 函数,但是修改之后编译,发现又出问题了!
    在这里插入图片描述
    如上图,在 unordered_set 中,insert 的返回值虽然看上去是:pair<iterator, bool> 但是因为 unordered_set 的 iterator 与 const_iterator 都是 const_iterator 。因此,unordered_set 的 insert 函数返回值的实际类型是:pair<const_iterator, bool> 自然会出现类型不兼容,无法转换的问题。
    解决办法在 mapset 的模拟实现部分已经讲解过了。
    我们可以为 __HashIterator 加上一个非常像构造函数的函数:
typedef __HashIterator<K, T, T*, T&, KeyOfT, HashFunc> Iterator;
__HashIterator(const Iterator& it)
	:_node(it._node)
	,_pht(it._pht)
{}

Iterator 是用 T*,T&定义的,而不是 Ref 和 Ptr,因此当这个迭代器是普通迭代器的时候,就是拷贝构造函数。当这个迭代器是 const 迭代器的时候,就是一个类型转换。非常巧妙。

unordered_map 的 opertor[]

原理比较简单,就是调用 insert 函数。无论插入成功还是失败,都将 insert 返回值对应的 iterator 的数据中的 second 返回即可。

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

完整代码

Hash.h

#pragma once
#include <vector>

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

// 12:00
template <>
struct DefaultHashFunc<string>
{
	size_t operator()(const string &str)
	{
		// BKDR
		size_t hash = 0;
		for (auto ch : str)
		{
			hash *= 131;
			hash += ch;
		}

		return hash;
	}
};

template <class T>
struct HashNode
{
	T _data;
	HashNode<T> *_next;

	HashNode(const T &data)
		: _data(data), _next(nullptr)
	{
	}
};

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

template <class K, class T, class Ref, class Ptr, class KeyOfT, class HashFunc>
struct __HashIterator
{
	typedef HashNode<T> Node;
	typedef __HashIterator<K, T, Ref, Ptr, KeyOfT, HashFunc> self;
	typedef __HashIterator<K, T, T&, T*, KeyOfT, HashFunc> Iterator;
	
	//构造函数
	__HashIterator(Node* node, const HashTable<K, T, KeyOfT, HashFunc>* pht)
	{
		_node = node;
		_pht = pht;
	}

	__HashIterator(const Iterator& it)
		:_node(it._node)
		,_pht(it._pht)
	{}

	self operator++()
	{
		if(_node->_next) //当前的哈希桶还有节点
		{
			_node = _node->_next;
			return *this;
		}
		else
		{
			KeyOfT kot;
			HashFunc hf;
			size_t hashi = hf(kot(_node->_data)) % _pht->_table.size();
			++hashi;
			while(hashi < _pht->_table.size())
			{
				if(_pht->_table[hashi])
				{
					_node = _pht->_table[hashi];
					return *this;
				}
				else
				{
					++hashi;
				}
			}

			_node = nullptr;
			return *this;
		}
	}

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

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

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

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

template <class K, class T, class KeyOfT, class HashFunc = DefaultHashFunc<K>>
class HashTable
{
public:
	typedef HashNode<T> Node;
	typedef __HashIterator<K, T, T&, T*, KeyOfT, HashFunc> iterator;
	typedef __HashIterator<K, T, const T&, const T*, KeyOfT, HashFunc> const_iterator;

private:
	template <class U, class Q, class W, class E, class Y, class I>
	friend struct __HashIterator;

public:


	iterator begin()
	{
		for(size_t i = 0; i < _table.size(); i++)
		{
			Node* cur = _table[i];
			if(cur) return iterator(cur, this);
		}
		return iterator(nullptr, this);
	}

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


	const_iterator begin() const
	{
		for(size_t i = 0; i < _table.size(); i++)
		{
			Node* cur = _table[i];
			if(cur) return const_iterator(cur, this);
		}
		return const_iterator(nullptr, this);
	}

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

	HashTable()
	{
		_table.resize(10, nullptr);
	}

	~HashTable()
	{
		for (size_t i = 0; i < _table.size(); i++)
		{
			Node *cur = _table[i];
			while (cur)
			{
				Node *next = cur->_next;
				delete cur;
				cur = next;
			}

			_table[i] = nullptr;
		}
	}

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

		iterator it = Find(kot(data));
		if(it != end())
		{
			return make_pair(it, false);
		}

		HashFunc hf;

		// 负载因子到1就扩容
		if (_n == _table.size())
		{
			// 16:03继续
			size_t newSize = _table.size() * 2;
			vector<Node *> newTable;
			newTable.resize(newSize, nullptr);

			// 遍历旧表,顺手牵羊,把节点牵下来挂到新表
			for (size_t i = 0; i < _table.size(); i++)
			{
				Node *cur = _table[i];
				while (cur)
				{
					Node *next = cur->_next;

					// 头插到新表
					size_t hashi = hf(kot(cur->_data)) % newSize;
					cur->_next = newTable[hashi];
					newTable[hashi] = cur;

					cur = next;
				}

				_table[i] = nullptr;
			}

			_table.swap(newTable);
		}

		size_t hashi = hf(kot(data)) % _table.size();
		// 头插
		Node *newnode = new Node(data);
		newnode->_next = _table[hashi];
		_table[hashi] = newnode;
		++_n;
		return make_pair(iterator(newnode, this), true);
	}

	iterator Find(const K &key)
	{
		HashFunc hf;
		KeyOfT kot;
		size_t hashi = hf(key) % _table.size();
		Node *cur = _table[hashi];
		while (cur)
		{
			if (kot(cur->_data) == key)
			{
				return iterator(cur, this);
			}

			cur = cur->_next;
		}

		return iterator(nullptr, this);
	}

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

				delete cur;
				return true;
			}

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

		return false;
	}

	void Print()
	{
		for (size_t i = 0; i < _table.size(); i++)
		{
			printf("[%d]->", i);
			Node *cur = _table[i];
			while (cur)
			{
				cout << cur->_kv.first << ":" << cur->_kv.second << "->";
				cur = cur->_next;
			}
			printf("NULL\n");
		}
		cout << endl;
	}

private:
	vector<Node *> _table; // 指针数组
	size_t _n = 0;		   // 存储了多少个有效数据
};

unodered_map.h

#pragma once

#include "Hash.h"

template<class K, class V>
class unordered_map
{
public:
    struct MapKeyOfT
    {
        const K& operator()(const pair<K, V>& kv)
        {
            return kv.first;
        }
    };
    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT>::iterator iterator;
    typedef typename HashTable<K, pair<const K, V>, MapKeyOfT>::const_iterator const_iterator;

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

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

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


private:
    HashTable<K, pair<const K, V>, MapKeyOfT> _ht;
};

unordered_set.h

#pragma once
#include"Hash.h"

template<class K>
class unordered_set
{
public:
    struct SetKeyOfT
    {
        const K& operator()(const K& key)
        {
            return key;
        }
    };
    typedef typename HashTable<K, K, SetKeyOfT>::const_iterator iterator;
    typedef typename HashTable<K, K, SetKeyOfT>::const_iterator const_iterator;

    pair<iterator, bool> insert(const K& key)
    {
        pair<typename HashTable<K, K, SetKeyOfT>::iterator, bool> ret = _ht.Insert(key);
        return pair<iterator, bool>(ret.first, ret.second);
    }

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

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

private:
    HashTable<K, K, SetKeyOfT> _ht;
};


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

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

相关文章

Find My鼠标|苹果Find My技术与鼠标结合,智能防丢,全球定位

随着折叠屏、多屏幕、OLED 等新兴技术在个人计算机上的应用&#xff0c;产品更新换代大大加速&#xff0c;进一步推动了个人计算机需求的增长。根据 IDC 统计&#xff0c;2021 年全球 PC 市场出货量达到 3.49 亿台&#xff0c;同比增长 14.80%&#xff0c;随着个人计算机市场发…

【从浅识到熟知Linux】基本指定之zip、unzip和tar

&#x1f388;归属专栏&#xff1a;从浅学到熟知Linux &#x1f697;个人主页&#xff1a;Jammingpro &#x1f41f;每日一句&#xff1a;周五写博客更刺激了&#xff0c;想到明天可以晚起床半小时&#xff0c;瞬间精神抖擞。再写它10篇博客。 文章前言&#xff1a;本文介绍zip…

Tars-GO 开发

默认环境是安装好的 创建服务: tarsgo make App Server Servant GoModuleName Tars 实例的名称&#xff0c;有三个层级&#xff0c;分别是 App&#xff08;应用&#xff09;、Server&#xff08;服务&#xff09;、Servant&#xff08;服务者&#xff0c;有时也称 Object&am…

什么是AWS CodeWhisperer?

AWS CodeWhisperer https://aws.amazon.com/cn/codewhisperer/ CodeWhisperer 经过数十亿行代码的训练&#xff0c;可以根据您的评论和现有代码实时生成从代码片段到全函数的代码建议。 ✔ 为您量身定制的实时 AI 代码生成器 ✔ 支持热门编程语言和 IDE ✔ 针对 AWS 服务的优…

前端大厂(腾讯、字节跳动、阿里......)校招面试真题解析,让你面试轻松无压力!

前言 校招很重要&#xff0c;应届生的身份很珍贵&#xff01;在校招的时候与我们竞争的大部分都是没有工作经验的学生&#xff0c;而且校招企业对学生的包容度高&#xff0c;一般对企业来说&#xff0c;社招更看重实际工作经验&#xff0c;而校招更愿意“培养人”&#xff0c;校…

Linux(7):Vim 程序编辑器

vi 基本上 vi 共分为三种模式&#xff0c;分别是【一般指令模式】、【编辑模式】与【指令列命令模式】。 这三种模式的作用分别是&#xff1a; 一般指令模式(command mode) 以 vi 打开一个文件就直接进入一般指令模式了(这是默认的模式&#xff0c;也简称为一般模式)。在这个模…

Rust UI开发(二):iced中如何为窗口添加icon图标

注&#xff1a;此文适合于对rust有一些了解的朋友 iced是一个跨平台的GUI库&#xff0c;用于为rust语言程序构建UI界面。 想要了解如何构建简单窗口的可以看本系列的第一篇&#xff1a; Rust UI开发&#xff1a;使用iced构建UI时&#xff0c;如何在界面显示中文字符 本篇是系…

量子计算的发展

目录 一、量子力学的发展历程二、量子计算的发展历程三、量子计算机的发展历程四、量子信息科学的发展 一、量子力学的发展历程 量子力学是现代物理学的一个基本分支&#xff0c;它的发展始于20世纪初。以下是量子力学发展的几个重要阶段&#xff1a; 普朗克&#xff08;1900&…

ubuntu环境删除qtcreator方法

文章目录 方法1方法2方法3参考不同的安装方法,对应不同的删除方法 方法1 apt-get或者dpkg 方法2 QtCreatorUninstaller 方法3 MaintenanceTool

机器学习【02】在 Pycharm 里使用 Jupyter Notebook

只有 Pycharm 的 Professional 版才支持 Jupyter Notebook 本教程结束只能在pycharm中使用&#xff0c;下载的库在pycharm选中的虚拟环境中 ssh -L localhost:9999:localhost:8888 usernameip这句话每次都要用 准备 1.服务器安装jupyter sudo snap install jupyter2.在 Jup…

39 关于 binlog 日志

前言 bin log 相关 呵呵 记得之前是做过基于 binlog 的数据同步到的, 这里 可以来了解一下 binlog 的产生, 以及 相关更加详细的信息 说不定 之后也可以 做一个 binlog 的解析工具 这里 来看一下 各个常见的 binlog event 的相关格式 open-replicator 解析binlog失败 a…

npm pnpm yarn(包管理器)的安装及镜像切换

安装Node.js 要安装npm&#xff0c;你需要先安装Node.js。 从Node.js官方网站&#xff08;https://nodejs.org&#xff09;下载并安装Node.js。 根据你的需要选择相应的版本。 一路Next&#xff0c;直到Finish 打开CMD&#xff0c;输入命令来检查Node.js和npm是否成功安装 nod…

C语言-指针讲解(3)

文章目录 1.字符指针变量1.1 字符指针变量类型是什么1.2字符指针变量的两种使用方法&#xff1a;1.3字符指针笔试题讲解1.3.1 代码解剖 2.数组指针变量2.1 什么是数组指针2.2 数组指针变量是什么&#xff1f;2.2.3 数组指针变量的举例 2.3数组指针和指针数组的区别是什么&#…

AI模型训练——入门篇(一)

前言 一文了解NLP&#xff0c;并搭建一个简单的Transformers模型&#xff08;含环境配置&#xff09; 一、HuggingFace 与NLP 自从ChatGPT3 问世以来的普及性使用&#xff0c;大家或许才真正觉察AI离我们已经越来越近了&#xff0c;自那之后大家也渐渐的开始接触stable diff…

vue+springboot读取git的markdown文件并展示

前言 最近&#xff0c;在研究一个如何将我们git项目的MARKDOWN文档获取到&#xff0c;并且可以展示到界面通过检索查到&#xff0c;于是经过几天的摸索&#xff0c;成功的研究了出来 本次前端vue使用的是Markdown-it Markdown-it 是一个用于解析和渲染 Markdown 标记语言的 …

虹科Pico汽车示波器 | 汽车免拆检修 | 2011款瑞麒M1车发动机起动困难、加速无力

一、故障现象 一辆2011款瑞麒M1车&#xff0c;搭载SQR317F发动机&#xff0c;累计行驶里程约为10.4万km。该车因发动机起动困难、抖动、动力不足、热机易熄火等故障进厂维修。用故障检测仪检测&#xff0c;发动机控制单元&#xff08;ECU&#xff09;中存储有故障代码“P0340相…

C#/.NET/.NET Core推荐学习书籍(已分类)

前言 古人云&#xff1a;“书中自有黄金屋&#xff0c;书中自有颜如玉”&#xff0c;说明了书籍的重要性。作为程序员&#xff0c;我们需要不断学习以提升自己的核心竞争力。以下是一些优秀的C#/.NET/.NET Core相关学习书籍&#xff0c;值得.NET开发者们学习和专研。书籍已分类…

Vue3框架中让table合计居中对齐

第一步&#xff1a;给它加一个类名 center-table 如下&#xff1a; <el-table:data"datas.shows"max-height"600px"show-summarystripeborderstyle"width: 100%":header-cell-style"{ textAlign: center }":cell-style"{ text…

java - 选择排序

一、什么是选择排序 选择排序&#xff08;Selection sort&#xff09;是一种简单直观的排序算法。它的基本思想是每次从待排序的元素中选择最小&#xff08;或最大&#xff09;的元素&#xff0c;将其放到已排序序列的末尾&#xff0c;直到所有元素排序完成。 具体步骤如下&…

LemMinX-Maven:帮助在eclipse中更方便地编辑maven的pom文件

LemMinX-Maven&#xff1a;https://github.com/eclipse/lemminx-maven LemMinX-Maven可以帮助我们在eclipse中更方便地编辑maven工程的pom.xml文件&#xff0c;例如补全、提示等。不用单独安装&#xff0c;因为在安装maven eclipse插件的时候已经自动安装了&#xff1a; 例…