c++STL——set和map的使用

文章目录

  • set和map的使用
    • set系列
      • 声名和定义
      • 默认成员函数
      • 迭代器
      • set的增删查
        • lower_bound和upper_bound
        • Insert接口
          • pair类
        • 对于查找的另一种使用
      • set和multiset的区别
    • map系列
      • 声名和定义
      • pair类的进一步介绍
      • 默认成员函数
      • map的增删查
      • map的数据修改
      • map和multimap的差异

set和map的使用

本篇文章将讲解一些STL库中的两个新的容器,即map系列和set系列。这两个看着很高深,其实就是我们在上一篇文章中实现的二叉搜索树。只不过底层是使用红黑树来实现的。红黑树就是对二叉搜索树进行了一些规定,使其查找效率会更高。但是本质也是搜索树。所以我们可以先直接学习使用。

学习完使用后我们再来一步步的引入底层实现的学习。

set系列

set系列对应的二叉搜索树其实就是key模式,即没有映射。

由于我们以前已经有了对序列式容器string、vector、list等的学习,我们已经初步掌握了STL库中接口的使用,现在我们通过查阅文档并且结合一些使用来看:
文档:
https://legacy.cplusplus.com/reference/set/

声名和定义

在这里插入图片描述
这里的T其实就是key,这里写的确实会令人误解。因为本质上二叉树对应的这些关联式容器都是以key作为关键字来进行查找、删除等操作的。这里我们跟着看一下文档就好了。如果到后期自行实现了改过来就好。

这个Compare模板参数类型给了一个less< T >,这是干什么用的呢?这个是用来规定搜索树的规则的。我们默认的规则是对于一个搜索树,右子树的key应该比根节点的大,左子树的key比根节点要小。默认传入的是小于的比较逻辑。但是如果想要反过来,需要传入大于的比较逻辑。比较逻辑本质是仿函数。

还有一个模板参数就是空间配置器了,这个知道就好,当前先不作了解。

默认成员函数

构造函数有很多个,甚至在c++不同的标准下有不同版本的。我们虽然学习的主要是c++98的语法,但是这个太古老了,有时候使用起来也不太方便。对于这些接口的使用可以查看一下c++11的标准:

//empty (1)	默认构造函数
explicit set (const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());
explicit set (const allocator_type& alloc);//range (2)	使用迭代器区间构造
template <class InputIterator>set (InputIterator first, InputIterator last,const key_compare& comp = key_compare(),const allocator_type& = allocator_type());//copy (3)	拷贝构造
set (const set& x);
set (const set& x, const allocator_type& alloc);//initializer list (5)	列表构造
set (initializer_list<value_type> il,const key_compare& comp = key_compare(),const allocator_type& alloc = allocator_type());//析构函数
~set();//copy (1) 赋值重载	set& operator= (const set& x);

这几个构造函数是我们需要重点掌握的。其实这些在之前都见过,所以就不细讲了。

迭代器

迭代器这种用法其实也是用过很多次。只不过之前我们学习的都是序列式容器,迭代器的遍历时很好理解的。但是现在学的时关联式容器,迭代器是如何遍历内部的数据的呢?

我们在实现BinaryTree的时候就说过,如果按照默认的搜索逻辑,那么对这个树进行中序遍历,得到的序列一定是升序(没有重复数据的前提下,也就是这里的set)。所以迭代器的遍历出来的结果就是升序的。只不过这个底层实现可能会有一些麻烦,但今天这篇文章只是介绍使用,还不需要对底层原理进行了解。

在这里插入图片描述
验证一下结果确实如此。因为c++的封装性,我们可以不用关注底层而直接调用这些接口来完成一些相同的功能。这就是学习STL的必要性。

至于其它的迭代器我就不再赘述了,因为这里的用法和以往学习的并没有太大的差别。

set的增删查

一般来讲,搜索树中的key是不允许被修改的。这是很好理解的,如果轻易被修改很容易破坏这个二叉搜索树的一些性质(如左小右大、又或是不能有重复的值)。所以在set系列中,是没有提供修改内容的接口的。我们重点看看增删查这三个功能即可。

Member types
key_type -> The first template parameter (T)
value_type -> The first template parameter (T)// 单个数据插⼊,如果已经存在则插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找val,返回val所在的迭代器,没有找到返回end()
iterator find (const value_type& val);
// 查找val,返回Val的个数
size_type count (const value_type& val) const;
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除val,val不存在返回0,存在返回1
size_type erase (const value_type& val);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回⼤于等于val位置的迭代器
iterator lower_bound (const value_type& val) const;
// 返回⼤于val位置的迭代器
iterator upper_bound (const value_type& val) const;

里面大部分都是非常熟悉的,只需要注意的就是使用迭代器位置或者区间进行一些修改容量的操作的时候,是很容易出现迭代器失效的问题的。在vs2022下是会直接触发警告并终止程序的。所以使用的后应当及时更新迭代器。

还有就是对于set系列和map系列,我们不要去使用算法库中包含的find接口。因为那个接口是按照迭代器去暴力查找的,时间复杂度是O(N),但是对于搜索树来讲,在比较接近完全二叉树的时候,查找效率非常高,基本上就是对数级别,效率是天差地别。这点注意一下。

重点要讲的是Insert这个接口和最后写的两个接口。

lower_bound和upper_bound

这两个接口是用来查找在指定位置数据的,举个例子:
比如有一个以{ 8, 3, 1, 10, 1, 6, 4, 7, 14, 13 }进行构造的set,那么中序遍历就是:
1 3 4 6 7 8 10 13 14。假设我现在就想要找出6到12之间的数据的位置,并且只对在这一段区间内的数据进行遍历,应当怎么办呢?

那就需要确定这段区间的上限和下限。也就是使用 lower_bound和upper_bound这两个接口。
lower_bound是找出第一个大于等于某个值的位置的。所以我们直接使用 lower_bound(6),返回的迭代器位置就是6那个位置的。
upper_bound是找出第一个大于传入值位置的迭代器,使用upper_bound(12),虽然这个序列中并不存在有12这个数据,但第一个大于12的位置是14,所以返回的迭代器是指向14的。

我们又知道,在c++STL库中,对于迭代器的使用都是左闭右开的,那这个时候不就刚好的将要找到的区间包含在了下限和上限中吗?

就算有相同的值,比如序列:1 3 3 3 4 4 5 6 7 8 8 10 11 13 14 18
找出2到17之间的数据,那么我们使用这两个接口:lower_bound找出第一个大于等于2的位置,即第一个3的位置,upper_bound找出第一个大于17的位置,即18。这不就刚好左闭右开找到了指定的区间吗?

这里需要注意的是,要什么区间,就传入对应的值。这两个接口必须这么实现。如果不这样实现会出问题。因为在使用这两个接口的时候,我们很可能不太知道里面的数据。那么在不知道的情况下,我们要找出2到17的数据是很困难的。如果一定要指定明确的数据开头(3和18),我们又不知道内部数据,那是很麻烦的,总不能每使用一次前都打印出来看一次。

当然我们查阅文档后会发现,还有一个equal_range的接口,这其实是找出指定数据的迭代器区间的。但是对于set来讲,没有太大的意义,因为内部的key不会重复,所以找到了迭代器区间也是多此一举,直接使用Find接口更好。这个接口我们放在后面map系列中讲解。

Insert接口

来看Insert接口。其实正常来讲,插入逻辑就是先按照二叉搜索树的插入逻辑插入后再按照红黑树的逻辑对树进行调整就可以了。我们当前也只关注使用,所以在底层目前不需要了解的情况下我们是会使用这些接口的。但是为什么,对于插入单个值的时候,返回值出现了一个pair的类呢?

pair类

这就要介绍一下这个类是什么了。我们以前写程序经常会遇到的一个问题,就是返回值不能一次性返回两个值。如果要返回多个值,需要通过外界传地址进来进行修改,实现间接的返回。但是这样比较麻烦。c++程序专门针对这一点进行改进,如果要返回的值是两个,就将这两个返回值分别作为pair类的第一个和第二个成员变量,同时进行一些函数的重载或者接口的实现。这样子就可以很轻松的将两个值同时带出来了。

所以对于第一个插入一个值的Insert接口,其实就是将插入后的位置和是否插入成功返回来而已。如果失败了那就让迭代器为默认迭代器,第二个bool类型的返回值给false即可。

当然库中提供了一个函数叫make_pair,可以很方便的将两个值构造成一个pair类。这些我们目前再使用set的时候不太需要关注,正常使用即可。具体的内容需要在map系列中进行讲解。

对于查找的另一种使用

在set中有一个接口叫count,这个接口正常来说是返回容器中指定数据的个数有多少的。但是set中元素的个数只可能是0或者1,所以这就很天然的能作为是否存在这个元素的返回值。如果是正常使用find接口,找到了返回指定元素位置的迭代器,找不到返回的是其end迭代器。这二者原理有差别。但是写法上用count更简单一些。如果只是想判断一下某个元素在不在,直接调用count接口就可以了:
在这里插入图片描述
这点注意一下即可。

set和multiset的区别

multiset其实和set差不多,只不过其支持有重复的值。其一些接口的用法相较于set有所改动。

对于其查找操作find,找到的数据就是中序遍历的第一个。这个要实现就需要修改一下查找逻辑。我们可以大致介绍一下:

假设已经在某个节点位置找到3这个数据了,但是我们并不能知道是否后续还有3的出现。要找的必须是中序遍历的第一个3。此时就得往下找。因为中序遍历排的是升序(默认规则为左小于等于 右大于),左子树必然先比根节点打印出来。所以找到3后应该继续往下找,也就是往左子树找,如果后续不存在3了,就说明只有一个3,那第一个3自然就是原来那个位置的。但是如果后续在左子树部分又找到了一个3,那就需要将中序遍历的第一个3的位置进行更新一下,然后继续执行上述操作,知道找不到为止。

其lower_bound和upper_bound的使用在前面也讲过了,也是可以正常使用的。

对于其删除操作,就需要注意的是传入什么值,就会把容器中所有的这些值删除。比如multiset中有5个3,现在调用erase接口删除3,那么全部3都会被删除,返回的是被删除的最后一个元素位置的下一个位置的迭代器。

所以set和mulitiset最大的区别就是,前者可以进行排序和去重,但是multiset只能进行排序。

map系列

讲完了set系列,再来看map系列:
map系列其实就是key_value形式,实现方式其实差不多,我们只挑出一些重点来讲一下。
文档:
https://legacy.cplusplus.com/reference/map/

声名和定义

map的声明如下,Key就是map底层关键字的类型,T是map底层value的类型,set默认要求Key支持小于比较,如果不支持或者需要的话可以自行实现仿函数传给第二个模版参数,map底层存储数据的内存是从空间配置器申请的。⼀般情况下,我们都不需要传后两个模版参数。map底层是用红黑树实现,增删查改效率是 O(logN) ,迭代器遍历是走的中序,所以是按key有序顺序遍历的。

template < class Key, // map::key_type
class T, // map::mapped_type
class Compare = less<Key>, // map::key_compare
class Alloc = allocator<pair<const Key,T> //map::allocator_type
> class map;

这一次文档内又把关键字写成Key了,但是值没有写成value,变成了T。这里注意一下。

pair类的进一步介绍

map系列底层对于key和value的存储就不是像我们实现的那样进行分开存储了。而是把两个数据放在一个pair类里面:

typedef pair<const Key, T> value_type;
template <class T1, class T2>
struct pair{
typedef T1 first_type;
typedef T2 second_type;T1 first;T2 second;pair(): first(T1()), second(T2()){}pair(const T1& a, const T2& b): first(a), second(b){}template<class U, class V>pair (const pair<U,V>& pr): first(pr.first), second(pr.second){}
};
template <class T1,class T2>
inline pair<T1,T2> make_pair (T1 x, T2 y){
return ( pair<T1,T2>(x,y) );
}

pair本质也是类,所以也是会有其默认成员函数。但是现在面临一个问题,如果说每个节点存储的数据域是一个pair类,我们怎么样访问到内部的数据呢?

这个时候有两种方法,直接使用操作符.进行访问,又或是可以重载->,就像实现list的时候那样,这样子就可以进行访问了。这一点我们等下结合迭代器仔细讲解。

默认成员函数

typedef pair<const Key, T> value_type;// empty (1) ⽆参默认构造
explicit map (const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());// range (2) 迭代器区间构造
template <class InputIterator>
map (InputIterator first, InputIterator last,
const key_compare& comp = key_compare(),
const allocator_type& = allocator_type());// copy (3) 拷⻉构造
map (const map& x);// initializer list (5) initializer 列表构造
map (initializer_list<value_type> il,
const key_compare& comp = key_compare(),
const allocator_type& alloc = allocator_type());//析构函数
~map();//copy (1) 赋值重载	
map& operator= (const map& x);

构造还是那么些构造方法,只不过存储的数据变成了一个pair类:
在这里插入图片描述
在使用的时候就需要特别注意了。传入的数据必须是pair才可以,可以像图中使用列表构造,将里面的每一个括号内的内容走隐式类型转换给一个pair。也可以自行使用make_pair进行构造出一个pair,又或是调用pair的构造函数也可以。

不转换为pair类再存储是会报错的。

map的增删查

map的增删查关注以下几个接口即可:
map增接口,插入的pair键值对数据,跟set所有不同,但是查和删的接口只用关键字key跟set是完全类似的,不过find返回iterator,不仅仅可以确认key在不在,还找到key映射的value,同时通过迭代还可以修改value。

现在我们来看一下:

Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>// 单个数据插⼊,如果已经key存在则插⼊失败,key存在相等value不相等也会插⼊失败
pair<iterator,bool> insert (const value_type& val);
// 列表插⼊,已经在容器中存在的值不会插⼊
void insert (initializer_list<value_type> il);
// 迭代器区间插⼊,已经在容器中存在的值不会插⼊
template <class InputIterator>
void insert (InputIterator first, InputIterator last);
// 查找k,返回k所在的迭代器,没有找到返回end()
iterator find (const key_type& k);
// 查找k,返回k的个数
size_type count (const key_type& k) const;
// 删除⼀个迭代器位置的值
iterator erase (const_iterator position);
// 删除k,k存在返回0,存在返回1
size_type erase (const key_type& k);
// 删除⼀段迭代器区间的值
iterator erase (const_iterator first, const_iterator last);
// 返回⼤于等k位置的迭代器
iterator lower_bound (const key_type& k);
// 返回⼤于k位置的迭代器
const_iterator lower_bound (const key_type& k) const;

需要注意的是,这里的逻辑都是依靠key在操作的,也就是只能操作key。

用法其实和set也差不了太多,只是需要注意,插入一个数据的时候是传入一个pair类,可以是匿名对象,可以是实例化的对象,也可以是走隐式类型转换。

其他的接口用法其实和set差不了太多,在这里就不讲了。

map的数据修改

这个部分我们需要重点讲解,即映射关系value的修改。

前面提到map⽀持修改mapped_type 数据,不支持修改key数据,修改关键字数据,破坏了底层搜索树的性质。
map第一个支持持修改的方式时通过迭代器,迭代器遍历时或者find返回key所在的iterator修改,map还有⼀个非常重要的修改接口operator[],但是operator[]不仅仅支持修改,还支持插入数据和查找数据,所以这是⼀个多功能复合接口。
需要注意从内部实现角度,map这里把我们传统说的value值,给的是T类型,typedef为
mapped_type。而value_type是红黑树结点中存储的pair键值对值。日常使用我们还是习惯将这里的T映射值叫做value。

Member types
key_type -> The first template parameter (Key)
mapped_type -> The second template parameter (T)
value_type -> pair<const key_type,mapped_type>//查找k,返回k所在的迭代器,没有找到返回end()
//如果找到了通过iterator可以修改key对应的mapped_type值
iterator find (const key_type& k);
// ⽂档中对insert返回值的说明
// The single element versions (1) return a pair, with its member pair::first
//set to an iterator pointing to either the newly inserted element or to the
//element with an equivalent key in the map. The pair::second element in the 
//pair is set to true if a new element was inserted or false if an equivalent 
//key already existed.// insert插⼊⼀个pair<key, T>对象
// 1、如果key已经在map中,插⼊失败,则返回⼀个pair<iterator,bool>对象,返回pair对象
//first是key所在结点的迭代器,second是false// 2、如果key不在在map中,插⼊成功,则返回⼀个pair<iterator,bool>对象,返回pair对象
//first是新插⼊key所在结点的迭代器,second是true//也就是说无论插⼊成功还是失败,返回pair<iterator,bool>对象的first都会指向key所在的迭代器
//那么也就意味着insert插⼊失败时充当了查找的功能,正是因为这⼀点,insert可以⽤来实现operator[]// 需要注意的是这⾥有两个pair,不要混淆了,⼀个是map底层红⿊树节点中存的pair<key, T>,
//另⼀个是insert返回值pair<iterator,bool>pair<iterator,bool> insert (const value_type& val);mapped_type& operator[] (const key_type& k);// operator的内部实现
mapped_type& operator[] (const key_type& k){
// 1、如果k不在map中,insert会插⼊k和mapped_type默认值,同时[]返回结点中存储
//mapped_type值的引⽤,那么我们可以通过引⽤修改返映射值。所以[]具备了插⼊+修改功能
// 2、如果k在map中,insert会插⼊失败,但是insert返回pair对象的first是指向key结点的
//迭代器,返回值同时[]返回结点中存储mapped_type值的引⽤,所以[]具备了查找+修改的功能pair<iterator, bool> ret = insert({ k, mapped_type() });iterator it = ret.first;return it->second;
}

我们重点关注的就是operator[]的重载,说白点就是,如果以key查找,存在就返回这个位置的迭代器和插入失败。不存在就插入进去,然后返回其节点的value的引用。

那这样子来统计出现次数就很简单了:

int main()
{string arr[] = { "苹果", "西瓜", "苹果", "西瓜", "苹果", "苹果", "西瓜",
"苹果", "香蕉", "苹果", "香蕉" };map<string, int> countMap;for (const auto& str : arr){// 先查找⽔果在不在map中// 1、不在,说明⽔果第⼀次出现,则插⼊{⽔果, 1}// 2、在,则查找到的节点中⽔果对应的次数++auto ret = countMap.find(str);if (ret == countMap.end()){countMap.insert({ str, 1 });}else{ret->second++;}}for (const auto& e : countMap){cout << e.first << ":" << e.second << endl;}cout << endl;return 0;
}

这是我们之前实现的统计次数代码,我们还可以这样写:
在这里插入图片描述
即使用operator[],当传入给方括号内的str存在,就返回这个迭代器位置value的引用。所以直接让其自增就可以表达出此时有的个数。

如果不存在也不怕,因为operator[]调用了insert接口,会直接插入。而int的默认构造是0,所以再对其自增一次变为1。逻辑完美闭环。

既然是返回引用,我们也可以直接对value进行赋值修改:

int main()
{map<string, string> dict;dict.insert(make_pair("sort", "排序"));// key不存在->插⼊ {"insert", string()}dict["insert"];// 插⼊+修改dict["left"] = "左边";// 修改dict["left"] = "左边、剩余";// key存在->查找cout << dict["left"] << endl;return 0;
}

在这里插入图片描述
最后的答案很明显是符合预期的。

map和multimap的差异

multimap和map的使用基本完全类似,主要区别点在于multimap支持关键值key冗余,那么
insert/find/count/erase都围绕着⽀持关键值key冗余有所差异,这里跟set和multiset完全⼀样,比如find时,有多个key,返回中序第⼀个。其次就是multimap不支持[],因为支持key冗余,[]就只能支持插入了,不能支持修改。

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

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

相关文章

什么是DGI数据治理框架?

DGI数据治理框架是由数据治理研究所&#xff08;Data Governance Institute, DGI&#xff09;提出的一套系统性方法论&#xff0c;旨在帮助企业或组织建立有效的数据治理体系&#xff0c;确保数据资产的高质量管理、合规使用和价值释放。以下是关于DGI数据治理框架的核心内容&a…

chrome 浏览器怎么不自动提示是否翻译网站

每次访问外国语网页都会弹出这个对话框&#xff0c;很是麻烦&#xff0c;每次都得手动关闭一下。 不让他弹出来方法&#xff1a; 设置》语言》首选语言》添加语言&#xff0c;搜索英语添加上 如果需要使用翻译&#xff0c;就点击三个点&#xff0c;然后选择翻译

LeetCode 热题 100 54. 螺旋矩阵

LeetCode 热题 100 | 54. 螺旋矩阵 大家好&#xff0c;今天我们来解决一道经典的算法题——螺旋矩阵。这道题在LeetCode上被标记为中等难度&#xff0c;要求我们按照顺时针螺旋顺序返回矩阵中的所有元素。下面我将详细讲解解题思路&#xff0c;并附上Python代码实现。 问题描述…

生成式AI将重塑的未来工作

在人类文明的长河中,技术革命始终是推动社会进步的核心动力。从蒸汽机的轰鸣到互联网的浪潮,每一次技术跃迁都在重塑着人类的工作方式与生存形态。而今,生成式人工智能(Generative AI)的崛起,正以超越以往任何时代的速度与深度,叩响未来工作范式变革的大门。这场变革并非…

【2025软考高级架构师】——2024年05月份真题与解析

摘要 本文内容是关于2025年软考高级架构师考试的相关资料&#xff0c;包含2024年05月份真题与解析。其中涉及体系结构演化的步骤、OSI协议中能提供安全服务的层次、数据库设计阶段中进行关系反规范化的环节等知识点&#xff0c;还提及了软考高级架构师考试的多个模块&#xff…

KAG:通过知识增强生成提升专业领域的大型语言模型(三)

目录 摘要 Abstract 1 Schema 2 Prompt 3 KAG-Builder 3.1 reader 3.2 splitter 3.3 extractor 3.4 vectorizer 3.5 writer 3.6 可选组件 4 示例 总结 摘要 本周深入学习了 KAG 项目中的 Schema、Prompt 以及 KAG-Builder 相关代码知识&#xff0c;涵盖了其定义、…

Gitea windows服务注册,服务启动、停止、重启脚本

修改配置文件 查看COMPUTERNAME echo %COMPUTERNAME%进入配置文件D:\gitea\custom\conf\app.ini&#xff0c;将 Gitea 设置为以本地系统用户运行 如果结果是 USER-PC&#xff0c;那么 RUN_USER USER-PC$ RUN_USER COMPUTERNAME$SQLite3 PATH配置&#xff0c;更改为包含完整…

矿泉水瓶的绘制

1.制作中心矩形&#xff0c;大小为60&#xff0c;注意设置矩形的两条边相等 2.点击拉伸&#xff0c;高度为150mm 3.使用圆角命令&#xff0c;点击连接到开始面&#xff0c;同时选中4条边&#xff0c;进行圆角转化&#xff0c;圆角大小为10mm&#xff0c;点击多半径圆角&#xf…

【程序+论文】大规模新能源并网下的火电机组深度调峰经济调度

目录 1 主要内容 讲解重点 2 讲解视频及代码 1 主要内容 该视频为《大规模新能源并网下的火电机组深度调峰经济调度》代码讲解内容&#xff0c;该程序有完全对照的论文&#xff0c;以改进IEEE30节点作为研究对象&#xff0c;系统包括5个火电机组和2个新能源机组&#xff0c;…

​​工业机器人智能编程:从示教器到AI自主决策​​

工业机器人智能编程:从示教器到AI自主决策 引言 工业机器人作为智能制造的核心装备,其编程方式正经历革命性变革。传统示教器编程效率低下,平均每个路径点需要30秒人工示教,而复杂轨迹编程可能耗时数周。随着AI技术的发展,工业机器人编程正朝着"所见即所得"的…

n8n 构建一个 ReAct AI Agent 示例

n8n 构建一个 ReAct AI Agent 示例 0. 引言1. 详细步骤创建一个 "When Executed by Another Workflow"创建一个 "Edit Fields (Set)"再创建一个 "Edit Fields (Set)"创建一个 HTTP Request创建一个 If 节点在 true 分支创建一个 "Edit Fiel…

Monorepo项目多项目一次性启动工具对比与实践

Monorepo项目多项目一次性启动工具对比与实践 在现代软件开发中&#xff0c;Monorepo&#xff08;单一仓库&#xff09;模式越来越受到开发者的青睐。Monorepo将多个相关的项目或包集中在一个仓库中进行管理&#xff0c;方便依赖共享、代码复用和统一发布。在Monorepo项目开发…

笔记整理六----OSPF协议

OSPF 动态路由的分类&#xff1a; 1.基于网络范围进行划分--将网络本身划分为一个个AS&#xff08;自治系统---方便管理和维护&#xff09; 内部网关协议---负责AS内部用户之间互相访问使用的协议 IGP--RIP EIGRP ISIS OSPF 外部网关协议--负责AS之间&#xff08;整个互联网&…

网络编程,使用select()进行简单服务端与客户端通信

这里在Ubuntu环境下演示 一般流程 服务端常用函数&#xff1a; socket()&#xff1a;创建一个新的套接字。bind()&#xff1a;将套接字与特定的IP地址和端口绑定。listen()&#xff1a;使套接字开始监听传入的连接请求。accept()&#xff1a;接受一个传入的连接请求&#xff…

智能决策支持系统的基本概念与理论体系

决策支持系统是管理科学的一个分支&#xff0c;原本与人工智能属于不同的学科范畴&#xff0c;但自20世纪80年代以来&#xff0c;由于专家系统在许多方面取得了成功&#xff0c;于是人们开始考虑把人工智能技术用于计算机管理中来。在用计算机所进行的各种管理中&#xff0c;如…

驱动开发系列55 - Linux Graphics QXL显卡驱动代码分析(二)显存管理

一:概述 前面介绍了当内核检测到匹配的PCI设备后,会调用 qxl_pci_probe 初始化设备,其中会调用qxl_device_init 来初始化设备,为QXL设备进行内存映射,资源分配,环形缓冲区初始化,IRQ注册等操作,本文展开说说这些细节,以及介绍下QXL的显存管理。 二:QXL设备初始化细节…

洛谷 P1495:【模板】中国剩余定理(CRT)/ 曹冲养猪

【题目来源】 https://www.luogu.com.cn/problem/P1495 https://www.acwing.com/problem/content/225/ 【题目描述】 自从曹冲搞定了大象以后&#xff0c;曹操就开始捉摸让儿子干些事业&#xff0c;于是派他到中原养猪场养猪。可是曹冲满不高兴&#xff0c;于是在工作中马马虎…

配置和使用持久卷

配置和使用持久卷 文章目录 配置和使用持久卷[toc]一、PV与PVC的持久化存储机制二、PV和PVC的生命周期三、创建基于NFS的PV1.准备NFS共享目录2.创建PV 四、基于PVC使用PV1.创建PVC2.使用PVC 五、基于StorageClass实现动态卷制备1.获取NFS服务器的连接信息2.获取nfs-subdir-exte…

FreeRTOS菜鸟入门(十)·消息队列

目录 1. 基本概念 2. 数据存储 3. 运作机制 4. 阻塞机制 4.1 出队阻塞 4.2 入队阻塞 5. 操作示意图 5.1 创建队列 5.2 向队列发送第一个消息 5.3 向队列发送第二个消息 5.4 从队列读取消息 6. 消息队列控制块 7. 消息队列常用函数 7.1 消息队列创建…

java 洛谷题单【算法2-2】常见优化技巧

P1102 A-B 数对 解题思路 输入读取与初始化&#xff1a; 使用 Scanner 读取输入。n 表示数组的长度&#xff0c;c 表示目标差值。使用一个 HashMap 存储数组中每个数字及其出现的次数&#xff0c;方便快速查找。数组 a 用于存储输入的数字。 构建哈希映射&#xff1a; 遍历数…