【C++】Vector的简易模拟与探索

💞💞 前言

hello hello~ ,这里是大耳朵土土垚~💖💖 ,欢迎大家点赞🥳🥳关注💥💥收藏🌹🌹🌹
在这里插入图片描述

💥个人主页:大耳朵土土垚的博客
💥 所属专栏:C++入门至进阶
这里将会不定期更新有关C++的内容,希望大家多多点赞关注收藏💖💖

vector模拟实现完整代码

#pragma once
#include<iostream>
using namespace std;
#include<assert.h>
#define _CRT_SECURE_NO_WARNINGS 1
namespace tutu
{
	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;
		}


		//构造函数
		//拷贝构造,深拷贝
		vector(const vector<T>& v)
		{
			//提前预留空间
			reserve(v.capacity());
			for (auto i : v)
			{
				push_back(i);
			}
		}

		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()
			:_start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
		}

		//迭代器区间初始化
		//函数模板
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{

			while (first != last)
			{
				push_back(*first);
				++first;
			}

		}

		//n个val构造
		vector(size_t n, const T& val = T())//缺省值不能给0,因为T可能是string,所以给匿名对象
		{
			reserve(n);
			while (n--)
			{
				push_back(val);
			}
		}

		//n个val构造重载,第一个参数为int类型
		vector(int n, const T& val = T())//缺省值不能给0,因为T可能是string,所以给匿名对象
		{
			reserve(n);
			while (n--)
			{
				push_back(val);
			}
		}


		//initializer_list构造
		vector(initializer_list<T> il)
		{
			reserve(il.size());
			for (auto i : il)
			{
				push_back(i);
			}
		}


		//析构函数
		~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 oldsize = _finish - _start;
				T* tmp = new T[n];
				//memcpy(tmp, _start, oldsize * sizeof(T));	//拷贝数据,浅拷贝对于string类等不适用
		
				if (_start)//如果_start为空就不需要拷贝数据
				{
					for (size_t i = 0; i < oldsize; i++)
					{
						tmp[i] = _start[i];//使用赋值来实现深拷贝
					}
				}
				delete[] _start;	//释放旧的空间
				_start = tmp;
				_finish = _start + oldsize;
				_end_of_storage = _start + n;
			}
		}


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

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

		//const对象使用
		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}


		//尾删
		void pop_back()
		{
			if (_finish != _start)//判断是否为空
			{
				--_finish;
			}
		}


		//插入
		iterator insert(iterator pos, const T& x)
		{
			//防止迭代器因为后面的扩容失效,所以要提前记录pos位置
			size_t pos_size = pos - _start;

			//断言防止越界
			assert(pos_size <= size());
			assert(pos_size >= 0);

			//看是否扩容
			if (_finish == _end_of_storage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(newcapacity);
			}

			//如果扩容,pos可能会失效,所以要更新pos
			pos = begin() + pos_size;
			//往后挪动数据
			iterator cur = _finish;
			while (cur != pos)
			{
				*cur = *(cur - 1);
				cur--;
			}

			//插入数据
			*pos = x;
			_finish++;

			return pos;
		}


		//删除
		iterator erase(iterator pos)
		{
			size_t pos_size = pos - _start;
			//断言防止越界
			assert(pos >= _start);
			assert(pos < _finish);

			//往前挪动数据
			iterator cur = pos;
			while (cur != _finish - 1)
			{
				*cur = *(cur + 1);
				cur++;
			}
			_finish--;
			//更新pos
			pos = _start + pos_size;
			return pos;
		}
	private:
		iterator _start = nullptr;
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	};

	//测试代码尾插和[]
	void vectortest1()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(4);
		v1.push_back(4);
		v1.push_back(4);


		for (int i = 0; i < v1.size(); i++)
		{
			cout << v1[i] << " ";
		}
		cout << endl;
	}

	//迭代器测试
	void vectortest2()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		int* it = v1.begin();//指定类域 //int* it  = v1.begin();//也可以
		while (it != v1.end())
		{
			cout << *it << " ";
			it++;
		}
	}

	//范围for测试
	void vectortest3()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		for (auto i : v1)
		{
			cout << i << " ";
		}
	}


	//插入删除数据测试
	void vectortest4()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);

		v1.insert(v1.begin(), 0);

		v1.erase(v1.begin());
		v1.erase(v1.end() - 1);

		for (auto i : v1)
		{
			cout << i << " ";
		}
	}



	//n个val测试代码1
	void vectortest5()
	{
		vector<string> s1(10);//使用缺省值
		vector<string> s2(10, "hello");

		for (auto i : s1)
		{
			cout << i << " ";
		}
		cout << endl;
		for (auto i : s2)
		{
			cout << i << " ";
		}
	}
	//n个val测试代码2
	void vectortest6()
	{

		vector<int> v1(4, 2);

		for (auto i : v1)
		{
			cout << i << " ";
		}
	}

	//initializer_list构造测试
	void vectortest7()
	{
		vector<int> v1({ 1,2,3,4,5 });
		vector<int> v2 = { 6,7,8,9,10 };
		for (auto i : v1)
		{
			cout << i << " ";
		}
		cout << endl;
		for (auto i : v2)
		{
			cout << i << " ";
		}
	}

	//扩容测试代码
	void vectortest8()
	{
		vector<string> s1;
		s1.push_back("1111111111111");
		s1.push_back("1111111111111");
		s1.push_back("1111111111111");
		s1.push_back("1111111111111");
		s1.push_back("1111111111111");
		s1.push_back("1111111111111");

		for (auto i : s1)
		{
			cout << i << " ";
		}
	}
}

1.vector成员变量

在查看STL库里面vector的实现时,我们发现它是一个类模板并且定义了三个成员变量,分别是iterator startiterator finishiterator end_of_storage用来标记开始,结束,以及总容量,对于vector来说其迭代器iterator就是T*,例如我们之前学习过的顺序表插入的是int类型的数据,所以对存放int类型的vector来说T*就是int*

如下图所示:
假设vector里已经插入了6个数据
在这里插入图片描述

#include<iostream>
using namespace std;

namespace tutu
{
	template<class T>
	class vector
	{
	public:
		typedef T* iterator;	//将T*typedef成iterator
	private:
		iterator _start = nullptr;	//这里给缺省值
		iterator _finish = nullptr;
		iterator _end_of_storage = nullptr;
	}
};

因为还没有写构造函数,所以成员变量那里先给缺省值,方便使用

这里将vector的实现都放在一个头文件下,放置多个文件可能会出现链接错误;并设置自己的命名空间tutu

2.尾插push_back()

void push_back(const T& x)
{
	//判断容量是否够用
	if (_finish == _end_of_storage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
		//扩容
		reserve(newcapacity);
	}
	//插入数据
	*_finish = x;
	_finish++;
}

插入数据首先都应该判断一下容量是否够用,不够用就需要扩容,这里使用reserve()函数扩容,该函数将在后面实现,此外插入数据后_finish要往后偏移一位

🥳🥳有关容量和数据个数的函数:

//容量
size_t capacity() const
{
	return _end_of_storage - _start;
}

//数据个数
size_t size()	const
{
	return _finish - _start;
}

3.扩容reserve()

void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t oldsize = _finish - _start;	//记录大小
		T* tmp = new T[n];
		memcpy(tmp, _start, oldsize*sizeof(T));	//拷贝数据
		delete[] _start;	//释放旧的空间
		_start = tmp;
		_finish = _start + oldsize;
		_end_of_storage = _start + n;
	}
}

扩容时,因为我们使用的实现vector类的成员变量是指针(或者说迭代器),所以改变空间后不仅仅_start改变,_finish指向的空间会被销毁,所以这时候如果再使用扩容后的_finish-_start来找到容量size来确定现在_finish指向的空间肯定是不对的,所以我们要提前记录好oldsize

这里拷贝数据使用的是memcpy,一个字节一个字节拷贝

4.operate[]

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

这里返回的是T的引用,也就是vector里面存储的数据

//const对象使用
const T& operator[](size_t pos) const
{
	assert(pos < size());
	return _start[pos];
}

5.析构函数

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

✨测试代码

void vectortest1()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	for (int i = 0; i < v1.size(); i++)
	{
		cout << v1[i] << " ";
	}
	cout << endl;
}

结果如下:
在这里插入图片描述

可以看到尾插成功,并且可以使用[]来访问vector v1里面的元素

6.迭代器

//迭代器
iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}


在最开始vector成员变量那里我们将T*typedef成iterator,所以对于vector类来说其迭代器实质上就是T*,是一个指针;但要注意不是所有的迭代器都是指针,例如list的迭代器就不是,我们后续再学习

const迭代器

提供给const对象使用的迭代器,指向的内容不可以被修改

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

✨迭代器测试代码

使用迭代器来遍历数据

//迭代器测试
void vectortest2()
{
	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();//指定类域 //T* it  = v1.begin();//也可以
	while (it != v1.end())
	{
		cout << *it << " ";
		it++;
	}
}

结果如下:
在这里插入图片描述

因为我们这里的迭代器实质上就是T*,所以vector<int>::iterator it = v1.begin();也可以写成这样:int* it = v1.begin();,但是最好还是使用第一个,因为这个是在所有的地方通用的,屏蔽了底层实现,体现了C++的封装的思想

此外范围for其实质上就是通过迭代器来实现的,所以我们写完了迭代器就可以使用范围for来遍历数据了,代码如下:

//范围for
void vectortest3()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	for (auto i : v1)
	{
		cout << i << " ";
	}
}

7.插入insert()

//插入
iterator insert(iterator pos,const T& x)
{
	//防止迭代器因为后面的扩容失效,所以要提前记录pos位置
	size_t pos_size = pos - _start;

	//断言防止越界
	assert(pos_size <= size());
	assert(pos_size >= 0);

	//看是否扩容
	if (_finish == _end_of_storage)
	{
		size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
		reserve(newcapacity);
	}

	//如果扩容,pos可能会失效,所以要更新pos
	pos = begin() + pos_size;
	//往后挪动数据
	iterator cur = _finish;
	while (cur != pos)
	{
		*cur = *(cur - 1);
		cur--;
	}

	//插入数据
	*pos = x;
	_finish++;
	return pos;//返回更新后的迭代器
}

这里要注意迭代器因为扩容导致pos失效的问题(野指针),所以要提前规避,记录好pos相对位置,然后再即时更新pos迭代器,否则就会出现随机值;

此外,insert的参数pos是对实参的拷贝,形参的改变不会影响实参,所以外部的实参也会失效,但是我们也不能通过引用传参,因为其迭代器返回的是临时拷贝具有常性不能通过引用传参,所以这里我们就可以通过控制insert函数的返回值来解决,我们会返回更新后的迭代器,这样就可以访问该位置了

🥳🥳有了插入函数之后尾插push_back()就可以使用insert()来实现啦,代码如下:

//尾插
void push_back(const T& x)
{
	insert(end(), x);
}

8.删除erase()

//删除
iterator erase(iterator pos)
{
	size_t pos_size = pos - _start;
	//断言防止越界
	assert(pos >= _start);
	assert(pos < _finish);

	//往前挪动数据
	iterator cur = pos;
	while (cur != _finish - 1)
	{
		*cur = *(cur + 1);
		cur++;
	}
	_finish--;
	//更新pos
	pos = _start + pos_size;
	return pos;
}

erase()之后迭代器失效问题

  • 有可能删除之后缩容
  • 删除最后一个位置会导致越界访问

所以我们认为删除操作之后迭代器也会失效,和插入函数一样通过返回迭代器来更新迭代器使用才行

✨插入删除测试代码

//插入删除数据
void vectortest4()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	v1.insert(v1.begin(), 0);

	v1.erase(v1.begin());
	v1.erase(v1.end()-1);

	for (auto i : v1)
	{
		cout << i << " ";
	}
}

结果如下:
在这里插入图片描述

9.构造函数

✨拷贝构造

//拷贝构造,深拷贝
vector(const vector<T>& v)
{
	//提前预留空间
	reserve(v.capacity());
	for (auto i : v)
	{
		push_back(i);
	}
}

拷贝构造测试代码

void vectortest5()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(2);
	v1.push_back(3);
	v1.push_back(4);

	vector<int> v2(v1);//用v1拷贝构造v2

	for (auto i : v2)
	{
		cout << i << " ";
	}
}

结果如下:
在这里插入图片描述

✨默认构造

//默认构造
vector()
	:_start(nullptr)
	,_finish(nullptr)
	,_end_of_storage(nullptr)
{

}

✨迭代器区间初始化

//迭代器区间初始化
//函数模板
template<class InputIterator>
vector(InputIterator first, InputIterator last)
{
 
    while (first != last)
    {
        push_back(*first);
        ++first;
    }
   
}

这里使用了函数模板用来匹配不同类型的迭代器,因为vector可以存储不同类型的数据,相应的迭代器也会有所不同,所以使用函数模板

✨n个val构造

//n个val构造
vector(size_t n, const T& val=T())//缺省值不能给0,因为T可能是string,所以给匿名对象
{
	reserve(n);
	while (n--)
	{
		push_back(val);
	}
}

注意这里给的缺省参数是匿名对象T(),而不是0,因为vector除了能存储int类型外还可以存储其他类型的数据比如string等

n个val构造测试代码

void vectortest5()
{
	vector<string> s1(10);//使用缺省值
	vector<string> s2(10, "hello");

	for (auto i : s1)
	{
		cout << i << " ";
	}
	cout << endl;
	for (auto i : s2)
	{
		cout << i << " ";
	}
}

结果如下:
在这里插入图片描述

上述代码s1使用的是缺省值匿名对象string()也就是’\0’,所以什么都没打印,第一行是空的,第二行打印s2的10个"hello"

但是当我们使用下面的代码测试的时候就会发现:

//n个val测试代码
void vectortest6()
{

	vector<int> v1(4, 2);

	for (auto i : v1)
	{
		cout << i << " ";
	}
}

运行后:
在这里插入图片描述

出现了非法的间接寻址,并且报错在vector(InputIterator first, InputIterator last)迭代器区间初始化这里

这是因为编译器在匹配函数时vector<int> v1(4, 2);4和2都是int类型恰好和vector(InputIterator first, InputIterator last)这两个函数模板的参数匹配上了,而我们写的n个val初始化vector(size_t n, const T& val=T())第一个参数类型是size_t,第二个才可以隐式类型转换为int类型,没有迭代器区间初始化的函数匹配

所以编译器会选择使用迭代器区间来初始化v2,但是迭代器区间初始化函数里面写了解引用,对于int类型来说是行不通的,所以出现了错误

这时我们只需要在初始化v2时将第一个参数给为unsigned int就行:vector<int> v1(4u, 2);
或者可以在重载一个n个val构造的函数,第一个参数给为int类型:

//n个val构造重载,第一个参数为int类型
vector(int n, const T& val = T())//缺省值不能给0,因为T可能是string,所以给匿名对象
{
	reserve(n);
	while (n--)
	{
		push_back(val);
	}
}

这样上述代码的结果就如下:
在这里插入图片描述

✨initializer_list构造

initializer_list是C++新增的一个类型,方便初始化,支持将花括号括起来的值给initializer_list,initializer_list对象里面有两个指针,指向花括号里面值开始和结尾的下一个,并支持迭代器,所以可以使用范围for来遍历,当然这个要编译器支持将花括号传给它

//initializer_list构造
vector(initializer_list<T> il)
{
	reserve(il.size());//size表示数据个数
	for (auto i : il)
	{
		push_back(i);
	}
}

initializer_list构造测试代码

C++11引入

//initializer_list构造测试
void vectortest7()
{
	隐式类型转换
	vector<int> v1({ 1,2,3,4,5 });	
	vector<int> v2={ 6,7,8,9,10 };	
	for (auto i : v1)
	{
		cout << i << " ";
	}
	cout << endl;
	for (auto i : v2)
	{
		cout << i << " ";
	}
}

结果如下:
在这里插入图片描述

10.赋值运算符重载

//赋值运算符重载
vector<T>& operator=(vector<T> v)
{
	swap(v);
	return *this;
}

这里使用传值传参,是实参的拷贝,所以我们将它与被赋值的对象交换后返回即可完成赋值,并且交换后形参生命周期结束就会自动调用析构函数释放原来的空间

🥳🥳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);

}

11.reserve()扩容存在的问题

✨测试代码

//扩容测试代码
void vectortest8()
{
	vector<string> s1;
	s1.push_back("1111111111111");
	s1.push_back("1111111111111");
	s1.push_back("1111111111111");
	s1.push_back("1111111111111");
	s1.push_back("1111111111111");
	s1.push_back("1111111111111");

	for (auto i : s1)
	{
		cout << i << " ";
	}
}

结果如下:
在这里插入图片描述

可以看到程序异常退出,这是因为我们在使用reserve()扩容时,使用的是 memcpy(tmp, _start, oldsize*sizeof(T)); 来拷贝数据,如果数据是int类型不会有什么问题,但如果是string类,memcpy进行的是一个字节一个字节拷贝,是浅拷贝,释放原来的空间后,就会存在野指针,访问已经释放的空间出现错误,所以reserve实现应该如下:

//扩容
void reserve(size_t n)
{
	if (n > capacity())
	{
		size_t oldsize = _finish - _start;
		T* tmp = new T[n];
		//memcpy(tmp, _start, oldsize * sizeof(T));	//拷贝数据,浅拷贝对于string类等不适用

		if (_start)//如果_start为空就不需要拷贝数据
		{
			for (size_t i = 0; i < oldsize; i++)
			{
				tmp[i] = _start[i];//使用赋值来实现深拷贝
			}
		}
		delete[] _start;	//释放旧的空间
		_start = tmp;
		_finish = _start + oldsize;
		_end_of_storage = _start + n;
	}
}

上述代码拷贝使用赋值,如果是类类型会调用赋值运算符重载实现你想要的拷贝,这样上述测试代码就可以测试成功啦🥳🥳

结果如下:

在这里插入图片描述

结语

以上就是C++STL标准库中vector的模拟实现了,在实现过程中,我们使用了动态内存分配来实现vector的大小动态调整,并通过指针来管理内存。我们还实现了一些常用的成员函数,如push_back、pop_back、at等,以及一些运算符重载,如[]、=等。
通过实现这个简单的vector类,我们不仅加深了对vector容器的理解,还学习了一些C++的底层原理和技巧。同时我们也遇见并解决了一些问题比如迭代器失效,深浅拷贝…以上就是今天所有的内容啦~ 完结撒花 ~🥳🎉🎉

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

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

相关文章

【C++初阶】auto关键字

目录 1.auto简介 2.auto的使用 1.auto简介 在早期C/C中auto的含义是&#xff1a;使用auto修饰的变量&#xff0c;是具有自动存储器的局部变量&#xff0c;但遗憾的 是一直没有人去使用它&#xff0c;大家可思考下为什么&#xff1f; C11中&#xff0c;标准委员会赋予了auto全…

Go语言

Go语言 Go语言全称Golanguage&#xff0c;Go&#xff08;又称 Golang&#xff09;是 Google 的 Robert Griesemer&#xff0c;Rob Pike 及 Ken Thompson 开发的一种静态强类型、编译并发型语言。于2009年首次发布 官网 特点 简单易学&#xff1a;Go语言语法简洁明了&#x…

【AD21】原理图PDF文件的输出

原理图PDF文件可以共享给团队成员&#xff0c;用于设计审核、讨论和协同工作。 菜单栏中点击文件->智能PDF。 在弹出的界面点击Next&#xff0c;勾选当前项目&#xff0c;修改文件名&#xff0c;避免与制造装备图PDF文件重名将其覆盖&#xff0c;点击Next。 只输出原理图…

SmartEDA革新电路设计,效率飙升,Multisim与Proteus迎来强劲对手!

在电路设计领域&#xff0c;Multisim和Proteus一直以其强大的仿真功能和广泛的应用范围受到设计师们的青睐。然而&#xff0c;随着科技的不断进步和创新&#xff0c;一款名为SmartEDA的新兴软件正以其独特的优势&#xff0c;重新定义着电路设计的效率。 SmartEDA的崛起&#x…

基于Ubuntu的Bash脚本实现SystemUI的编译真机验证

使用场景描述 当开发SystemUI的时候&#xff0c;开发完一个需求后需要到真机上验证&#xff0c;虽然SystemUI模块开发最后的产物也是APK&#xff0c;但是这个APK 却不能单独安装查看效果&#xff0c;因为SystemUI是系统级别的应用&#xff0c;需要放置到系统指定的目录下。这时…

这13个前端库,帮我在工作中赢得了不少摸鱼时间

前言 平时开发的过程中&#xff0c;常常会使用到一些第三方库来提高开发效率&#xff0c;我总结了自己工作这么久以来经常用到的 13 个库&#xff0c;希望对大家有帮助&#xff5e; antd 全称应该是Ant Design&#xff0c;这是一个 React 的组件库&#xff0c;旨在提供一套常…

Android Studio 中gradle的bin和all区别

1.在android studio中设置安装gradle时&#xff0c;真各种版本看到眼花缭乱&#xff0c;还有疑惑gradle-*.*-all.zip与gradle-*.*-bin.zip的区别是什么。下面解压如下: bin&#xff1a; all: 其实&#xff0c;用bin就可以了&#xff0c;all文件就是多了docs(文档)和src(源码)两…

本周日晚8点预约宣讲会 | 深入了解项目,开启你的开源之旅!

引言 社区的亲爱的同学们&#xff01;为了帮助大家在这个夏天更好的参加“开源之夏”的活动&#xff0c;我们联合2位资深开源项目导师&#xff0c;给大家策划了这次“开源之夏”宣讲会。 这不仅是一个了解如何参与开源项目的机会&#xff0c;更是一个直接与项目导师面对面交流…

华火硬核专利库丨登创新科技之巅,探创新未至之境

十年的艰苦卓越&#xff0c;“灶”就了华火科技之巅&#xff1b;电生明火的应用&#xff0c;不仅是一次颠覆性的创新&#xff0c;更是对未来厨房的无尽遐想与探索。在当今日新月异的科技时代&#xff0c;创新已成为推动社会进步的重要动力。 华火烹饪科技&#xff0c;以其深厚的…

Unity 直线间隔放置物体

直线间隔放置物体 0. 新建一个空物体&#xff0c;挂上脚本ZYF_QuickPlaceObj 设置 间隔距离 和 预制体在Scene中拖动即可按间隔距离实例化物体物体的朝向始终朝向统一方向&#xff0c;并且可以在Scene中拖拽更改 传送门

Object类——toString方法和equals方法

前言&#xff1a; 在java中&#xff0c;所有类都是有继承关系存在的&#xff0c;都默认继承Object类。当一个类继承了其他父类&#xff0c;它并不会直接继承Object类&#xff0c;但是它的父类若是没有其他继承关系也会默认继承Object类&#xff0c;子类也可以继续调用Object类…

深度学习——图像分类(CNN)—测试模型

测试模型 1.导入必要的库2.加载测试数据集3.假设CSV文件中的图像文件名是完整的路径4.随机选择一张图片进行展示5.加载图像6.使用模型进行预测7.设置模型的预测结果8.计算准确率9.指定test文件夹路径10.读取名为image_path的图片11.加载图像12.检查图像是否为空 训练的模型是上…

Easy IP + DNAT(服务器NAT转换)

第一章 Easy IP 1.1 一般家庭和企业使用的地址转换方式 直接使用出接口的地址做转换Easy IP适用于小规模居于网中的主机访问Internet的场景如&#xff1a;家庭、小型网吧、小型办公室中&#xff0c;这些地方内部主机不多&#xff0c;出接口可以通过拨号方式获取一个临时公网I…

做抖音小店不懂这四个“重点”!那就别怪你的店铺,做不长久!

我相信大家做抖音小店&#xff0c;都去抖音刷过知识点&#xff0c;也去浏览器学习过技巧 但在这里&#xff0c;我给大家泼盆冷水 方法再多&#xff01;这四点不搞明白&#xff0c;那你的店铺出几天单&#xff0c;也就再也做不起来了 哪四点&#xff1f;请认真的看下去&#…

面试官:讲讲为什么SpringBoot的 jar 可以直接运行?

Spring Boot 是一个用于简化 Spring 应用程序开发的框架&#xff0c;它通过约定优于配置和大量的自动化配置&#xff0c;使得开发者可以更轻松地创建和部署 Spring 应用程序。一个特别引人注目的特性是 Spring Boot 应用可以打包成一个可执行的 JAR 文件&#xff0c;并且可以直…

新计划,不断变更!做自己,接受不美好!猪肝移植——早读(逆天打工人爬取热门微信文章解读)

时间不等人 引言Python 代码第一篇 做自己&#xff0c;没有很好也没关系第二篇结尾 引言 新计划&#xff1a; 早上一次性发几个视频不现实 所以更改一下 待后面有比较稳定的框架再优化 每天早上更新 早到8点 晚到10点 你刚刚好上班或者上课 然后偷瞄的看两眼 学习一下 补充知…

吴恩达2022机器学习专项课程C2W2:2.22 多类 softmax softmax与神经网络 softmax的代码改良 多标签分类

目录 多分类问题1.什么是多分类问题2.多分类问题案例3.二分类与多分类的区别 Softmax1. 什么是Softmax2.逻辑回归预测的计算过程3. Softmax预测的计算过程4.Softmax 回归与逻辑回归的关系5. Softmax的损失函数 softmax与神经网络1.设置Softmax层2.Softmax层的计算3.softmax激活…

Yolov5——训练目标检测模型详解(含完整源码)

项目的克隆 打开yolov5官网&#xff08;GitHub - ultralytics/yolov5 at v5.0&#xff09;&#xff0c;下载yolov5的项目&#xff1a; 环境的安装&#xff08;免额外安装CUDA和cudnn&#xff09; 打开anaconda的终端&#xff0c;创建新的名为yolov5的环境&#xff08;python选…

源码编译安装LAMP(安装apeche mysql php 论坛 网站 巨详细版)

目录 一.LAMP架构相关概述 1.各组件作用 Linux&#xff08;平台&#xff09; Apache&#xff08;前台&#xff09; MySQL&#xff08;后台&#xff09; PHP/Perl/Python&#xff08;中间连接&#xff09; 总结 二.编译安装Apache httpd服务 1.关闭防火墙&#xff0c;将…