C++ stl容器vector的底层模拟实现

目录

前言:  

1.成员变量,容量与大小

2.构造函数

无参构造:

带参的使用值进行构造:

 使用迭代器区间进行构造:

3.交换

4.拷贝构造

5.赋值重载

6.迭代器

7.扩容

reserve:

resize:

8.插入与删除

insert:

erase:

insert迭代器失效问题:

erase迭代器失效问题:

9.头插头删

10.[]重载

11.完整代码与测试

总结:


前言:  

本篇来实现stl容器vector,有了前面string的基础,会实现起来更加的顺畅同样也是挑重要的接口来实现,结尾会附上完整代码。

1.成员变量,容量与大小

	public:typedef T* iterator;typedef const T* const_iterator;private:iterator _start;iterator _finish;iterator _end_of_storage;

这里的成员变量都是指针实现的,_start指向开始的位置也就是第一元素,_finish指向最后一个元素的下一个位置,所以是开区间,_end_of_storage是容量大小,也就是指向容器的最后一个位置。

		size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}

大小就是最后一个元素的下一个位置减去开始的位置,也就是有效数据的个数。

容量就是可能扩容好的最后的位置减去开始的位置。

注意可以是const成员调用的,所以用const成员修饰函数,这里没有分开const修饰的函数与普通的函数是因为我们实际就算是访问容量与大小也不会修改数据;而像迭代器要分开是因为,我们会使用普通迭代器解引用修改数据,要区分开。

 

2.构造函数

无参构造:
vector():_start(nullptr)//不管调用哪个构造,带参的还是不带参数的,都要给初始化列表,因为不给初始化后成员变量都是随机值;或者是成员变量声明时给缺省,_finish(nullptr),_end_of_storage(nullptr){}

注意不管是带参的构造还是无参的构造,都要在初始化列表给初始化,因为不然成员变量里面都是随机值;或者是在声明成员变量的时候给上缺省值。

带参的使用值进行构造:
		vector(size_t n, const T& val = T())//任意类型都有默认构造,例如模版参数是int也就成0了:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);for (size_t i = 0; i < n;++i){push_back(val);}}vector(int n, const T& val = T())//没有这个会出现非法的间接寻址的错误,编译器识别不出来两个参数的构造是调用迭代器的那个还是这个。:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}}

首先参数里面的第二个参数是使用匿名对象进行初始化的,我们要知道c++既然设计了面对对象的特性,那构造就不能只针对自定义类型来说了,或者说构造的时候让编译器取区分自定义类型与内置类型会带来麻烦,所以我们使用匿名对象进行构造初始化,也就是说这个构造的缺省值是T类的对象,例如:

	const int& a = int();//匿名对象具有常性const char& b = char();int c = int();//任意类型都有默认构造,这是使用匿名对象进行初始化

 

我们观察调试窗口可以发现:

 这些内置类型使用默认的构造也被初始化为0了。

还有注意匿名对象是具有常性的,所以使用引用接收要加上const修饰 ,而如果使用引用接收匿名对象,匿名对象的生命周期会跟着引用而延长直道引用销毁。

现在我们搞懂了第二个参数,我们再来看看第一个参数为什么要重载成两个形式,因为我们如果直接使用size_t,再结合下面的迭代器的构造,编译器就识别不出了掉用哪一个了(但会选择最合适的,就是迭代器的构造,根据模版推演类型),所以最好还是重载一个,让编译器识别的更全面。

	my_vector::vector<int> v1(10, 2);

 上面的代码会报错如果没有int n这个参数的重载,因为10默认是int,会先优先匹配迭代器构造的模版参数,而不是识别size_t(因为这个会发生类型转换,模版直接就推演了)。

还可以这样解决:

然后函数体内就是根据要初始化的数据个数,进行扩容,再尾插即可。

 

 

 使用迭代器区间进行构造:
		//类模版的成员函数还可以带模版,这个模版可以用主模板的模版参数,也可以用自己的template<class InputIterator>vector(InputIterator first, InputIterator last)//用迭代器区间初始化,这个迭代器区间的类型可以是任何迭代器区间的类型,所以给上模版参数:_start(nullptr), _finish(nullptr),_end_of_storage(nullptr){while (first != last)//不能用小于,假设是链表传过来的迭代器区间,空间不是连续的,不能保证前面的节点的地址就小于后面的{push_back(*first);++first;}}

首先,类模版的成员函数还可以带模版,这个模版可以用主模板的模版参数,也可以用自己的,就是这里会跟上面的带参的构造矛盾。

其次,使用迭代器区间初始化就是保证可以让任意类型的迭代器区间都可以初始化vector,例子已在vector的使用中举过。

然后就是将传过来的迭代器区间一个一个尾插到vector这个容器中,注意判断条件不能使用小于,如果传的迭代器区间的类型是一个list也就是链表,我们知道链表的存储并不是连续的,所以不能保证前面的空间的地址一个小于后面的空间地址。 

3.交换

		void swap(vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}

直接调用库中的交换即可,直接写还需要有三个深拷贝。

 注意这里的swap的参数不能是const,如果使用const修饰,那v就不能变了,所以里面的成员指向的空间也不能变,交换可是要交换空间的。

4.拷贝构造

		不传统的拷贝构造,但是是可以的//vector(const vector<T>& v)//{//	reserve(v.capacity());//	for (auto e : v)//	{//		push_back(e);//this->push_back(e)//	}//}//vector(const vector<T>& v)//{//	//reserve(v.capacity());//	//扩容也可不复用//	_start = new T[v.capacity()];//	//memcpy(_start, v._start, sizeof(T) * v.size());//	for (size_t i = 0; i < v.size(); ++i)//	{//		_start[i] = v._start[i];//[]就是解引用//	}//	_finish = _start + v.size();//	_end_of_storage = _start + v.capacity();//}//很现代的拷贝构造(不对???为什么???要提供两个迭代器区间的构造才能用)//vector(const vector<T>& v)  //语法上支持拷贝构造和赋值不加模版参数vector(const vector<T>& v){vector<T> tmp(v.begin(), v.end());//这个要提供两个迭代器区间的构造才能用swap(tmp);//swap(v);//不能这样直接调用,v的类型不支持swap的参数}

这里一个提供了3个拷贝构造,第一个我们让它叫做不传统的拷贝构造,因为它利用了范围for,其实就是先根据拷贝的对象扩容,然后再进行赋值尾插到要拷贝的内容中去。

第二拷贝构造是传统的拷贝构造,先扩容再开空间深拷贝,注意这里的赋值就是深拷贝,因为如果假设模版是string类型的,在赋值的时候解引用找到的就是string类型的元素,再赋值就是去调用string内部的赋值重载;而这样使用memcpy是按字节进行拷贝的,所以还会造成拷贝完了,new出来的变量是指向同一块空间的,然后析构两次会出错,并且插入删除数据会混乱。

最后要更新大小与容量。

第三个是更现代的拷贝构造,也就是复用了swap,这样不能直接调用swap,因为swap的参数没有const修饰,而这里的v是const类型的,不匹配,所以要先用要拷贝的对象构造出一个对象,再进行交换。

5.赋值重载

		vector<T>& operator=(vector<T>& v){swap(v);return *this;}

直接复用即可,这里就可以直接交换了, 参数是匹配的;最后要有返回值,支持连续赋值。

6.迭代器

		iterator begin(){return _start;}iterator end(){return _finish;}iterator begin() const{return _start;}iterator end() const//这里的const是修饰的调用者的const,所以调用者不能修改数据;而返回值是如果是const迭代器那就是迭代器不能走了,不能遍历了,不行{return _finish;}

7.扩容

reserve:
		void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start)//如果旧空间为空,就不进if了,直接让旧空间开好的空间;其次也因为if中要对_start解引用{//memcpy是浅拷贝,因为按字节拷贝还是两个开辟出了的指针指向同一块空间//memcpy(tmp,_start,sizeof(T)*size());//这里乘capacity可能会造成浪费,因为capacity开的空间比较多for (size_t i = 0; i < size(); ++i){tmp[i] = _start[i];//如果模版参数是string这样的自定义类型,而且有开辟的空间,肯定是要深拷贝的,而这里的赋值就会调用string的赋值,符合深拷贝}delete[] _start;}_start = tmp;_finish = sz + _start;//虽然扩容了,但是原有的finish的位置不能变,所以上面要记录一下;而且_start也变了,原来的size()就不能用了_end_of_storage = _start + n;}}

 扩容首先要记录大小,因为虽然扩容了,但是_finish与_statr的距离不会变,如果不记录,后面的_start要么销毁要么找不到了,不能使用了。

扩容就是先开空间,然后旧空间内容深拷贝给新空间,释放旧空间,再让旧空间指向新空间,原来在string的实现中以说过。

注意后面的更新需要用原来的旧空间的大小更新(这里就体现了记录原来大小的作用),容量就用扩好的容量更新。

resize:
		void resize(size_t n,T val=T())//任意类型都有默认构造{if (n < capacity()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}while ((_start + n) != _finish){*_finish = val;//源码使用的是定位new,但这里的空间都是直接new出来的,定位new针对的是内存池_finish++;}}}

 如果是传的容量小于原来的容量,就是删除数据,否则就是扩容,然后将扩容后的数据给上初始化的值,默认是0。

 

8.插入与删除

insert:
		iterator insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;//虽然扩容了,但是pos与_start的距离不会变,所以记录下来reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//reserve走完,pos指向的旧空间就找不到或者销毁了,要更新pos的位置,否则会面临迭代器失效的问题}iterator end = _finish - 1;while (pos <= end){*(end + 1) = *end;--end;//不用担心pos为0,end--变成负的,因为这里是指针}*pos = val;_finish++;return pos;}

插入时如果为空,就扩容,然后挪到数据。

注意这里的迭代器失效的问题,这里的迭代器失效是指的野指针的问题,因为pos在走完reserve后pos指向的还是旧空间,但是reserve已经将就旧空间释放了,再使用pos就野指针了,但是pos与_start的距离是不会变的,所以用它们记录这个距离,再reserve后再更新pos就能解决问题。

注意返回插入后的迭代器的位置。

erase:
		iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;}

 删除,挪动数据。

insert迭代器失效问题:

首先就是上面的野指针问题,迭代器失效。

其次就是迭代器虽然能用,但不是想要的位置了的问题。

此问题分为两种情况:

第一。如果之前调用过insert了,扩好容了(这个前提很重要),再插入数据就会面临不是想要的位置的问题:

开始调用了一下insert,说明已经扩好容了,要修改3位置的值,但是实际插入完数据pos的位置指向的数据就变了,指向的就是插入的数据30的位置。

这样也是, 开始调一次insert扩好容了,但是pos还是不是想要的位置。

第二种情况,没有扩好容,此时调用insert就需要扩容,但是扩容后pos的位置更新,pos就指向其它的位置了,再使用这个pos就找不到了,再改动数据也没有用。(但是这并不属于野指针,所以不会报错,因为pos并没有被销毁,只是指向了其它的位置。)

如何解决?

能不能用引用解决呢? 

 

 

不能,因为begin里面是一个传值返回,返回的是临时变量的拷贝,临时变量具有常性,不能权限放大。

使用返回值记录迭代器的位置即可:

但是最好还是不要用insert后的pos,就认为它失效了,因为不知道insert里面什么时候扩容,一旦扩容就要考虑是否失效的问题;库里面实现的效果是,如果不用返回值接收pos的位置,还去使用迭代器的位置,就会断言失败。

erase迭代器失效问题:

(还剩一个更深的拷贝,erase迭代器失效,笔记中的)

 

9.头插头删

		void push_back(const T& val){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity()* 2);//不能直接扩容二倍,如果开始是0,就错了;这里走完reserve成员变量都被初始化了}*_finish = val;//_finsih不会是空指针,因为如果_finish为空,说明都是_start,_end_of_storage都为空,肯定会走上面的if,然后走reserve就初始化了_finish++;}void pop_back(){assert(!empty());--_finish;//这样就找不到末尾的数据了}

注意头插扩容与头删判空。判空就是_start==_finish为真就是空。 

10.[]重载

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

注意提供const版本,const的对象调用[]不能修改本身,但是普通对象调用[]后是可以修改的。 

 

11.完整代码与测试

#pragma once
#include <iostream>
#include <assert.h>
#include <vector>
#include <string>
#include <string.h>using namespace std;//不能放到头文件的前面,不然头文件展开后,往下找就找不到库了,头文件就不能用了namespace my_vector
{template<class T>class vector{public:typedef T* iterator;typedef const T* const_iterator;vector():_start(nullptr)//不管调用哪个构造,带参的还是不带参数的,都要给初始化列表,因为不给初始化后成员变量都是随机值;或者是成员变量声明时给缺省,_finish(nullptr),_end_of_storage(nullptr){}vector(size_t n, const T& val = T())//使用匿名对象初始化,例如模版参数是int也就成0了:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);for (size_t i = 0; i < n;++i){push_back(val);}}vector(int n, const T& val = T())//没有这个会出现非法的间接寻址的错误,编译器识别不出来两个参数的构造是调用迭代器的那个还是这个。:_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(n);for (size_t i = 0; i < n; ++i){push_back(val);}}不传统的拷贝构造,但是是可以的//vector(const vector<T>& v)//{//	reserve(v.capacity());//	for (auto e : v)//	{//		push_back(e);//this->push_back(e)//	}//}//vector(const vector<T>& v)//{//	//reserve(v.capacity());//	//扩容也可不复用//	_start = new T[v.capacity()];//	//memcpy(_start, v._start, sizeof(T) * v.size());//	for (size_t i = 0; i < v.size(); ++i)//	{//		_start[i] = v._start[i];//[]就是解引用//	}//	_finish = _start + v.size();//	_end_of_storage = _start + v.capacity();//}//很现代的拷贝构造(不对???为什么???要提供两个迭代器区间的构造才能用)//vector(const vector<T>& v)  //语法上支持拷贝构造和赋值不加模版参数vector(const vector<T>& v){vector<T> tmp(v.begin(), v.end());//这个要提供两个迭代器区间的构造才能用swap(tmp);//swap(v);//不能这样直接调用,v的类型不支持swap的参数}void swap( vector<T>& v){std::swap(_start, v._start);std::swap(_finish, v._finish);std::swap(_end_of_storage, v._end_of_storage);}~vector(){delete[] _start;//销毁_start指向的这块空间_start = _finish = _end_of_storage = nullptr;}vector<T>& operator=(vector<T>& v){swap(v);return *this;}size_t size() const{return _finish - _start;}size_t capacity() const{return _end_of_storage - _start;}iterator begin(){return _start;}iterator end(){return _finish;}iterator begin() const{return _start;}iterator end() const//这里的const是修饰的调用者的const,所以调用者不能修改数据;而返回值是如果是const迭代器那就是迭代器不能走了,不能遍历了,不行{return _finish;}//类模版的成员函数还可以带模版,这个模版可以用主模板的模版参数,也可以用自己的template<class InputIterator>vector(InputIterator first, InputIterator last)//用迭代器区间初始化,这个迭代器区间的类型可以是任何迭代器区间的类型,所以给上模版参数:_start(nullptr), _finish(nullptr),_end_of_storage(nullptr){while (first != last)//不能用小于,假设是链表传过来的迭代器区间,空间不是连续的,不能保证前面的节点的地址就小于后面的{push_back(*first);++first;}}void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];if (_start)//如果旧空间为空,就不进if了,直接让旧空间开好的空间;其次也因为if中要对_start解引用{//memcpy是浅拷贝,因为按字节拷贝还是两个开辟出了的指针指向同一块空间//memcpy(tmp,_start,sizeof(T)*size());//这里乘capacity可能会造成浪费,因为capacity开的空间比较多for (size_t i = 0; i < size(); ++i){tmp[i] = _start[i];//如果模版参数是string这样的自定义类型,而且有开辟的空间,肯定是要深拷贝的,而这里的赋值就会调用string的赋值,符合深拷贝}delete[] _start;}_start = tmp;_finish = sz + _start;//虽然扩容了,但是原有的finish的位置不能变,所以上面要记录一下;而且_start也变了,原来的size()就不能用了_end_of_storage = _start + n;}}void push_back(const T& val){if (_finish == _end_of_storage){reserve(capacity() == 0 ? 4 : capacity()* 2);//不能直接扩容二倍,如果开始是0,就错了;这里走完reserve成员变量都被初始化了}*_finish = val;//_finsih不会是空指针,因为如果_finish为空,说明都是_start,_end_of_storage都为空,肯定会走上面的if,然后走reserve就初始化了_finish++;}void pop_back(){assert(!empty());--_finish;//这样就找不到末尾的数据了}void resize(size_t n,T val=T())//任意类型都有默认构造{if (n < capacity()){_finish = _start + n;}else{if (n > capacity()){reserve(n);}while ((_start + n) != _finish){*_finish = val;//源码使用的是定位new,但这里的空间都是直接new出来的,定位new针对的是内存池_finish++;}}}iterator insert(iterator pos, const T& val){assert(pos >= _start);assert(pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;//虽然扩容了,但是pos与_start的距离不会变,所以记录下来reserve(capacity() == 0 ? 4 : capacity() * 2);pos = _start + len;//reserve走完,pos指向的旧空间就找不到或者销毁了,要更新pos的位置,否则会面临迭代器失效的问题}iterator end = _finish - 1;while (pos <= end){*(end + 1) = *end;--end;//不用担心pos为0,end--变成负的,因为这里是指针}*pos = val;_finish++;return pos;}iterator erase(iterator pos){assert(pos >= _start);assert(pos < _finish);iterator start = pos + 1;while (start != _finish){*(start - 1) = *start;++start;}--_finish;return pos;}void empty(){return _start == _finish;}T& operator[](size_t pos){assert(pos < size());return _start[pos];}T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}private:iterator _start;iterator _finish;iterator _end_of_storage;};void func(const vector<int>& v){for (size_t i = 0; i < v.size(); ++i){cout << v[i] << " ";}cout << endl;vector<int>::const_iterator it = v.begin();while (it != v.end()){cout << *it << " ";++it;}cout << endl;}void Testvector1(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);for (size_t i = 0; i < v1.size(); ++i){cout << v1[i] << " ";}cout << endl;vector<int>::iterator it = v1.begin();while (it != v1.end()){cout << *it << " ";++it;}cout << endl;for (auto e : v1){cout << e << " ";}cout << endl;func(v1);}//template<class T>//void f()//{//	T x = T();//都是初始化为0//	cout << x << endl;//}//void test_vector2()//{//	//内置类型有没有构造函数?没有,但是能这样写//	/*int i = int();这样结果是0//	int j = int(1);*///	f<int>();//匿名对象//	f<int*>();//	f<double>();//}void Testvector2(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);cout << v1.size() << endl;cout << v1.capacity() << endl;v1.resize(10);cout << v1.size() << endl;cout << v1.capacity() << endl;v1.resize(3);func(v1);}void Testvector3(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//为什么只插入4个会越界呢?因为只插入4个再调insert会扩容,一扩容pos指向的位置会变,就会面临迭代器失效//而插入5个数据在push_back的时候就已经扩容了,insert就不再扩容了/*v1.insert(v1.begin(), 0);func(v1);*/auto pos = find(v1.begin(), v1.end(), 3);if (pos != v1.end()){pos = v1.insert(pos, 30);}func(v1);//最好不要用,不知道insert什么时候扩容,扩容就会面临着pos失效的问题(*pos)++;//想对pos位置++但却对在pos位置插入的数据++了,怎么办?func(v1);}void Testvector4(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);//为什么只插入4个会越界呢?因为只插入4个再调insert会扩容,一扩容pos指向的位置会变,就会面临迭代器失效//而插入5个数据在push_back的时候就已经扩容了,insert就不再扩容了/*v1.insert(v1.begin(), 0);func(v1);*/auto pos = find(v1.begin(), v1.end(), 3);if (pos != v1.end()){v1.erase(pos);}func(v1);}void Testvector5(){vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);vector<int>::iterator it = v1.begin();while (it != v1.end()){if (*it % 2 == 0){it = v1.erase(it);//erase以后就不能访问了,g++可以,还是认为erase后迭代器失效,用返回值接收即可}else{it++;}}for (auto e : v1){cout << e << " ";}cout << endl;}void Testvector6(){vector<int> v1(10, 5);for (auto e : v1){cout << e << " ";}cout << endl;}void Testvector7(){vector<int> v1(10, 5);for (auto e : v1){cout << e << " ";}cout << endl;vector<int> v2(v1);//不写构造,对于动态开辟的new出来的需要深拷贝,默认的值拷贝就不行了for (auto e : v2){cout << e << " ";}cout << endl;vector<std::string> v3(3, "1111111111111111111");for (auto e : v3){cout << e << " ";}cout << endl;vector<std::string> v4(v3);for (auto e : v4){cout << e << " ";}cout << endl;v4.push_back("2222222");v4.push_back("2222222");v4.push_back("2222222");for (auto e : v4){cout << e << " ";}}
}

总结:

本篇的重点就是迭代器失效的问题,很重要,list篇还会提及。

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

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

相关文章

通过pre标签进行json格式化展示,并实现搜索高亮和通过鼠标进行逐个定位的功能

功能说明 实现一个对json进行格式化的功能添加搜索框&#xff0c;回车进行关键词搜索&#xff0c;并对关键词高亮显示搜索到的多个关键词&#xff0c;回车逐一匹配监听json框&#xff0c;如果发生了编辑&#xff0c;需要在退出时提示&#xff0c;在得到用户确认的情况下再退出…

30天精通Linux系统编程-----第一天:底层文件I/O (建议收藏)

目录 1.什么是底层文件I/O 2.底层文件I/O常用库函数 2.1 write函数 2.2 read函数 2.3 open函数 2.4 close函数 2.5 lseek函数 2.6 ioctl函数 2.7 fcntl()函数 2.8 pread()函数 2.9 pwrite()函数 1.什么是底层文件I/O 底层I/O指的是与硬件设备之间的直接输入输出操作…

Pytest精通指南(04)前后置和测试用例执行优先级

文章目录 Pytest 固件核心概念Pytest 固件原理Pytest 固件分类方法级函数级类级模块级夹具优先级测试用例执行优先级固件不仅如此后续大有文章 Pytest 固件核心概念 在 pytest 测试框架中&#xff0c;固件是一个核心概念&#xff1b; 它是一种特殊的函数&#xff0c;用于在测试…

蓝桥杯物联网竞赛_STM32L071KBU6_全部工程及国赛省赛真题及代码

包含stm32L071kbu6全部实验工程、源码、原理图、官方提供参考代码及国、省赛真题及代码 链接&#xff1a;https://pan.baidu.com/s/1pXnsMHE0t4RLCeluFhFpAg?pwdq497 提取码&#xff1a;q497

【Python】报错ModuleNotFoundError: No module named fileName解决办法

1.前言 当我们导入一个模块时&#xff1a; import xxx &#xff0c;默认情况下python解释器会搜索当前目录、已安装的内置模块和第三方模块。 搜索路径存放在sys模块的path中。【即默认搜索路径可以通过sys.path打印查看】 2.sys.path.append() sys.path是一个列表 list ,它里…

JVM常用参数一

jvm启动参数 JVM&#xff08;Java虚拟机&#xff09;的启动参数是在启动JVM时可以设置的一些命令行参数。这些参数用于指定JVM的运行环境、内存分配、垃圾回收器以及其他选项。以下是一些常见的JVM启动参数&#xff1a; -Xms&#xff1a;设置JVM的初始堆大小。 -Xmx&#xff1…

证书生成和获取阿里云备案获取密钥流程

1.在java文件夹下 输入 cmd 打开命令行窗口 2. keytool -genkey -alias 证书名 -keyalg RSA -keysize 2048 -validity 36500 -keystore 证书名.keystore 输入这一行&#xff0c;把证书名三个字 改成 项目的名称&#xff08;例如&#xff1a;D23102802&#xff09; 3. 密码默认填…

天工 AI 爆赞的数据分析能力

分享一个 AI 应用。 天工 AI 天工AI - 首页 (tiangong.cn) 可以上传数据&#xff0c;给出数据分析命令&#xff0c;并能出图。 数据分析师岌岌可危。 又知道其他好用的数据分析应用么&#xff0c;可以告诉我下。

vscode + wsl1 搭建远程C/C++开发环境

记录第一次搭建环境过程。 如何选择开发环境 搭建C/C开发环境有很多种方式&#xff0c;如 MinGW vscode&#xff08;MinGW 是GCC的Windows版本&#xff0c;本地编译环境&#xff09;SSH隧道连接 vscode&#xff08;远程Linux主机&#xff09;wsl vscode&#xff08;远程Li…

Axios网络请求

Axios网络请求主要用于前后端请求&#xff0c;前后端分离时前端需要通过url请求后端的接口&#xff0c;并且处理后端传过来的数据。 Axios官网教程 安装 npm install axios在main.js导入 import axios from axios;//声明一个http变量&#xff01;&#xff01;&#xff01…

初步了解Zookeeper

目录 1. Zookeeper定义 2. Zookeeper工作机制 3. Zookeeper特点 4. Zookeeper数据结构 5. Zookeeper应用场景 5.1 统一命名服务 5.2 统一配置管理 5.3 统一集群管理 5.4 服务器动态上下线 5.5 软负载均衡 6. Zookeeper 选举机制 6.1 第一次启动选举机制 6.2 非第一…

分类预测 | Matlab实现KPCA-IDBO-LSSVM基于核主成分分析和改进蜣螂优化算法优化最小二乘支持向量机分类预测

分类预测 | Matlab实现KPCA-IDBO-LSSVM基于核主成分分析和改进蜣螂优化算法优化最小二乘支持向量机分类预测 目录 分类预测 | Matlab实现KPCA-IDBO-LSSVM基于核主成分分析和改进蜣螂优化算法优化最小二乘支持向量机分类预测分类效果基本描述程序设计参考资料 分类效果 基本描述…

【Python数据分析】让工作自动化起来,无所不能的Python

这里写目录标题 前言一、Python是办公自动化的重要工具二、Python是提升职场竞争力的利器三、Python是企业数字化的重要平台四、Python是AI发展的重要通道之一编辑推荐内容简介作者简介前言读者对象如何阅读本书目录 前言 随着我国企业数字化和信息化的深入&#xff0c;企业对…

大屏可视化展示平台解决方案(word原件获取)

1.系统概述 1.1.需求分析 1.2.重难点分析 1.3.重难点解决措施 2.系统架构设计 2.1.系统架构图 2.2.关键技术 2.3.接口及要求 3.系统功能设计 3.1.功能清单列表 3.2.数据源管理 3.3.数据集管理 3.4.视图管理 3.5.仪表盘管理 3.6.移动端设计 3.1.系统权限设计 3.2.数据查询过程设…

【论文阅读】MCTformer: 弱监督语义分割的多类令牌转换器

【论文阅读】MCTformer: 弱监督语义分割的多类令牌转换器 文章目录 【论文阅读】MCTformer: 弱监督语义分割的多类令牌转换器一、介绍二、联系工作三、方法四、实验结果 Multi-class Token Transformer for Weakly Supervised Semantic Segmentation 本文提出了一种新的基于变换…

ONERugged车载平板电脑厂家丨工业车载电脑优势体现丨3年质保

作为现代社会中必不可少的出行工具&#xff0c;汽车不仅仅是代步工具&#xff0c;更是我们生活中的重要一部分。而在如此多功能的汽车内&#xff0c;一款高可靠性、适应不同行业应用的车载平板电脑成为了当下的热门选择。ONERugged车载平板电脑以其卓越的品质和强大的功能而备受…

Java前置一些知识

文章目录 搭建Java环境安装path环境变量Java技术体系 Java执行原理JDK组成跨平台Java内存分配 IDEA管理Java程序 搭建Java环境 安装 oralce官网下载 JDK17 Windows 傻瓜式的点下一步就行&#xff0c;注意&#xff1a;安装目录不要有空格、中文 java 执行工具 javac 编译工具…

MWeb Pro For Mac v4.5.9 强大的 Markdown 软件中文版

MWeb 是专业的 Markdown 写作、记笔记、静态博客生成软件&#xff0c;目前已支持 Mac&#xff0c;iPad 和 iPhone。MWeb 有以下特色&#xff1a; 软件下载&#xff1a;MWeb Pro For Mac v4.5.9 软件本身&#xff1a; 使用原生的 macOS 技术打造&#xff0c;追求与系统的完美结合…

为什么需要网络切片?

网络切片是电信领域的一个突破性概念&#xff0c;它允许将物理网络基础设施划分为多个虚拟网络&#xff0c;称为切片。每个切片作为一个独立的网络运行&#xff0c;拥有自己的专用资源和定制的特性&#xff0c;满足不同应用、行业或用户的特定需求。 将网络切片视为在共享物理…

Linux云计算之Linux基础3——Linux系统基础part-2

1、终端、shell、文件理论 1、终端 终端(terminal)&#xff1a;人和系统交互的必要设备&#xff0c;人机交互最后一个界面&#xff08;包含独立的输入输出设备&#xff09; 物理终端(console)&#xff1a;直接接入本机器的键盘设备和显示器虚拟终端(tty)&#xff1a;通过软件…