<C++> vector模拟实现

目录

前言

一、定义命名空间

二、构造函数

三、拷贝构造

四、赋值运算符重载

五、push_back && reserve

六、深拷贝问题

七、iterator 迭代器

1. 可读可写

2. 只读

八、operator[ ]

1. 可读可写

2. 只读

九、insert

问题:内部迭代器失效

十、erase

十一、resize 

总结


前言

        vector的使用与string大致相同,本节我们来参考stl中的vecor,模拟实现vector

        由于空间适配器较难,我们直接采用new、delete来完成开空间操作 ,后期再学习空间适配器。


一、定义命名空间

  • vector内的数据使用模板代替,因为vector内可能是指针、整形、string、vector等类型
  • 成员变量仿照stl内的vector成员变量,三个指针:start、finish、end_of_storage
  • 将指针类型重命名为iterator
namespace my_vector
{template<class T>class vector{public: typedef T* iterator;private:iterator _start;iterator _finish;iterator _end_of_storage;};
}

二、构造函数

  • 成员变量初始化为nullptr
		vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){}
  • 初始化n个val,先初始化三个指针为nullptr,不然resize时会使用野指针,这也就体现了C++11可以给成员变量缺省值的优点,不用再为指针初始化为nullptr
		//先初始化为nullptrvector(size_t n, const T& val = T()):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){resize(n, val);}

 迭代器区间初始化构造函数

        因为迭代器指针类型不同,所以需要使用模板

		//迭代器模板,适用各种类型指针template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}

        但是当我们构造一个v

vector<int> v(10, 1);

        编译器会优先调迭代器版本的构造函数,相比 vector(size_t n, const T& val = T()) ,这两个参数更匹配 vector(InputIterator first, InputIterator last),这是因为有两种选择,编译器选择了更加适合的,所以我们可以重载一个比它更适合的构造函数

		//重载一个更适合的版本vector(int n, const T& val = T()){resize(n, val);}

三、拷贝构造

  • 首先初始化列表初始化各指针为空
  • 再开空间,拷贝,最后修改指针
		//方法一:vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){_start = new T[v.capacity()];memcpy(_start, v._start, sizeof(T)*v.size());_finish = _start + v.size();_end_of_storage = _start + v.capacity();}

现代写法:我们可以调用内部的函数,而不用手动开空间 

		//方法二:vector(const vector<T>& v):_start(nullptr), _finish(nullptr), _end_of_storage(nullptr){reserve(v.capacity());for (auto e: v){push_back(e);}}

        在深拷贝问题分析中,我们会再次修改拷贝构造函数 

		vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){_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();}

四、赋值运算符重载

  •  同模拟实现string的赋值运算符重载,我们将实参拷贝构造形参,交换this对象与形参对象,返回*this,使得this对象指向了拷贝构造的形参对象,而形参对象指向了原this,这样,在出了函数后,形参自动销毁,而*this的生命周期还在函数外,所以引用返回

 

		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<T>& operator=(vector<T> v){swap(v);return *this;}

五、push_back && reserve

  • 尾插前先判断空间是否还有剩余,如果满了,就reserve一段空间
  • 由于经常需要用到size、capacity的值,这些值需要指针相减计算,所以我们直接封装为size()、capacity() 成员函数,方便使用
  • reserve时,判断原数组_start是否为空。若不为空,再进行数据拷贝,并释放_start空间,最后更新成员变量
  • 在更新成员变量_finish时,如果直接使用size(),会出现错误,因为_start更新了,_finish还没更新,size()返回的是 旧的finish - 新的start会导致第一次更新的_finish为空,所以*finish = x 时就会出错。所以,我们可以提前记录size(),或者调换_start 和 _finish 的更新顺序
		size_t capacity() const{return _end_of_storage - _start;}size_t size() const{return _finish - _start;}void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];//如果原数组不为空,拷贝数据if (_start){//size(T)算的是成员变量的内存大小,数组内有多少元素没有影响memcpy(tmp, _start, sizeof(T) * sz);delete[] _start;}_start = tmp;//这里如果直接使用size(),会出现错误//因为start更新了,finish还没更新,size()返回的是 旧的finish - 新的start,//会导致第一次更新的finish为空,所以*finish = x 时就会出错//所以,提前记录size()即可,或者调换_start 和 _finish 的更新顺序_finish = _start + sz;_end_of_storage = _start + n;}}//尽量用引用,因为vector里的数据可能是string、vector等较大的数据void push_back(const T& x){if (_finish == _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;}

        size(T)算的是成员变量的内存大小,数组内有多少元素没有影响 

 

六、深拷贝问题

	vector<std::string> v;v.push_back("11111");v.push_back("22222");v.push_back("33333");v.push_back("44444");v.push_back("55555");for (auto& e : v){cout << e << endl;}

        这里的 “11111” 会隐式类型转换为string,因为string有一个单参数的构造函数const char*,因为push_back参数是const T&,所以会发生构造临时变量的操作

        当vector内的数据类型为内置类型,进行扩容时reserve函数memcpy没有问题,是按字节拷贝。

        但是当vector内的数据是拷贝时需要深拷贝的自定义类型,扩容reserve函数的memcpy就有问题了,虽然vector数组是深拷贝,但是数组元素没有深拷贝,我们是按自定义类型字节大小进行的浅拷贝,拷贝的vector内的对象指向了被拷贝的vector对象的数组空间,又因为memcpy之后,立即调用了 delete[] _str,delete直接调用了数组内所有对象的析构函数,并释放数组空间,最终导致新拷贝的vector内的元素string的_str都指向了被释放的空间

解决方法:

        按元素个数遍历,逐个赋值运算符重载赋值给tmp

		void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];//如果原数组不为空,拷贝数据if (_start){//size(T)算的是成员变量的内存大小,数组内有多少元素没有影响/*memcpy(tmp, _start, sizeof(T) * sz);*/for (size_t i = 0; i < sz; ++i){//每个自定义类型数据都采用赋值运算符重载//若为内置类型,也没有影响tmp[i] = _start[i];}delete[] _start;}_start = tmp;/*这里如果直接使用size(),会出现错误因为start更新了,finish还没更新,size()返回的是 旧的finish - 新的start,会导致第一次更新的finish为空,所以*finish = x 时就会出错所以,提前记录size()即可,或者调换_start 和 _finish 的更新顺序*/_finish = _start + sz;_end_of_storage = _start + n;}}

        所以,拷贝构造函数的memcpy也要跟着修改

		vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){_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<vector<int>> vv

七、iterator 迭代器

1. 可读可写

  • begin()、end() 分别对应 _start 和 _finish,返回两指针即可
		iterator begin(){return _start;}iterator end(){return _finish;}

 2. 只读

  • 对于被const修饰的对象,在使用迭代器时,迭代器需要被const修饰,权限平移
		typedef const T* const_iterator;const_iterator begin() const{return _start;}const_iterator end() const{return _finish;}

八、operator[ ]

1. 可读可写

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

2. 只读

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

九、insert

  • 第一步:对于insert函数,参考STL中vector的insert函数,参数为iterator迭代器,以及要插入的值,所以第一步要判断iterator是否在合法的范围
  • 第二步:进行扩容判断,如果要扩容就扩二倍,reserve()
  • 第三步:进行移位(这里略微体现了STL的vector为什么成员变量要用指针表示,而不用size_t 型的size以及capacity,因为对于size_t类型,如果pos指向_start,即pos == 0,那么end--时会出现end < 0 的情况,而size_t是无符号整形,-1是最大值,循环不会中断,陷入死循环)
  • 第四步:进行填充,并使_finish++,相等于size++
		void insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finfish;}

问题:内部迭代器失效

        当我们插入的数据超过8个时(第一次扩容为4,第二次扩容二倍为8,第三次扩容为16),就会插入失败,是随机值。为什么呢?

        分析扩容处出现错误,因为每次扩容都是开辟一个新的空间,_start、_finish、_end_of_storage都会随之修改,而形参iterator pos是不变的,它还指向的原空间,成为野指针。

        解决:扩容前记录pos相对_start位置,并在扩容后更新

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

        对于 push_back 我们可以复用 insert 函数

		void push_back(const T& x){/*if (_finish == _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;*/insert(end(), x);}

新问题:外部的迭代器

        因为insert函数形参是iterator类型,是临时拷贝,如果扩容了,函数内部的pos会更新,但是在外部,实参pos还是原先的迭代器,如果此时在外部修改pos,相当于修改野指针,很容易出错,也就是说,在insert之后迭代器可能会失效(之所以是可能,是因为平台不一样扩容机制不一样,Linus扩容2倍,VS扩容1.5倍)

        所以,记住!insert以后就不要使用这个形参迭代器了,因为它很可能失效了,如果再进行 

	vector<int>::iterator pos = v.begin() + 3;v.insert(pos, 500);*pos += 10;

*pos += 10; 这是高危行为

        所以,STL的vector将insert的返回值设置为iterator,

        返回指向第一个新插入元素的迭代器,所以如果想继续修改,就用返回的迭代器,不要用实参传的迭代器

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

十、erase

  • 删除元素,移动后面元素即可
  • --_finish
		void erase(iterator pos){assert(pos >= _start && pos < _finish);iterator begin = pos + 1;while (begin != _finish){*(begin - 1) = *begin;++begin;}--_finish;}

erase的迭代器会失效吗?

        会,在特殊的情景会失效,当迭代器指向最后一个元素,删除元素后,迭代器就会失效。

我们先看一般情况:

void test2()
{vector<int> v;v.push_back(5);v.push_back(4);v.push_back(3);v.push_back(2);v.push_back(1);for (auto& e : v){cout << e << " ";}cout << endl;vector<int>::iterator it = v.begin();v.erase(it);cout << *it << endl;++it;cout << *it << endl;for (auto& e : v){	cout << e << " ";}
}

        可以看出,此时的迭代器没有失效,解引用和++操作都没有异常,但是当it指向最后一个元素时,问题就出现了

void test2()
{vector<int> v;v.push_back(5);v.push_back(4);v.push_back(3);v.push_back(2);v.push_back(1);for (auto& e : v){cout << e << " ";}cout << endl;vector<int>::iterator it = v.begin() + 4;v.erase(it);cout << *it << endl;++it;cout << *it << endl;for (auto& e : v){	cout << e << " ";}
}

​​​

        可以看到,当删除了最后一个元素后,第一次解引用是被删除的值 —— 1,相当于野指针(因为该空间按理来说已经 “释放” 了,对于数组删除元素,我们一般不抹除值,只进行覆盖),当 it++ 后再次解引用,it 还是野指针,但指向的空间是随机值,所以打印出了随机值

        对于VS的vector,它会强制判断,如果进行上面的操作,即使不是删除最后一个元素后再使用迭代器,VS都会直接报错;而g++不同,它不进行检查,但是不检查的行为,会导致出现很多问题。

        所以,同 insert 函数 erase 之后也不要使用迭代器,这也是高危行为

        返回一个迭代器,指向删除的最后一个元素之后的元素的新位置。如果操作删除了序列中的最后一个元素,则容器结束。

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

        如果要在vector中删除偶数,那么迭代器要使用erase返回的迭代器,如果使用自己的迭代器,VS就不通过,代码没有移植性。

        其实对于erase,可能会出现其他平台,很珍惜内存空间,在删除了一半的数据后,会缩容,那么缩容时,迭代器肯定是失效的

        总结:vector在使用insert和erase之后,不能再访问这个迭代器,虽然insert之后迭代器有可能没有失效,但是我们都认为失效了,即访问结果是未定义的

十一、resize 

        对于vector来说resize是很重要的,在resize时,我们要指定默认初始为什么,这里就用到了模板和匿名对象。

void resize(size_t n, const T& val = T())

        对于自定义类型,构造时会调用其类型的默认构造函数,如果没有默认构造,那么这个resize函数就编译不通过,这也说明了,我们在写类的时候一定要写默认构造,不然坑害的是自己。

        对于内置类型,比如 int ,int( )在理论上是不行的,因为int并没有构造函数,但是这个需求是肯定的!所以C++的内置类型在C++出了模板之后就升级了,对于内置类型也有其构造函数,对于整形默认初始化为0,浮点型为0.0,指针类型为空。

int main()
{int i = 0;int j = int();int k = int(1);cout << "i = " << i << endl;cout << "j = " << j << endl;cout << "k = " << k << endl;return 0;
}

		//T类型匿名对象,对于自定义类型,调用它的默认构造,没有默认构造,那么程序就编不过void resize(size_t n, const T& val = T()){if (n < _finish){_finish = _start + n;}else{reserve(n);while (_finish != start + n){*_finish = val;++_finish;}}}

 整体代码:

#pragma once
#include<iostream>
#include<stdlib.h>
#include<assert.h>
using std::cin;
using std::cout;
using std::endl;namespace my_vector
{template<class T>class vector{public: 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;}//先初始化为nullptr,因为成员变量给了初始值,所以可以不使用初始化列表vector(size_t n, const T& val = T())//:_start(nullptr)//, _finish(nullptr)//, _end_of_storage(nullptr){resize(n, val);}//重载一个更适合的版本vector(int n, const T& val = T()){resize(n, val);}//迭代器模板,使用所有自定义类型的迭代器template<class InputIterator>vector(InputIterator first, InputIterator last){while (first != last){push_back(*first);++first;}}vector():_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){}//方法一:vector(const vector<T>& v):_start(nullptr),_finish(nullptr),_end_of_storage(nullptr){_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)//	:_start(nullptr)//	, _finish(nullptr)//	, _end_of_storage(nullptr)//{//	reserve(v.capacity());//	for (auto e: v)//	{//		push_back(e);//	}//}//赋值运算符重载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<T>& operator=(vector<T> v){swap(v);return *this;}~vector(){if (_start){delete[] _start;_start = _finish = _end_of_storage = nullptr;}}size_t capacity() const{return _end_of_storage - _start;}size_t size() const{return _finish - _start;}void reserve(size_t n){if (n > capacity()){size_t sz = size();T* tmp = new T[n];//如果原数组不为空,拷贝数据if (_start){//size(T)算的是成员变量的内存大小,数组内有多少元素没有影响/*memcpy(tmp, _start, sizeof(T) * sz);*/for (size_t i = 0; i < sz; ++i){//每个自定义类型数据都采用赋值运算符重载//若为内置类型,也没有影响tmp[i] = _start[i];}delete[] _start;}_start = tmp;/*这里如果直接使用size(),会出现错误因为start更新了,finish还没更新,size()返回的是 旧的finish - 新的start,会导致第一次更新的finish为空,所以*finish = x 时就会出错所以,提前记录size()即可,或者调换_start 和 _finish 的更新顺序*/_finish = _start + sz;_end_of_storage = _start + n;}}//T类型匿名对象,对于自定义类型,调用它的默认构造,没有默认构造,那么程序就编不过void resize(size_t n, const T& val = T()){if (n < _finish){_finish = _start + n;}else{reserve(n);while (_finish != _start + n){*_finish = val;++_finish;}}}//尽量用引用,因为vector里的数据可能是string、vector等较大的数据void push_back(const T& x){/*if (_finish == _end_of_storage){size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);}*_finish = x;++_finish;*/insert(end(), x);}T& operator[](size_t pos){assert(pos < size());return _start[pos];}const T& operator[](size_t pos) const{assert(pos < size());return _start[pos];}iterator insert(iterator pos, const T& x){assert(pos >= _start && pos <= _finish);if (_finish == _end_of_storage){size_t len = pos - _start;size_t newcapacity = capacity() == 0 ? 4 : capacity() * 2;reserve(newcapacity);//解决pos迭代器失效问题pos = _start + len;}iterator end = _finish - 1;while (end >= pos){*(end + 1) = *end;--end;}*pos = x;++_finish;return pos;}iterator erase(iterator pos){assert(pos >= _start && pos < _finish);iterator begin = pos + 1;while (begin != _finish){*(begin - 1) = *begin;++begin;}--_finish;return pos;}private:iterator _start = nullptr;iterator _finish = nullptr;iterator _end_of_storage = nullptr;};
}

总结

        多练习string、vector的模拟实现,下节我们学习list并模拟实现list

        最后,如果小帅的本文哪里有错误,还请大家指出,请在评论区留言(ps:抱大佬的腿),新手创作,实属不易,如果满意,还请给个免费的赞,三连也不是不可以(流口水幻想)嘿!那我们下期再见喽,拜拜!

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

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

相关文章

《RT-DETR改进实战》专栏介绍 专栏目录

《RT-DETR改进实战专栏》介绍及目录 介绍&#xff1a;欢迎来到最新专栏《RT-DETR改进实战》&#xff01;这个专栏专注于基于 YOLOv8 项目的魔改版本&#xff0c;而不是百度飞桨框架中的 RT-DETR。 本专栏为想通过改进 RT-DETR 算法发表论文的同学设计。每篇文章均包含完整的改…

探讨下前端测试的常见场景

前端测试 场景 这边指的测试是指白盒测试&#xff0c;用代码来测试代码。 测试有利于提升代码质量。 代码功能和需求一致。根据需求&#xff0c;写测试。测试通过了&#xff0c;则表明需求实现了。保证代码重构后&#xff0c;未改坏以前的功能。代码重构后&#xff0c;能通过…

分类预测 | Matlab实现KOA-CNN-BiGRU-selfAttention多特征分类预测(自注意力机制)

分类预测 | Matlab实现KOA-CNN-BiGRU-selfAttention多特征分类预测&#xff08;自注意力机制&#xff09; 目录 分类预测 | Matlab实现KOA-CNN-BiGRU-selfAttention多特征分类预测&#xff08;自注意力机制&#xff09;分类效果基本描述程序设计参考资料 分类效果 基本描述 1.M…

jenkins详细安装教程

这里写目录标题 一、Jenkins安装与部署1-1、Jenkins的简介1-2、下载需要的软件1-2-1 jekins.war1-2-2 tomcat安装方式 1-3、使用11版本的jdk1-4、开启jenkins1-5、获取密码1-5 修改镜像(可改可不改) 二、卸载Jenkins 一、Jenkins安装与部署 1-1、Jenkins的简介 Jenkins是一个…

Java电商平台 - API 接口设计之 token、timestamp、sign 具体架构与实现|电商API接口接入

一&#xff1a;token 简介 Token&#xff1a;访问令牌access token, 用于接口中, 用于标识接口调用者的身份、凭证&#xff0c;减少用户名和密码的传输次数。一般情况下客户端(接口调用方)需要先向服务器端申请一个接口调用的账号&#xff0c;服务器会给出一个appId和一个key, …

mysql 间隙锁

mysql 默认是可重复读的隔离级别&#xff0c;这种默认会有幻读&#xff0c;幻读指的什么现象呢&#xff0c;就是在同一个事物中前后两次查到的结果不一致&#xff0c;那么mysql是怎么解决幻读的呢&#xff0c;这就是mvcc mvcc 什么是mvcc呢&#xff0c;就是多版本并发控制&am…

迁移学习 - 微调

什么是与训练和微调&#xff1f; 你需要搭建一个网络模型来完成一个特定的图像分类的任务。首先&#xff0c;你需要随机初始化参数&#xff0c;然后开始训练网络&#xff0c;不断调整参数&#xff0c;直到网络的损失越来越小。在训练的过程中&#xff0c;一开始初始化的参数会…

【Python算法】算法练习(一)

❤️博客主页&#xff1a; iknow181 &#x1f525;系列专栏&#xff1a; Python、JavaSE、JavaWeb、CCNP &#x1f389;欢迎大家点赞&#x1f44d;收藏⭐评论✍ 目录 1、输出n以内的质数 2、求n以内最大的m个质数的和&#xff0c;并打印这些质数以及它们的和 方法一 方法二…

GaussDB数据库管理系统介绍

1.GaussDB的发展 2.GaussDB的生态 内部&#xff1a; 云化自动化方案。通过数据库运行基础设施的云化将DBA(数据库管理员)和运维人员的日常工作 自动化。外部&#xff1a; 采用与数据库周边生态伙伴对接与认证的生态连接融合方案&#xff0c;解决开发者/DBA难获取、应用难对接等…

2023年【安全员-C证】报名考试及安全员-C证考试内容

题库来源&#xff1a;安全生产模拟考试一点通公众号小程序 安全员-C证报名考试根据新安全员-C证考试大纲要求&#xff0c;安全生产模拟考试一点通将安全员-C证模拟考试试题进行汇编&#xff0c;组成一套安全员-C证全真模拟考试试题&#xff0c;学员可通过安全员-C证考试内容全…

[UDS] --- CommunicationControl 0x28

1 0x28功能描述 根据ISO14119-1标准中所述&#xff0c;诊断服务28服务主要用于网络中的报文发送与接受&#xff0c;比如控制应用报文的发送与接收&#xff0c;又或是控制网络管理报文的发送与接收&#xff0c;以便满足一定场景下的应用需求。 2 0x28应用场景 一般而言&#…

<多线程章节八> 单例模式中的饿汉模式与懒汉模式的讲解,以及懒汉模式中容易引起的Bug

&#x1f490;专栏导读 本篇文章收录于多线程&#xff0c;也欢迎翻阅博主的其他文章&#xff0c;可能也会让你有不一样的收获&#x1f604; &#x1f337;JavaSE &#x1f342;多线程 &#x1f33e;数据结构 文章目录 &#x1f490;专栏导读&#x1f4a1;饿汉模式&#x1f4a1;…

【算法练习Day32】 斐波那契数爬楼梯使用最小花费爬楼梯

​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;练题 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 文章目录 斐波那契数爬楼梯使用最小花…

Vue的动画与过度

一、Vue的动画效果 &#xff08;一&#xff09;编写CSS关键帧动画 keyframes show{from{transform: translateX(-100%);}to{transform: translateX(0px);} } &#xff08;二&#xff09;定义动画效果 .v-enter-active { animation: 配置项 } // 显示动画 .v-leave-active…

洛谷 P5717 三角形分类 C++代码

目录 前言 题目描述 思路点拨 优化 AC代码 AC截图 结尾 前言 马上就要突破10000浏览量了&#xff0c;再写一篇加加油&#xff01; 图片截图时间:2023.10.25.20:23 题目描述 网址&#xff1a;【深基3.习8】三角形分类 - 洛谷 思路点拨 这道题是给定三条边的长度&#…

一天收入500元的货拉拉运费差项目靠谱吗?

最近的货拉拉运费差项目有点火呀&#xff01;收费也不低&#xff0c;1680-16980的比比皆是。 这个项目去年我就在某些平台看到过&#xff0c;今天就跟大家详细聊聊这个项目&#xff0c;想入坑的不妨先看看这篇文章。 一&#xff1a;项目原理 有人叫它货拉拉搬砖项目&#xf…

骨传导耳机怎么佩戴,骨传导蓝牙耳机什么牌子好用

市面上的传统耳机一直以来都存在一些问题&#xff0c;比如长时间佩戴会导致耳朵不适&#xff0c;或者声音过大可能会伤害到耳膜。但是&#xff0c;现在有一种独特的耳机正在迅速走红&#xff0c;它被称为骨传导耳机&#xff0c;而骨传导耳机是怎么佩戴的呢&#xff0c;它在佩戴…

勒索病毒最新变种.locked勒索病毒来袭,如何恢复受感染的数据?

导言&#xff1a; 在当今数字化时代&#xff0c;网络威胁呈指数级增长&#xff0c;而.locked勒索病毒已经成为网络犯罪分子的犯罪工具之一。这种病毒以其高度破坏性和高级加密技术而著名&#xff0c;将用户的重要数据文件锁定&#xff0c;然后要求支付赎金以解锁这些文件。在本…

window安装es服务及删除

elasticsearch-service.bat install 删除es服务&#xff0c;先停止es服务运行&#xff0c;管理员cmd模式&#xff0c;sc delete "elasticsearch-service-x64"

gRPC初体验

一、gRPC简介 1、RPC是远程过程调用的简称&#xff0c;在分布式系统中&#xff0c;客户端可以像调用本地对象一样调用远程机器上服务端对象&#xff0c;用于系统的垂直拆分&#xff0c;常见的JAVA RPC框架有JAVA自带的RMI、基于Http的Hessian、阿里基于TCP的Dubbo、淘宝基于TC…