C++STL---vector模拟实现

通过上篇文章,我们知道vector的接口实际上和string是差不多的,但是他俩的内部结构却大不一样,vector内有三个成员变量:_start、_finish、_endofstorage:

_start指向容器的头元素,_finish指向有效元素末尾的元素,_endofstorage指向整个容器的尾。

而vector的迭代器就是typedef T*,也就是元素的指针。

所以我们在模拟之前就可以先做出一个小框架,为了不与库中的vector冲突,我们把他放在我们自己的命名空间中:

namespace CYF
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
	private:
		iterator _start;//指向容器的头
		iterator _finish;//指向有效数据的尾
		iterator _endofstorage;//指向容器的尾
	};
}

构造函数

构造函数1

这个是无参构造函数,我们将他所有的成员对象都制成空指针就行:

		//构造函数1
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{}

构造函数2

vector还支持使用一段迭代区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间,也就是说函数接收的迭代器的类型是不确定的,所以我们这里需要将构造函数设计为一个函数模板,在函数体内将该迭代区间的数据一个个尾插到容器当中即可:

		//构造函数2
		template<class InputIterator>//模板参数,因为迭代器区间可以是其他容器的迭代器
		vector(InputIterator first, InputIterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)//将迭代器区间在[first,last)中的数据一个个尾插到容器中
			{
				push_back(*first);
				first++;
			}
		}

构造函数3

vector还支持构造一种初始化有n个值为val的元素的对象。我们可以先使用reserve函数将容器的容量设为n,然后尾插n个值为val的数据到该对象即可:

		vector(size_t n, const T& val)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);//调用reserve函数将容器容量设为n,可以避免效率降低
			for (size_t i = 0; i < n; i++)//尾插n个值为val的元素
			{
				push_back(val);
			}
		}

但是这个函数需要再实现两个重载函数:

vector(long n, const T& val)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(n); 
	for (size_t i = 0; i < n; i++) 
	{
		push_back(val);
	}
}
vector(int n, const T& val)
	:_start(nullptr)
	, _finish(nullptr)
	, _endofstorage(nullptr)
{
	reserve(n); 
	for (int i = 0; i < n; i++) 
	{
		push_back(val);
	}
}

这两个重载函数和他不同的就是第一个参数n的类型,如果没有这两个重载函数的话,我们调用下面的代码时,编译器就会优先和构造函数2匹配。

vector<int> v1(1, 3); //这里调用的是构造函数2 

并且因为构造函数2当中对两个参数进行了解引用(即将int类型解引用)报错:

补充:若我们知道需要多少空间,我们最好先使用reserve函数一次性将空间开辟好,避免调用push_back函数时再增容,导致效率降低。

拷贝构造函数

传统写法1

这里仍然需要深拷贝,先开一段与拷贝对象大小相等的空间,然后将该对象内的元素一个个拷贝过来即可,最后记得更新成员对象的值:

		拷贝构造(传统写法1)
		vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
			_start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间
			for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy
			{
				_start[i] = v[i];
			}
			_finish = _satrt + v.size();//有效数据的尾
			_endofstorage = _start + v.capacity();//整个容器的尾
		}

注意我们这里数据需要一个个拷贝过来,不能使用memcpy函数。若vector内部存储的是内置类型或者无需深拷贝的自定义类型时,可以使用memcpy函数,但是当vector内存储的数据是需要深拷贝的自定义类型的时候,使用memcpy就不行了。例如:我们拿存储的数据类型是string举例:

如图:我们vector对象内元素都是string类型,每个元素都指向自己的一个字符串。若我们使用memcpy函数的话,就会出现下面的情况:

memcpy会将每个string元素内成员变量的值原封不动地拷贝过去,也就导致了两个vector对象内的元素都指向同一组string元素。最后析构环节也就会导致内存错误。

而我们拷贝时采用string类的operator=的方式,采用的是深拷贝,最终的结果是这样:

就不会导致内存问题了。

传统写法2

这里我们直接调用reserve函数进行空间开辟,而后通过范围for将原有对象一个个尾插:

		//拷贝构造(传统写法2)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(v.capacity());
			for (const auto& e : v)
			{
				push_back(e);
			}
		}
现代写法

这里我们将v1作为临时对象,通过构造函数构造出来。而后将v1与*this互换即可,经典的现代写法,无需多言:

		//拷贝构造(现代写法)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T>v1(v.begin(),v.end());
			swap(v1);
		}

赋值运算符重载

传统写法

先判断是否为自己给自己赋值,若给自己赋值则不用做任何操作。若不是,则:先开辟一块和要拷贝对象capacity大小一样的空间,然后将数据一个个拷过来,最后更新成员对象的值:


		//赋值运算符重载函数(传统写法)
		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				delete[]_start;
				_start = new T[v.capacity()];
				_finish = _start + v.size();
				_endofstorage = _satrt + v.capacity();
				for (const auto& e : v)
				{
					push_back(e);
				}
			}
            return *this;
		}

现代写法

operator=的现代写法很巧妙,右值传参的时候使用传值传参而不是引用传参,这样就直接调用了vector的拷贝构造函数,然后用这个拷贝出来的局部对象和左值进行交换,就相当于做了赋值操作,而v是局部变量,在函数结束时会自动调用析构函数销毁。

//现代写法
vector<T>& operator=(vector<T> v) 
{
	swap(v); 
	return *this; //支持连续赋值
}

析构函数

先判断是否为空指针,若不是,将空间释放,再将成员变量全都置为nullptr:

		//析构函数
		~vector()
		{
			delete[]_start;
			_start = nullptr;
			_finish = nullptr;
			_endofstorage = nullptr;
		}

迭代器相关的函数(begin&&end)

我们上面讲过,vector的迭代器实际上就相当于存储数据类型的指针,begin()就是返回第一个元素地址,end()就是返回最后一个元素的下一个数据的地址:

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

所以我们使用迭代器遍历vector对象实际上也就是使用指针遍历。并且实现了迭代器后,也就可以使用范围for,因为范围for在编译的时候,会自动替换为迭代器的形式。

关于容量和大小的函数

size && capacity

这里的size和capacity的算法跟string不同,string内直接就有两个成员变量表示size和capacity,而vector这里需要用_finish - _start和_endofstorage - _start分别得到:

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

reserve

 1、当n大于对象当前的capacity时,将capacity扩大到n或大于n。
 2、当n小于对象当前的capacity时,不用做任何操作

我们先判断n是否大于容量,若大于,就开辟一块容量为n的空间,将原来容器中的有效数据拷贝进去,再将原来的空间释放,记得更新容器内的成员变量:

void reserve(size_t n)
{
	if (n > capacity()) 
	{
		size_t sz = size(); //记录当前容器当中有效数据的个数
		T* tmp = new T[n]; //开辟一块可以容纳n个数据的空间
		if (_start) //判断是否为空容器
		{
			for (size_t i = 0; i < sz; i++) //拷贝数据
			{
				tmp[i] = _start[i];
			}
			delete[] _start; //释放原有空间
		}
		_start = tmp; //更新成员对象
		_finish = _start + sz; 
		_endofstorage = _start + n; 
	}
}

我们这里要注意要先记录元素的个数,否则我们最后更新_finish指针指向的时候,若调用size()函数,而size()函数是通过_finish-_start得到的,而此时_start已经指针指向已经改变,我们就会得到一个随机值,所以我们需要提前记录。

resize

 1、当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
 2、当n小于当前的size时,将size缩小到n。

我们需要先判断n是否小于size,若小于,将_finish改变即可,若不小于,还需要判断是否需要增容,再将扩大的数据赋值为val即可:

		void resize(size_t n, const T& val = T())
		{
			if (n < size()) //当n小于size时
			{
				_finish = _start + n; 
			}
			else 
			{
				if (n > capacity()) 
				{
					reserve(n);
				}
				while (_finish < _start + n) 
				{
					*_finish = val;
					_finish++;
				}
			}
		}

在C++中内置类型也可以看作是一个类,也有自己的默认构造函数,所以val的缺省参数可以设为T()临时对象。

empty

判断对象是否为空:

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

有关修改对象的函数

push_back

先判断是否需要增容,再尾插,最后更新_finish的指向:

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

pop_back

判断对象是否为空,不为空将_finish--即可:

		void pop_back()
		{
			assert(!empty());//容器不能为空
			_finish--;
		}

insert

我们需要先判断pos是否合法,再判断是否需要增容,然后将pos后的元素都向后移一位,将x插在pos的位置,最后将_finish++:

		void insert(iterator pos, const T& x)
		{
			if (pos <= _finish)//判断pos是合法
			{
				size_t n = pos - _start;//记录pos和_start之间的间隔
				if (_finish == _endofstorage)
				{
					size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
					reserve(newcapacity);
				}
				pos = _start + n;//根据记录的间隔数算出pos
				iterator i = _finish;
				while (i != pos)
				{
					*(i + 1) = *i;
					i--;
				}
				*pos = x;
				_finish++;
			}
		}

提示:这里我们需要提前记录pos和_start之间的间隔,通过间隔确定增容后的pos的指向,否则pos还是指向原来已经被释放的空间。(我自己在写的时候就出现了这样的错误)

erase

先判断对象不能为空,删除时将pos后面的数据统一前移一位,覆盖掉pos的数据,返回值还为pos,即删除元素的下一个元素的位置:

		iterator erase(iterator pos)
		{
			assert(!empty());
			iterator it = pos;
			while (it != _finish)
			{
				*it = *(it+1);
				it++;
			}
			_finish--;
			return pos;
		}

swap

我们调用库中的swap函数将两个对象中的成员变量交换即可(这样就相当于交换了两个对象内部指针的指向,不耗费太多资源,若我们直接调用swap交换两个对象,若遇见例如string这样的对象,swap交换时做的全都是深拷贝操作,太耗费资源了,所以我们只交话两个对象内部的成员变量即可):

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

operator[ ]

通过下标访问vector内元素,直接返回数据即可:

		T& operator[](size_t i)
		{
			if(i<size())
			{
				return *(_start + i);
			}
		}

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

最后贴上整体代码:

#pragma once
#include<iostream>
#include<cassert>

namespace CYF
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;

		//构造函数1
		vector()
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{}
		//构造函数2
		template<class InputIterator>//模板参数,因为迭代器区间可以是其他容器的迭代器
		vector(InputIterator first, InputIterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)//将迭代器区间在[first,last)中的数据一个个尾插到容器中
			{
				push_back(*first);
				first++;
			}
		}

		//构造函数3
		vector(size_t n, const T& val)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n);//调用reserve函数将容器容量设为n,可以避免效率降低
			for (size_t i = 0; i < n; i++)//尾插n个值为val的元素
			{
				push_back(val);
			}
		}

		//拷贝构造(传统写法1)
		//vector(const vector<T>& v)
		//	:_start(nullptr)
		//	,_finish(nullptr)
		//	,_endofstorage(nullptr)
		//{
		//	_start = new T[v.capacity()];//开辟一块和容器cipacity大小相同的空间
		//	for (size_t i = 0; i < v.size(); i++)//将v中数据拷贝过来,注意这里不能用memcpy
		//	{
		//		_start[i] = v[i];
		//	}
		//	_finish = _satrt + v.size();//有效数据的尾
		//	_endofstorage = _start + v.capacity();//整个容器的尾
		//}

		//拷贝构造(传统写法2)
		//vector(const vector<T>& v)
		//	:_start(nullptr)
		//	, _finish(nullptr)
		//	, _endofstorage(nullptr)
		//{
		//	reserve(v.capacity());
		//	for (const auto& e : v)
		//	{
		//		push_back(e);
		//	}
		//}

		//拷贝构造(现代写法)
		vector(const vector<T>& v)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			vector<T>v1(v.begin(),v.end());
			swap(v1);
		}

		赋值运算符重载函数(传统写法)
		//vector<T>& operator=(const vector<T>& v)
		//{
		//	if (this != &v)
		//	{
		//		delete[]_start;
		//		_start = new T[v.capacity()];
		//		_finish = _start + v.size();
		//		_endofstorage = _satrt + v.capacity();
		//		for (const auto& e : v)
		//		{
		//			push_back(e);
		//		}
		//	}
		//	return *this;
		//}

		//赋值运算符重载函数(现代写法)
		vector<T>& operator=(const vector<T>& v)
		{
			if (this != &v)
			{
				vector<T> v1(v.begin(),v.end());
				swap(v1);
			}
			return *this;
		}


		//析构函数
		~vector()
		{
			delete[]_start;
			_start = nullptr;
			_finish = nullptr;
			_endofstorage = nullptr;
		}

		//迭代器相关函数
		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}
		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}

		//容量和大小相关函数
		size_t size()const
		{
			return _finish - _start;
		}
		size_t capacity()const
		{
			return _endofstorage - _start;
		}
		void reserve(size_t n)
		{
			if (n >= capacity())
			{
				iterator _start_ = new T[n];
				_finish = _start_ + size();
				_endofstorage = _start_ + n;
				size_t i = 0;
				while (_start_ + i != _finish)
				{
					_start_[i] = _start[i];
					i++;
				}
				delete[]_start;
				_start = _start_;
				_start_ = nullptr;
			}
		}

		//void resize(size_t n, const T& val = T())
		//{
		//	if (n <= size())
		//	{
		//		_finish = _start + n - 1;//这时候只会size,不会改变capacity
		//	}
		//	else if (n > size() && n <= capacity())
		//	{
		//		size_t more = n - size();
		//		while (more > 0)
		//		{
		//			*_finish = val;
		//			_finish++;
		//			more--;
		//		}
		//	}
		//	else
		//	{
		//		reserve(n);
		//		while (_finish != _start + n)
		//		{
		//			*_finish = val;
		//			_finish++;
		//		}
		//	}
		//}

		void resize(size_t n, const T& val = T())
		{
			if (n < size()) //当n小于当前的size时
			{
				_finish = _start + n; //将size缩小到n
			}
			else //当n大于当前的size时
			{
				if (n > capacity()) //判断是否需要增容
				{
					reserve(n);
				}
				while (_finish < _start + n) //将size扩大到n
				{
					*_finish = val;
					_finish++;
				}
			}
		}

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

		//修改容器内容相关函数
		void push_back(const T& x)
		{
			if (_finish==_endofstorage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(newcapacity);
			}
			*_finish = x;
			_finish++;
		}
		void pop_back()
		{
			assert(!empty());//容器不能为空
			_finish--;
		}

		void insert(iterator pos, const T& x)
		{
			if (pos <= _finish)//判断pos是合法
			{
				size_t n = pos - _start;
				if (_finish == _endofstorage)
				{
					size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
					reserve(newcapacity);
				}
				pos = _start + n;
				iterator i = _finish;
				while (i != pos)
				{
					*(i + 1) = *i;
					i--;
				}
				*pos = x;
				_finish++;
			}
		}

		iterator erase(iterator pos)
		{
			assert(!empty());
			iterator it = pos;
			while (it != _finish)
			{
				*it = *(it+1);
				it++;
			}
			_finish--;
			return pos;
		}

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

		//访问容器相关函数
		T& operator[](size_t i)
		{
			if(i<size())
			{
				return *(_start + i);
			}
		}

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

	private:
		iterator _start;//指向容器的头
		iterator _finish;//指向有效数据的尾
		iterator _endofstorage;//指向容器的尾
	};
}

以上就是这篇文章的全部内容,谢谢大家!!

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

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

相关文章

【计算机毕业设计】359微信小程序校园失物招领系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Java基础知识点(反射、注解、JDBC、TCP/UDP/URL)

文章目录 反射反射的定义class对象反射的操作 注解注解的定义注解的应用注解的分类基准注解元注解 自定义注解自定义规则自定义demo JDBCTCP/UDP/URLTCPUDPURL 反射 反射的定义 Java Reflection是Java被视为动态语言的基础啊&#xff0c; 反射机制允许程序在执行期间接入Refl…

Vue进阶之Vue无代码可视化项目(一)

Vue无代码可视化项目 项目搭建初始步骤拓展:工程项目从0-1项目规范化package.jsoncpell.jsoncustom-words.txtts-eslint规则.eslintrc.cjsgit钩子检查有没有问题type-checkspellchecklint:stylehusky操作安装pre-commitpnpm的commit规范package.json:commitlint.config.cjs安装…

Java数组操作

数组拓展 1.1 数组拷贝 需求&#xff1a;定义一个方法arraycopy, 从指定源数组中从指定的位置开始复制指定数量的元素到目标数组的指定位置。 1.2. 排序操作 需求&#xff1a;完成对int[] arr new int[]{2,9,6,7,4,1}数组元素的升序排序操作. 1.2.1.冒泡排序 对未排序的各元素…

【计算机毕设】基于SpringBoot的教师工作量管理系统设计与实现 - 源码免费(私信领取)

免费领取源码 &#xff5c; 项目完整可运行 &#xff5c; v&#xff1a;chengn7890 诚招源码校园代理&#xff01; 1. 研究目的 随着高校规模的扩大和教学任务的增加&#xff0c;教师的工作量管理变得越来越复杂和重要。传统的教师工作量管理方式效率低下&#xff0c;容易出错&…

【typescript/flatbuffer】在websocket中使用flatbuffer

目录 说在前面场景fbs服务器代码前端typescript代码问题 说在前面 操作系统&#xff1a;Windows11node版本&#xff1a;v18.19.0typescript flatbuffer版本&#xff1a;24.3.25 场景 服务器(本文为golanggin)与前端通信时使用flatbuffer进行序列化与反序列化通信协议为websock…

CSDN UI 2024.06.01

当我们的栏目很多的时候&#xff0c;通过【置顶】来排列顺序是很麻烦的&#xff0c;应该加一列&#xff0c;设置优先级别。太难用了 或者加两个按钮【上移】 【下移】

正邦科技(day4)

烧录 一、烧录固件二、 通讯模块升级1&#xff1a;USB的方式升级固件2&#xff1a;通过mqtt的方式升级固件3&#xff1a;切换环境 三、 烧录WiFi1&#xff1a;短接2&#xff1a;烧录脚本 设备注意事项&#xff1a; 第一种方式&#xff1a;通信模组和MCU都可以统一烧录BoodLoade…

内网穿透-FRP流量改造

前言 在拿下一台机器作为入口时&#xff0c;内网代理就变得尤为重要。他是我们进行横向渗透一个中间人&#xff0c;没了代理在内网中就寸步难行。而内网穿透的工具有很多&#xff0c;比如frp&#xff0c;reGeorg等等非常优秀的代理工具。使用方法不在赘述&#xff0c;这篇文章…

ssm_mysql_高校自习室预约系统(源码)

博主介绍&#xff1a;✌程序员徐师兄、8年大厂程序员经历。全网粉丝15w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

Ansible05-Ansible进阶(流程控制、Roles角色、加密优化调优等)

目录 写在前面7 Ansible 进阶7.1 流程控制7.1.1 handlers触发器与notify7.1.1.1 未使用handlers7.1.1.2 使用handlers 7.1.2 when判断7.1.2.1 when的语法7.1.2.2 when判断主机名选择模块输出7.1.2.3 when结合register变量 7.1.3 loop/with_items循环7.1.3.1 with_items案例7.1.…

微信小程序注册流程及APPID,APPSecret获取

1.注册微信小程序 注册链接&#xff1a;公众号 (qq.com) 1.1填写邮箱、密码、验证码 1.2邮箱登录点击邮件中链接激活&#xff0c;即可完成注册 1.3用户信息登记 接下来步骤&#xff0c;将用个人主题类型来进行演示 填写主体登记信息&#xff0c;使用管理员本人微信扫描二维码…

行政工作如何提高效率?桌面备忘录便签软件哪个好

在行政管理工作中&#xff0c;效率的提高无疑是每个行政人员都追求的目标。而随着科技的发展&#xff0c;各种便捷的工具也应运而生&#xff0c;其中桌面备忘录便签软件便是其中的佼佼者。那么&#xff0c;这类软件又如何帮助我们提高工作效率呢&#xff1f; 首先&#xff0c;…

(2024,LoRA,全量微调,低秩,强正则化,缓解遗忘,多样性)LoRA 学习更少,遗忘更少

LoRA Learns Less and Forgets Less 公和众和号&#xff1a;EDPJ&#xff08;进 Q 交流群&#xff1a;922230617 或加 VX&#xff1a;CV_EDPJ 进 V 交流群&#xff09; 目录 0. 摘要 1. 引言 2. 背景 3. 实验设置 3.2 使用编码和数学基准测试来衡量学习&#xff08;目标域…

C++:细谈Sleep和_sleep

ZINCFFO的提醒 还记得上上上上上上上上上上上上上上上上上上&#xff08;上的个数是真实的&#xff09;篇文章吗&#xff1f; 随机应变——Sleep()和_sleep() 但在ZINCFFO的C怪谈-02中&#xff1a; 我不喜欢Sleep...... 奤&#xff1f;媜煞鷥&#xff01; 整活&#xff01;…

容器项目之前后端分离

容器化部署ruoyi项目 #需要的镜像nginx、java、mysql、redis、 #导入maven镜像、Java镜像和node镜像 docker load -i java-8u111-jdk.tar docker load -i maven-3.8.8-sapmachine-11.tar docker load -i node-18.20.3-alpine3.20.tar #拉取MySQL和nginx镜像 docker pull mysql…

权限修饰符和代码块

一.权限修饰符 1.权限修饰符:是用来控制一个成员能够被访问的范围的。 2.可以修饰成员变量&#xff0c;方法&#xff0c;构造方法,内部类。 3.例子&#xff1a; public class Student {priviate String name;prviate int age;} 二.权限修饰符的分类 有四种作用范围大小…

牛客网刷题 | BC102 带空格直角三角形图案

目前主要分为三个专栏&#xff0c;后续还会添加&#xff1a; 专栏如下&#xff1a; C语言刷题解析 C语言系列文章 我的成长经历 感谢阅读&#xff01; 初来乍到&#xff0c;如有错误请指出&#xff0c;感谢&#xff01; 描述 KiKi学习了循环&am…

#1 深度优先搜索

深搜思想 DFS其实是针对图论的一种搜索算法&#xff0c;由一个节点出发&#xff0c;不撞南墙不回头式的遍历所有的节点。 如先遍历1&#xff0c;沿&#xff08;1,2&#xff09;遍历2&#xff0c;再沿&#xff08;2,4&#xff09;遍历4&#xff0c;撞南墙&#xff08;边界条件…

bcaktrader策略编写1

。 1 Backtrader策略类编写说明 在上一篇&#xff0c;我大体记录了整个backtrader整体最简流程&#xff0c;策略类中没有实现任何买卖逻辑&#xff0c;只是单纯的打印了每日的收盘价。今天&#xff0c;我将详细介绍策略编写类的构建过程&#xff0c;并构建一个简单的均线策略…