C++STL剖析(二)—— vector的概念和使用

news2024/11/13 20:36:10

文章目录

  • 1. vector的介绍
  • 2. vector的常见构造
  • 3. vector的遍历方式
    • 🍑 [ ] + 下标
    • 🍑 迭代器
    • 🍑 范围for
  • 4. vector 迭代器使用
    • 🍑 begin 和 end
    • 🍑 rbegin 和 rend
  • 5. vector 空间增长问题
    • 🍑 size
    • 🍑 capacity
    • 🍑 reserve
    • 🍑 resize
    • 🍑 empty
  • 6. vector 的增删查改
    • 🍑 push_back
    • 🍑 pop_back
    • 🍑 insert
    • 🍑 erase
    • 🍑 find
    • 🍑 swap
    • 🍑 operator[ ]
    • 🍑 sort
  • 7. vector 迭代器失效问题
    • 🍑 失效场景
    • 🍑 解决办法
  • 8. 总结


1. vector的介绍

  • vector 是表示可变大小数组的序列容器。

  • vector 就像数组一样,采用连续的存储空间来存储元素。也就是意味着可以采用下标对 vector 的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。

  • vector 使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小为了增加存储空间。其做法是,分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector 并不会每次都重新分配大小。

  • vector 分配空间策略:vector 会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。

  • vector 占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。

  • 与其它动态序列容器相比(deques、lists and forward_lists), vector 在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起 lists 和 forward_lists 统一的迭代器和引用更好。

我们学习 vector 也和学习 string 一样,参考官方的文档:vector的文档介绍

2. vector的常见构造

这里主要有 4 中构造方式:

在这里插入图片描述

(1)无参构造一个空容器

vector<int> v1; //构造一个int类型的空容器

(2)构造并初始化 n 个 val 的容器

vector<int> v2(10, 5); //构造含有10个5的int类型容器

(3)拷贝构造某类型容器

vector<int> v3(v2); //拷贝构造int类型的v2容器

(4)使用迭代器进行初始化构造

vector<int> v4(v2.begin(), v2.end()); //使用迭代器拷贝构造v2容器的某一段内容

注意,vector 不只是能够用来构造 int 类型容器,还可以使用迭代器构造其他类型的容器

string s("hello world");

vector<char> v5(s.begin(), s.end()); //拷贝构造string对象的某一段内容

3. vector的遍历方式

vector 的遍历和 string 一样,也分为三种。

🍑 [ ] + 下标

vector 对 [ ] 运算符进行了重载,所以我们可以直接使用 [ ]+下标 访问对象中的元素。

还可以通过 [ ]+下标 修改对应位置的元素。

代码示例

int main()
{
	vector<int> v; // 定义容器v1

	// 尾插5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	// 使用下标访问数据
	for (size_t i = 0; i < v.size(); ++i)
	{
		cout << v[i] << " ";
	}
	cout << endl;

	// 使用下标修改数据
	for (size_t i = 0; i < v.size(); ++i)
	{
		v[i] += 1;
		cout << v[i] << " ";
	}
	cout << endl;

	return 0;
}

运行结果

在这里插入图片描述

🍑 迭代器

begin 获取一个字符的迭代器 + end获取最后一个字符下一个位置的迭代器

代码示例

int main()
{
	vector<int> v; // 定义容器v1

	// 尾插5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	// 使用迭代器访问数据
	vector<int>::iterator it1 = v.begin();
	while (it1 != v.end())
	{
		cout << *it1 << " ";
		it1++;
	}
	cout << endl;

	// 使用迭代器修改数据
	vector<int>::iterator it2 = v.begin();
	while (it2 != v.end())
	{
		*it2 += 1;
		cout << *it2 << " ";
		it2++;
	}
	
	return 0;
}

运行结果

在这里插入图片描述

🍑 范围for

和 string 一样,如果我们是通过范围 for 来修改对象的元素,那么接收元素的变量 e 的类型必须是引用类型,否则 e 只是对象元素的拷贝,对 e 的修改不会影响到对象的元素。

代码示例

int main()
{
	vector<int> v; // 定义容器v1

	// 尾插5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	// 使用范围for访问数据
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	// 使用范围for修改数据
	for (auto& e : v)
	{
		e += 1;
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

4. vector 迭代器使用

vector 的迭代器和 string 一样,也分为 正向迭代器 和 反向迭代器。

正向迭代器和 const正向迭代器:

在这里插入图片描述

反向迭代器 和 const 反向迭代器

在这里插入图片描述

它们的原理如下图所示(和 string 一样):

在这里插入图片描述

🍑 begin 和 end

通过 begin 函数可以得到容器中第一个元素的正向迭代器,通过 end 函数可以得到容器中最后一个元素的后一个位置的正向迭代器。

正向迭代器遍历容器:

int main()
{
	//定义容器v
	vector<int> v;

	//使用push_back插入5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//正向迭代器遍历容器
	vector<int>::iterator it = v.begin();
	while (it != v.end()) {
		cout << *it << " ";
		it++;
	}

	return 0;
}

调试运行

在这里插入图片描述

🍑 rbegin 和 rend

通过 rbegin 函数可以得到容器中最后一个元素的反向迭代器,通过 rend 函数可以得到容器中第一个元素的前一个位置的反向迭代器。

反向迭代器遍历容器:

int main()
{
	//定义容器v
	vector<int> v;

	//使用push_back插入5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//反向迭代器遍历容器
	vector<int>::reverse_iterator rit = v.rbegin();
	while (rit != v.rend()) {
		cout << *rit << " ";
		rit++;
	}

	return 0;
}

调试运行

在这里插入图片描述

注意:const 正向和反向这里就不演示了,和 string 的原理一样

5. vector 空间增长问题

主要学习以下几个函数

在这里插入图片描述

🍑 size

size 函数获取当前容器中的有效元素个数。

在这里插入图片描述

代码示例

int main()
{
	//定义容器v并初始化为20个5
	vector<int> v(20, 5);

	cout << v.size() << endl; //获取当前容器中的有效元素个数

	return 0;
}

运行结果

在这里插入图片描述

🍑 capacity

capacity 函数获取当前容器的最大容量。

在这里插入图片描述

代码示例

int main()
{
	//定义容器v并初始化为30个5
	vector<int> v(30, 5);

	cout << v.capacity() << endl; //获取当前容器的最大容量

	return 0;
}

运行结果

在这里插入图片描述

这里我们思考一个问题:capacity 是如何增容的呢?

可以用下面代码来验证一下

int main()
{
	size_t sz;
	vector<int> v;
	sz = v.capacity();
	cout << "making foo grow:\n";
	for (int i = 0; i < 100; ++i) {
		v.push_back(i);
		if (sz != v.capacity()) {
			sz = v.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}
	
	return 0;
}

运行结果

在这里插入图片描述

可以看到,在 VS2019 下,capacity 是按照大概 1.5 倍增长的。

那么 Linux 下呢?这里我也测试了一下,如图,大概是按照 2 倍增长的。

在这里插入图片描述

为什么 VS 和 Linux 下,capacity 增长的方式是不一样的呢?

很简单,因为早期的 STL 其实就是一个规范:

  • VS 下用的是 PJ 版本,大概是按 1.5 倍进行增容的。
  • Linux g++ 下是 SGI 版本,大概是按 2 倍进行增容的。

我们之前学数据结构知道,顺序表增容都是 2 倍的,所以不要固化的认为 vector 也是一样,具体增长多少是根据具体的需求定义的。

🍑 reserve

reserse 函数改变容器的最大容量。

(1)当所给值大于容器当前的 capacity 时,将 capacity 扩大到该值。

(2)当所给值小于容器当前的 capacity 时,什么也不做。

在这里插入图片描述

假设我事先知道要插入 100 个字符,那么我们可以使用 reserve 提前开好空间

int main()
{

	size_t sz;
	vector<int> v;
	v.reserve(100); // 提前开好100空间
	sz = v.capacity();
	cout << "making foo grow:\n";
	for (int i = 0; i < 100; ++i) {
		v.push_back(i);
		if (sz != v.capacity()) {
			sz = v.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}

	return 0;
}

可以看到,当我们提前开好空间以后,容器并没有自己再去开辟空间

在这里插入图片描述

🍑 resize

resize 函数改变容器中的有效元素个数。

(1)当所给值大于容器当前的 size 时,将 size 扩大到该值,扩大的元素为第二个所给值,若未给出,则默认为 0。

(2)当所给值小于容器当前的 size 时,将 size 缩小到该值。

在这里插入图片描述

代码示例

int main()
{

	size_t sz;
	vector<int> v;
	v.resize(100); // 开好100空间并全部初始化
	sz = v.capacity();
	cout << "making foo grow:\n";
	for (int i = 0; i < 100; ++i) {
		v.push_back(i);
		if (sz != v.capacity()) {
			sz = v.capacity();
			cout << "capacity changed: " << sz << endl;
		}
	}

	return 0;
}

可以看到,当我们使用 resize 的时候,除了进行开空间,还对容器进行了初始化

在这里插入图片描述

reserve 和 resize 综合演示

代码示例

int main()
{

	//初始化容器
	vector<int> v(10, 5);

	//打印size和capacity
	cout << v.size() << endl; //10
	cout << v.capacity() << endl; //10

	cout << endl;

	//改变容器的capacity为20,size不变
	v.reserve(20);
	cout << v.size() << endl; //10
	cout << v.capacity() << endl; //20

	cout << endl;

	//改变容器的size为15,capacity不变
	v.resize(15);
	cout << v.size() << endl; //15
	cout << v.capacity() << endl; //20

	return 0;
}

运行结果

在这里插入图片描述

注意:

  • reserve 只负责开辟空间,如果确定知道需要用多少空间,reserve 可以缓解 vector 增容的代价缺陷问题。

  • resize 在开空间的同时还会进行初始化,影响 size。

🍑 empty

empty 函数判断当前容器是否为空。

(1)如果容器为空,那么就输出 1

(2)如果容器不为空,那么就输出 0

在这里插入图片描述

代码示例

int main()
{
	vector<int> v1(10, 5);
	cout << v1.empty() << endl;

	vector<int> v2;
	cout << v2.empty() << endl; 

	return 0;
}

运行结果

在这里插入图片描述

6. vector 的增删查改

对于 vector 的增删查改主要是以下几个函数
在这里插入图片描述

🍑 push_back

push_back 函数对容器进行尾插。

在这里插入图片描述

代码示例

int main()
{

	//定义容器v
	vector<int> v;

	//使用push_back插入5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//打印
	for (auto e : v) {
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

🍑 pop_back

pop_back 函数对容器进行尾删

在这里插入图片描述

代码示例

int main()
{
	//定义容器v
	vector<int> v;

	//使用push_back尾插5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//使用pop_back尾删4个数据
	v.pop_back();
	v.pop_back();
	v.pop_back();
	v.pop_back();

	//打印
	for (auto e : v) {
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

🍑 insert

insert 函数可以在所给迭代器位置插入一个或多个元素

在这里插入图片描述

(1)在 pos 位置插入一个值

代码示例

int main()
{
	//定义容器v
	vector<int> v;

	//使用push_back尾插5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//在容器头部插入0
	v.insert(v.begin(), 10);

	//打印
	for (auto e : v) {
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

(2)在 pos 位置插入 n 个值

代码示例

int main()
{
	//定义容器v
	vector<int> v;

	//使用push_back尾插5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//在容器头部插入5个10
	v.insert(v.begin(), 5, 10);

	//打印
	for (auto e : v) {
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

🍑 erase

erase 函数可以删除所给迭代器位置的元素,或删除所给迭代器区间内的所有元素(左闭右开)

在这里插入图片描述

(1)删除一个值

代码示例

int main()
{
	//定义容器v
	vector<int> v;

	//使用push_back尾插5个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//头删
	v.erase(v.begin());

	//打印
	for (auto e : v) {
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

(2)删除一段区间

代码示例

int main()
{
	//定义容器v
	vector<int> v;

	//使用push_back尾插8个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);
	v.push_back(7);
	v.push_back(8);

	//删除在该迭代器区间内的元素(左闭右开]
	v.erase(v.begin(), v.begin() + 3);

	//打印
	for (auto e : v) {
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

注意:以上都是按照特定的位置进行插入或删除元素的方式,若要按值进行插入或删除(在某一特定值位置进行插入或删除),则需要用到 find 函数。

🍑 find

vector 没有 find 函数,那么假设我要查找某个元素怎么办呢?

很简单,可以去调用算法库里面的一个函数接口:find

在这里插入图片描述

find 函数共三个参数,前两个参数确定一个迭代器区间(左闭右开),第三个参数确定所要寻找的值。

函数在所给迭代器区间寻找第一个匹配的元素,并返回它的迭代器,若未找到,则返回所给的第二个参数。

代码示例

int main()
{
	//定义容器v
	vector<int> v;

	//使用push_back尾插6个数据
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);

	// 假设我要3的前面插入300
	//vector<int>::iterator pos = find(v.begin(), v.end(), 3);
	auto pos = find(v.begin(), v.end(), 3);
	if (pos != v.end())
	{
		cout << "找到了" << endl;
		v.insert(pos, 300);
	}
	else
	{
		cout << "没有找到" << endl;
	}

	//打印
	for (auto e : v) {
		cout << e << " ";
	}

	return 0;
}

调试运行

在这里插入图片描述

注意:因为 find 函数是在算法库里面的,所以需要加头文件 #include<algorithm>

🍑 swap

通过 swap 函数可以交换两个容器的数据空间,实现两个容器的交换。

在这里插入图片描述

代码示例

int main()
{
	//定义v1容器
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(1);
	v1.push_back(1);

	//定义v2容器
	vector<int> v2;
	v2.push_back(2);
	v2.push_back(2);
	v2.push_back(2);

	//交换v1和v2的数据
	v1.swap(v2);

	//打印v1
	for (auto e1 : v1) {
		cout << e1 << " ";
	}

	//打印v2
	for (auto e2 : v2) {
		cout << e2 << " ";
	}
	
	return 0;
}

运行结果

在这里插入图片描述

🍑 operator[ ]

vector 中实现了 [ ] 操作符的重载,因此我们也可以通过 下标 + [ ] 的方式对容器当中的元素进行访问。

在这里插入图片描述

代码示例

int main()
{
	//定义v1容器
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);

	// 通过[]读写第0个位置。
	cout << v[0] << endl;

	// 通过[i]的方式遍历vector
	for (size_t i = 0; i < v.size(); ++i) {
		cout << v[i] << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

🍑 sort

sort 是算法库里面一个函数接口,它默认是排升序的,要传一段 左闭右开 的区间。

在这里插入图片描述

代码示例

int main()
{
	//定义v1容器
	vector<int> v;
	v.push_back(9);
	v.push_back(1);
	v.push_back(5);
	v.push_back(2);
	v.push_back(0);
	v.push_back(-1);

	// 默认排升序
	sort(v.begin(), v.end());

	// 打印
	for (auto e : v)
	{
		cout << e << " ";
	}
	
	return 0;
}

运行结果

在这里插入图片描述

如果想要排降序的话,需要使用仿函数,而且要带头文件 #include <functional>

代码示例

int main()
{
	//定义v1容器
	vector<int> v;
	v.push_back(9);
	v.push_back(1);
	v.push_back(5);
	v.push_back(2);
	v.push_back(0);
	v.push_back(-1);

	// 默认排升序
	sort(v.begin(), v.end(), greater<int>());

	// 打印
	for (auto e : v)
	{
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

7. vector 迭代器失效问题

我们上面学习了 insert 和 erase 函数,那么思考一个问题,为什么 insert 和 erase 的返回值是 iterator 迭代器呢?

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对指针进行了封装,比如:vector 的迭代器就是原生态指针 T*

因此,迭代器失效,实际就是迭代器底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即如果继续使用已经失效的迭代器,程序可能会崩溃)。

🍑 失效场景

(1)场景一

在下面代码中,我们在数组中 2 的位置插入一个 10,然后将 2 删除。

我们使用 find 获取的是指向 2 的指针以后,当我们在 2 的位置插入 10 后,该指针就指向了 10,所以我们之后删除的实际上是 10,而不是 2。

代码示例

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	auto pos = find(v.begin(), v.end(), 3); //获取值为3的元素的迭代器

	// 1 2 30 3 4 5
	v.insert(pos, 10); // 在3的位置前插入30

	// 1 2 3 4 5
	v.erase(pos); // 删除pos位置的数据,导致pos迭代器失效。

	//此处我们再访问pos位置的值,会导致非法访问
	cout << *pos << endl;

	return 0;
}

运行结果

在这里插入图片描述

erase 删除 pos 位置元素后,pos 位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效。

但是,如果 pos 刚好是最后一个元素,删完之后 pos 刚好是 end 的位置,而 end 位置是没有元素的,那么 pos 就失效了。

因此删除 vector 中任意位置上元素时,vs 就认为该位置迭代器失效了。

(1)场景二

把 vector 数组中的偶数全部删除。

代码示例

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);

	auto it = v.begin();

	while (it != v.end())
	{
		if (*it % 2 == 0) //删除容器当中的全部偶数
		{
			v.erase(it);
		}
		it++;
	}

	return 0;
}

运行结果

在这里插入图片描述

为什么运行起来会报错呢?因为迭代器访问到了不属于容器的内存空间,导致程序崩溃。

在这里插入图片描述

不仅如此,而且在迭代器遍历容器中的元素进行判断时,并没有对 1、3、5 元素进行判断。

以上操作,都有可能会导致 vector 扩容,也就是说 vector 底层原理旧空间被释放掉,而在打印时,it 还使用的是释放之间的旧空间,在对 it 迭代器操作时,实际操作的是一块已经被释放的空间,而引起代码运行时崩溃。

🍑 解决办法

vector 迭代器失效有 2 种:

  • 扩容、缩容,导致野指针失效
  • 迭代器指向的位置意义变了

所以我们要重新接收,重新处理,也就是说每次使用前,对迭代器进行重新赋值。

(1)场景一解决方案

对于实例一,我们在使用迭代器删除元素 2 时对其进行重新赋值便可以解决。

代码示例

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);

	//获取值为2的元素的迭代器
	auto pos = find(v.begin(), v.end(), 2);

	//在值为2的元素的位置插入10
	v.insert(pos, 10);

	//重新获取值为2的元素的迭代器
	pos = find(v.begin(), v.end(), 2);

	//删除元素2
	v.erase(pos);

	// 打印
	for (auto e : v)
	{
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

(2)场景二解决方案

对于实例二,我们可以接收 erase 函数的返回值(erase 函数返回删除元素的后一个元素的新位置)。

并且控制代码的逻辑是:当元素被删除后继续判断该位置的元素(因为该位置的元素已经更新,需要再次判断)。

代码示例

int main()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	v.push_back(6);

	auto it = v.begin();

	while (it != v.end())
	{
		if (*it % 2 == 0) // 删除容器当中的全部偶数
		{
			it = v.erase(it); // 删除后获取下一个元素的迭代器
		}
		else
		{
			it++; // 是奇数则it++
		}
	}

	// 打印
	for (auto e : v)
	{
		cout << e << " ";
	}

	return 0;
}

运行结果

在这里插入图片描述

8. 总结

通过上面的练习我们发现 vector 常用的接口更多是插入和遍历。

遍历更喜欢用数组 operator[i] 的形式访问,因为这样便捷。

大家可以多去刷一些 OJ 题以此来增强学习 vector 的使用。

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

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

相关文章

37.Isaac教程--自由空间分割(道路分割)

自由空间分割 ISAAC教程合集地址: https://blog.csdn.net/kunhe0512/category_12163211.html 文章目录自由空间分割快速开始推理训练数据模拟数据设置与模拟器的通信来自公共数据集的真实数据具有自主数据收集的自由空间分割的真实数据自主数据收集通过地图规划路径监测机器人位…

JavaEE 突击 5 - Spring 更简单的读取和存储对象(2)

Spring 更简单的读取和存储对象 - 2三 . 获取 Bean 对象3.1 属性注入3.1.1 原理3.1.2 相关问题能在启动类里面调用 [Autowired ](/Autowired ) 注解吗[Autowired ](/Autowired ) 能使用多次吗Autowired 修饰的私有方法名字可以是其他的吗3.1.3 属性注入的优点和缺点3.2 Setter …

关于Kubernetes 桌面客户端 Aptakube 的一些笔记整理

写在前面 分享一个 k8s 桌面客户端 AptakubeAptakube 不是一个开源的产品&#xff0c;现在需要付费&#xff0c;最初是开源的这里简单了解下理解不足小伙伴帮忙指正 我所渴求的&#xff0c;無非是將心中脫穎語出的本性付諸生活&#xff0c;為何竟如此艱難呢 ------赫尔曼黑塞《…

redis学习看这一篇文章就够了

第一章 redis简介 第1节 NoSQL 1.1 NoSQL简介 NoSQL&#xff0c;泛指非关系型的数据库&#xff0c;NoSQL即Not-Only SQL&#xff0c;它可以作为关系型数据库的良好补充。随着互联网web2.0网站的兴起&#xff0c;非关系型的数据库现在成了一个极其热门的新领域&#xff0c;非…

使用Stable-Diffusion生成视频的完整教程

本文是关于如何使用cuda和Stable-Diffusion生成视频的完整指南&#xff0c;将使用cuda来加速视频生成&#xff0c;并且可以使用Kaggle的TESLA GPU来免费执行我们的模型。 #install the diffuser package#pip install --upgrade pip!pipinstall--upgradediffuserstransformerssc…

JUC并发编程(1.Java线程)

博客指南&#x1f4a1; JUC并发编程博客将持续更新&#xff0c;内容将参考黑马程序员深入学习Java并发编程以及相关阅读的书籍&#xff0c;内容包括进程&#xff0c;线程&#xff0c;并发和并行。 学习的路上永远不是一个人&#xff0c;相信努力会有所收获&#xff01; 希望我的…

【自步课程学习】 Paced-Curriculum Learning

引入: Confidence-Aware Paced-Curriculum Learning by Label Smoothing for Surgical Scene Understanding 【Code:https://github.com/XuMengyaAmy/P-CBLS】 先说

JavaEE初阶第二课:文件操作与IO

欢迎来到javaee初阶的第二课&#xff0c;这节课我会带大家了解文件的概念和java中如何操作文件。 这里写目录标题1.了解文件1.1文件概念1.2文件存储1.3文件路径2.Java中的文件操作&#xff08;文件系统操作&#xff09;2.1File类2.1.1方法实践3.Java的文件操作&#xff08;文件…

字节青训前端笔记 | Webpack入门

本节课将重点围绕「 Webpack 」这一核心话题展开。简述前端工程化的常用工具webpack 的原理和使用 webpack的作用 webpack的作用是把很多文件打包整合到一起, 缩小项目体积, 提高加载速度&#xff0c;常用的场景是&#xff1a; 代码压缩 将JS、CSS代码混淆压缩&#xff0c;…

25. 迭代器和生成器的详解

1. 迭代器 (1) 迭代是Python最强大的功能之一&#xff0c;是访问集合元素的一种方式。 (2) 迭代器是一个可以记住遍历的位置的对象。 (3) 迭代器对象从集合的第一个元素开始访问&#xff0c;直到所有的元素被访问完结束。迭代器只能往前不会后退。 (4) 迭代器有两个基本的方法…

Tkinter的Canvas控件

Canvas控件是Tkinter界面设计的一个画图工具&#xff0c;也可以用它导入外部图案到界面中 创建画布 import tkinter as tk roottk.Tk() #创建界面 root.title(Canvas) #界面命名 root.geometry(500x300) #设置界面大小 canvastk.Canvas(root) …

03_class创建device创建_kobject_uevent发送

总结 根据之前的kobject知道 /sys/目录下的每个文件夹都是一个 kobject的对象 使用class_create() 创建 /sys/class/xxx目录 同时返回class对象 使用device_create() 创建/sys/class/xxx/yyy目录 和创建/dev/yyy的文件节点 同时返回device对象 class和device 都间接继承于kobj…

【老卫搞机】135期:华为开发者联盟社区2022年牛人之星奖品开箱!

首先祝大家兔年大吉&#xff0c;身体安康&#xff0c;钱兔似锦&#xff01;今天咱们来开箱一件特殊的奖品&#xff0c;来自华为开发者联盟社区的新年祝福——2022年牛人之星。 华为有钱&#xff01;惯例用的是顺丰快递&#xff0c;各位看一下这里面是有很多件的 有这两件。第一…

三、TCP/IP---ARP和ICMP协议

ARP协议 简介&#xff1a;号称TCP/IP中最不安全的协议&#xff0c;安全工具&#xff0c;黑客工具大多数基于ARP协议。它是地址解析协议&#xff0c;用于实现从IP到MAC地址的映射&#xff0c;即询问目标Ip对应的MAC地址是多少&#xff0c;局域网通信不仅需要源目地址封装&#…

学习率衰减、局部最优、Batch归一化、Softmax回归

目录1.学习率衰减(Learning rate decay)在训练初期&#xff0c;梯度下降的步伐大一点&#xff0c;开始收敛的时候&#xff0c;小一些的学习率能让步伐小一些。1 epoch 遍历一遍训练集学习率衰减公式&#xff1a;例&#xff1a;假设衰减率decayrate 1&#xff0c;0.2epochNumα…

蓝桥杯-刷题-补基础

十道入门题 题目来源,题目,简单解析,代码,输入输出 目录 前言 一,汉诺塔 二,判断闰年 三,大写变小写 四&#xff0c;破译密码 五&#xff0c;反向数相加 六&#xff0c;Excel表中的列号 七&#xff0c;饮料兑换 八&#xff0c;角谷猜想 九&#xff0c;数字统计…

小喵2022年的年度总结,啊滴妈呀,开了眼了。

宝子&#xff0c;你不点个赞吗&#xff1f;不评个论吗&#xff1f;不收个藏吗&#xff1f; 最后的最后&#xff0c;关注我&#xff0c;关注我&#xff0c;关注我&#xff0c;你会看到更多有趣的博客哦&#xff01;&#xff01;&#xff01; 喵喵喵&#xff0c;你对我真的很重…

Qt扫盲- QUdpSocket 类理论总结

QUdpSocket 类理论总结一、概述二、使用流程三、QNetworkDatagram 简述一、概述 UDP (User Datagram Protocol)是一种轻量级的、不可靠的、面向数据报的、无连接的协议。当可靠性不重要时&#xff0c;可以使用它。QUdpSocket是QAbstractSocket的子类&#xff0c;允许发送和接收…

SpringBoot+Vue--token,vue导航守卫,axios拦截器-笔记3

自己学习记录,写的不详细,没有误导,不想误导 大概的登录逻辑,前后端完整实现: 1.用户名,密码验证成功后,后端签发token返回给前端 2.前端把token保存到本地存储 3.每次请求前,通过axios请求拦截器,统一发送token 4.通过Vue导航守卫,和axios响应拦截器,统一保护页面 新建个…

【华为上机真题 2023】事件推送

&#x1f388; 作者&#xff1a;Linux猿 &#x1f388; 简介&#xff1a;CSDN博客专家&#x1f3c6;&#xff0c;华为云享专家&#x1f3c6;&#xff0c;Linux、C/C、云计算、物联网、面试、刷题、算法尽管咨询我&#xff0c;关注我&#xff0c;有问题私聊&#xff01; &…