[C++进阶]map和set

news2025/2/1 3:02:14

一、关联式容器

STL中的部分容器,比如:vector、list、deque、forward_list(C++11)等,这些容器统称为序列式容器,因为其底层为线性序列的数据结构,里面存储的是元素本身。

那什么是关联式容器?它与序列式容器有什么区别?

关联式容器也是用来存储数据的,与序列式容器不同的是,其里面存储的是<key, value>结构的键值对,在数据检索时比序列式容器效率更

二、pair键值对

用来表示具有一一对应关系的一种结构,该结构中一般只包含两个成员变量key和value,key代表键值,value表示与key对应的信息

stl里面对于pair的定义是这样的

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)
	{}
};

https://legacy.cplusplus.com/reference/utility/pair/?kw=pair

pair有三种构造函数,不难看出,分别是无参的构造,拷贝构造,以及通过两个值来进行构造

除了三种构造函数以外,它还有一种方式,也可以生成pair对象。这个不是一个成员函数,所以可以直接使用

先看看这些,记住就好

三、set

1. set的介绍

如下图所示:

我们可以注意到它的模板参数是要比其他容器多一个的,这个容器我们也可以看到是一个仿函数。我们使用优先级队列的时候也用过这个仿函数。

集合是按照特定顺序存储唯一元素的容器。

在一个集合中,元素的值也标识它(值本身就是键,类型为 T),并且每个值必须是唯一的。集合中元素的值在容器中不能修改(元素总是 const 类型的),但是可以从容器中插入或删除元素。

在内部,集合中的元素总是按照其内部比较对象(类型为 Compare )指示的特定严格弱排序标准排序。

在通过键访问单个元素时,set 容器通常比 unordered_set 容器慢,但是它们允许基于次序对子集进行直接迭代。

集合通常以二叉搜索树的形式实现。这颗二叉搜索树是红黑树。

set其实就相当于key模型的二叉搜索树

注意:set里面的值是不可以被修改的,它实现这一点的原理就是将迭代器和const迭代器都是const迭代器没有任何区别。

2. set的部分接口

1.构造函数

可以注意到,一共有三个构造函数,第一个是全缺省的默认构造函数,第二个是迭代器区间构造,第三个是拷贝构造。

不过这个拷贝构造的代价比较大,因为它是一个树的拷贝,而且析构也一样有很大的代价。

2.insert

pair<iterator,bool> insert (const value_type& val);

pair<iterator,bool> insert (value_type&& val);

插入一个元素,返回一个pair对象,其中包含一个迭代器和bool值

如果插入成功(set中没有该元素),pair中存放插入位置的迭代器和true

如果插入失败(set中已经存在该元素),pair中存放对应元素的迭代器和false

3,erase

size_type erase (const value_type& val);

删除一个元素,返回删除的元素个数

4.swap

void swap (set& x);

交换两个set中的元素

5.clear

void clear() noexcept;

清除set中的所有元素

6.find

const_iterator find (const value_type& val) const;

iterator find (const value_type& val);

在set中寻找目标元素并返回其位置的迭代器

如果没有该元素,则返回end()

7.count

size_type count (const value_type& val) const;

返回set中值为val的元素个数

因为set的去重性,所以返回值只会是0或1

8.size

size_type size() const noexcept;

返回set中的元素个数

9.empty

bool empty() const noexcept;

判断set是否为空

还有很多接口其实看一眼大概知道是干嘛的

我们也可以简单的测试一下代码:

#include<iostream>
#include<set>
using namespace std;
void test()
{
	set<int> s;
	s.insert(1);
	s.insert(5);
	s.insert(2);
	s.insert(2);
	s.insert(6);
	s.insert(9);
	s.insert(8);
	s.insert(4);
	s.insert(4);
	s.insert(3);

	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}
int main()
{
	test();
	return 0;
}

运行结果:

我们可以发现,set可以自动去重和排序

而它的去重原理就是:一个值已经有了,我们就不插入即可

同时我们也可以试一下set的删除,我们在后面加上:

	auto pos = s.find(3);
	s.erase(pos);
	s.erase(4);
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

也就是:

#include<iostream>
#include<set>
using namespace std;
void test()
{
	set<int> s;
	s.insert(1);
	s.insert(5);
	s.insert(2);
	s.insert(2);
	s.insert(6);
	s.insert(9);
	s.insert(8);
	s.insert(4);
	s.insert(4);
	s.insert(3);

	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	auto pos = s.find(3);
	s.erase(pos);
	s.erase(4);
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

}
int main()
{
	test();
	return 0;
}

运行结果:

如上有演示了两种删除,从表面来看,看上去好像没有什么大的区别。我们可以认为下面的是通过上面的进行实现的。

不过在find中如果没有找到的话,是会直接报错的。

像这样:

#include<iostream>
#include<set>
using namespace std;
void test()
{
	set<int> s;
	s.insert(1);
	s.insert(5);
	s.insert(2);
	s.insert(2);
	s.insert(6);
	s.insert(9);
	s.insert(8);
	s.insert(4);
	s.insert(4);
	s.insert(3);

	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	auto pos = s.find(7);
	s.erase(pos);
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

}
int main()
{
	test();
	return 0;
}

运行一下:

而如果用另一种方式没有找到是什么也不会发生的

#include<iostream>
#include<set>
using namespace std;
void test()
{
	set<int> s;
	s.insert(1);
	s.insert(5);
	s.insert(2);
	s.insert(2);
	s.insert(6);
	s.insert(9);
	s.insert(8);
	s.insert(4);
	s.insert(4);
	s.insert(3);

	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//auto pos = s.find(7);
	//s.erase(pos);
	s.erase(7);
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

}
int main()
{
	test();
	return 0;
}

运行结果:

为了反正上面报错,我们可以加一个判断来解决这个问题

这是因为find找不到会返回一个end迭代器导致的,在容器中搜索与val等效的元素,如果找到则返回一个迭代器,否则返回一个迭代器给set::end。

但是我们知道算法库里面也有一个find,这个find似乎也能完成这个操作

#include<iostream>
#include<algorithm>
#include<set>
using namespace std;
void test()
{
	set<int> s;
	s.insert(1);
	s.insert(5);
	s.insert(2);
	s.insert(2);
	s.insert(6);
	s.insert(9);
	s.insert(8);
	s.insert(4);
	s.insert(4);
	s.insert(3);

	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	//auto pos = s.find(8);
	auto pos = find(s.begin(), s.end(), 2);
	if(pos!=s.end())
	s.erase(pos);
	s.erase(7);
	for (auto e : s)
	{
		cout << e << " ";
	}
	cout << endl;

}
int main()
{
	test();
	return 0;
}

运行结果:

其实这两个find是存在一定的差异的,set里面的find是根据二叉搜索树的性质来进行查找的(实际上红黑树,效率更优),时间复杂度为O(logN),而算法库里面的find是一个一个查找的,时间复杂度为O(N)。


count的作用是然后返回一个值出现了几次。不过因为set容器里面的值是唯一的,所以一个元素在这里面,返回1,否则返回0

如下的代码可以演示find和count寻找一个数据的使用

void test()
{
	set<int> s;
	s.insert(1);
	s.insert(5);
	s.insert(2);
	s.insert(4);
	s.insert(4);
	s.insert(3);

	set<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;

	if (s.find(5) != s.end())
	{
		cout << "找到了" << endl;
	}
	if (s.count(5))
	{
		cout << "找到了" << endl;
	}
}
int main()
{
	test();
	return 0;
}

运行结果:

10.lower_bound和upper_bound

将迭代器返回到下限

返回一个迭代器,该迭代器指向容器中的第一个元素,该元素不被视为位于 val 之前(即,它要么是等效的,要么是位于 val 之后)。
该函数使用其内部比较对象 (key_comp) 来确定这一点,并向第一个元素返回一个迭代器,key_comp(element,val) 将返回 false。
如果使用默认比较类型 (less) 实例化 set 类,则该函数将返回一个迭代器到不小于 val 的第一个元素。
类似的成员函数 upper_bound 具有与 lower_bound 相同的行为,但集合包含等效于 val 的元素的情况除外:在这种情况下,lower_bound返回指向该元素的迭代器,而 upper_bound返回指向下一个元素的迭代器。

将迭代器返回到上限

返回一个迭代器,该迭代器指向容器中的第一个元素,该元素被视为位于 val 之后。
该函数使用其内部比较对象 (key_comp) 来确定这一点,并向第一个元素返回迭代器,key_comp(val,element) 将返回 true。
如果 set 类使用默认比较类型 (less) 进行实例化,则该函数将返回一个大于 val 的第一个元素的迭代器。
类似的成员函数 lower_bound 具有与 upper_bound 相同的行为,但集合包含等效于 val 的元素的情况除外:在这种情况下,lower_bound返回指向该元素的迭代器,而 upper_bound返回指向下一个元素的迭代器。

我们继续写一段代码来测试:

void test()
{
	set<int> myset;
	set<int>::iterator itlow, itup;

	for (int i = 1; i < 10; i++) myset.insert(i * 10); // 10 20 30 40 50 60 70 80 90

	itlow = myset.lower_bound(30);
	itup = myset.upper_bound(60);

	myset.erase(itlow, itup);   // 10 20 70 80 90

	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;
}
int main()
{
	test();
	return 0;
}

运行结果:

lower_bound和upper_bound一个设置为>=,一个设置为<。这样刚好可以将我们输入值所处的区间进行控制,刚好满足左闭右开。无论是构造也好,删除也好,插入也好都是刚好十分方便的。

11. equal_range

获取相等元素的范围

返回一个范围的边界,该范围包括容器中等效于 val 的所有元素。
由于 set 容器中的所有元素都是唯一的,因此返回的范围最多包含单个元素。
如果未找到匹配项,则返回的范围长度为零,根据容器的内部比较对象 (key_comp),两个迭代器都指向被视为位于 val 之后的第一个元素。
如果容器的比较对象反射性地返回 false(即,无论元素作为参数传递的顺序如何),则认为集合中的两个元素是等效的。

我们可以看这段代码:

void test()
{
	std::set<int> myset;

	for (int i = 1; i <= 5; i++) myset.insert(i * 10);   // myset: 10 20 30 40 50

	std::pair<std::set<int>::const_iterator, std::set<int>::const_iterator> ret;
	ret = myset.equal_range(35);

	std::cout << "the lower bound points to: " << *(ret.first) << '\n';
	std::cout << "the upper bound points to: " << *(ret.second) << '\n';


	myset.erase(ret.first, ret.second);

	for (auto e : myset)
	{
		cout << e << " ";
	}
	cout << endl;
}
int main()
{
	test();
	return 0;
}

运行结果:

这是因为这段区间内并不存在35,所以会返回一个比他大的数值所在的区间。且这两个是相等的。如果我们要找的是等于30的区间的话,就是这样的。由于set里面没有重复元素,所以其实只能找到那一个元素,从这个容器的角度来看,似乎这个寻找相等区间的函数并没有什么太大的用处,还不如find呢?其实关于这些函数,主要还是为了另外一个容器设置的

3.multiset容器

这个容器是是一个允许键值冗余的一个容器,其接口和set一模一样。所以我们可以认为,刚刚的关于一些范围的容器,都是为了它而设计的

我们可以试用一下这个容器:

void test()
{
	multiset<int> s;
	s.insert(1);
	s.insert(5);
	s.insert(2);
	s.insert(2);
	s.insert(6);
	s.insert(9);
	s.insert(8);
	s.insert(4);
	s.insert(4);
	s.insert(3);

	multiset<int>::iterator it = s.begin();
	while (it != s.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
}
int main()
{
	test();
	return 0;
}

运行结果:

对于这个容器,find找到某个值,从找到的那个值开始进行打印,就会将从这个值以后的全部打印。其次我们的count也就可以计算出这个值的数量了,之前的在set中的count,由于set天然的去重了,所以只能用于检测是否存在某个值,而现在的话就可以统计数量了。然后关于我们的找某个数的范围,这个函数也就可以查找2的所有范围了。于是我们就可以删除掉2所在的区间了。

所以count和equal_range这两个函数对于multiset容器而言更有意义。

四、map

1.map的介绍

如下所示,这个容器一共有四个参数,Key和T

映射是关联容器,存储由键值和映射值按照特定顺序组合而成的元素。

在map中,键值通常用于排序和唯一标识元素,而映射值存储与该键相关联的内容。键和映射值的类型可能不同,组合在成员类型value_type中,这是一种组合了两者的pair类型:

typdef pair<const Key, T> value_type;

在内部,map中的元素总是按照键进行严格的弱排序,排序标准由内部比较对象(类型为Compare)表示。在通过键访问各个元素时,Map容器通常比unordered_map容器慢,但它们允许根据键的顺序直接迭代子集。映射后的值可以通过方括号运算符((operator[])直接访问。

映射通常以二叉查找树的形式实现。这里的模板参数中,Key和T类似于key-val模型中的key和val的模板参数。这些模板类型都被define为了key_type和mapped_type。同时还有value_type就相当于将这两个给结合到一块,放到了pair容器中。方便我们操控里面的数据,并且里面的key_type给的是const类型,这就说明了map中的key是不可以被修改的,但是value是可以被修改的。

2. map的一些接口

1.insert

这个函数有三个重载,后两个是使用迭代器区间进行插入的。第一个是直接插入一个value_type类型的数据。value_type其实就是键值对,因为他是key-val模型的.

插入元素:

通过插入新元素来扩展容器,从而有效地通过插入的元素数来增加容器大小。
由于map中的元素键是唯一的,因此插入操作会检查每个插入的元素是否具有与容器中已有的元素的键等效的键,如果是,则不会插入该元素,从而返回一个迭代器到此现有元素(如果函数返回一个值)。
有关允许重复元素的类似容器,请参阅 multimap。
在map中插入元素的另一种方法是使用成员函数 map::operator[]。
在内部,map容器按照其比较对象指定的标准,按键保持其所有元素的排序。元素始终按照此顺序插入到其各自的位置。
这些参数确定插入了多少个元素以及它们被初始化到哪些值:

对于这个函数的返回值,他返回的也是一个pair类型的对象。

如果插入的时候key已经在树里面,那么返回pair<树里面key的迭代器,false>

如果插入的时候key并未在树里面,那么返回pair<新插入key的迭代器,true>

所以insert从某种程度上也具有了查找的功能

如下代码所示,该段代码演示了我们对map里面插入数据的几种用法,我们可以直接传一个pair对象过去,也可以传pair的匿名对象,也可以使用make_pair函数来进行,当然我们可能会认为make_pair函数要通过调用一个函数来进行创建对象对否开销有点大,其实不是的,在这里编译器会直接将这个变成内联函数进行优化,实际效率相当于直接传入一个对象。除了前面三种以外,C++11还支持了多参数的构造函数隐式类型转换。所以我们可以直接使用多参数的构造函数隐式类型转换。

上面几种方式都是非常不错的,但是比较建议使用make_pair函数来创建。这个比较简洁,且有的C++编译器如果不支持C++11的话这个函数也是可以直接使用的。

在map里面我们取出的数据都是pair类型的,这是因为C++只能返回一个值,不能返回多个值。所以我们必须使用pair对象进行返回。然后C++也不支持pair的流插入和流提取,因为并没有进行重载。所以我们需要解引用后,拿到的只是一个结构体,我们还需要在访问里面的值。或者我们可以直接使用->也是很方便的。

void test_map1()
{
	map<string, string> dict;
	pair<string, string> kv1("insert", "插入");
	dict.insert(kv1);
	dict.insert(pair<string, string>("sort", "排序"));
	dict.insert(make_pair("remove", "改革"));
	dict.insert({ "process","过程" });//C++11 多参数的构造函数隐式类型转换

	map<string, string>::iterator it = dict.begin();
	while (it != dict.end())
	{
		cout << (*it).first << (*it).second << endl;
		cout << it->first << it->second << endl;
		it++;
	}

	for (const auto& e : dict)
	{
		cout << e.first << " " << e.second << endl;
	}
}

int main()
{
	test_map1();
	return 0;
}

运行结果:

需要注意的是,如果插入的时候,key相同,但是val不相同,是不会插入进去的,也不会覆盖进去的。即插入过程中,只比较key。key相同就不插入了。

2.erase

上面是关于map的一些插入接口,还有一些接口是删除接口。也比较常见,三种删除,分别是直接删除某个迭代器位置的删除,或者给一个key去删除,注意不是val,只需要一个key就可以删除了。第三种就是删除一个迭代器区间。

我们也可以注意到,查找和删除都只与key有关系,与其他无关。

擦除元素

从容器中删除单个元素或一系列元素 ([first,last))。
这有效地减小了容器尺寸,减少了被移除的元素数量,这些元素被破坏了。

3.find

获取元素的迭代器

在容器中搜索键等同于 k 的元素,如果找到,则返回一个迭代器,否则返回一个迭代器到 map::end。
如果容器的比较对象反射性地返回 false(即,无论元素作为参数传递的顺序如何),则认为两个是等效的。
另一个成员函数 map::count 可用于检查特定键是否存在。

4.count

对具有特定键的元素进行计数

在容器中搜索键等效于 k 的元素,并返回匹配项数。
由于map容器中的所有元素都是唯一的,因此该函数只能返回 1(如果找到元素)或 0(否则)。
如果容器的比较对象反射性地返回 false(即,无论键作为参数传递的顺序如何),则认为两个是等效的。

3. map的[]运算符重载

当我们使用map的insert接口和find接口的时候,我们可以来实现在之前二叉搜索树中的统计个数的代码。

void test_map2()
{
	string arr[] = { "铅笔", "书本", "书本", "铅笔", "钢笔", "钢笔", "橡皮","铅笔", "橡皮", "书本", "橡皮" };
	map<string, int> countMap;
	for (auto e : arr)
	{
		map<string, int>::iterator pos = countMap.find(e);
		if (pos == countMap.end())
		{
			countMap.insert(make_pair(e, 1));
		}
		else
		{
			pos->second++;
		}
	}
	map<string, int>::iterator it = countMap.begin();
	while (it != countMap.end())
	{
		cout << it->first << ":" << it->second << endl;
		it++;
	}
}

int main()
{
	test_map2();
	return 0;
}

运行结果:

但是事实上我们可以将代码变得更加简洁。我们来看一下map的[]运算符重载

访问元素

如果 k 与容器中元素的键匹配,则该函数将返回对其映射值的引用。
如果 k 与容器中任何元素的键不匹配,则该函数将插入一个带有该键的新元素,并返回对其映射值的引用。请注意,即使没有为元素分配映射值(元素是使用其默认构造函数构造的),这也会始终将size增加 1。
类似的成员函数 map::at 在具有键的元素存在时具有相同的行为,但在不存在时会引发异常。
对此函数的调用等效于:

(*((this->insert(make_pair(k,mapped_type())).first)).second

简而言之,就是给一个key,如果这个key在map中存在,返回它的val,如果不存在,那么就创建一个pair对象插入进去,这个pair对象的first是key,pair中的second是val类型的默认构造函数。

这样我们就可以将上面代码简化为下面代码了。countMap对象中,它的两个参数是string和int,第一次的时候不存在,所以会创建一个pair<string,int>对象。int则会调用它的默认构造函数,即结果为0。然后有一个++,所以最终会将这个值给插入进去。

void test_map3()
{
	string arr[] = { "铅笔", "书本", "书本", "铅笔", "钢笔", "钢笔", "橡皮","铅笔", "橡皮", "书本", "橡皮" };
	map<string, int> countMap;
	for (auto e : arr)
	{
		countMap[e]++;
	}
	map<string, int>::iterator it = countMap.begin();
	while (it != countMap.end())
	{
		cout << it->first << ":" << it->second << endl;
		it++;
	}
}
int main()
{
	test_map3();
	return 0;
}

运行结果:

这个[]运算符重载其实就是靠插入函数实现的,因为无论插入成功与否,insert会返回一个pair对象,pair对象的first就是就是新插入进去结点或者已有结点的迭代器。然后我们直接访问这个迭代器指向的second即可。

除了上面的统计个数的场景,我们还可以试一下下面的单词翻译的场景

void test_map4()
{
	map<string, string> dict;
	pair<string, string> kv1("insert", "插入");
	dict.insert(kv1);
	dict.insert(pair<string, string>("sort", "排序"));
	dict.insert(make_pair("remove", "改革"));
	dict.insert({ "process","过程" });//C++11 多参数的构造函数隐式类型转换

	dict["remov"] = "xxx";
	dict["process"] = "进程";
	dict["access"] = "接受,道路";
	cout << (dict["set"] = "集合") << endl;
	for (auto e : dict)
	{
		cout << e.first << " " << e.second << endl;
	}
}
int main()
{
	test_map4();
	return 0;
}

运行结果:

我们可以注意到,通过[]运算符重载,我们可以实现对原来的值进行修改,如果原来没有可以插入。也可以进行查找+插入等等一系列操作。

4. multimap容器

这个容器与map之间的关系就好像set与multiset之间的关系一样。接口都是一样的,不同的就是这个容器允许重复元素出现

还有一共不同就是这个容器没有提供[]运算符重载了,其实也是比较合理的,因为此时一个key可以有很多个val,是没法确定要哪一个的。

insert也有一些变化,他的返回值就不在是一共pair了,里面就没有所谓的bool了,只是单纯的返回新插入结点的迭代器,因为他插入永远成功

那么既然一个key可以有多个val,我们可以注意到他是可以根据key进行删除的,那么它是全删除掉吗?

擦除元素

从multimao容器中移除元素。
这有效地减小了容器size,减少了被移除的元素数量,这些元素被破坏了。
这些参数确定删除的元素:


本节主要讲解了map与set的基本使用。如有错误感谢指正!

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

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

相关文章

2.复杂度分析

2.1 算法效率评估 在算法设计中&#xff0c;我们先后追求以下两个层面的目标。 找到问题解法&#xff1a;算法需要在规定的输入范围内可靠地求得问题的正确解。寻求最优解法&#xff1a;同一个问题可能存在多种解法&#xff0c;我们希望找到尽可能高效的算法。 也就是说&a…

JavaScript_7_练习:随机抽奖案例

效果图 代码 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>练习&#xff1a;随机抽奖案例</tit…

做谷歌seo如何选择好的服务器?

做谷歌seo如何选择好的服务器&#xff0c;如果你选择自托管平台&#xff0c;那么服务器的选择就非常关键了&#xff0c;服务器的好与坏影响着你的网站的表现&#xff0c;所以选择服务器要慎重。以下是一些建议&#xff0c;帮你做出明智的选择&#xff1a; 安全性&#xff1a;安…

Python Web框架 Django学习记录:1 项目安装,启动

Windows上学习Django # 创建一个虚拟环境 python -m venv tutorial-env# 激活虚拟环境 tutorial-env\Scripts\activate# 安装Django py -m pip install Django# 查看Django版本 py -m django --version# 使用脚手架创建一个项目 django-admin startproject mysite# 启动项目 cd…

linux出现sql密码被忘记的解决方法

目录 前言正文 前言 此处放置在运维篇章&#xff0c;对应sql的修改密码&#xff0c;推荐阅读&#xff1a;修改sql密码&#xff08;涵盖多个版本&#xff09; 如果补充Sql的基本知识&#xff0c;推荐阅读&#xff1a;Mysql底层原理详细剖析常见面试题&#xff08;全&#xff0…

git本地仓库同步到远程仓库

整个过程分为如下几步&#xff1a; 1、本地仓库的创建 2、远程仓库的创建 3、远程仓库添加key 4、同步本地仓库到远程仓库 1、本地仓库的创建&#xff1a; 使用如下代码创建本地仓库&#xff1a; echo "# test" >> README.md git init git add README.md …

shell脚本的编写规范和变量类型

1.shell的作用 shell是Linux系统中后台运行的一种特殊程序也可以理解 成一种特殊的软件&#xff0c;提供了用户与内核进行 交互操作的 一种接口。(简单的说就是shell把人类的高级语言转换成二进制数据&#xff0c;让机器明白你的指令) 过程&#xff1a;用户发出指令&#xff0c…

图像数据处理19

四、形态学图像处理 4.6 灰度图像的形态学处理 4.6.1灰度图像的腐蚀操作 灰度图像的腐蚀处理会让图像整体变暗&#xff0c;增强较暗的细节&#xff0c;抑制较亮的细节。其有助于分割图像、平滑图像边缘。 import cv2 import numpy as np# 读取图像 image cv2.imread(fu.jp…

魔珐科技出席WWEC教育者大会,给出AI时代教培行业精细化运营赋能方案

AI与教育的结合&#xff0c;已经成为教育行业发展的关键增长点。头部机构纷纷寻求AI技术与产品融合&#xff0c;以增强市场竞争力&#xff0c;希望在这场技术引发的行业洗牌中保持领先。 喜忧之中&#xff0c;展望未来&#xff0c;教培机构如何继续找准航向&#xff0c;贴近政…

表格解析调研

表格解析调研 TextInTools TextInTools&#xff1a;https://tools.textin.com/table 可以将表格图片解析成可编辑的表格/json&#xff0c;效果不错 白描 地址&#xff1a;https://web.baimiaoapp.com/image-to-excel 可以将表格图片识别成可编辑的表格&#xff0c;可复制、…

OpenCV4特征匹配

目录 一.特征检测的基本概念二.Harris角点检测三.Shi-Tomasi角点检测四.SIFT关键点检测五.SURF特征检测&#xff08;属于opencv_contrib&#xff09;六.ORB特征检测七.特征匹配方法八.FLANN特征匹配 流程梳理 一.特征检测的基本概念 OpenCV特征的场景 1.图像搜索&#xff0c;…

“论软件的可靠性评价”写作框架,软考高级,系统架构设计师

论文真题 软件可靠性评价是软件可靠性活动的重要组成部分&#xff0c;既适用于软件开发过程&#xff0c;也可针对最终软件系统。在软件开发过程中使用软件可靠性评价&#xff0c;可以使用软件可靠性模型&#xff0c;估计软件当前的可靠性&#xff0c;以确认是否可以终止测试并…

数据结构与算法(算法篇)

学数据结构与算法不是仅仅学算法本身&#xff08;经验&#xff09;&#xff0c;而是学习思维&#xff08;解决问题的能力)。 数据结构与算法&#xff08;算法篇&#xff09; 1、算法的性能分析1.1 时间复杂度1.2 空间复杂度1.3 小结 2、高精度2.1 高精度加法2.2 高精度减法2.3…

【鸿蒙学习】HarmonyOS应用开发者高级认证 - 一次开发,多端部署

一、学习目的 掌握鸿蒙的核心概念和端云一体化开发、数据、网络、媒体、并发、分布式、多设备协同等关键技术能力&#xff0c;具备独立设计和开发鸿蒙应用能力。 二、总体介绍 HarmonyOS 系统面向多终端提供了“一次开发&#xff0c;多端部署”&#xff08;后文中简称为“一…

win双击运行jar文件

常规运行&#xff1a;java -jar xxx.jar 方法一、 1、jar包右键属性 2、更改打开方式&#xff0c;设置为默认 选择打开方式使用 javaw.exe C:\Program Files\Java\jdk1.8.0_201\bin\javaw.exe 3、修改注册表 winr 中 regedit 打开注册表 计算机\HKEY_CLASSES_ROOT\Appli…

同态加密及HElib

一、实验原理 1.同态加密概念 同态加密是密码学领域自1978年以来的经典难题,也是实现数据隐私计算的关键技术,在云计算、区块链、隐私计算等领域均存在着广泛的应用需求和一些可行的应用方案。 同态加密(Homomorphic Encryption)是很早之前密码学界就提出来的一个Open Pr…

Java Web —— 第七天(Mybatis案例1)

环境搭建 准备数据库表(dept、emp) -- 部门管理 create table dept(id int unsigned primary key auto_increment comment 主键ID,name varchar(10) not null unique comment 部门名称,create_time datetime not null comment 创建时间,update_time datetime not null commen…

Ubuntu24.04安装MYSQL8.0

更新源 sudo apt update安装mysql服务 默认安装最新版本 sudo apt install mysql-server检查安装版本 mysql --version检查mysql运行状态 systemctl status mysql开启远程访问&#xff0c;在ubuntu下mysql默认是只允许本地访问 sudo vim /etc/mysql/mysql.conf.d/mysqld.…

jdbc连接池之C3P0

C3P0&#xff1a;JDBC 连接池概述 C3P0 是一个开源的 JDBC 连接池库&#xff0c;用于管理数据库连接的获取与释放。它提供了连接池的自动管理和高效复用&#xff0c;从而减少了创建数据库连接所需的时间和资源消耗。C3P0 的核心功能是优化和管理数据库连接&#xff0c;以提高应…

python : Requests请求库入门使用指南 + 简单爬取豆瓣影评

Requests 是一个用于发送 HTTP 请求的简单易用的 Python 库。它能够处理多种 HTTP 请求方法&#xff0c;如 GET、POST、PUT、DELETE 等&#xff0c;并简化了 HTTP 请求流程。对于想要进行网络爬虫或 API 调用的开发者来说&#xff0c;Requests 是一个非常有用的工具。在今天的博…