C++STL之set与map的使用

news2024/11/18 1:43:22

本文目录

  • 前言
  • 一、关联式容器
  • 二、键值对(pair)
  • 三、树形结构的关联式容器
    • 1.set
      • 1.1set的介绍
      • 1.2set的使用
        • 1.2.1set的模板参数列表
        • 1.2.2set的构造
          • (1)构造空的set
          • (2)以数组的形式构造
          • (3)用迭代器区间进行构造
          • (4)拷贝构造
        • 1.2.3set的迭代器
          • (1)iterator begin()
          • (2)iterator end()
          • (3)const_iterator cbegin() const
          • (4)const_iterator cend() const
          • (5)reverse_iterator rbegin()
          • (6)reverse_iterator rend()
          • (7)const_reverse_iterator crbegin() const
          • (8)const_reverse_iterator crend() const
        • 1.2.4set的输出
          • (1)范围for输出
          • (2)通过正向迭代器遍历数据
          • (3)通过反向迭代器遍历数据
          • 补充知识:(4)数据的排序
        • 1.2.5set的容量
          • (1)bool empty() const
          • (2)size_t size() const
        • 1.2.6set的修改操作
          • (1)插入操作
          • (2)删除操作
            • 1.删除方法一:直接删除数据
            • 2.通过迭代器删除数据
            • 3.迭代器删除操作与直接删除操作对比
    • 2.multiset
    • 3.map
      • 3.1map的介绍
      • 3.2map的使用
        • 3.2.1map的模板参数说明
        • 3.2.2map的构造
        • 3.2.3map的修改操作
          • (1)插入操作
          • (2)删除操作
          • (3)交换操作
          • (4)清空元素操作
          • (5)查找元素操作
          • (5)个数记录(不常用)
        • 3.2.4map的迭代器
          • (1)begin()和end()
          • (2)cbegin()和cend()
          • (3)rbegin()和rend()
          • (4)crbegin()和crend()
        • 3.2.5map的容量
        • 3.2.6map中元素的访问
    • 4.multimap
  • 总结

前言

关于C++的STL库相信大家已经知道是什么了,也正是因为有了它才能让C++变得如此方便,今天我们就来看一下STL库中map与set的使用方法。

一、关联式容器

我们之前已经接触过STL中的部分容器,比如vector、list、deque等,这些容器称为序列式容器,因为其底层为线性的数据结构,里面存储的是元素本身,那么什么是关联式容器呢?
关联式容器也是用来存放数据的,与序列式容器不同的是,里面存储的是 <key,value> 结构的键值对,在数据检索是比序列式容器效率更高。

二、键值对(pair)

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息。
在这里插入图片描述

pair对象的构建
在这里插入图片描述

三、树形结构的关联式容器

根据应用场景的不同,STL总共实现了两种不同结构的关联式容器:树型结构与哈希结构。树型结构的关联式容器主要有四种:map、set、multimap、multiset。这四种容器的共同点是:使用平衡搜索树(即红黑树)作为其底层结构,容器中的元素是一个有序的序列。下面我们会依次介绍每一个容器。

1.set

1.1set的介绍

  1. set是按照一定次序存储元素的容器
  2. 在set中,元素的value也标识它(value就是key,类型为T),并且每个value必须是唯一的。
    set中的元素不能在容器中修改(元素总是const),但是可以从容器中插入或删除它们。
  3. 在内部,set中的元素总是按照其内部比较对象(类型比较)所指示的特定严格排序准则进行排序。
  4. set在底层是用二叉搜索树(红黑树)实现的。
    注意:
  5. 与map/multimap不同,map/multimap中存储的是真正的键值对<key, value>,set中只放value,但在底层实际存放的是由<value, value>构成的键值对。
  6. set中插入元素时,只需要插入value即可,不需要构造键值对。
  7. set中的元素不可以重复(因此可以使用set进行去重)。
  8. 使用set的迭代器遍历set中的元素,可以得到有序序列
  9. set中的元素默认按照小于来比较
  10. set中查找某个元素,时间复杂度为: l o g 2 n log_2 n log2n
  11. set中的元素不允许修改
  12. set中的底层使用二叉搜索树(红黑树)来实现

1.2set的使用

1.2.1set的模板参数列表

在这里插入图片描述

1.2.2set的构造

(1)构造空的set
set<int> s; //注意一定指明存储元素的类型
(2)以数组的形式构造
set<int> s = { 0,2,5,1,4,0,6 };
(3)用迭代器区间进行构造
int a[] = { 0,1,5,1,3,6,7 };
set<int> s(a, a + sizeof(a) / sizeof(int));
(4)拷贝构造
set<int> s = { 0,2,5,6,7 };
set<int> copy(s);

1.2.3set的迭代器

(1)iterator begin()

返回set中起始位置元素的迭代器

set<int> s = { 6,5,4,3,2,1 };
//插入数据后默认顺序为1 2 3 4 5 6 
set<int>::iterator it = s.begin();
cout << *it << endl;
//最后输出的结果为1
(2)iterator end()

返回set中最后一个元素的后面的迭代器

set<int> s = { 6,5,4,3,2,1 };
//插入数据后默认顺序为1 2 3 4 5 6 
set<int>::iterator it = s.end();
cout << *(it - 1) << endl;
//最后输出的结果为6

后面的迭代器的用法与前面的用法相同,我们就不再举例了。

(3)const_iterator cbegin() const

返回set中起始位置元素的const迭代器

(4)const_iterator cend() const

返回set中最后一个元素后面的const迭代器

(5)reverse_iterator rbegin()

返回set第一个元素的反向迭代器,即set的最后一个元素

(6)reverse_iterator rend()

返回set中最后一个元素后面的反向迭代器
注意: 这里经过特殊的处理,下一个元素并不是我们所理解的第一个元素的下一个元素。
在这里插入图片描述
这样进行处理的话,我们的使用迭代器遍历数据的操作就和普通迭代器一致了,不同的迭代器以相同的做法达到相同的目的。后面遍历以迭代器遍历数据的时候我们还会提及。

(7)const_reverse_iterator crbegin() const

返回set第一个元素的const反向迭代器,即set的最后一个元素

(8)const_reverse_iterator crend() const

返回set中最后一个元素后面的const反向迭代器

1.2.4set的输出

(1)范围for输出
set<int> s = { 1,0,9,3,2 };
for (auto e : s)
{
	cout << e << " ";
}
//输出结果:0 1 2 3 9

注意: set的输出结果是有序的,这是因为它的底层结构为红黑树的原因,可以自动排序,自动去重,所以在set中是没有重复的元素的。
set天生 去重 + 排序

set<int> s = { 1,0,1,0,9,3,2,9,3 };
for (auto e : s)
{
	cout << e << " ";
}
//最后的输出结果为: 0 1 2 3 9
(2)通过正向迭代器遍历数据
set<int> s = { 6,5,4,3,2,1 };
set<int>::iterator it = s.begin();
while (it != s.end())
{
	cout << *it << " ";
	it++;
}
//1 2 3 4 5 6
cout << endl;

相信这个对于大家来说都没有问题

(3)通过反向迭代器遍历数据
set<int> s = { 6,5,4,3,2,1 };
//1 2 3 4 5 6
set<int>::reverse_iterator it = s.rbegin();
while (it != s.rend())
{
	cout << *it << " ";
	it++;
}
//6 5 4 3 2 1
cout << endl;

但是不知道这个操作大家会怎么去理解。
错误思路
很多人会想rbegin()返回的就是最后一个元素的位置,rend()返回的是第一元素前一个位置,所以我要让it–,来实现遍历所有元素的目的,这样是不正确的,并且会报出错误。
正确思路
因为我们的语法操作要实现与使用begin(),end()时的一致,所以在这里it也是进行++,即it++,我们要把整个集合反过来看,即:6 5 4 3 2 1,反过来后rbegin()就可以理解为begin(),rend()就可以理解为end(),其他的操作与正式遍历一致,这样就很好理解了,it++也就不成问题。

补充知识:(4)数据的排序

这里涉及到了我们前面所学的仿函数less和greater,less是按照升序进行排序,greater是按照降序进行排序
1.set默认排升序,即less,我们在构造set时,如果没有给定仿函数,就以升序的形式输出数据
2.如果想排降序的话,就要在构造set对象时显示传仿函数

set<int,greater<int>> s = {0,1,2,3,4,5}; 

这样输出数据是就是降序

1.2.5set的容量

(1)bool empty() const

检测set是否为空,空返回true,否则返回false

(2)size_t size() const

返回set中有效元素的个数

1.2.6set的修改操作

(1)插入操作

insert()函数,只能用insert函数插入数据

set<int> s;
s.insert(4);
s.insert(2);
(2)删除操作
1.删除方法一:直接删除数据
int main()
{
	int a[] = { 0,1,5,1,3,6,7 };
	set<int> s(a, a + sizeof(a) / sizeof(int));
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		//*it = 10;//注意在set中无法赋值,否则会破坏树
		cout << *it << " ";
		it++;
	}
	//0 1 3 5 6 7
	cout << endl;
	//删除方法1:
	s.erase(3);//可以直接删除
	for (auto e : s)
	{
		cout << e << " ";
	}
	//0 1 5 6 7
	cout << endl;

	s.erase(30);//没有该元素,删除操作不会执行
	for (auto e : s)
	{
		cout << e << " ";
	}
	//0 1 5 6 7
	cout << endl;
	return 0;
}
2.通过迭代器删除数据

1.错误删除方式

int main()
{
	int a[] = { 0,1,5,1,3,6,7 };
	set<int> s(a, a + sizeof(a) / sizeof(int));
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	//0 1 3 5 6 7
	cout << endl;
	//通过迭代器删除数据
	//但是以这种方式查找没有的数据的位置时,返回的是end(),后面删除就会出错。所以不能直接删除
	set<int>::iterator it1 = s.find(20);
	//不进行判断,直接对找到的位置进行删除
	s.erase(it1);
	for (auto e : s)
	{
		cout << e << " ";
	}
	//0 1 3 5 6 7
	cout << endl;
	return 0;
}

在这里插入图片描述

如果不加判断条件就会报出这样的错误。
2.正确删除方式

int main()
{
	int a[] = { 0,1,5,1,3,6,7 };
	set<int> s(a, a + sizeof(a) / sizeof(int));
	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	//0 1 3 5 6 7
	cout << endl;
	set<int>::iterator it1 = s.find(20);
	if (it1 != s.end())//这里要先要判断一下是否有该数据
	{
		//存在该数据就会执行删除操作
		s.erase(it1);
	}
	for (auto e : s)
	{
		cout << e << " ";
	}
	//0 1 3 5 6 7
	cout << endl;
	return 0;
}

3.迭代器删除操作与直接删除操作对比

为什么第一种直接删除操作没有要删除的元素时不报错,那是因为直接删除操作就类似于我们所说的正确的迭代器删除元素的方法,相当于自己加了if判断语句,所以才没有报出错误。

2.multiset

既然已经有set容器了,那么multset又是用来干什么的呢?
multset允许键值冗余,也就是里面可以存相同的数据元素。在其他的一些函数和操作方面multiset与set是一致的,下面我们直接看他比较特殊的性质
直接通过代码和测试结果查看

int main()
{
	//multiset允许键值冗余
	int a[] = { 3,1,2,1,6,3,8,3,5,3 };
	multiset<int> s(a, a + sizeof(a) / sizeof(int));
	//排序但不去重
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	cout << s.count(1) << endl;//输出2,有两个1
	auto pos = s.find(3);//如果有多个值,返回中序的第一个
	while (pos != s.end())
	{
		cout << *pos << " ";
		++pos;
	}
	cout << endl;
	s.erase(3);
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;
	pos = s.find(1);//找到第一个1的位置,然后将第一个1删除
	if (pos != s.end())
	{
		s.erase(pos);
	}
	for (auto e : s)
	{
		cout << e << " ";
	}
	return 0;
}

在这里插入图片描述
通过上面的代码和结果的分析,我们就可以很好的了解multiset的使用了。

3.map

3.1map的介绍

  1. map是关联容器,它按照特定的次序(按照key来比较)存储由键值key和值value组合而成的元素。
  2. 在map中,键值key通常用于排序和唯一地标识元素,而值value中存储与此键值key关联的内容。键值key和值value的类型可能不同,并且在map的内部,key与value通过成员类型
    value_type绑定在一起,为其取别名称为pair:
    typedef pair<const key, T> value_type;
  3. 在内部,map中的元素总是按照键值key进行比较排序的。
  4. map中通过键值访问单个元素的速度通常比unordered_map容器慢,但map允许根据顺序对元素进行直接迭代(即对map中的元素进行迭代时,可以得到一个有序的序列)。
  5. map支持下标访问符,即在[]中放入key,就可以找到与key对应的value。
  6. map通常被实现为二叉搜索树(更准确的说:平衡二叉搜索树(红黑树))。

3.2map的使用

3.2.1map的模板参数说明

在这里插入图片描述

3.2.2map的构造

构造空的map

map<string,string> dict;

3.2.3map的修改操作

(1)插入操作

pair<iterator,bool> insert(const value_type& x)
在map中插入键值对,注意x是一个键值对,返回值也是键值对:iterator代表插入元素的位置,bool代表是否插入成功,这里insert函数的设计与后面map可以使用operator[]有密切关系

map<string, string> dict;//创建一个键值对为<string,string>类型的map
//插入方式一
pair<string,string> kvl("sort","排序");//创建一个结构体,隐式类型转换
dict.insert(kvl);//可以直接将kvl插入
//也可以使用下面的匿名对象将值插入dict对象中
dict.insert(pair<string, string>("test", "测试"));
//插入方式二
//如果感觉pair<string, string>太长就可以将其进行typedef,但是这个其实也不方便,毕竟还要typedef一下
typedef pair<string, string> DictKV;
dict.insert(DictKV("string", "字符串"));
插入方式三(平时都习惯这么定义)
dict.insert(make_pair("left", "左边"));
(2)删除操作

1.size_type erase(const key_type& x)
删除键值为x的元素

map<string, string> dict;
dict.insert(make_pair("left", "左边"));
dict.insert(make_pair("right", "右边"));
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("string", "字符串"));
//这里插入元素以后会按照属于key的比较方式进行排序
dict.erase("left");//删除key为left的元素
dict.erase("string");//删除key为string的元素

2.void erase(iterator position)
删除position位置上的元素

map<string, string> dict;
dict.insert(make_pair("left", "左边"));
dict.insert(make_pair("right", "右边"));
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("string", "字符串"));
map<string, string>::iterator it = dict.find("left");
dict.erase(it);//删除it位置的元素

3.void erase(iterator first,iterator last)
删除[first,last)区间中的元素

map<string, string> dict;
dict.insert(make_pair("left", "左边"));
dict.insert(make_pair("right", "右边"));
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("string", "字符串"));
dict.erase(dict.begin(),dict.end());//直接删除全部的元素
(3)交换操作

void swap (map& x)
交换两个map中的元素

map<string, string> dict1;
dict1.insert(make_pair("left", "左边"));
dict1.insert(make_pair("right", "右边"));
map<string, string> dict2;
dict2.insert(make_pair("sort", "排序"));
dict2.insert(make_pair("string", "字符串"));
//交换两个map中的元素
dict1.swap(dict2);
(4)清空元素操作

void clear()
将map中的元素清空

(5)查找元素操作

iterator find(const key_type& x)
在map中查找key为x的元素,找到返回该元素的位置的迭代器,否则返回end()

(5)个数记录(不常用)

size_type count(const key_type& x) const
返回key为x的键值在map中的个数,注意map中key是唯一的,因此该函数的返回值要么为0,要么为1,因此也可以用该函数来
检测一个key是否在map中。

3.2.4map的迭代器

(1)begin()和end()

begin:首元素的位置,end:最后一个元素的下一个位置

(2)cbegin()和cend()

与begin和end意义相同,但cbegin和cend所指向的元素不能修改

(3)rbegin()和rend()

反向迭代器,rbegin在end位置,rend在begin位置,其
++和–操作与begin和end操作移动相反

(4)crbegin()和crend()

与rbegin和rend位置相同,操作相同,但crbegin和crend所
指向的元素不能修改


以迭代器的方式遍历map中的元素

map<string, string> dict;
dict.insert(make_pair("left", "左边"));
dict.insert(make_pair("right", "右边"));
dict.insert(make_pair("sort", "排序"));
dict.insert(make_pair("string", "字符串"));
map<string, string>::iterator it = dict.begin();
//元素访问方式一(但是一般不这样使用)
while (it != dict.end())
{
	
	cout << (*it).first << ":" << (*it).second << endl;
	//*it为结构体,然后 以结构体.成员变量的形式访问
	it++;
}
cout << endl;

//元素访问方式二(一般使用方式)
it = dict.begin();
while (it != dict.end())
{
	cout << it->first << ":" << it->second << endl;
	//it->first 其实是 it->->first(it->返回的是结构体指针,但是这里编译器进行了优化)
	it++;
}
cout << endl;

3.2.5map的容量

bool empty() const
检测map中的元素是否为空,是返回true,否则返回false
size_type size() const
返回map中有效元素的个数

3.2.6map中元素的访问

mapped_type& operator[] (const key_type& k)
返回key对应的value
map的精髓就在operator[]这里,下面我们一起来看看他的功能,他远远没有表面看起来这么简单。
先来看一下该函数的介绍
在这里插入图片描述
下面我们来看看插入数据时的几种情景
在这里插入图片描述
上面的这几种情景是如何产生的呢,其实与该函数介绍中的最后一行有关,我将他单独拿了出来
在这里插入图片描述
这里可以看到该函数与insert函数有关系,我们还要再分析insert函数
在这里插入图片描述
下面我们分析整个函数流程
在这里插入图片描述
最后我们来看一下这里的[]有什么用处,或者说是他的应用场景,下面我会就举一个例子,但是用不同的方法实现,将二者对比一下
例:给一个水果的数组,我们要统计该数组中各种水果出现的次数
string a[] = { “苹果”,“香蕉”, “西瓜”, “西瓜”, “苹果”, “苹果”, “苹果”, “香蕉”, “苹果” };

string a[] = { "苹果","香蕉", "西瓜", "西瓜", "苹果", "苹果", "苹果", "香蕉", "苹果" };
map<string, int> CountMap;
for (auto& str : a)
{
	map<string, int>::iterator it = CountMap.find(str);
	if (it != CountMap.end())
	{
		it->second++;
	}
	else
	{
		CountMap.insert(make_pair(str, 1));
	}
}
map<string, int>::iterator it1 = CountMap.begin();
while (it1 != CountMap.end())
{
	cout << it1->first << ":" << it1->second << endl;
	it1++;
}

最后的输出结果:
在这里插入图片描述
上面的代码看上去还可以,但是有了operator[]后还有更简洁的代码,实现起来更加方便。

string a[] = { "苹果","香蕉", "西瓜", "西瓜", "苹果", "苹果", "苹果", "香蕉", "苹果" };
map<string, int> CountMap;
for (auto& e : a)
{
	//1.e不在CountMap中,插入pair(str,int()),然后对返回的次数++
	//2.str在CountMap中,返回value(次数)的引用,次数++
	CountMap[e]++;
}
map<string, int>::iterator it = CountMap.begin();
while (it != CountMap.end())
{
	cout << it->first << ":" << it->second << endl;
	it++;
}

最后的输出结果:
在这里插入图片描述
我们可以看到第二种代码实现起来更加方便简洁,这就是operator[]的作用。
注意: 在元素访问时,有一个与operator[]类似的操作at()(该函数不常用)函数,都是通过key找到与key对应的value然后返回其引用,不同的是:当key不存在时,operator[]用默认value与key构造键值对然后插入,返回该默认value,at()函数直接抛异常。

4.multimap

multimap与map的函数的作用都是相似的,就和multiset与set之间的关系一样,map中的key是唯一的,而multimap中key是可以重复的。并且在multimap中没有重载operator[]操作, 因为在该容器中key的值是可以重复的,这也就说明一个key值可能会对应多个value,所以不会实现该函数。

总结

关于C++中的map与set我们就讲解到这里了,map与set都是很重要的容器,大家下来后一定要自己动手去敲一敲代码,加深一下印象,同时也有利于后面的使用,如果文章中有错误大家可以在评论区指出,我看到后一定会第一时间修改,最后如果你觉得本章内容对你有用的话,就给一波三连吧,你们的支持就是我写博客最大的动力。

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

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

相关文章

一天变现100w就是这么简单!呆头鹅批量剪辑软件批量剪辑带货视频

呆头鹅剪辑是一款全自动的视频剪辑软件&#xff0c;包含剪辑、合成、去重、特效、配音、字幕、水印、后期处理、自动生成片头等功能&#xff0c;可以用于视频批量搬运&#xff0c;给视频增加特效&#xff0c;图片合成视频&#xff0c;视频混剪&#xff0c;自动加配音字幕&#…

Linux一看就会——make/Makefile

Linux一看就会——make/Makefile 背景 1.会不会写makefile&#xff0c;从一个侧面说明了一个人是否具备完成大型工程的能力。 2.一个工程中的源文件不计数&#xff0c;其按类型、功能、模块分别放在若干个目录中&#xff0c;makefile定义了一系列的 规则来指定&#xff0c;哪…

分享126个ASP源码,总有一款适合您

ASP源码 分享126个ASP源码&#xff0c;总有一款适合您 126个ASP源码DownLoad链接&#xff1a;https://pan.baidu.com/s/1wekzBbNE6JSFWtyLb_CdQg?pwdu1e0 提取码&#xff1a;u1e0 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下..…

I16-python中的Counter类

在很多场景中经常会用到统计计数的需求&#xff0c;比如在实现 KNN算法时统计 k 个标签值的个数&#xff0c;进而找出标签个数最多的标签值作为最终 kNN 算法的预测结果。Python内建的 collections 集合模块中的 Counter 类能够简洁、高效的实现统计计数。Counter 是 dict 字典…

Unity 3D 三维模型简介||

Unity 3D 三维模型简介 三维模型是用三维建模软件建造的立体模型&#xff0c;也是构成 Unity 3D 场景的基础元素。 Unity 3D 几乎支持所有主流格式的三维模型&#xff0c;如 FBX 文件和 OBJ 文件等。 开发者可以将三维建模软件导出的模型文件添加到项目资源文件夹中&#xf…

【服务器数据恢复】意外断电导致linux服务器故障的数据恢复案例

服务器数据恢复环境&故障&#xff1a; 某品牌730服务器&#xff0c;linux操作系统。 机房意外断电导致服务器部分文件丢失。 服务器数据备份&故障分析&#xff1a; 1、将linux服务器连接到准备好的数据恢复服务器上&#xff0c;以只读模式对服务器数据做镜像备份&#…

geoserver 图层访问权限控制

目录 一、环境 二、问题 三、设置登录才能访问图层 ①、创建身份验证过滤器 ②、创建过滤器 ③、校验 ④、过滤后的问题 一、环境 geoserver2.17.0 二、问题 1、geoserver图层不需要登录就能访问&#xff0c;是否存在安全隐患&#xff1f; 答&#xff1a;会有一定的安全隐…

【Linux操作系统】计算机体系结构和操作系统与进程概念深入理解

文章目录一.现代计算机体系结构1.和冯诺依曼体系结构的异同2.计算机的五大核心部件3.举例子:“我爱你”4.CPU,内存,磁盘的联系a.三者读写速度对比b.规定:CPU不直接和外设打交道二.操作系统1.操作系统三段论2.系统调用接口3.用户操作接口三.进程1.什么是进程?2.PCB3.查看进程4.…

222.完全二叉树的节点个数 |递归优化思路 + 复杂度分析

完全二叉树的节点个数 leetcode : https://leetcode.cn/problems/count-complete-tree-nodes/ 递归思路 递归的思路很简单, 假设们要统计一棵树的节点数, 那么 只要统计根节点的左子树的节点数, 和右子树的节点数加上根节点即可 那么, 假设我们要统计左子树的节点数, 其实就…

Centos7 Minimal 版本基本配置记录

每次搭测试环境之前都需要先装一台干净的虚拟机&#xff0c;然而 Centos7 Minimal 版本快速装完之后还需要配置&#xff1a;网络、国内源、一些基础工具&#xff08;net-tools、vim&#xff09;等才能远程连接和使用。记录一下&#xff0c;方便下次快速配置使用。 目录 1、网…

Docker镜像加载原理

文章目录什么是镜像 &#xff1f;Docker镜像加载原理UnionFS &#xff08;联合文件系统&#xff09;Docker镜像加载原理分层理解镜像Commit什么是镜像 &#xff1f; 镜像是一种轻量级、可执行的独立软件包&#xff0c;用来打包软件运行环境和基于运行环境开发的软件&#xff0…

C语言-自定义类型-枚举和联合(11.3)

目录 思维导图&#xff1a; 1.枚举 1.1 枚举类型的定义 1.2 枚举的优点 1.3 枚举的使用 2. 联合&#xff08;共用体&#xff09; 2.1 联合类型的定义 2.2 联合的特点 2.3 联合大小的计算 写在最后&#xff1a; 思维导图&#xff1a; 1.枚举 1.1 枚举类型的定义 例&…

Spring复习(三)

AOP AOP(Aspect Oriented Programming)面向切面编程&#xff0c;aop是一种设计思想&#xff0c;是oop面向对象编程的一种补充和完善&#xff0c;它通过预编译方式和运行期间动态代理的方式达成在不修改源代码的情况下&#xff0c;实现对业务逻辑的增强。 相关术语 横切关注点…

论文投稿指南——中文核心期刊推荐(农业基础科学)

【前言】 &#x1f680; 想发论文怎么办&#xff1f;手把手教你论文如何投稿&#xff01;那么&#xff0c;首先要搞懂投稿目标——论文期刊 &#x1f384; 在期刊论文的分布中&#xff0c;存在一种普遍现象&#xff1a;即对于某一特定的学科或专业来说&#xff0c;少数期刊所含…

【计组】存储器层次结构全景和局部性原理--《深入浅出计算机组成原理》(八)

课程链接&#xff1a;深入浅出计算机组成原理_组成原理_计算机基础-极客时间 目录 一、存储器层次结构全景 &#xff08;一&#xff09;SRAM &#xff08;二&#xff09;DRAM &#xff08;三&#xff09;存储器的层级结构 二、局部性原理 一、存储器层次结构全景 &…

Eureka读时加写锁,写时加读锁,到底是故意为之还是一个bug?

在对于读写锁的认识当中&#xff0c;我们都认为读时加读锁&#xff0c;写时加写锁来保证读写和写写互斥&#xff0c;从而达到读写安全的目的。但是就在我翻Eureka源码的时候&#xff0c;发现Eureka在使用读写锁时竟然是在读时加写锁&#xff0c;写时加读锁&#xff0c;这波操作…

uni-app | 从零创建一个新项目以及关于网络请求配置和分包

一、uni-app简介uni-app 是一个使用 Vue.js 开发所有前端应用的框架。开发者编写一套代码&#xff0c;可发布到 iOS、Android、H5、以及各种小程序&#xff08;微信/支付宝/百度/头条/QQ/钉钉/淘宝&#xff09;、快应用等多个平台。二、开发工具uni-app官方推荐使用HBuilderX来…

【JS 逆向百例】X-Bogus 逆向分析,JSVMP 纯算法还原

声明 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;不提供完整代码&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01; 本文章未…

72小时灵感冲刺,创意就该这么玩 | LigaAI Hackathon特别策划

2023 年 1 月 9 日至 12 日&#xff0c;LigaAI 团队全员出逃&#xff1a;放下迭代&#xff0c;暂缓需求&#xff0c;处处充斥着「可以摸鱼➕卷死他们」的矛盾又欢乐的气息。职场和远程的伙伴们无一不在热烈讨论、积极组队、抢占会议室、搜刮零食饮料…… 是什么让矜持内敛的技…

微信小程序的启动和渲染过程(加组件分类和组件的基本使用以及API分类)

小程序的启动过程 把小程序的代码包下载到本地解析app.json全局配置文件执行app.js小程序入口文件,调用App()创建小程序实例渲染小程序首页小程序启动完成 小程序页面渲染的过程 加载解析页面的.json配置文件加载页面的.wxml模板和.wxss样式执行页面的.js文件,调用page(创建…