11.vector的介绍及模拟实现

1.vector的介绍

记得之前我们用C语言实现过顺序表,vector本质上也是顺序表,一个能够动态增长的数组。

vector 的底层实现机制
 
- 动态数组:vector 的底层实现是动态数组。它在内存中连续存储元素,就像一个可以自动调整大小的数组。
- 内存分配策略:
- 当向 vector 中添加元素导致容量不足时,vector 会重新分配一块更大的内存空间,将原有元素复制到新空间中,然后释放旧空间。这个过程可能会比较耗时,尤其是当 vector 中存储的元素数量较大时。
- 初始时,vector 通常会分配一定大小的内存空间,随着元素的不断添加,逐步扩大容量。
- 迭代器失效:在进行插入、删除等操作时,可能会导致指向 vector 中元素的迭代器失效。这是因为这些操作可能会引起内存的重新分配和元素的移动。

迭代器失效后面模拟实现会详细讲解

其实vector的常用接口和string大部分相似,但是也有不同,那有什么不同呢?

一、存储内容不同
 
- vector:可以存储各种类型的数据,如整数、浮点数、结构体等。例如,可以存储一组整数 vector<int> v = {1, 2, 3} 。
- string:专门用于存储字符序列,即字符串。例如 string s = "hello" 。
 
二、操作不同
 
- vector:
- 支持随机访问,可以通过下标快速访问元素。例如 v[2] 可以快速访问 vector 中的第三个元素。
- 可以动态添加和删除元素,使用 push_back 添加元素, pop_back 删除最后一个元素。
- string:
- 提供了丰富的字符串操作函数,如查找、替换、拼接等。例如 s.find("ll") 可以查找字符串中“ll”的位置。
- 可以直接使用 + 进行字符串拼接。
 
三、性能特点不同
 
- vector:
- 在内存中连续存储元素,有利于提高访问速度,但在插入和删除元素时可能需要移动大量元素,效率较低。
- 可以预先分配一定的空间,避免频繁的内存分配和释放。
- string:
- 通常会进行一些优化,如小字符串优化等,以提高性能。
- 字符串的长度可以动态变化,但在进行大量修改操作时可能会有一定的性能开销。

2.vector的使用

2.1vector的初始化

无参构造:

vector<int> v1;

构造并初始化:用n个value初始化

vector<int> v2(10, 1);//10个1

迭代器区间初始化:

vector<int> v3(v2.begin(), v2.end());

拷贝构造:

vector<int> v4(v2);

2.2vector iterator 的使用

iterator 的使用接口说明
begin + end
获取第一个数据位置的iterator/const_iterator, 获取最后一个数据的下一个位置的iterator/const_iterator
rbegin + rend
获取最后一个数据位置的reverse_iterator,获取第一个数据前一个位置的reverse_iterator

2.3 vector 空间

容量空间接口说明
size
获取数据个数
capacity获取容量大小
empty判断是否为空
resize改变vector的size
reserve改变vector的capacity
1.capacity的代码在vs和g++下分别运行会发现,vs下capacity是按1.5倍增长的,g++是按2
倍增长的。这个问题经常会考察,不要固化的认为,vector增容都是2倍,具体增长多少是
根据具体的需求定义的。vs是PJ版本STL,g++是SGI版本STL。
2.reserve只负责开辟空间,如果确定知道需要用多少空间,reserve可以缓解vector增容的代
价缺陷问题。
3.resize在开空间的同时还会进行初始化,影响size
这些接口和string是类似的,就不一一调用介绍了

2.4 vector 增删查改

vrector增删查改接口说明
push_back尾插
pop_back尾删
find (#include <algorithm>)
查找。(注意这个是算法模块实现,不是vector的成员接口)
insert在position之前插入value
erase删除position位置的数据
swap交换两个vector的数据空间
operator[]像数组一样访问

在 C++中,vector 并非没有实现 find 接口,只是没有像一些容器(如关联容器)那样有专门的成员函数 find。
 
原因如下:
 
1. 效率考虑:对于顺序容器(如 vector),线性查找的效率相对较低,通常可以使用更高效的算法如二分查找等,而不是直接调用 find 成员函数。
2. 设计理念:C++标准库的设计尽量保持不同容器的特性和用途明确,vector 主要用于存储连续的元素,更强调随机访问和高效的插入/删除尾部元素等操作,而不是查找。
 
可以通过标准算法中的  find  函数来在 vector 中进行查找。例如: 

auto it = std::find(vector.begin(), vector.end(), target); 。

对于其他增删查改接口和string同样是类似的,就不详细介绍了,但是insert和erase这两个接口是不同的,这两个接口需要配合迭代器使用,但是配合迭代器使用这里就会有一个迭代器失效的问题

2.5 vector 迭代器失效问题。(重点)

迭代器的主要作用就是让算法能够不用关心底层数据结构,其底层实际就是一个指针,或者是对
指针进行了封装比如vector的迭代器就是原生态指针T* 。因此迭代器失效,实际就是迭代器
底层对应指针所指向的空间被销毁了,而使用一块已经被释放的空间,造成的后果是程序崩溃(即
如果继续使用已经失效的迭代器,程序可能会崩溃)。
#include <iostream>
#include <vector>
using namespace std;
int main()
{
    int a[] = { 1, 2, 3, 4 };
    vector<int> v(a, a + sizeof(a) / sizeof(int));
    // 使用find查找3所在位置的iterator
    vector<int>::iterator pos = find(v.begin(), v.end(), 3);
    // 删除pos位置的数据,导致pos迭代器失效。
    v.erase(pos);
    cout << *pos << endl; // 此处会导致非法访问
    return 0;
}
erase删除pos位置元素后,pos位置之后的元素会往前搬移,没有导致底层空间的改变,理
论上讲迭代器不应该会失效,但是:如果pos刚好是最后一个元素,删完之后pos刚好是end
的位置,而end位置是没有元素的,那么pos就失效了。因此删除vector中任意位置上元素
时,vs就认为该位置迭代器失效了。

迭代器失效解决办法:在使用前,对迭代器重新赋值即可

3.vector的模拟实现

3.1结构的定义

这里我们参考STL源码解析实现一个简易版本的vector

成员变量的定义

#include <iostream>
#include <assert.h>
using namespace std;
namespace Ro
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;

	};
}

这里大家可能会发现和模拟实现string的写法怎么不一样?

其实这就是参考STL的写法,虽然写法不同,但其实效果是大差不差的。

如图:

我们可以通过指针减指针的做法得到size和capacity。

这里同样和模拟实现string一样使用命名空间来和库中的vector区分开来,而且这里使用类模板是为了存储不同类型的数据,

这里顺带直接将size()和capacity()的接口实现出来,同时给成员变量加一个缺省值给初始化列表用

namespace Ro
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		size_t size() const
		{
			return _finish - _start;
		}
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;

	};
}

3.2构造函数和析构函数

构造:

vector(){}

这里初始化列表不写也会走,通过给的缺省值来初始化

析构函数:

~vector()
{
	if (_start)
	{
		delete[] _start;
		_start = _finish = _end_of_storage = nullptr;
	}
}

3.3reverse()

由于后面大部分函数接口都需要扩容,所以为了后面方便,我们先实现reverse

void reverse(size_t n)
{
	if (n > capacity())
	{
		if (_finish == _end_of_storage)
		{
			size_t old_size = size();
			T* tmp = new[n];
			if (_start)
			{
				memcpy(tmp, _start, sizeof(T) * old_size);
				delete[] _start;
			}
			_start = tmp;
			_finish = tmp + old_size;
			_end_of_storage = tmp + n;
		}
	}
}

这里我们要先将size()存下来,不然在给_finish更新时,会出现野指针。如图:

一般情况下都不会进行缩容的,所以我们在实现的时候不考虑缩容。另外,这里还会有一个坑,后面出错时我们再解决并说明

3.4push_back()和operator[]

为了让我们的vector能够跑起来,先来实现一下push_back接口

void push_back(const T& val)
{
	if (_finish == _end_of_storage)
	{
		reverse(capacity() == 0 ? 4 : capacity() * 2);
	}
	*_finish = val;
	_finish++;
}

先检查容量有没有满,满了就扩容,这里我们扩容就扩2倍,然后再插入数据,_finish指针++,指向下一个位置。

为了接下来方便测试我们先来实现 下标+[] 来访问数据

operator[]:

T&: T是我们不知道数据的类型,加&是为了减少拷贝

T& operator[](size_t i)
{
	assert(i < size());
	return _start[i];
}
const T& operator[](size_t i) const
{
	assert(i < size());
	return _start[i];
}

测试一下:

void test_vector1()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	//v.push_back(5);
	for (int i = 0; i < v.size(); i++)
	{
		cout << v[i] << ' ';
	}
	cout << endl;
}

先测试一下扩容前的1 2 3 4,然后再测试一下扩容后的1 2 3 4 5

没有问题

3.5迭代器的实现

这里我们来实现一下普通迭代器和const迭代器

typedef T* iterator;
typedef const T* const_iterator;
iterator begin()
{
	return _start;
}
iterator end()
{
	return _finish;
}
const_iterator begin() const
{
	return _start;
}
const_iterator end() const
{
	return _finish;
}

普通迭代器可读可写,const迭代器可读但是不可写。

测试一下迭代器遍历,同样范围for也可以使用了:

void test_vector2()
{
	vector<int> v;
	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++;
	}
	cout << endl;
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
}

普通迭代器可写

void test_vector2()
{
	vector<int> v;
	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())
	{
		*it *= 2;
        it++;
	}
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
}

const迭代器不可写

所以迭代器要正确匹配使用。

3.6深拷贝

前面我们提到过,扩容时还有一个坑没说,其实就是memcpy浅拷贝的问题。

如果我们vector存的是string这种自定义类型会发生什么?

void test_vector3()
{
	vector<string> v;
	v.push_back("111111111111111111111111");
    v.push_back("111111111111111111111111");
    v.push_back("111111111111111111111111");
    v.push_back("111111111111111111111111");
    //v.push_back("111111111111111111111111");
	for (string& s : v)
	{
		cout << s << ' ';
	}
	cout << endl;
}

我们来看看扩容前和扩容后:

扩容前:

扩容前没有问题。那扩容后呢?

扩容后:

扩容后出问题了,运行崩溃,且打印结果错了,这是为什么?

其实就是memcpy因为浅拷贝导致的,如图:

那么整个时候就应该要深拷贝,tmp创建自己的空间存放拷贝的数据

void reverse(size_t n)
{
	if (n > capacity())
	{
		if (_finish == _end_of_storage)
		{
			size_t old_size = size();
			T* tmp = new T[n];
			if (_start)
			{
				//memcpy(tmp, _start, sizeof(T) * old_size);
				for (int i = 0; i < old_size; i++)
				{
					tmp[i] = _start[i];//T会调用自己的深拷贝
				}
				delete[] _start;
			}
			_start = tmp;
			_finish = tmp + old_size;
			_end_of_storage = tmp + n;
		}
	}
}

这里我们可以直接赋值,如果T是内置类型,一个一个拷贝没有问题,如果是自定义类型,就让T自己去调用它自己的深拷贝,也没有问题。

测试一下:

现在就不会出错了。

3.7resize()

如果n小于size,有效数据就会变为n个,容量不变

大于size小于capacity就会将数据扩大到n个,且会把size到n之间的数据初始化为val

大于capacity的话就会先扩容,再初始化。

void resize(size_t n, const T& val = T())
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else
	{
		reverse(n);
		while (_finish != _start + n)
		{
			*_finish = val;
			_finish++;
		}
	}
}

这里缺省值我们不能直接给0或者其他,因为存储的数据类型我们不知道,这里可以用T()匿名对象作为缺省值,让T调用自己的构造去初始化。

注意:匿名对象的生命周期只在那一行,所以要使用const引用匿名对象,目的就是延长匿名对象的生命周期。

测试一下:

void test_vector4()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
	v.resize(10, 1);
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
}

3.8pop_back()和empty()

当没有数据时,即为空时不能尾删

所以先来实现判空:

bool empty() const
{
	return _start == _finish;
}

尾删:

void pop_back()
{
	assert(!empty());
	_finish--;
}

比较简单就不测试了。

3.9insert()

在pos前插入val,先将pos后元素全部向后移动一格,在将val插入pos位置

void insert(iterator pos, const T& val)
{
	assert(pos <= _finish && pos >= _start);
	if (_finish == _end_of_storage)
	{
		reverse(capacity() == 0 ? 4 : capacity() * 2);
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	_finish++;
}

分别测试一下扩容前和扩容后:

void test_vector5()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	//v.push_back(4);
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
	vector<int>::iterator pos = find(v.begin(), v.end(), 2);
	v.insert(pos, 10);
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
}

扩容前:

扩容后:

这里结果出错了,为什么呢?

其实就是因为我们前面提到的迭代器失效问题。

由于扩容之后,pos还是指向旧空间的2,但是我们现在要在新空间的2前面插入10,所以我们应该在扩容后更新pos指针。

void insert(iterator pos, const T& val)
{
	assert(pos <= _finish && pos >= _start);
	if (_finish == _end_of_storage)
	{
        size_t len = pos - _start;
		reverse(capacity() == 0 ? 4 : capacity() * 2);
        pos = _start + len;
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	_finish++;
}

现在再测试一下:

现在就没有问题了。

但是需要注意的是,在insert中我们虽然更新了形参pos,但是外面的实参pos并没有改变,形参是实参的临时拷贝,所以形参改变不会影响形参。

那怎么办?引用形参可以吗?这里我们给出解决办法,不推荐引用,可以通过返回值来返回更新之后的pos。

不采用引用形参的原因
- 避免意外修改:
- 如果 insert 函数通过引用形参返回插入位置,这可能会导致意外的修改。因为引用本身可以被重新赋值,函数调用者可能会不小心修改这个引用,从而改变了原本应该表示插入位置的信息。
- 语义不符:
- 从语义角度看, insert 操作主要是向容器中添加元素,重点在于插入操作本身和插入后的元素位置。返回一个表示插入位置的迭代器更符合这个操作的语义,而通过引用形参返回位置不太符合这种直观的理解。引用形参更多地用于在函数内部修改外部变量的值,而 insert 的主要目的不是修改外部传入的表示位置的变量,而是告知调用者新元素的位置。

iterator insert(iterator pos, const T& val)
{
	assert(pos <= _finish && pos >= _start);
	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;
		reverse(capacity() == 0 ? 4 : capacity() * 2);
		pos = _start + len;
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	_finish++;
	return pos;
}

4.0erase()

同样erase也会有迭代器失效的问题,所以我们也可以和insert一样通过返回值来更新一下pos

iterator erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);
    //挪动数据
	iterator end = pos + 1;
	while (end < _finish)
	{
		*(end - 1) = *end;
		end++;
	}
	_finish--;
	return pos;
}

测试一下:

void test_vector6()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
	vector<int>::iterator pos = find(v.begin(), v.end(), 2);
	v.erase(pos);
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
}

如果我们要删除所有的偶数呢?

void test_vector6()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
	v.push_back(5);
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
	vector<int>::iterator it = v.begin();
	while (it != v.end())
	{
		if (*it % 2 == 0) it = v.erase(it);
		else it++;
	}
	/*vector<int>::iterator pos = find(v.begin(), v.end(), 2);
	v.erase(pos);*/
	for (int e : v)
	{
		cout << e << ' ';
	}
	cout << endl;
}

4.1拷贝构造

拷贝构造可以使用传统写法,也可以使用现代写法,这里我们直接干

vector(const vector<T>& v)
{
	reverse(v.size());
	for (auto& e : v)
	{
		push_back(e);
	}
}

测试一下:

void test_vector7()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	v1.push_back(5);
	for (int e : v1)
	{
		cout << e << ' ';
	}
	cout << endl;
	vector<int> v2 = v1;
	for (int e : v1)
	{
		cout << e << ' ';
	}
	cout << endl;
}

4.2赋值重载

赋值重载我们用现代写法来写:

void Swap(vector<T>& v)
{
	swap(_start, v._start);
	swap(_finish, v._finish);
	swap(_end_of_storage, v._end_of_storage);
}
vector<T>& operator=(vector<T> v)
{
	Swap(v);
	return *this;
}

测试一下:

void test_vector8()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);
	cout << "v1:";
	for (int e : v1)
	{
		cout << e << ' ';
	}
	cout << endl;
	vector<int> v2;
	v2.push_back(10);
	v2.push_back(20);
	v2.push_back(30);
	cout << "v2赋值前:";
	for (int e : v2)
	{
		cout << e << ' ';
	}
	cout << endl;
	v2 = v1;
	cout << "v2赋值后:";
	for (int e : v2)
	{
		cout << e << ' ';
	}
	cout << endl;
}

4.3迭代器区间构造

template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

一个类模板的成员函数,还可以是一个函数模板

这里InputIterator就是函数模板,可以自动实例化出不同类型的迭代器。

来测试一下:

	void test_vector9()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		cout << "v1:";
		for (int e : v1)
		{
			cout << e << ' ';
		}
		cout << endl;
		vector<int> v2(v1.begin(), v1.end());
		cout << "v2:";
		for (int e : v2)
		{
			cout << e << ' ';
		}
		cout << endl;
	}

OK,这次vector的认识就到这里了。

欢迎指正和补充。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

封装(2)

大家好&#xff0c;今天我们来介绍一下包的概念&#xff0c;知道包的作用可以更好的面对今后的开发&#xff0c;那么我们就来看看包是什么东西吧。 6.3封装扩展之包 6.3.1包的概念 在面向对象体系中,提出了一个软件包的概念,即:为了更好的管理类,把多个类收集在一起成为一组…

go官方日志库带色彩格式化

go默认的 log 输出的日志样式比较难看&#xff0c;所以通过以下方式进行了美化和格式化&#xff0c;而且加入了 unicode 的ascii码&#xff0c;进行色彩渲染。 package mainimport ("fmt""log""os""runtime""strings""…

0基础学前端系列 -- 深入理解 HTML 布局

在现代网页设计中&#xff0c;布局是至关重要的一环。良好的布局不仅能提升用户体验&#xff0c;还能使内容更具可读性和美观性。HTML&#xff08;超文本标记语言&#xff09;结合 CSS&#xff08;层叠样式表&#xff09;为我们提供了多种布局方式。本文将详细介绍流式布局、Fl…

Windows开启IIS后依然出现http error 503.the service is unavailable

问题背景 已启用IIS服务&#xff0c;配置步骤可以参考Windows10 IIS Web服务器安装配置 问题描述 在这一步浏览网站时&#xff0c;并没有出现默认首页&#xff0c;而是 http error 503 the service is unavailable 问题解决 参考 成功解决http error 503.the service is un…

BuildCTF 公开赛web部分wp

文章目录 LovePopChainRedFlagWhy_so_serials?babyuploadeazyl0ginez!httpez_md5find-the-idsubtflock刮刮乐我写的网站被rce了&#xff1f; LovePopChain payload: <?php class MyObject{public $NoLove"Do_You_Want_Fl4g?";public $Forgzy;public functi…

diff 算法实现的几种方法和前端中的应用

diff 算法原理和几种实现方法 diff 是什么 diff 算法就是比较两个数据的差异&#xff0c;例如字符串的差异&#xff0c;对象的差异。 常用于版本管理&#xff08;git&#xff09;例如下面的实际案例。 github 上某个 commit&#xff0c;旧代码和新代码之间的不同 diff 展示…

Nacos源码搭建

拉取并配置代码 仓库地址 https://github.com/alibaba/nacos找到config 模块中找到 \resources\META-INF\mysql-schema.sql&#xff0c;在本地mysql中创建数据库nacos-config&#xff0c;将该脚本导入执行创建表。 找到console模块下的配置文件application.properties&#x…

C# Winfrom chart图 实例练习

代码太多了我就不展示了&#xff0c;贴一些比较有代表性的 成品效果展示&#xff1a; Excel转Chart示例 简单说一下我的思路 \ 先把Excel数据展示在dataGridView控件上 XLIST 为 X轴的数据 XLIST 为 Y轴的数据 ZLIST 为 展示的数据进行数据处理点击展示即可 // 将Excel数…

# 起步专用 - 哔哩哔哩全模块超还原设计!(内含接口文档、数据库设计)

↑ 上方下载文档 (大小374KB) 接口文档预览 (超过50个接口) 一、数据库25张表er-关系清晰构图&#xff01;(tip: 鼠标右键图片 > 放大图像) 二、难点/经验 详细说明 热门评论排序评论点赞列表|DTO封装经验分享|精华接口文档说明 组员都说喜欢分档对应枚举码 如果这篇文章…

【Go学习】从一个出core实战问题看Go interface赋值过程

0x01 背景 版本中一个同学找我讨论一个服务出core的问题&#xff0c;最终他靠自己的探索解决了问题&#xff0c;给出了初步的直接原因结论&#xff0c;"Go 中 struct 赋值不是原子的”。间接原因的分析是准确的&#xff0c;直接原因&#xff0c;我有点怀疑。当时写了一些…

leetcode之hot100---54螺旋矩阵(C++)

思路一&#xff1a;模拟 模拟螺旋矩阵的路径&#xff0c;路径超出界限&#xff0c;顺时针旋转&#xff0c;使用一个数组记录当前数字是否被访问到&#xff0c;直到所有的数字全部被访问 class Solution {//一个静态的常量数组&#xff0c;用于标记螺旋矩阵的移动方向(行列变化…

新能源汽车锂离子电池各参数的时间序列关系

Hi&#xff0c;大家好&#xff0c;我是半亩花海。为了进一步开展新能源汽车锂离子电池的相关研究&#xff0c;本文主要汇总并介绍了电动汽车的锂离子电池的各项参数&#xff0c;通过 MATLAB 软件对 Oxford Dataset 的相关数据集进行数据处理与分析&#xff0c;进一步研究各项参…

FastStone 10.x 注册码

简介 FastStone Capture是一款经典好用的屏幕截图软件&#xff0c;在屏幕截图领域具有广泛的应用和众多优势。 软件基本信息 FastStone Capture体积小巧&#xff0c;占用内存少&#xff0c;这使得它在运行时不会给计算机系统带来过多的负担&#xff0c;即使在配置较低的电脑…

AI合成图片是什么意思?有什么用?

随着人工智能的发展&#xff0c;现在市面上出现了很多对企业帮助很大的AI工具&#xff0c;比如说AI合成图片、AI换模特、AI穿衣、AI图片设计等等&#xff0c;下面小编就以AI合成图片为例&#xff0c;为大家详细介绍下。 一、AI合成图片是什么意思? AI合成图片主要就是指利用人…

【示例】Vue AntV G6 base64自定义img 动画效果,自适应宽高屏

需求&#xff1a;拓扑图中需要用动画的线条连接node&#xff0c;在此之前将HTML页面改成了vue页面。需要使用到G6的registerEdge 自定义边&#xff0c;小车的图片需要转成base64格式&#xff08;并翻转&#xff09;&#xff0c;可以通过base64转image查看原来的样子。 另外&am…

MySQL的分析查询语句

【图书推荐】《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;》-CSDN博客 《MySQL 9从入门到性能优化&#xff08;视频教学版&#xff09;&#xff08;数据库技术丛书&#xff09;》(王英英)【摘要 书评 试读】- 京东图书 (jd.com) MySQL9数据库技术_夏天又到了…

【递归,搜索与回溯算法 综合练习】深入理解暴搜决策树:递归,搜索与回溯算法综合小专题(二)

优美的排列 题目解析 算法原理 解法 &#xff1a;暴搜 决策树 红色剪枝&#xff1a;用于剪去该节点的值在对应分支中&#xff0c;已经被使用的情况&#xff0c;可以定义一个 check[ ] 紫色剪枝&#xff1a;perm[i] 不能够被 i 整除&#xff0c;i 不能够被 per…

观察者模式(sigslot in C++)

大家&#xff0c;我是东风&#xff0c;今天抽点时间整理一下我很久前关注的一个不错的库&#xff0c;可以支持我们在使用标准C的时候使用信号槽机制进行观察者模式设计&#xff0c;sigslot 官网&#xff1a; http://sigslot.sourceforge.net/ 本文较为详尽探讨了一种观察者模…

GitCode 光引计划投稿|智能制造一体化低代码平台 Skyeye云

随着智能制造行业的快速发展&#xff0c;企业对全面、高效的管理解决方案的需求日益迫切。然而&#xff0c;传统的开发模式往往依赖于特定的硬件平台&#xff0c;且开发过程繁琐、成本高。为了打破这一瓶颈&#xff0c;Skyeye云应运而生&#xff0c;它采用先进的低代码开发模式…

高校就业管理:系统设计与实现的全流程分析

3.1可行性分析 在项目进行开发之前&#xff0c;必须要有可行性分析报告&#xff0c;分别从技术角度&#xff0c;经济角度&#xff0c;操作角度上面进行分析&#xff0c;经过可行性分析是实现科学开发的必要步骤。 3.1.1技术可行性 从技术的角度出发&#xff0c;目前采用开发的技…