<C++> vector模拟实现

news2024/12/26 11:04:40

目录

前言

一、定义命名空间

二、构造函数

三、拷贝构造

四、赋值运算符重载

五、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
		//先初始化为nullptr
		vector(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.coloradmin.cn/o/1147680.html

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

相关文章

【网络安全】Seeker内网穿透追踪定位

Seeker追踪定位对方精确位置 前言一、kali安装二、seeker定位1、ngrok平台注册2、获取一次性邮箱地址3、ngrok平台登录4、ngrok下载5、ngrok令牌授权6、seeker下载7、运行seeker定位8、运行隧道开启监听9、伪装链接10、用户点击&#xff08;获取定位成功&#xff09;11、利用经…

(速进)完美解决“用户在命令行上发出了 EULAS_AGREED=1,表示不接受许可协议。”以及“此产品安装程序不支持降级”

安装VMware时候&#xff0c;出现以下两种情况的原因是&#xff1a;未彻底卸载&#xff08;之前安装过VMware&#xff09;&#xff0c;例如&#xff1a;还有相关配置信息、注册表信息等。只要彻底清理就可以解决此问题。 网上很多帖子使用了powershell里的命令 例如&#xff1…

Linux病毒疯狂增长,我们该如何…

导读国家信息中心日前与瑞星联合发布的《2017年上半年中国网络安全报告》&#xff08;以下简称《报告》&#xff09;指出&#xff0c;目前Linux系统病毒已快速增长。《报告》对2017年1至6月的网络安全现状与趋势进行统计、研究和分析后指出&#xff0c;Linux系统的勒索软件数量…

帆软finereport10.0 多个筛选框根据不同条件必须选一个才能查询

效果&#xff1a; 方法一&#xff1a; 方法二&#xff1a; 方法一&#xff1a;在查询里写上一段js&#xff0c;此方法会把端口和IP暴露出来&#xff0c;方法二比较完美。 var diff this.options.form.getWidgetByName("diff").getValue();//正反向 var fllh …

【多线程面试题 八】、说一说Java同步机制中的wait和notify

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;说一说Java同步机制中的…

Day 12 python学习笔记

模块 内置模块 sys模块 概述&#xff1a;Python 的 sys 模块提供访问解释器使用或维护的变量&#xff0c;和与解释器进行交互的函数。通俗来讲&#xff0c;sys 模块为程序与 Python 解释器的交互&#xff0c;提供了一系列的函数和变量&#xff0c;用于操控 Python 运行时的环境…

多输入多输出 | Matlab实现k-means-ELM(k均值聚类结合极限学习机)多输入多输出组合预测

多输入多输出 | Matlab实现k-means-ELM&#xff08;k均值聚类结合极限学习机&#xff09;多输入多输出组合预测 目录 多输入多输出 | Matlab实现k-means-ELM&#xff08;k均值聚类结合极限学习机&#xff09;多输入多输出组合预测预测效果基本描述程序设计参考资料 预测效果 基…

lazarus开发:提升sqlite数据插入速度

目录 1 前言 2 优化数据容器 3 开启事务插入数据 4 其他方面优化 1 前言 近期有一个需求是向数据库中插入excel文件中的10万多条数据&#xff0c;接近70个字段。最初整个插入数据时间是大约40分钟&#xff0c;经过优化调整后&#xff0c;大幅优化为大约5分钟。这里简单介绍…

CV计算机视觉每日开源代码Paper with code速览-2023.10.27

精华置顶 墙裂推荐&#xff01;小白如何1个月系统学习CV核心知识&#xff1a;链接 点击CV计算机视觉&#xff0c;关注更多CV干货 论文已打包&#xff0c;点击进入—>下载界面 点击加入—>CV计算机视觉交流群 1.【基础网络架构&#xff1a;Transformer】&#xff08;Ne…

Jetpack:020-Jetpack导航示例:底部导航栏

文章目录 1. 概念介绍2. 使用方法3. 代码与分析3.1 示例代码3.2 代码分析 4. 内容总结 我们在上一章回中介绍了Jetpack中导航相关的内容&#xff0c;本章回中主要介绍 导航的综合示例&#xff1a;底部导航栏。闲话休提&#xff0c;让我们一起Talk Android Jetpack吧&#xff0…

2-多媒体数据压缩国际标准-Part3

文章目录 视频压缩的国际标准MPEG-1&MPEG-2/H.262视频标准MPEG-4 AVC/H.264视频标准H.264编码框架概述H.264视频编码的技术创新点 H.265/HEVC视频标准HEVC性能与编解码框架概述Quadtree-based coding structureDeblocking & SAO FilterHEVC各模块运算量 视频压缩的国际…

leetcode-哈希表

1. 理论 从哈希表的概念、哈希碰撞、哈希表的三种实现方式进行学习 哈希表&#xff1a;用来快速判断一个元素是否出现集合里。也就是查值就能快速判断&#xff0c;O&#xff08;1&#xff09;复杂度&#xff1b; 哈希碰撞&#xff1a;拉链法&#xff0c;线性探测法等。只是一种…

leetcode 1353. 最多可以参加的会议数目

给你一个数组 events&#xff0c;其中 events[i] [startDayi, endDayi] &#xff0c;表示会议 i 开始于 startDayi &#xff0c;结束于 endDayi 。 你可以在满足 startDayi < d < endDayi 中的任意一天 d 参加会议 i 。注意&#xff0c;一天只能参加一个会议。 请你返回…

面试测试工程师一般问什么问题?

面试和项目一起&#xff0c;是自学路上的两大拦路虎。面试测试工程师一般会被问什么问题&#xff0c;总结下来一般是下面这4类&#xff1a; 1.做好自我介绍 2.项目相关问题 3.技术相关问题 4.人事相关问题 接下来&#xff0c;主要从以上四个方向分别展开介绍。为了让大家更有获…

【多线程面试题十三】、说一说synchronized与Lock的区别

文章底部有个人公众号&#xff1a;热爱技术的小郑。主要分享开发知识、学习资料、毕业设计指导等。有兴趣的可以关注一下。为何分享&#xff1f; 踩过的坑没必要让别人在再踩&#xff0c;自己复盘也能加深记忆。利己利人、所谓双赢。 面试官&#xff1a;说一说synchronized与Lo…

使用Python获取建筑网站数据,进行可视化分析,并实现网站JS解密

哈喽兄弟们&#xff0c;今天来实现一下建筑市场公共服务平台的数据采集&#xff0c;顺便实现一下网站的JS解密。 话不多说&#xff0c;我们直接开始今天的内容。 首先我们需要准备这些 环境使用 Python 3.8Pycharm 模块使用 requests --> pip install requestsexecjs -…

scratch图书的ISBN码校验 2023年9月中国电子学会图形化编程 少儿编程 scratch编程等级考试三级真题和答案解析

目录 scratch图书的ISBN码校验 一、题目要求 1、准备工作 2、功能实现 二、案例分析

10 MIT线性代数-四个基本子空间 four fundamental subspaces

1. 四个子空间 Four subspaces (mxn) 列空间 Column space C(A) in 零空间Nullspace N(A) in 行空间Row space all combs of rows all combs of columns of AT C(AT) in 左零空间Left nullspace Nullspace of AT N(AT) left nullspace of A in 2. 基和维数 Basis&…

【Qt之QLocale】使用

描述 QLocale类可以在多种语言之间进行数字和字符串的转换。 QLocale类在构造函数中使用语言/国家对进行初始化&#xff0c;并提供类似于QString中的数字转字符串和字符串转数字的转换函数。 示例&#xff1a; QLocale egyptian(QLocale::Arabic, QLocale::Egypt);QString s1 …

分布式:一文吃透分布式事务和seata事务

目录 一、事务基础概念二、分布式事务概念什么是分布式事务分布式事务场景CAP定理CAP理论理解CAPCAP的应用 BASE定理强一致性和最终一致性BASE理论 分布式事务分类刚性事务柔性事务 三、分布式事务解决方案方案汇总XA规范方案1&#xff1a;2PC第一阶段&#xff1a;准备阶段第二…