C++:Vector的模拟实现

news2024/9/24 9:20:31

                                                   创作不易,感谢三连 !!

一,前言 

       在学习string类的时候,我们可能会发现遍历的话下标访问特别香,比迭代器用的舒服,但是下标其实只能是支持连续的空间,他的使用是非常具有局限性的,随着STL学习的深入我们会发现其实迭代器才是大佬!!Vector虽然也支持下标访问,但是很多成员函数都是用的迭代器,所以我们要模拟实现的话迭代器十分重要,vs使用的是PJ版的STL版本,比较难懂,所以我们模拟实现统一用SGI版本去实现,所以在模拟实现之前,我们要去看看他的源码到底有哪些成员变量

       SGI下的vector有三个成员变量,通过观察其他源码可以大致推断  _start是指向起始位置,_finish是指向有效数据的下一个位置(迭代器都遵循左闭右开),end_of_storage是指向有效容量的最后一个位置。

     通过这个我们可以观察到SGI版本下的迭代器其实就是一个原生指针,value_type*类型相当于是模板T对应的指针类型,有了这些大致了解,我们就可以去模拟实现啦!!

二,vector的模拟实现

    大致框架需要有模板(类外定义)/迭代器以及迭代器的获取(public定义,要有可读可写的也要有可读不可写的)/成员变量(private定义)  并且为了不和库的vector冲突,我们需要自己搞一个命名空间

namespace cyx
{
//模板
template<class T>
//迭代器(可读可写)
class vector
{
public:
typedef T* iterator;

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}
//迭代器(可读不可写)
typedef const T* const_iterator;

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}
private:
//成员变量
iterator _start;
iterator _finish;
iterator _end_of_storage;
}
}

然后我们开始实现!! 

2.1 构造函数和析构函数

2.1.1 无参构造函数

	//无参构造函数
	vector()
		:_start(nullptr)
		,_finish(nullptr)
		,_end_of_storage(nullptr)
	{}

2.1.2 迭代器区间构造

//传别人的迭代器进行构造
template<class InputIterator>
vector(InputIterator first, InputIterator last)
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
     //这里传的是别人的迭代器,不知道会传多少数据,不能提前扩容,只能让pushback的时候去判断
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

 push_back是尾插数据,具体实现后面会写。

思考:为什么迭代器也要搞个类模板呢?

        答:本质上是为了让这个函数更加灵活,可以传不同类型的迭代器来帮助我们初始化!!

比如这个地方我们传string类的迭代器

 传vector类的迭代器

 2.1.3 有参构造函数(对n个存储的类型去调用他们的构造)

//有参构造函数(对n个存储的类型去调用他们的构造)
vector(size_t n,const T&val=T() )
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	reserve(n);//因为我们知道会进多少数据,所以可以提前开空间
	for (int i = 0; i < n; ++i)
		push_back(val);
}

reserve是扩容到n,具体实现后面会写。

思考:

1.缺省值T( )是什么意思

      答:这个地方的缺省值不能给0!!因为vector可能会存储内置类型,也可能会存储自定义类型,比如vector<string>,所以如果我们没给值,缺省值就要给他的默认无参构造函数,这个默认构造函数可以使用匿名对象。

2.const T&val=T()  T()不是用一次就析构吗,为什么可以用引用

     答:T()是一个用一次就析构的匿名对象,其实本质上是因为他没有名字,用T引用val可以充当他的名字,此时用val就相当于用这个匿名对象,所以匿名对象的生命周期被延长到和val一样但是由于匿名对象是一个临时变量,所以具有常性,所以必须用const修饰的val才可以当他的别名,否则会出现权限放大!!

3.非法的间接寻址是为什么?

如下图我传(10,5),会出非法间接寻址

 但是我传(10u,5)就可以正常使用了,为什么会这样??

  答:

根据上图写出一个重载的有参构造

//重载一个防止间接寻址
vector(int n, const T val = T())
	:_start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	reserve(n);//因为我们知道会进多少数据,所以可以提前开空间
	for (int i = 0; i < n; ++i)
		push_back(val);
}

 2.1.4 拷贝构造+memcpy拷贝问题+赋值重载

 但是真的有这么顺利吗??

思考:

为什么存string类就会崩了??    这就涉及到memcpy的拷贝问题

 我们以上述问题来画图解释一下

总结:

1. memcpy是内存的二进制格式拷贝,将一段内存空间中内容原封不动的拷贝到另外一段内存空间中
2. 如果拷贝的是自定义类型的元素,memcpy既高效又不会出错,但如果拷贝的是自定义类型元素,并且自定义类型元素中涉及到资源管理时,就会出错,因为memcpy的拷贝实际是浅拷贝。
       如果对象中涉及到资源管理时,千万不能使用memcpy进行对象之间的拷贝,因为memcpy是
浅拷贝,否则可能会引起内存泄漏甚至程序崩溃。

 

所以在这个地方我们的拷贝构造不能用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());  不能用memcpy 是浅拷贝
	for (int i = 0; i < v.size(); ++i)
		_start[i] = v._start[i];//实现重载运算符
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

 但是真的没有问题了吗??看看这个

 但道理来说得打印出9个1  结果呢??

 原因是什么呢,我们先看看resize函数

//重载赋值=(传统)
vector<T>& operator=(const vector<T> &v)
{
	T* temp = new T [v.capacity()];
	for(int i=0;i<v.size();++i)
		temp[i] = v[i];
	delete[]_start;
	_start = temp;
	_finish = _start +v.size();
	_end_of_storage = _start +v.capacity();
	return *this;
}

 2.1.5 拷贝构造和赋值重载的现代写法

先写个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);
}

        拷贝构造的现代写法思路:创建一个临时对象利用被拷贝对象的迭代器区间构造,然后再swap一下就可以了!

	vector(const vector<T>& v)
		:_start(nullptr)
		, _finish(nullptr)
		, _end_of_storage(nullptr)
	{
		vector<T> temp(v.begin(), v.end());//让临时对象借助迭代器区间构造出来
		swap(temp);//窃取革命成果
	}

       赋值重载的现代写法的思路:反正我自己的空间也不要了,被赋值对象传值过来(这样被赋值对象不会被修改),然后直接和临时对象swap就可以了!!

vector<T>& operator=(vector<T> v)
{
	swap(v);//反正我原来的空间也要销毁,我跟你传值过来的v直接交换,而且不会改变你
	return *this;
}

2.1.6 析构函数 

~vector()
{
	/*if (_start)*///delete 会自动检查空指针  没必要
	delete[] _start;
	_start = _finish = _end_of_storage = nullptr;
}

注意:delete空指针是没关系的,delete会自己判断     delete出问题一般都是野指针

2.1.7 构造函数相关的全部代码

 我们发现大部分都设计要到初始化为nullptr,c11后是支持直接在成员变量那边给缺省值的,所以们可以美化一下

全部代码

		//无参构造函数
		vector()
		{}
		//有参构造函数(对n个存储的类型去调用他们的构造)
		vector(size_t n, const T& val = T())
		{
			reserve(n);//因为我们知道会进多少数据,所以可以提前开空间
			for (int i = 0; i < n; ++i)
				push_back(val);
		}
		//重载一个防止间接寻址
		vector(int n, const T val = T())
		{
			reserve(n);//因为我们知道会进多少数据,所以可以提前开空间
			for (int i = 0; i < n; ++i)
				push_back(val);
		}
		//传别人的迭代器区间进行构造
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			//这里传的是别人的迭代器,不知道会传多少数据,不能提前扩容,只能让pushback的时候去判断
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		//拷贝构造(传统写法)
		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			//memcpy(_start, v._start, sizeof(T)*v.size());  不能用memcpy 是浅拷贝
			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<T> temp(v.begin(), v.end());//让临时对象借助迭代器区间构造出来
		//	swap(temp);//窃取革命成果
		//}
		//交换
		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=(const vector<T>& v)
		{
			T* temp = new T[v.capacity()];
			for (int i = 0; i < v.size(); ++i)
				temp[i] = v[i];
			delete[]_start;
			_start = temp;
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
			return *this;
		}
		//赋值重载现代写法
		//vector<T>& operator=(vector<T> v)
		//{
		//	swap(v);//反正我原来的空间也要销毁,我跟你传值过来的v直接交换,而且不会改变你
		//	return *this;
		//}
		//析构函数
		~vector()
		{
			/*if (_start)*///delete 会自动检查空指针  没必要检查
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}

 2.2 常见接口

2.2.1 获取size和capacity

//获取size
size_t size() const
{
	return _finish - _start;
}
//获取capacoty
size_t capacity() const
{
	return _end_of_storage - _start;
}

 2.2.2 判空

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

2.2.3 重载[ ]

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

3.三种访问方法

下标

//下标遍历
for (int i = 0; i < v1.size(); ++i)
	cout << v1[i] << " ";
cout << endl;

迭代器

	//迭代器遍历
	vector<int>::const_iterator it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;

范围for 

//范围for遍历
for (auto e : v1)
	cout << e << " ";
cout << endl;

v1.resize(100);
cout << v1.size() << endl;

for (auto e : v1)
	cout << e << " ";
cout << endl;

 2.2.4 提前扩容

void reserve(size_t n)
{
	size_t sz = size();//防止丢失
	if (n > capacity())
	{
		T* temp = new T[n];
		if (_start)//如果为空,就不需要拷贝也不需要释放
		{
			for (size_t i = 0; i < sz; ++i)
				temp[i] = _start[i];
			delete[] _start;
		}
		_start = temp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

 考虑到之前的memcpy拷贝问题,这里不能用memcpy了!!

还要注意的是要提前记录size(),否则原空间销毁了就找不到了。

2.2.5 提前扩容+初始化

有三种情况,第一种是给的n比原来的size小,第二种是n比size大但是比capacity小,第三种是n比capacity大,这个时候需要扩容

	//提前扩容+初始化
	void resize(size_t n, T val = T())
	{
		//给小
		if (n < size())
			_finish = _start + n;
		//给大
		else
		{
			//容量不够就扩
			if (n > capacity())
				reserve(n);
			while (_finish != _start + n)
			{
				*_finish = val;
				++_finish;
			}
		}
	}

2.2.6 尾插和尾删

void push_back(const T& val)
{
	if (_finish == _end_of_storage)
		reserve(capacity() == 0 ? 4 : 2 * capacity());
	*_finish = val;
	++_finish;
}
//尾删
void pop_back()
{
	//防止没有元素可删
	assert(!empty());
	--_finish;
}

 尾插要注意扩容之前要判断一下,因为如果是0的话怎么扩都是0

我们会发现这次的指定位置插入删除不像string那样用size_t pos 而是iterator pos

2.2.7 指定位置插入

 这样写有什么问题吗??

看似好像没有什么问题,但是如果把pushback(5)去掉

 为什么会这样呢?

原因就是扩容后空间变了,但是pos还是指向原来的空间!!

所以我们解决方案就是pos在扩容的时候要更新一下

iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	if (_finish == _end_of_storage)
	{
		size_t len = pos - _start;//记录相对距离,方便更新pos
		reserve(capacity() == 0 ? 4 : 2 * capacity());
		pos = _start + len;
	}
	iterator end = _finish - 1;
	while (end >= pos)
	{
		*(end + 1) = *end;
		--end;
	}
	*pos = val;
	++_finish;
	return pos;
}

2.2.8 指定位置删除

返回值是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;
	}

2.3 迭代器失效问题

会引起其底层空间改变的操作,都有可能使得迭代器失效。

 比如:resize、reserve、insert、erase、 push_back等。

2.3.1.insert的失效

就是因为扩容导致pos失效,我们需要去及时更新pos

      但是我们传的pos是值传递,所以我们更新的后pos更新,我们在后面解引用pos就会出现经典的解引用野指针问题。

 那我们怎么传回pos呢??就得用返回值!!这也是为什么insert的返回值用iterator的原因,我们想继续用的话就得去接收一下返回值,就可以了

     虽然有了返回值,我们可以去接收更新后的pos,但是一旦我们使用了任意一个可能扩容的函数,都会到时pos的失效,从而有可能回引发野指针问题,这个问题是不太好避免的,所以我们认为迭代器只能用一次,因为结果不可预测!

2.3.2 erase的失效

        erase 删除 pos 位置元素后,pos 位置之后的元素会往前搬移,没有导致底层空间的改变,理论上讲迭代器不应该会失效,但是:如果 pos 刚好是最后一个元素,删完之后 pos 刚好是 end 的位置,而 end 位置是没有元素的,那么 pos 就失效了。因此删除 vector 中任意位置上元素时,vs 就认为该位置迭代器失效了。

vs和g++对比

 结果是未定义的!!不同编译器场景可能不同,严格来说vs更严谨 

思考:

假设没有强制检查(比如我们自己写的vector),想删除删除 vector 中所有偶数

 但是如果只有4个

为什么会这样呢,我们画图分析

   从这边我们也能看到为什么erase返回值也要用iterator的原因,我们想继续用的话就得去接收一下返回值

2.3.3 扩容导致的失效

可能本来还能用,但是中间扩容过,所以也不能用了

用pos前用一样reserve,也会失效

总而言之:尽量不要复用pos迭代器,因为任何一个可能扩容的操作都会导致失效

2.4 比较不常用的接口

2.4.1 清理元素

	void clear() const
	{
		_finish = _start;
	}

2.4.2 缩容

void shrink_to_fit()
{
	size_t sz = size();//记录
	T* temp = new T[sz];
	for (size_t i = 0; i < sz; ++i)
		temp[i] = _start[i];
	delete _start;
	_start = temp;
	_finish = _start + sz;
	_end_of_storage = _start + sz;
}

三,vector实现的全部代码

namespace cyx
{
	template<class T>
	class vector
	{
	public:
		//迭代器(可读可写)
		typedef T* iterator;

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finish;
		}
		//迭代器(可读不可写)
		typedef const T* const_iterator;

		const_iterator begin() const
		{
			return _start;
		}

		const_iterator end() const
		{
			return _finish;
		}
		//无参构造函数
		vector()
		{}
		//有参构造函数(对n个存储的类型去调用他们的构造)
		vector(size_t n, const T& val = T())
		{
			reserve(n);//因为我们知道会进多少数据,所以可以提前开空间
			for (int i = 0; i < n; ++i)
				push_back(val);
		}
		//重载一个防止间接寻址
		vector(int n, const T val = T())
		{
			reserve(n);//因为我们知道会进多少数据,所以可以提前开空间
			for (int i = 0; i < n; ++i)
				push_back(val);
		}
		//传别人的迭代器进行构造
		template<class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			//这里传的是别人的迭代器,不知道会传多少数据,不能提前扩容,只能让pushback的时候去判断
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}
		//拷贝构造(传统写法)
		vector(const vector<T>& v)
		{
			_start = new T[v.capacity()];
			//memcpy(_start, v._start, sizeof(T)*v.size());  不能用memcpy 是浅拷贝
			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<T> temp(v.begin(), v.end());//让临时对象借助迭代器区间构造出来
		//	swap(temp);//窃取革命成果
		//}
		//交换
		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=(const vector<T>& v)
		{
			T* temp = new T[v.capacity()];
			for (int i = 0; i < v.size(); ++i)
				temp[i] = v[i];
			delete[]_start;
			_start = temp;
			_finish = _start + v.size();
			_end_of_storage = _start + v.capacity();
			return *this;
		}
		//赋值重载现代写法
		//vector<T>& operator=(vector<T> v)
		//{
		//	swap(v);//反正我原来的空间也要销毁,我跟你传值过来的v直接交换,而且不会改变你
		//	return *this;
		//}
		//析构函数
		~vector()
		{
			/*if (_start)*///delete 会自动检查空指针
			delete[] _start;
			_start = _finish = _end_of_storage = nullptr;
		}
		//获取size
		size_t size() const
		{
			return _finish - _start;
		}
		//获取capacoty
		size_t capacity() const
		{
			return _end_of_storage - _start;
		}
		//判空
		bool empty() const
		{
			return _start == _finish;
		}
		//重载[](可读可写)
		T& operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		//重载[](可读不可写)
		const T& operator[](size_t pos) const
		{
			assert(pos < size());
			return _start[pos];
		}
		//提前扩容+初始化
		void resize(size_t n, T val = T())
		{
			//给小
			if (n < size())
				_finish = _start + n;
			//给大
			else
			{
				//容量不够就扩
				if (n > capacity())
					reserve(n);
				while (_finish != _start + n)
				{
					*_finish = val;
					++_finish;
				}
			}
		}
		//提前扩容
		void reserve(size_t n)
		{
			size_t sz = size();//防止丢失
			if (n > capacity())
			{
				T* temp = new T[n];
				if (_start)//如果为空,就不需要拷贝也不需要释放
				{
					for (size_t i = 0; i < sz; ++i)
						temp[i] = _start[i];
					delete[] _start;
				}
				_start = temp;
				_finish = _start + sz;
				_end_of_storage = _start + n;
			}
		}
		//尾插
		void push_back(const T& val)
		{
			if (_finish == _end_of_storage)
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			*_finish = val;
			++_finish;
		}
		//尾删
		void pop_back()
		{
			//防止没有元素可删
			assert(!empty());
			--_finish;
		}
		//指定位置插入
		iterator insert(iterator pos, const T& val)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			if (_finish == _end_of_storage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : 2 * capacity());
				pos = _start + len;
			}
			iterator end = _finish - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--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 clear() const
		{
			_finish = _start;
		}
		//缩容
		void shrink_to_fit()
		{
			size_t sz = size();//记录
			T* temp = new T[sz];
			for (size_t i = 0; i < sz; ++i)
				temp[i] = _start[i];
			delete _start;
			_start = temp;
			_finish = _start + sz;
			_end_of_storage = _start + sz;
		}
	private:
		iterator _start= nullptr;
		iterator _finish= nullptr;
		iterator _end_of_storage= nullptr;
	};

有什么不懂得可以问博主哦!

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

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

相关文章

迷不迷糊?前后端、三层架构和MVC傻傻分不清

现在的项目都讲究前后端分离&#xff0c;那到底什么是前后端&#xff0c;前后端和以前的MVC以及三层架构啥关系呢&#xff1f;今天就这个问题展开一下&#xff0c;方面后面的学习&#xff0c;因为前面讲的jsp、servlet和javabean根据实例&#xff0c;基本上有一个框架的理解了&…

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的按键扫描、数码管显示按键值、显示按键LED应用

基于STC12C5A60S2系列1T 8051单片机的TM1638键盘数码管模块的按键扫描、数码管显示按键值、显示按键LED应用 STC12C5A60S2系列1T 8051单片机管脚图STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式及配置STC12C5A60S2系列1T 8051单片机I/O口各种不同工作模式介绍TM1638键盘…

【如何在Docker中,修改已经挂载的卷(Volume)】

曾梦想执剑走天涯&#xff0c;我是程序猿【AK】 提示&#xff1a;添加投票&#xff01;&#xff01;&#xff01; 目录 简述概要知识图谱 简述概要 如何在Docker中&#xff0c;修改已经挂载的卷&#xff08;Volume&#xff09; 知识图谱 在Docker中&#xff0c;修改已经挂载…

消息队列-kafka-消息发送流程(源码跟踪)

官方网址 源码&#xff1a;https://kafka.apache.org/downloads 快速开始&#xff1a;https://kafka.apache.org/documentation/#gettingStarted springcloud整合 发送消息流程 主线程&#xff1a;主线程只负责组织消息&#xff0c;如果是同步发送会阻塞&#xff0c;如果是异…

安装Proxmox VE虚拟机平台

PVE是专业的虚拟机平台&#xff0c;可以利用它安装操作系统&#xff0c;如&#xff1a;Win、Linux、Mac、群晖等。 1. 下载镜像 访问PVE官网&#xff0c;下载最新的PVE镜像。 https://www.proxmox.com/en/downloads 2. 下载balenaEtcher balenaEtcher用于将镜像文件&#…

【Vue3】3-6 : 仿ElementPlus框架的el-button按钮组件实

文章目录 前言 本节内容实现需求完整代码如下&#xff1a; 前言 上节,我们学习了 slot插槽&#xff0c;组件内容的分发处理 本节内容 本小节利用前面学习的组件通信知识&#xff0c;来完成一个仿Element Plus框架的el-button按钮组件实现。 仿造的地址&#xff1a;uhttps://…

docker pull 拉取失败,设置docker国内镜像

遇到的问题 最近在拉取nginx时&#xff0c;显示如下错误&#xff1a;Error response from daemon: Get “https://registry-1.docker.io/v2/”: net/http: request canceled (Client.Timeout exceeded while awaiting headers)。 这个的问题是拉取镜像超时&#xff0c;通过检索…

基于Golang客户端实现Nacos服务注册发现和配置管理

基于Golang客户端实现Nacos服务注册发现和配置管理 背景 最近需要把Golang实现的一个web项目集成到基于Spring Cloud Alibaba的微服务体系中&#xff0c;走Spring Cloud Gateway网关路由实现统一的鉴权入口。 软件版本 组件名称组件版本Nacos2.2.0Go1.21.0Ginv1.9.1Nacos-s…

项目部署发布

目录 上传数据库 修改代码中的数据源配置 修改配置文件中的日志级别和日志目录 打包程序 ​编辑​编辑 上传程序 查看进程是否在运行 以及端口 云服务器开放端口(项目所需要的端口) 上传数据库 通过xshell控制服务器 创建目录 mkdir bit_forum 然后进入该目录 查看路…

【AI+CAD】(一)ezdxf 解析DXF文件

DXF文件格式理解 DXF文件格式是矢量图形文件格式&#xff0c;其详细说明了如何表示不同的图形元素。 DXF是一个矢量图形文件&#xff0c;它捕获CAD图形的所有元素&#xff0c;例如文本&#xff0c;线条和形状。更重要的是&#xff0c;DXF是用于在CAD应用程序之间传输数据的图形…

Java日志框架的纷争演进与传奇故事

在Java的世界里&#xff0c;日志记录是每一个应用不可或缺的部分。它帮助开发者了解应用的运行状态、调试问题、监控性能等。而在这背后&#xff0c;是一系列日志框架的发展与演进。今天&#xff0c;就让我们一起回顾这些日志框架的历史&#xff0c;探寻它们背后的故事。 1. Lo…

分布式数据库中全局自增序列的实现

自增序列广泛使用于数据库的开发和设计中&#xff0c;用于生产唯一主键、日志流水号等唯一ID的场景。传统数据库中使用Sequence和自增列的方式实现自增序列的功能&#xff0c;在分布式数据库中兼容Oracle和MySQL等传统数据库语法&#xff0c;也是基于Sequence和自增列的方式实现…

使用Visual Studio 2022 创建lib和dll并使用

概述&#xff1a;对于一个经常写javaWeb的人来说,使用Visual Studio似乎没什么必要&#xff0c;但是对于使用ffi的人来说&#xff0c;使用c或c编译器&#xff0c;似乎是必不可少的&#xff0c;下面我将讲述如何用Visual Studio 2022 来创建lib和dll&#xff0c;并使用。 静态库…

UNIapp实现局域网内在线升级

首先是UNIapp 生成apk 用Hbuilder 进行打包 可以从网站https://www.yunedit.com/reg?gotocert 使用自有证书&#xff0c;目测比直接使用云证书要快一些。 发布apk 网站 用IIS发布即可 注意事项中记录如下内容 第一、需要在 iis 的MiMe 中添加apk 的格式&#xff0c;否则无法…

Java架构之路-架构应全面了解的技术栈和工作域

有时候我在想这么简单简单的东西&#xff0c;怎么那么难以贯通。比如作为一个架构师可能涉及的不单单是技术架构&#xff0c;还包含了项目管理&#xff0c;一套完整的技术架构也就那么几个技术栈&#xff0c;只要花点心思&#xff0c;不断的往里面憨实&#xff0c;总会学的会&a…

UE4升级UE5 蓝图节点变更汇总(4.26/27-5.2/5.3)

一、删除部分 Ploygon Editing删除 Polygon Editing这个在4.26、4.27中的插件&#xff0c;在5.1后彻底失效。 相关的蓝图&#xff0c;如编辑器蓝图 Generate mapping UVs等&#xff0c;均失效。 如需相关功能&#xff0c;请改成Dynamic Mesh下的方法。 GetSupportedClass删…

在K8S集群中部署SkyWalking

1. 环境准备 K8S 集群kubectlhelm 2. 为什么要部署SkyWalking&#xff1f; 我也不道啊&#xff0c;老板说要咱就得上啊。咦&#xff0c;好像可以看到服务的各项指标&#xff0c;像SLA&#xff0c;Apdex这些&#xff0c;主要是能够进行请求的链路追踪&#xff0c;bug排查的利…

C向C++的一个过渡

思维导图 输入输出&#xff0c;以及基础头文件 在c语言中我们常用scanf("%d",&n);和printf("%d\n",n);来输出一些变量和常量&#xff0c;在C中我们可以用cin;和cout;来表示输入输出。 在C语言中输入输出有头文件&#xff0c;在C也有头文件&#xff0…

解放人力,提升品质:码垛输送机的工业应用与价值

在现代工业生产中&#xff0c;码垛输送机已成为许多企业自动化生产线上的关键设备。它不仅可以提高生产效率&#xff0c;降低人力成本&#xff0c;还能确保产品质量&#xff0c;并为企业带来许多其他方面的实际好处。 1. 提高生产效率&#xff1a; 快速码垛&#xff1a;码垛输…

蓝桥杯练习题——dp

五部曲&#xff08;代码随想录&#xff09; 1.确定 dp 数组以及下标含义 2.确定递推公式 3.确定 dp 数组初始化 4.确定遍历顺序 5.debug 入门题 1.斐波那契数 思路 1.f[i]&#xff1a;第 i 个数的值 2.f[i] f[i - 1] f[i - 2] 3.f[0] 0, f[1] 1 4.顺序遍历 5.记得特判 …