【C++】vector类的模拟实现(SGI版本)

news2025/1/13 17:27:19

🏖️作者:@malloc不出对象
⛺专栏:C++的学习之路
👦个人简介:一名双非本科院校大二在读的科班编程菜鸟,努力编程只为赶上各位大佬的步伐🙈🙈
在这里插入图片描述

目录

    • 前言
    • 一、vector类的模拟实现
      • 1.1 vector的主体框架
      • 1.2 无参构造函数
      • 1.3 size 和 capacity
      • 1.4 正向迭代器
      • 1.5 reserve && resize
      • 1.6 push_back 和 pop_back
      • 1.7 [ ] 运算符重载
      • 1.8 insert && erase
        • 迭代器失效问题
        • insert导致迭代器失效
        • erase导致迭代器失效
      • 1.9 swap
      • 1.10 clear
      • 1.11 ~vector
      • 1.12 构造函数
        • n个val的构造
        • 迭代器构造
      • 1.13 赋值运算符重载
      • 1.14 拷贝构造
    • 二、vector类模拟实现的完整代码


前言

本篇文章我们要来模拟实现一下SGI版本的vector类以及讲解迭代器失效的问题。

一、vector类的模拟实现

1.1 vector的主体框架

对于类来说最重要的是知道它的成员变量,只有知道它的成员变量了你才能知道它需要利用成员变量干什么事,SGI版本下vector类的成员变量是由三个迭代器组成的,而这里的迭代器就充当原生指针的作用,这三个迭代器(指针)_start 指向第一个位置,_finish 指向最后一个元素的下一个位置,_end_of_storage 指向最大容量的下一个位置。

namespace curry
{
	template<class T>
	class  vector
	{
	public:
		typedef T* iterator;
		typedef const T* const_iterator;
	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

1.2 无参构造函数

vector()
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{}

对三个成员变量进行初始化操作。

1.3 size 和 capacity

vector类元素的个数以及容量大小,不需要修改的成员函数最好加上const防止被修改。

bool empty() const
{
	return _start == _finish;  // 当_start与_finish指向同一位置时,_finish - _start = size = 0
}

size_t size() const
{
	return _finish - _start;  //指针相减为两个指针之间的元素个数
}

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

1.4 正向迭代器

iterator begin()
{
	return _start;
}

iterator end()
{
	return _finish;
}

const_iterator begin() const
{
	return _start;
}

const_iterator end() const
{
	return _finish;
}

在这里插入图片描述

1.5 reserve && resize

reserve与resize都起到扩容的作用,而关于这两者的区别我们在讲解string类的时候就已经对比过了,对于vector类来说在尾部或者某一位置处插入元素前都是需要检查容量的,一旦size等于capacity我们就需要进行扩容操作,在SGI版本中vector类的扩容机制是每次以2倍容量进行扩容。

void reserve(size_t n)
{
	if (capacity() < n)
	{
		T* tmp = new T[n];
		size_t sz = size();

		if (_start) //_start为nullptr时,不需要拷贝数据
		{
			memcpy(tmp, _start, sizeof(T) * size()); // 浅拷贝
			delete[] _start;
		}
		_start = tmp;
		_finish = tmp + sz;  
		_end_of_storage = tmp + n;
	}
}

void resize(size_t n, T val = T()) // 使用缺省值,模板调用无参构造函数进行初始化,T()是一个匿名对象
{
	if (n < size())
	{
		_finish = _start + n;
	}
	else if (n > size())
	{
		if (n > capacity())
		{
			reserve(n);
		}

		iterator end = _start + n;
		while (_finish != end)  // 对后续空间进行初始化操作
		{
			*_finish = val;
			_finish++;
		}
	}
}

注意事项1:reserve函数扩容后_start_finish以及_end_of_storage的位置发生了变化。

非常容易写成下面的错误代码:

_start = tmp;
_finish = tmp + size();  
_end_of_storage = tmp + n;

tmp + size() = _start + _finish - _start == _finish,注意此时的_start扩容后位置已经发生变化,要想得到_finish的位置必须知道变化之前_start与它的相对位置,即在变化前得到sz = _finish - _start

注意事项2: 在拷贝_start指向空间的数据时采用的是memcpy浅拷贝,它是一个字节一个字节拷贝数据的,但是在涉及对象空间资源的释放时就会析构两次,此时就产生了问题!!这个问题后续我们再来探究,现阶段使用memcpy对内置类型进行数据拷贝是安全的。

1.6 push_back 和 pop_back

下面我们进行vector常用函数接口尾插和尾删的实现,还是非常简单的。

// 尾插
void push_back(const T& x)
{
	if (_finish == _end_of_storage) // 当_finish与_end_of_storage指向的位置相等时说明此时容量已满需要进行扩容操作
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);  // 这里当容量为0时,我给了4个大小的容量,这里可以大家可以随意去控制
	}

	*_finish = x;
	_finish++;
}

// 尾删
void pop_back()
{
	assert(!empty()); // 断言检查是否为空
	--_finish;
}

1.7 [ ] 运算符重载

[ ]运算符重载,返回对应下标的值,有两个版本。

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

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

1.8 insert && erase

下面我们来模拟实现一下在某一合法位置进行插入和删除,下面的代码是最原始的insert与erase版本,这其中涉及迭代器失效的问题,大家也可以先看看这段代码有什么问题,后续我们会结合迭代器失效问题来给出最终的版本。

void insert(iterator pos, const T& val)
{
	assert(pos >= _start && pos <= _finish);
	if (_finish == _end_of_storage)
	{
		size_t sz = pos - _start;
		reserve(2 * capacity()); // 这里如果直接扩容的话,那么_start就改变了,pos位置没有了参照物,所以为了找到pos位置,我们可以利用扩容前的与_start的相对位置
		pos = _start + sz;  // 扩容之后再根据_start与pos的相对位置求出此时pos的位置
	}

	// 版本一:
	/*iterator end = _finish - 1;
	while (end != pos + 1)
	{
		*(end + 1) = *end;
		end--;
	}*/

	// 版本二:
	iterator end = _finish;
	while (end != pos)
	{
		*end = *(end - 1);
		end--;
	}

	*pos = val;
	_finish++;

	return pos;
}

void erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);

	/*iterator begin = pos;
	while (begin + 1 != _finish)
	{
		*begin = *(begin + 1);
		begin++;
	}*/
	
	iterator begin = pos + 1;
	while (begin != _finish)
	{
		*(begin - 1) = *begin;
		begin++;
	}

	_finish--;
}

迭代器失效问题

insert导致迭代器失效

我们先来测试一下原始版本的正确性:

在这里插入图片描述

从上图我们发现此时并没有什么问题,真的没问题吗?下面我们再插入一个数据:

在这里插入图片描述

我们发现这段代码已经崩了??这是为何?看到代码出现崩溃其实我们第一个问题就要想到是不是指针指向的空间位置是不是改变了、指向的空间是否已经释放了…为何插入4个元素时不会崩溃,插入5个元素就崩溃了呢?

我们想到我们的capacity容量大小起始给的是4,此时_start指向的位置不变,而一旦插入第五个元素了此时就需要进行扩容,扩容之后_start指向的位置发生了变化,所以此时pos的位置不正确造成了程序崩溃,虽然扩容之后_start指向的位置发生了变化,但是pos与_start之前的相对位置是不变的,所以我们可以在扩容前记录下pos与_start的相对位置,扩容后就知道了pos的位置。

修改后的代码如下:

void insert(iterator pos, const T& val)
{
	assert(pos >= _start && pos < _finish);
	if (_finish == _end_of_storage)  // 需要扩容
	{
		size_t sz = pos - _start; // 求出pos与_start的相对位置
		reserve(capacity() == 0 ? 4 : capacity() * 2);  
		pos = _start + sz;  // 扩容后pos的位置
	}

	iterator end = _finish - 1;  
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	_finish++;
}

在这里插入图片描述

通过上述代码我们解决了在扩容后pos迭代器失效的问题,那么还有没有其他的问题呢?我们看下下面这段代码:

在这里插入图片描述

为何上述输出的结果一致?我们的目的不是让pos位置处++吗?得到的答案应该是1 2 11 3 4啊,为什么这里没有任何变化?

虽然在insert内我们成功的控制了pos,但是形参改变不影响实参,所以此时的pos还是扩容前的位置,该位置已失效是野指针,所以此时其实是会造成非法访问的,至于这里为什么没报错跟编译器的处理有关,可能在另外版本的编译器上就崩溃了。

如何解决这个问题?

返回插入位置的迭代器pos,能用传引用吗?不能,iterator返回的是临时对象,临时对象具有常属性,要想使用需要加const修饰,可是我们的pos经过扩容后修改了!!所以肯定是不支持这种写法的。

修改后的最终代码:

iterator insert(iterator pos, const T& val)
{
	assert(pos >= _start && pos <= _finish);
	if (_finish == _end_of_storage)  
	{
		size_t sz = pos - _start; 
		reserve(capacity() == 0 ? 4 : capacity() * 2);  
		pos = _start + sz; 
	}

	iterator end = _finish - 1;  
	while (end >= pos)
	{
		*(end + 1) = *end;
		end--;
	}
	*pos = val;
	_finish++;

	return pos;
}

在这里插入图片描述

我们来回答一个问题:insert之后会使插入位置的迭代器失效吗(原始版本)?

在SGI版本中有可能能用有可能不能用,但它也失效了,因为我们的_finish不是++到pos位置处,而是++到挪动后的数据;第二种失效是因为pos位置在扩容之后就失效了。


对于插入数据如果未接收返回插入后的位置,P.J.版本VC++的态度是坚决不允许这种行为的出现,不管你此时的pos还是不是原来的pos都视为失效,此时(*pos)++进行访问直接就是非法访问导致程序崩溃!!

在这里插入图片描述

erase导致迭代器失效

同样的我们先来检查一下正确性:

在这里插入图片描述

通过上图我们发现原始版本的删除函数看起来是没有任何问题的,那么接下来我们看看在G++下SGI版本对这两段代码的态度:

删除中间位置:

在这里插入图片描述

删除最后一个位置:

在这里插入图片描述

删除中间位置时,我们的数据进行了挪动,其实pos位置是已经失效了的,此时它指向的是pos位置的下一个位置即4号位置,再对4号位置进行++,这也似乎说的过去;但是对于删除最后一个位置,此时我们的数据未进行挪动,但此时pos与_finish指向同一个位置,即最后一个元素的下一个位置,这个位置是的内容是未知的,此时我们进行(*pos)++不就非法访问了吗!!这肯定是不太合理的,而P.J.版本是更为合理的,一旦使用未接收删除后的返回值,P.J.版本认为erase以后pos位置失效了,不能进行访问并且强制进行检查,此时导致程序崩溃。

P.J.版本VC++下进行erase

在这里插入图片描述

我们发现在erase之后一旦使用未接收删除后的返回值,P.J.版本认为pos位置已经失效,此时再进行(*pos)++是在进行非法访问!!


下面我们继续来看一个问题:删除vector容器中所有的偶数,请你用迭代器实现?

在这里插入图片描述

上述代码好像已经实现了删除vector容器中的所有偶数?下面我们来看看这段代码:

在这里插入图片描述

这段代码似乎出现了错误,我们继续看下一种情况:

在这里插入图片描述

我们不是要删除所有的偶数吗?那这个4怎么没删掉呢??下面我们通过图来模拟一下这三种情况:

在这里插入图片描述

这里本质上的问题就是it不管是在遇到偶数还是未遇到时都跳到下一个位置上去了,这就导致出现越界或者跳过了某些偶数,我们的解决办法是在删除元素时不往后跳,我们的代码这样一改就好了:

在这里插入图片描述

还有一种情况大家自己下来去检测吧。

那么如何解决erase后迭代器失效的问题呢?返回pos的下一个位置。

erase的最终版本

iterator erase(iterator pos)
{
	assert(pos >= _start && pos < _finish);

	/*iterator begin = pos;
	while (begin + 1 != _finish)
	{
		*begin = *(begin + 1);
		begin++;
	}*/
	
	iterator begin = pos + 1;
	while (begin != _finish)
	{
		*(begin - 1) = *begin;
		begin++;
	}

	_finish--;
	
	return pos;
}

总结:对于insert以及erase我们从上述例子进行分析之后,其实P.J.版本的设计其实是更为合理的,一旦我们未接收插入/删除后的位置,无论此时插入/删除的位置有没有问题都认为该位置已经失效,如果我们再对此位置进行访问就是非法访问了!!我们最好不要对插入/删除后的位置进行访问!!!

另外,我们使用vector也是很少使用这两个函数接口的,因为它们需要进行大量的挪动数据影响效率。

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

1.10 clear

void clear()
{
	_finish = _start;
}

_finish = _start,size大小为0,但并不会进行缩容操作!!

1.11 ~vector

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

1.12 构造函数

n个val的构造

vector(size_t 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);
	}
}

迭代器构造

template <class InputIterator>  // 任意类型的迭代器
vector(InputIterator first, InputIterator last)
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	while (first != last)
	{
		push_back(*first);
		first++;
	}
}

我们来看一段代码:

在这里插入图片描述

这里为什么会出现非法间接寻址?我不是已经实现了n个val的构造函数吗???为什么调用n个val的构造函数会出现这种报错信息呢??

从编译器提示来看这是因为它的实参类型为int, int,而我们的n个val的构造函数参数类型为size_t, int;所以它最为匹配的函数是上述实例化出来的函数模板,但是*int是非法的行为,所以这里出现了非法寻址的报错信息!!

要想解决这个问题我们可以改变一下实参的类型,我们可以传v1(10u, 5),u代表无符号整型,这样就可以匹配到我们的n个val的构造函数了;又重新重载一份int,int参数类型的n个val的构造函数:

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

在这里插入图片描述

1.13 赋值运算符重载

vector<T>& operator=(vector<T>& v)
{
	vector<T> tmp(v.begin(), v.end()); // 迭代器构造
	swap(tmp);
			
	return *this;
}

1.14 拷贝构造

传统写法

// 拷贝构造01 
vector(const vector<T>& v)
	: _start(nullptr)
	, _finish(nullptr)
	, _end_of_storage(nullptr)
{
	reserve(v.capacity());
	for (auto& e : v)
	{
		push_back(e);
	}
}

// 拷贝构造02 -- 浅拷贝
vector(const vector<T>& v)
{
	reserve(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)
{
	vector<T> tmp(v.begin(), v.end()); // 迭代器构造
	swap(tmp);
}

我们以传统写法拷贝构造–02为例,看看浅拷贝遇到自定义类型会出现什么情况:

在这里插入图片描述

我们知道string类是自定义类型,当我们进行浅拷贝时会出现下图情形:

在这里插入图片描述

一旦我们出了函数作用域,v3和v4对象都会去调用析构函数,而此时它们的元素_str指向同一块空间,由此会被析构两次,于是就造成了程序崩溃。所以我们在此处不应该使用memcpy浅拷贝,我们应该使用深拷贝使_str指向不同的位置。

我们的代码应该这样来写:

vector(const vector<T>& v)
{
	reserve(v.capacity());
	for (size_t i = 0; i < v.size(); i++)
	{
		_start[i] = v._start[i];   // this->_start._str = v->_start._str, 调用string类中的赋值重载函数
	}
	_finish = _start + v.size();
	_end_of_storage = _start + v.capacity();
}

在这里插入图片描述

我们继续来看向这段代码:

在这里插入图片描述

v1尾插4次此时是没有问题的,下面我们再插入一次看看会出现什么情况:

在这里插入图片描述

这是由于push_back第五次时发生扩容,而扩容是浅拷贝,我们再次回头看看第一次我们写的reserve函数,当我们的需要扩容时先将_start指向的原始数据拷贝到新空间tmp中,此时进行的是memcpy浅拷贝,它们的元素指向的是同一块空间,当_start进行delete时将这块空间进行释放了,所以tmp指向的这块空间是无效的,所以最终_start指向的就是无效的空间。

在这里插入图片描述

最终我们的reserve函数代码应该如下:

void reserve(size_t n)
{
	if (capacity() < n)
	{
		T* tmp = new T[n];
		size_t sz = size();

		if (_start)
		{
			for (size_t i = 0; i < sz; i++)
			{
				tmp[i] = _start[i];	 // 自定义类型深拷贝
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = tmp + sz;  
		_end_of_storage = tmp + n;
	}
}

在这里插入图片描述

以上就是一些重要的vector类的模拟实现代码了。

二、vector类模拟实现的完整代码

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

		// n个val的构造函数
		vector(size_t 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(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);
			}
		}

		// 拷贝构造函数
		vector(const vector<T>& v)
		{
			reserve(v.capacity());
			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> tmp(v.begin(), v.end());
			swap(tmp);
		}*/

		// 赋值重载函数
		vector<T>& operator=(vector<T>& v)
		{
			vector<T> tmp(v.begin(), v.end());
			swap(tmp);

			return *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);
		}

		// 函数模板,任意类型的迭代器构造
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			: _start(nullptr)
			, _finish(nullptr)
			, _end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				first++;
			}
		}
		
		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 reserve(size_t n)
		{
			if (capacity() < n)
			{
				T* tmp = new T[n];
				size_t sz = size();

				if (_start)
				{
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i];	 // 自定义类型深拷贝
					}
					delete[] _start;
				}
				_start = tmp;
				_finish = tmp + sz;
				_end_of_storage = tmp + n;
			}
		}

		void resize(size_t n, T val = T()) // 使用缺省值,模板调用无参构造函数进行初始化,T()是一个匿名对象
		{
			if (n < size())
			{
				_finish = _start + n;
			}
			else if (n > size())
			{
				if (n > capacity())
				{
					reserve(n);
				}

				iterator end = _start + n;
				while (_finish != end)  // 进行初始化
				{
					*_finish = val;
					_finish++;
				}
			}
		}

		// 某一位置插入元素
		iterator insert(iterator pos, const T& val)
		{
			assert(pos >= _start && pos <= _finish);
			if (_finish == _end_of_storage)  // 需要扩容
			{
				size_t sz = pos - _start; // 求出pos与_start的相对位置
				reserve(capacity() == 0 ? 4 : capacity() * 2);  
				pos = _start + sz;  // 扩容后pos的位置
			}

			iterator end = _finish - 1;  
			while (end >= pos)
			{
				*(end + 1) = *end;
				end--;
			}
			*pos = val;
			_finish++;

			return pos;
		}

		// 某一位置删除元素
		iterator erase(iterator pos)
		{
			assert(pos >= _start && pos < _finish);

			/*iterator begin = pos;
			while (begin + 1 != _finish)
			{
				*begin = *(begin + 1);
				begin++;
			}*/
			iterator begin = pos + 1;
			while (begin != _finish)
			{
				*(begin - 1) = *begin;
				begin++;
			}

			_finish--;

			return pos;
		}

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

			*_finish = x;
			_finish++;
		}
		
		// 尾删
		void pop_back()
		{
			assert(!empty());
			--_finish;
		}

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

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

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

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

	private:
		iterator _start;
		iterator _finish;
		iterator _end_of_storage;
	};
}

以上就是本文的所有内容了,如有错处或者疑问欢迎大家在评论区相互交流orz~🙈🙈

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

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

相关文章

SpringCloud Alibaba实战和源码(7)Skywalking

什么是SkyWalking Skywalking是由国内开源爱好者吴晟开源并提交到Apache孵化器的产品&#xff0c;它同时吸收了Zipkin /Pinpoint /CAT 的设计思路。特点是&#xff1a;支持多种插件&#xff0c;UI功能较强&#xff0c;支持非侵入式埋点。目前使用厂商最多&#xff0c;版本更新较…

水溶性焊锡丝非水溶焊锡丝

Sn63Pb37&#xff0c;无FLUX&#xff0c;水清洗&#xff1b;A可理解为余量或者国标A类标准1% Sn63Pb37&#xff0c;FLUX 1.8% 焊锡膏 焊锡丝 焊锡丝&#xff0c;英文名称&#xff1a;solder wire&#xff0c;由锡合金和助剂两部分组成&#xff0c;合金成份分为锡铅、无铅助剂均…

PX4使用esp8266

文章目录 前言一、给esp8266下载固件接线下固件 二、配置esp8266 前言 硬件&#xff1a; esp01s(esp01好像有些问题&#xff0c;不建议用) usb转串口模块 pix飞控 软件&#xff1a; qgc PX4 参考&#xff1a; https://docs.px4.io/main/en/telemetry/esp8266_wifi_module.html…

1.4亿X区城市运行“一网统管”体系建设项目项目招标WORD

导读&#xff1a;原文《1.4亿X区城市运行“一网统管”体系建设项目项目招标WORD》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 部分内容&#xff1a; 各部分需求…

VS code 设置 资源管理器 对齐线

点击左上角的File --> Preformences --> Settings 然后搜索 workbench&#xff0c;把workbench.tree.renderIndentGuides选成always&#xff0c;这样会一直显示对齐的竖线。 找到workbench.tree.indent&#xff0c;这个值就是缩进的像素数量&#xff0c;值越大&#xff0…

electron+vue3全家桶+vite项目搭建【16.1】electron多窗口,pinia状态同步,扩展store方法,主动同步pinia的状态【推荐】

文章目录 引入实现效果如下实现步骤1.自定义pinia插件2.主进程补充同步处理 引入 demo项目地址 我们之前写了一个自动同步pinia状态的插件&#xff0c;可以参考如下文章 electronvue3全家桶vite项目搭建【16】electron多窗口&#xff0c;pinia状态无法同步更新问题解决 这里…

WinPlan经营大脑:专注企业经营分析预测的垂直大模型

目录 一、WinPlan的核心功能与优势 二、WinPlan的应用场景与案例 三、数利得的团队与未来发展 四、投资方观点 随着科技的不断进步和市场竞争的日益激烈,企业数字化转型已成为提高经营效率和管理水平的关键。然而,许多企业在经营决策过程中仍面临许多问题,尤其在管理层的…

2023年国赛 高教社杯数学建模思路 - 案例:随机森林

文章目录 1 什么是随机森林&#xff1f;2 随机深林构造流程3 随机森林的优缺点3.1 优点3.2 缺点 4 随机深林算法实现 建模资料 ## 0 赛题思路 &#xff08;赛题出来以后第一时间在CSDN分享&#xff09; https://blog.csdn.net/dc_sinor?typeblog 1 什么是随机森林&#xff…

免费开源大型商城系统_支持商用_无需授权_OctShop

一、OctShop免费开源大型商城系统&#xff0c;支持商用 OctShop是一个免费开源的大型商城系统&#xff0c;无需官方授权就可以直接商用&#xff0c;商城系统集B2B2C和O2O模式于一体。采用前后端分离 八大数据库 分布式系统 微服务架构&#xff0c;支持高并发&#xff0c;非…

Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(31,2)

[ 3.405676] No filesystem could mount root, tried: squashfs [ 3.411546] Kernel panic - not syncing: VFS: Unable to mount root fs on unknown-block(31,2)可能的原因之一&#xff1a; uboot中rootfs分配的大小不够 解决&#xff1a; 修改root到一个合适的大小…

基于启扬RK3399核心板消防控制图形显示装置的解决方案

在我们日常生活中&#xff0c;火灾的发生是不可避免的风险之一&#xff0c;为了能及时发现火灾&#xff0c;并能够迅速采取措施进行灭火和救援&#xff0c;消防系统起着至关重要的作用。而在消防系统中&#xff0c;消防控制室图形显示装置是其中的重要组成部分之一。 消防控制图…

javaScript:七夕特辑-对象的认识与应用(包含日期对象及相关案例)

目录 一.前言 二.认识对象 在js中声明对象的方法 1.直接使用{}声明对象 2.使用构造函数创建对象 获取属性的值 执行对象方法 解释 三.对象的应用 代码 效果图 ​编辑 四.日期对象 1.Date 日期对象 2. getFullYear() 获取当前年份 3.getMonth() 获取当前日期对象…

ethers.js2:provider提供商

1、Provider类 Provider类是对以太坊网络连接的抽象&#xff0c;为标准以太坊节点功能提供简洁、一致的接口。在ethers中&#xff0c;Provider不接触用户私钥&#xff0c;只能读取链上信息&#xff0c;不能写入&#xff0c;这一点比web3.js要安全。 除了之前介绍的默认提供者d…

DataSecurity Plus:守护企业数据安全的坚实屏障

在数字化时代&#xff0c;数据被誉为企业最重要的资产之一。然而&#xff0c;随着大数据的兴起和信息的日益增长&#xff0c;企业面临着前所未有的数据安全挑战。为了应对这些挑战&#xff0c;数据安全管理变得至关重要。在这个领域&#xff0c;ManageEngine的DataSecurity Plu…

TCP滑动窗口

为什么会有滑动窗口 在计算机网络中&#xff0c;数据通常被分成小块&#xff08;也叫数据段&#xff09;在网络中传输&#xff08;为什么会被分成小块&#xff0c;请了解拥塞窗口和流量控制&#xff09;。这些小块可能会在传输的过程中遇到延迟、丢失或乱序等问题。为了保证数据…

学习ts(六)数据类型(元组、枚举、Symbol、never)与类型推论

1.元组 元组&#xff08;Tuple&#xff09;是固定数量的不同类型的元素的组合。是数组的变种。 元组与集合的不同之处在于&#xff0c;元组中的元素类型可以是不同的&#xff0c;而且数量固定。元组的好处在于可以把多个元素作为一个单元传递。如果一个方法需要返回多个值&…

适配器模式实现stack和queue

适配器模式实现stack和queue 什么是适配器模式&#xff1f;STL标准库中stack和queue的底层结构stack的模拟实现queue的模拟实现 什么是适配器模式&#xff1f; 适配器是一种设计模式(设计模式是一套被反复使用的、多数人知晓的、经过分类编目的、代码设计经验的总结)&#xff…

微信小程序最新获取头像和昵称的方法 直接用!

调整背景 微信小程序获取用户头像和昵称一个开放接口是wx.getUserInfo&#xff0c;2021年4月5日被废弃&#xff0c;原因是很多开发者在打开小程序时就通过组件方式唤起getUserInfo弹窗&#xff0c;如果用户点击拒绝&#xff0c;无法使用小程序&#xff0c;这种做法打断了用户正…

Camunda_4:监听器相关

Camunda的监听器非常之多&#xff0c;常见的如任务监听与执行监听。我们可以实现相关监听器进行相关操作。 首先明确的是&#xff0c;当执行到某一个节点时&#xff0c;会先进入执行监听&#xff0c;然后进入任务监听。 执行监听和任务监听主要监听以下阶段。 然后我们就能去…

excel中如果A列中某项有多条记录,针对A列中相同的项,将B列值进行相加合并统计

excel中如果A列中某项有多条记录&#xff0c;针对A列中相同的项&#xff0c;将B列值进行相加合并统计。 如&#xff1a; 实现方法&#xff1a; C1、D1中分别输入公式&#xff0c;然后下拉 IF(COUNTIF($A$1:A1,A1)1, A1,"") #A1根据实际情况调整&#xff0c;如果…