c++ map,set封装

news2024/11/27 12:39:41

map 是一个 kv 结构, set 是 k结构。
我们前面模拟实现了 红黑树,但是我们实现的红黑树把 kv 结构写死了,怎么样才能用泛型编程的思想来实现map和set呢
我们先简单看一下原码中是怎么实现的

1.原码实现逻辑

我们打开这里的 stl_set.h
在这里插入图片描述
通过我们前面学习红黑树,我们看到这里的 rb_tree 的名字 就是一棵纯正的红黑树
我们打开 tree.h 的头文件去找这个 rb_tree
在这里插入图片描述
在这里插入图片描述

这里用 true 和 false 来表示 red 和 black,和我们的枚举有区别,但是没影响,只要能判断红黑即可。
这里map 和 set 都使用了红黑树的代码
接下来开始我们的重头戏
在这里插入图片描述
我们实现节点是一个 struct 就实现了,但是这里还用到了继承,并且我们的红黑树传入了两个模版参数 K, V ,但是这里的节点却只有一个 Value 的模版参数,我们接着往下看
在这里插入图片描述
我们看到,源代码实现的红黑树传入参数有点多,而且下这个整体的封装过程挺麻烦的。
在这里插入图片描述

首先,他的 __rb_tree_node 只传入了一个 Value 的模版参数。但是 map 是 kv 模型,只传入了一个 Value ,怎么能让 map 满足两个类型呢?
我们打开 stl_set.h ,虽然这里重命名了,但是 key_type 和 value_type 的原本类型都是 Key 类型
在这里插入图片描述
我们把这里联系起来
在这里插入图片描述
那这样我们猜测,map 中应该也有一个 key_type 和 value_type ,且这两个类型不同
在这里插入图片描述
这里我们能看到,key_type 和 value_type 的原本类型不同,且 value_type 还是用 pair<const Key, T> 来实现的
在这里插入图片描述
我们先简单理解一下,这里通过一个模版参数 value_type 来确定是哪种类型,如果传入的只是一个 value_type,就是 k 模型,如果传入的参数是键值对 pair<const Key, T>,那这里就是 kv 模型
这里就通过传入参数,使 map 和 set 可以使用同一段代码,实现了泛型编程的思想

2. 模拟实现

set

namespace xsz1
{
	template<class K>
	class set
	{
	private:
		RBTree<K, K> _t;
	}
}

map

namespace xsz2
{
	template<class K, class T>
	class map
	{
	private:
		RBTree<K, pair<K, T>> _t;
	}
}

这里我们先把 K 设置为可修改的。
在这里插入图片描述
我们这里的写法比源代码简化了很多,因为只是模拟实现,不是完全实现,所以只是实现基本功能。
上面的 map 和 set 写完后,我们还需要修改 红黑树的基本代码

template<class T>
struct RBTreeNode
{
	RBTreeNode<T>* _left;
	RBTreeNode<T>* _right;
	RBTreeNode<T>* _parent;
	//pair<K, V> _kv;
	Colour _col;
	T _data;
	RBTreeNode(const T& data)
		:_left(nullptr)
		,_right(nullptr)
		,_parent(nullptr)
		,_data(data)
		,_col(RED)
	{}
};
template<class K, class T>
class RBTree
{
public:
	typedef RBTreeNode<T> Node;
	//...
};

2.1. insert

但是还有一个问题需要处理,第二参数怎么比较大小?
如果是 set 比较大小很简单,都是 key,直接比较,那么 map 的键值对呢
在这里插入图片描述
我们看到,键值对是可以比较的,但是这个比较大小和我们想要的不一样,这里的比较大小,会先去比较 first,在 first 相同的情况下,会去比较 second,但是我们只需要比较 first,所以这里我们还需要单独处理一下
但是要怎么处理才能让 set 和 map 都适合这种用法呢

while(cur)
{
	if(cur->_data <data)
	{
		parent = cur;
		cur = cur->-right;
	}
	else if(cur->_data >data)
	{
		parent = cur;
		cur = cur->_left;
	}
	else
	{
		return false
	}
}

我们能不能用类型对比的方式?

if(cur->_data.type == K)
{
	//...
}

如果 data 的类型和传入的第一模版参数类型相同,我们就直接比,如果传入的类型是 pair 类型,我们再对比 data.first
但是这样写肯定不行,其实 c 语言中有一种写法能支持这种思路
typeid.name()

if(typeif(cur->_data).name == K)
{
	//...
}

typeif.name 可以把传入的内容转化成字符串,可以按照这个思路写下去,但是这样写不太符合我们的泛型编程的思想。
还有一个方法

template<class K, class T>
class map
{
public:
	struct MapKeyofT
	{
		const K& operator()(const K& key)
		{
			return key.first;
		}
	};
private:
	RBTree<K, pair<K, T>, MapKeyofT> _t;
};
template<class K>
class set
{
public:
	struct SetKeyofT
	{
		const K& operator()(const K& key)
		{
			return key;
		}
	};
private:
	RBTree<K, K, SetKeyofT> _t;
template<class K, class T, class KeyofT>
class RBTree
{
	//....
	KeyofT kot;
	kot(cur->_data <kot < kot(data));
}

我们在 set 和 map 中实现两个仿函数,这两个仿函数在调用时会返回该对象的 value 。
如果是 set 调用,会返回 key,如果是 map 调用,会返回 键值对中的 first。
我们把这两个类作为模版参数传入 RBTree 中,在 RBTree 中,我们只需要去调用这个 kot 即可,kot 会把set , map 中需要比较的部分返回供我们比较,也就不需要我们自己去考虑怎么处理了。

2.2. 迭代器

2.2.1. 红黑树迭代器

红黑树,树形结构,和之前学的链表的迭代器很像。

template<class T, class KeyofT>
struct __TreeIterator
{
	typedef RBTreeNode<T> Node;
	typedef __TreeIterator<T, KeyofT> Self;
	Node* _node;

	__TreeIterator(Node* node)
		:_node(node)
	{}
	T& operator*()
	{
		return _node->_data;
	}
	T* operator->()
	{
		return &_node->_data;
	}
}

我们先把最基础的部分写出来。
上面这些都没什么问题,迭代器支持 ++,那么我们的迭代器怎么写才能支持++呢?
拿这棵树研究研究
在这里插入图片描述

一般情况下,会从 begin() 到 end()
begin() 的位置很好确认,就是 _root 的最左节点
在这里插入图片描述
观察后,我们发现,如果当前节点不存在孩子节点时,会去找节点是父亲左节点的父亲节点节点。
在这里插入图片描述
比如当 it== 5 或者 it == 10 时,应该会去找 节点是父亲节点左孩子节点的那个父亲节点
在这里插入图片描述

比如这里如果 it 是 9 ,先去找 10 ,然后走到10 的右孩子节点,此时要向上走,10这棵树已经走完了,那么就去找 10 的父节点。it 在 10 的右孩子节点时,不满足 节点是父亲左节点的条件,接着往上找,10 是 11 的父亲节点的左节点,所以 _node = 11,开始下一步。
如果节点的右子树不为空,那就去右子树中找最左节点。
这句比较好理解,和 begin() 位置一样,直接找最左节点。
我们在实现 ++ 前,先把 begin() 和 end() 实现了

iterator begin()
{
	Node* cur = _root;
	while(cur && cur->_left)
	{
		cur = cur->_left;
	}
	return iterator(cur);
}
iterator end()
{
	return iterator(nullptr);
}

这里的 begin 和 end 函数都比较好理解
begin 最开始要指向最小的节点,所以最开始就是 cur 找最左节点即可
但是 end 要注意,并不是指向最右的节点,按照我们之前对vector 那些迭代器的理解,end 返回的是最后一个节点后面位置的位置,但是当遍历到最大节点时,树中是没有下一个节点的,所以我们默认直接返回 nullptr(按照上面++ 的逻辑,最后会指向 _root 的父节点,但是 _root 本身就是空节点,所以直接用 nullptr 返回即可)。
下面开始实现 ++ 的操作

Self& operator++()
{
	if(_node->_right)
	{
		Node* cur = _node->_right;
		while(cur && cur->_left)
		{
			cur = cur->_left;
		}
		_node = cur;
	}
	else
	{}
	return *this;
}

右不为空的情况下,去找右子树的最左节点
右为空的情况下,去找节点是父节左的那个父亲节点

Self& operator++()
{
	if(_node->_right)
	{
		Node* cur = _node->_right;
		while(cur && cur->_left)
		{
			cur = cur->_left;
		}
		_node = cur;
	}
	else
	{
		Node* cur = _node;
		Node* parent = cur->_parent;
		while(parent && cur == parent->_right)
		{
			cur = parent;
			parent = cur->_parent;
		}
		_node = parent;
	}
	return *this;
}

这样,我们的 ++ 逻辑就写完了
接下来是 != 操作

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

这里注意是节点指针进行比较,不是对比节点的值,或者是传入的 s 对象。
减减操作和加加操作相反。
++:
右树存在找右树最左节点
右树不存在,找节点是父亲左的那个父亲节点
–:
可以把情况想的极端一点,++是从最左节点遍历到最右节点,–是从最右节点遍历到最左节点,也就是说这俩操作是个完全对称的。
左树存在找最右节点
左树不存在,找节点是父亲右的那个父亲节点

        Self& operator--()
        {
                if (_node->_left)
                {
                        Node* cur = _node->_left;
                        while (cur->_right)
                        {
                                cur = cur->_right;
                        }
                        _node = cur;
                }
                else
                {
                        Node* cur = _node;
                        Node* parent = cur->_parent;
                        while (parent && cur == parent->_left)
                        {
                                cur = parent;
                                parent = cur->_parent;
                        }
                        _node = parent;
                }
                return *this;
        }

在这里插入图片描述

2.2.2. set,map封装迭代器

红黑树的迭代器写完了,接下来要在 set 中也实现迭代器,因为我们要通过 set 的接口去使用红黑树。

template<class K>
class set
{
public:
	//..
	typedef RBTree<K, K, SetKeyofT>::iterataor iterator;
	iterator begin()
	{
		return _t.begin();
	}
	iterator end()
	{
		return _t.end()
	}
private:
	RBTree<K, K, SetKeyofT> _t;
}

既然迭代器写好了,那么我们来试试吧

void test_set()
{
        xsz1::set<int> s;
        s.insert(4);
        s.insert(1);
        s.insert(2);
        s.insert(3);
        s.insert(2);
        xsz1::set<int>::iterator it = s.begin();
        while (it != s.end())
        {
                cout << *it << "  ";
                ++it;
        }
        cout << endl;
}

当我们运行这段代码后,我们会惊奇的发现
在这里插入图片描述
他崩了
注意这里显示出来的问题,iterator 不是类型,我们明明 typedef 了 红黑树的iterator, 为什么这里说 iterator 不是类型呢?
这里要注意,域作用访问限定符

typedef RBTree<K, K, SetKeyofT>::iterataor iterator;

一般除了这里会用到 这个操作符,我们在声明 其类中的静态成员变量也会用到这个操作符,因为我们的红黑树代码是用模版来实现的 ,所以这段代码在预编译的时候并不存在红黑树的代码(实例化后才会出现,想要实例化也是在运行期间),而预编译时访问到了这段代码,编译器就不知道你是想给 iterator 这个静态成员变量重命名,还是给这个类型命重命名,所以会报错。
因此如果想通过这里,我们必须强制说明一下

typedef typename  RBTree<K, K, SetKeyofT>::iterataor iterator;

现在可以开测我们的代码了
在这里插入图片描述
我们看到,通过迭代器,随机插入的数据正向输出了。
迭代器实现了,范围 for 也就随便用。
然后是 map 的迭代器,与 set 同理

template<class K>
class set
{
public:
	//..
	typedef RBTree<K, pair<K, T>, MapKeyofT>::iterataor iterator;
	iterator begin()
	{
		return _t.begin();
	}
	iterator end()
	{
		return _t.end()
	}
private:
	RBTree<K, pair<K, T>, MapKeyofT> _t;
}

我们运行之后
在这里插入图片描述
完全没什么问题。
虽然 set,map 大题功能已经实现,但不是还存在一个很严重的问题。
如果我们去修改 迭代器位置的值

(*it) = 100;

在这里插入图片描述
我们看到,修改后的数据并不会自动去调整,而且最小的值被修改成了 100,我们的红黑树结构也遭到了破坏。
所以接下来我们要想办法,如何修改才能使key 值不可修改。

2.2.3. key 值不可修改

想要解决这个问题,我们先去库里看看大佬的思路。
set.h
在这里插入图片描述
set 只含有一个 key,在这里 iterator 被处理为 const_iterator,我们去看看 map 是怎么处理的
在这里插入图片描述
map并的处理方式和 set 完全不一样。
set 只含有一个值,这个key值本身就不能被修改,所以这里的 const_iterator 就能保证其中的 key 值不会被修改。
但是 map 中含有 key 和 value,map 中的 key 值不能被修改,但是 value 值是可以被修改的,所以 map 就不能那么暴力的直接全部修改为 const_iterator。
这里 set 和 map 的实现逻辑就不太一样了。
首先,我们先把insert 的返回值修改一下,库里面的返回值是键值对,这里我们也返回键值对

pair<iterator, bool> insert(const T& data)
{
	//...
}

红黑树的 insert 修改后,要注意 map.h 和 set.h 中的 insert 的返回值也要修改。

pair<iterator, bool> insert(const T& data)
{
	return _t.insert(data);
}

因为 set 需要用到const_iterator 所以我们先在红黑树中把 const_iterator 实现了
这里和我们前面实现 list 的const_iterator 的做法一样,我们在迭代器的模版参数中传入 Ref(reference参考值) 和 Ptr(指针), 当我们调用 const_iterator 时,就给 迭代器传入 const T& 和 const T*,如果我们要用 iterator, 我们就传入 T& 和 T*

template<class T, class Ref, class Ptr, class KeyofT>
struct __TreeIterator
{
	//...
}
template<class K, class T, class KeyofT>
class RBTree
{
public:
	typedef __TreeIterator<T, T&, T*, KeyofT>  iterator;
	typedef __TreeIterator<T, const T&, const T*, KeyofT> const_iterator;
	//...
}

const_iteraor 写好后,我们就开始操作 set

typedef typename RBTree<K, K, SetKeyofT>::const_iterator iterator ;
typedef typename RBTree<K, K, SetKeyofT>::const_iterator iterator;
pair<iterator, bool> insert(const K& key)
{
	return _t.insert(key);
}
iterator begin()const
{
	return _t.begin();
}
iterator end()const
{
	return _t.end()
}

把这里写好之后,我们尝试修改 set 中的值
在这里插入图片描述
这个时候,这里就会报错,我们不拿这里处理之后,运行代码,我们发现
在这里插入图片描述
还是报错了。
这里的问题就比较复杂了,
先看我们 红黑树 和 set 中 insert 中的返回值

pair<iterator, bool> insert(const T& data)
{
	return make_pair(iterator(cur), true);
}
//set.h
pair<iterator , bool> insert(const K& key)
{
	return _t.insert(key);
}

这里和 pair 的特性相关
在这里插入图片描述
我们知道,pair 的牛逼之处在于它的拷贝,他不需要我们一定要传入相同类型的值才能拷贝构造,而是只要 传入参数 pair 中的 第一第二参数能构造 被传入 的 pair 中的第一第二参数,就能完成 pair 的拷贝构造

 void func(pair<string, const int> p1)
 {
 	//..
 }
 int main()
 {
 	pair<const char*, int> p;
 	func(p);
 	return 0;
 }

在这里插入图片描述这里是没有问题的,因为 const char* 能构造 string, int 能构造 const int,所以 p 就能去构造 p1。
现在回到上面的问题,为什么这里会报错。
我们先清楚一件事,set 的 insert 返回值pair 中的类型iterator,对红黑树来说是 const_iterator 类型。
红黑树返回的类型是 pair<iterator, bool> 类型,
这里的 iterator 构造出来的类型 是 __TreeIterator<T, Ref, Ptr, KeyofT> 的类型
但是我们set 接收到这个类型后·,再返回 pair<const_iterator, bool> 类,型这里的 返回类型 是
RBTree<K, K, SetKeyofT>::const_iterator 类型,也就是
__TreeIterator<K, T, const T&, const T*, KeyofT>。
红黑树的 insert 返回的 pair 中 的迭代器没有 const T 的类型,但是 从 set 的 insert 返回的 pair 的迭代器中含有 const T 类型,pair 能做到 模版参数只要参数类型能构造就行,但是 我们模拟实现的迭代器是不支持自我完善的,也就是说 __TreeIterator<T, Ref, Ptr, KeyofT> 不能去构造 __TreeIterator<K, T, const T&, const T*, KeyofT> 类型 。
解决方法
所以我们可以考虑,不要使用它的默认拷贝构造,我们自己去写一个拷贝构造函数
除了这个方法,还有个更简单的方法
既然 pair 不需要传入和目标相同的模版参数,只要类型能构造就行,那么我们管目标是什么迭代器,我们直接把节点给set,set拿到节点爱怎么构造不关我红黑树的事

pair<Node*, bool> insert(const T& datd)
{
	//...
	return make_pair(cur, true);
	//...
}

在这里插入图片描述
此时我们的代码就能正常运行了,set 拿到节点,就要去自己用节点去构造 const_iterator。
这里要注意 const_iterator 和 const iterator 不是一个东西,const iterator只能保证这个迭代器不能修改,也就不能进行++,–等操作, 但是是可以修改 iterator 内的值。而 const_iterator 是我们自己实现的可以进行 ++,–等操作的类,只是这个类中的成员都用 const 进行修饰,成员不能修改。

然后就是 map 了,map 因为迭代器不那样暴力处理,我们想既然 key 不能修改,但是 value 可以修改,那么把 所有返回和 K 相关的地方,都返回 const K 应该就行了吧。

        template<class K, class T>
        class map
        {
        public:
                struct MapKeyofT
                {
                        const K& operator()(const pair<K, T>& key)
                        {
                                return key.first;
                        }

                };
                typedef        typename RBTree<K, pair<const K, T>, MapKeyofT>::iterator iterator;
                typedef        typename RBTree<const K, const pair<const K, T>, MapKeyofT>::iterator const_iterator;
                pair<iterator, bool> insert(const pair<const K, T>& key)
                {
                        return _t.insert(key);
                }
                iterator begin()
                {
                        return _t.begin();
                }
                iterator end()
                {
                        return _t.end();
                }
                T& operator[](const K& key)
                {
                        pair<iterator, bool> ret = insert(make_pair(key, T()));
                        return (ret.first)->second;
                }
        private:
                RBTree<K, pair<const K, T>, MapKeyofT> _t;
        };

在这里插入图片描述
大概逻辑写完后,我们看到 map 中的 第一参数不能修改,第二参数可以修改。
在这里插入图片描述

2.2.4. operator[]

既然我们上面把迭代器整体都玩完了,那么再写一个 [] 操作助助兴。
在这里插入图片描述
这里的解析在 map_set 学习的那节,想要了解这个怎么操作的可以去那里了解,这里主要进行实现

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

注意这个操作要完成的事,如果 [] 位置不存在值则是插入,如果存在值则返回其中的 value

void test_map2()
{
        string arr[] = { "香蕉", "甜瓜", "苹果", "西瓜","香蕉", "甜瓜", "苹果", "西瓜", "香蕉", "甜瓜", "苹果", "西瓜", "香蕉", "甜瓜", "苹果", "西瓜", "香蕉", "甜瓜", "苹果", "西瓜" };
        xsz2::map<string, int> m;
        for (auto& e : arr)
        {
                m[e]++;;
        }
        for (auto& e : m)
        {
                cout << e.first << "  " << e.second << "    ";
        }
        cout << endl;
}

在这里插入图片描述
这样我们的 [] 操作节完成了。
最后一个问题,我们在 RBTree 中传入了 模版参数 K,但是我们这里全程没用到过这个类型,我们需要 key 时直接使用了 KeyofT,这个 K 模版参数真的有必要吗?
有,因为我们这里主要实现的是 insert 操作,set,map中含有其他操作,比如 find,如果要实现find 操作

iterator find(const k& key)
{
	//...
}

可以说我们在简单的模拟实现这里没用上,但是绝对不能说他没有用。

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

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

相关文章

数据结构(Java实现):List接口的介绍

1.什么是List 在集合框架中&#xff0c;List是一个接口&#xff0c;继承自Collection。 Collection也是一个接口&#xff0c;该接口中规范了后序容器中常用的一些方法&#xff0c;具体如下所示&#xff1a; Iterable也是一个接口&#xff0c;表示实现该接口的类是可以逐个元…

达梦数据库连接失败:Connect Failure! “Encryption module failed to load“

初次安装达梦数据库&#xff1a;V7 QT5.12.12版本开发调用数据库&#xff0c;最基础的原型调用&#xff1a; { //执行查询语句或则执行sql语句 QSqlDatabase qDb; QSqlDatabase db QSqlDatabase::addDatabase("QDM"); db.setHostName("192.168.2…

【大数据】HDFS

文章目录 [toc]HDFS 1.0NameNode维护文件系统命名空间存储元数据解决NameNode单点问题 SecondaryNameNode机架感知数据完整性校验校验和数据块检测程序DataBlockScanner HDFS写流程HDFS读流程HDFS与MapReduce本地模式Block大小 HDFS 2.0NameNode HANameNode FederationHDFS Sna…

红黑树的理解和简单实现

目录 1. 红黑树的概念和性质 2. 红黑树的插入 2.1. 情况一&#xff1a;新增节点的父亲为空 2.2. 情况二&#xff1a;新增节点的父亲非空且为黑色节点 2.3. 情况三&#xff1a;当父亲为红节点&#xff0c;叔叔存在且为红 2.3.1. 当祖父为根节点的时候 2.3.2. 当祖父不是根…

揭秘高效引流获客的艺术:转化技巧大公开

在数字化营销的海洋中&#xff0c;每个企业都如同一艘努力航行的船&#xff0c;而流量便是推动船只前行的风帆。如何有效吸引并获取潜在客户&#xff0c;即所谓的“引流获客”&#xff0c;已成为企业市场营销策略中不可或缺的一环。本文将详细探讨几种实用且高效的引流获客技巧…

【RAG 论文】AAR:训练一个LLM喜欢的检索器来做RAG

论文&#xff1a;Augmentation-Adapted Retriever Improves Generalization of Language Models as Generic Plug-In ⭐⭐⭐ ACL 2023, Tsinghua & Microsoft&#xff0c;arXiv:2305.17331 论文速读 以往 RAG 的工作通常联合微调 retriever 和 LLM 导致紧密耦合&#xff0…

实验0.0 Visual Studio 2022安装指南

Visual Studio 2022 是一个功能强大的开发工具&#xff0c;对于计算机专业的学生来说&#xff0c;它不仅可以帮助你完成学业项目&#xff0c;还能为你将来的职业生涯打下坚实的基础。通过学习和使用 Visual Studio&#xff0c;你将能够更高效地开发软件&#xff0c;并在编程领域…

公有云Linux模拟UDP端口并抓包

目录 写在前面操作步骤服务端开启UDP端口并监听客户端连接Wireshark抓包查看 写在前面 关于具体的操作&#xff0c;请参考我的上一篇文章 公有云Linux模拟TCP三次挥手与四次握手&#xff08;Wireshark抓包验证版&#xff09; 在本文&#xff0c;仅介绍与上一篇不同的地方。 操…

Matlab-粒子群优化算法实现

文章目录 一、粒子群优化算法二、相关概念和流程图三、例题实现结果 一、粒子群优化算法 粒子群优化算法起源于鸟类觅食的经验&#xff0c;也就是一群鸟在一个大空间内随机寻找食物&#xff0c;目标是找到食物最多的地方。以下是几个条件: (1) 所有的鸟都会共享自己的位置以及…

泰达克仿钻点水晶饰品包装印刷防滑UV胶特性及应用场景

仿钻点UV滴胶是一种特殊的胶水 常用于模拟钻石的效果 它是一种透明的胶水 具有高光泽度和折射率 可以在物体表面形成类似钻石的亮闪效果 仿钻点UV滴胶通常由紫外线固化胶组成 需要通过紫外线照射来固化和硬化 它具有以下特点&#xff1a; 1. 透明度&#xff1a;仿钻点UV滴胶具有…

B端弹窗设计指南,3000字讲清楚,内附大量案例。

B端系统弹窗是指在企业级&#xff08;Business to Business&#xff09;系统中&#xff0c;弹出的窗口或对话框&#xff0c;用于向用户展示信息、提供操作选项或者收集用户输入。 一、B端系统弹窗的作用 作用如下&#xff1a; 提示和通知&#xff1a;弹窗可以用于向用户展示重…

STM32F407-驱动SHT41采集温湿度

STM32F407-驱动SHT41采集温湿度 SHT41 SHT41通过I2C方式进行驱动 从机地址&#xff1a; 0x44 获取数据方式 1&#xff09;先发送I2C写&#xff0c;写入特定指令 2&#xff09;延时一段时间&#xff0c;等待SHT41处理 3&#xff09;再进行I2C读&#xff0c;读数据即可 一些…

打开远程连接的命令是什么?

远程连接是一种能够在不同设备之间建立连接并共享信息的技术。在许多情况下&#xff0c;我们需要通过远程连接来访问其他设备或处理一些远程任务。本文将介绍一些常用的打开远程连接的命令。 使用SSH连接远程设备 SSH&#xff08;Secure Shell&#xff09;是一种安全的网络协议…

1-02-02:虚拟化与容器化Docker环境搭建

1.02.02 虚拟化与容器化Docker环境搭建 一. 虚拟化与容器化技术简介1. 虚拟机环境2. docker环境 二. Docker 架构与隔离机制2.1 Docker 架构2.2 Docker 隔离机制2.3 资源限制2.4 Docker应用场景 三. 实战:Docker在Centos7安装与镜像加速 ❤❤❤3.1 docker安装3.2 设置镜像加速 …

NIOS II实现LED流水灯以及串口输出(DE2-115开发板)

NIOS II实现LED流水灯以及串口输出&#xff08;DE2-115开发板&#xff09; 前言什么是Qsys?什么是NIOSII?注意事项1、管脚配置2、配置NIOSII时的连接3、注意中断配置好后是这样的4、注意名称的配置5、设置双功能引脚 NIOS II的报错代码以及效果演示流水灯输出到电脑串口助手 …

人工智能轨道交通行业周刊-第79期(2024.4.22-5.12)

本期关键词&#xff1a;无人机巡检、车机联控、减速顶、Agent、GraphRAG、RAGFlow 1 整理涉及公众号名单 1.1 行业类 RT轨道交通人民铁道世界轨道交通资讯网铁路信号技术交流北京铁路轨道交通网铁路视点ITS World轨道交通联盟VSTR铁路与城市轨道交通RailMetro轨道世界铁路那…

【智能算法应用】遗传粒子群算法(GA-PSO)求解选址问题

目录 1.算法原理2.数学模型3.结果展示4.参考文献5.代码获取 1.算法原理 【智能算法】粒子群算法&#xff08;PSO&#xff09;原理及实现 经典PSO算法用于连续空间优化问题&#xff0c;选址问题作为组合优化问题&#xff0c;需要在离散空间中求解。因此&#xff0c;考虑遗传算…

阮怀俊谈如何盘活和挖掘乡村文旅资源

近年来&#xff0c;浙江凭借高水平建设新时代美丽乡村&#xff0c;各项工作持续走在全国前列&#xff0c;最近&#xff0c;在国家发展改革委关于恢复和扩大消费措施的通知中也提到&#xff1a; “推广浙江‘千万工程’经验&#xff0c;建设宜居宜业和美乡村。实施文化产业赋能乡…

Libcity 笔记:自定义模型

在/libcity/model/trajectory_loc_prediction/&#xff0c;我们复制一份Deepmove.py&#xff0c;得到DM_tst.py&#xff0c;我们不改变其中的机制&#xff0c;只动class name 然后修改相同目录下的__init__.py&#xff1a; 修改task_config文件&#xff1a; 在config/model/tra…

带头单链表 C++实现

节点定义 带头单链表&#xff1a;我们只需要一个结点指针指向整个链表的第一个节点&#xff0c;这样我们就可以通过next指针访问整个链表内的所有节点 template<class T> struct ListNode {T _val;ListNode* _next;ListNode(const T &val):_val(val),_next(nullptr){…