C++STL之vector的模拟实现

news2024/11/20 18:21:02

由于vector和string的接口使用方法类似,这里便不再详细讲解vector各种接口的使用了,可以参考之前所发布的string的使用,或者从官方文档中获取详细的使用方法.

目录

vector介绍

构造函数(有参,无参,迭代器)

析构函数

获取容量函数capacity()

获取有效数据大小size()

reserve()

resize()

push_back()

[]运算符重载

迭代器相关

begin()、end()

pop_back()

 insert()&&迭代器失效问题

erase()&&迭代器失效问题


vector介绍

这里直接开始讲解vector的模拟实现.

在此之前,先对vector进行一个简单的介绍.

1. vector是表示可变大小数组的序列容器。
2. 就像数组一样,vector也采用的连续存储空间来存储元素。也就是意味着可以采用下标对vector的元素进行访问,和数组一样高效。但是又不像数组,它的大小是可以动态改变的,而且它的大小会被容器自动处理。
3. 本质讲,vector使用动态分配数组来存储它的元素。当新元素插入时候,这个数组需要被重新分配大小,为了增加存储空间。其做法是:分配一个新的数组,然后将全部元素移到这个数组。就时间而言,这是一个相对代价高的任务,因为每当一个新的元素加入到容器的时候,vector并不会每次都重新分配大小。
4. vector分配空间策略:vector会分配一些额外的空间以适应可能的增长,因为存储空间比实际需要的存储空间更大。不同的库采用不同的策略权衡空间的使用和重新分配。但是无论如何,重新分配都应该是对数增长的间隔大小,以至于在末尾插入一个元素的时候是在常数时间的复杂度完成的。
5. 因此,vector占用了更多的存储空间,为了获得管理存储空间的能力,并且以一种有效的方式动态增长。
6. 与其它动态序列容器相比(deques, lists and forward_lists), vector在访问元素的时候更加高效,在末尾添加和删除元素相对高效。对于其它不在末尾的删除和插入操作,效率更低。比起lists和forward_lists统一的迭代器和引用更好。

这些后面模拟接口实现的时候,就会对这些概念有一个大概的了解.

先来说一下vector模拟实现的整体框架.

vector内部是指针实现的,所以类成员有三个:指向数据开始的指针,指向数据结束的指针以及指向数据容量大小的指针.它的迭代器也是一个原生指针.

主要是实现其构造、拷贝构造、赋值运算符重载、析构函数,容量相关类的接口函数,[]重载(访问下标),对数据的修改操作(插入,删除等等).

下面是所有的接口声明.

这里我们把迭代器提前命名好了.

namespace hmylq
{

	template<class T>

	class vector

	{

	public:

		// Vector的迭代器是一个原生指针

		    typedef T* iterator;

			iterator begin();

			iterator end();


			/construct and destroy///

			vector();

			vector(int n, const T& value = T());

			template<class InputIterator>
    
	    	vector(InputIterator first, InputIterator last);
    
			vector(const vector<T>& v);

			vector<T>& operator= (vector<T> v);

			~vector();

			/// capacity

			size_t size() const ;

			size_t capacity() const;

			void reserve(size_t n);

			void resize(size_t n, const T& value = T());



			///access///

			T& operator[](size_t pos);

			const T& operator[](size_t pos)const;



			///modify/

			void push_back(const T& x);

			void pop_back();

			void swap(vector<T>& v);

			iterator insert(iterator pos, const T& x);

			iterator erase(Iterator pos);

	private:

		iterator _start; // 指向数据块的开始

		iterator _finish; // 指向有效数据的尾

		iterator _endOfStorage; // 指向存储容量的尾

	};

}

构造函数(有参,无参,迭代器)

先来看无参的构造函数:初始化列表将类成员3个指针全部初始化为空即可.

		vector()
			:_start(nullptr)
			,_finsh(nullptr)
			,_end_of_storage(nullptr)
		{
			;
		}

有参构造函数:两个参数,分别是数据的总大小以及数据的内容.

比如传一个5,1.就相当于在vector里面存储1,1,1,1,1

这里给了两个缺省值.

		vector(int n, const T& value = T())//T()是一个匿名对象,如string,会调用它的构造函数,内置类型如int,也有构造函数,默认为0.
			:_start(nullptr)
			, _finsh(nullptr)
			, _end_of_storage(nullptr) 
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

这里的reserve()和push_back()函数我们后面会模拟实现.

还有最后一个是迭代器区间构造.

首先创建一个模板参数,我们认作它为迭代器类型,然后利用从起始位置不断++直到到了数据的结尾结束,每次从中插入每个数值.

		//迭代器区间构造
		vector(Inputlterator first, Inputlterator last)
			:start(nullptr)
			,_finsh(nullptr)
			,_end_of_storage(nullptr)
		{
			for (first != last)
			{
				push_back(*first);
				++first;
			}
		}

这里是保证到end时就一定结束,保证所有数据可以被访问到. 

析构函数

析构函数比较简单,首先delete掉数据的内容,再将三个指针指向的内容全部置为空即可.

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

获取容量函数capacity()

由于类成员是指针,我们没办法直接返回它,可以利用指针的相减来获得空间的大小.

用指向容量大小的指针 - 指向数据开始的指针既容量大小.

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

获取有效数据大小size()

这个和capacity()函数类似,只不过是用指向数据结束的指针 - 指向开始的指针.

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

reserve()

这个函数的作用是扩容,当用户输入的n小于原本容量时则不需要任何操作,仅当n大于_end_of_storage时才进行扩容

首先新开辟一块空间,然后将原来的数据拷贝到新空间中,再分别更新_start,_finsh,_end_of_storage,但是更新的时候会有一些问题.

先来看一个常见的错误版本.下面是错误的代码!只是拿来示例一下问题.

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				T* tmp = new T[n];//新开辟一块空间
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * size());//将原来的数据拷贝到新空间
					delete[] _start;//释放掉旧数据,此时三个指针都指向空.
				}

				_start = tmp;//将开始的指针指向数据的开始
				_finsh = tmp + size();//***wrong***
				_end_of_storage = _start + n;
			}
		}

以上代码看起来没有任何问题,但是在wrong的那一行,会有一个bug.

我们前面已经把空间释放掉了,所以3个指针都指向空,但是在执行_finsh = tmp + size();这条语句时,size()是如何计算的呢,size()是_finsh - _start,而此时只有_start更新了,而_finsh依然是空,所以替换过来,相当于抵消了_start,结果还是_finsh,是空.

所以解决办法是一开始先保存上数据的大小.正确代码如下:

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * sz);
					delete[] _start;
				}
 
				_start = tmp;
				_finsh = _start + sz ;
				_end_of_storage = _start + n;
			}
		}

resize()

这个和string类型的很像,

1.如果n > 总容量,则直接扩容到n,并且初始化

2.如果n > size(),但是小于容量,直接初始化即可

3.如果n < size(),需要删除数据


		void resize(size_t n, const T& value = T())
		{
			if (n > capacity())
			{
				reserve(n);
			}
			if (n > size() )
			{
				while (_finsh < _start + n)
				{
					*_finsh = value;
					++_finsh;
				}
			}
			else
			{
				_finsh = _start + n;
			}
		}

 

push_back()

这个函数的作用是尾部插入一个数据,这个比string的简单,因为vector只能插入一个数据.

首先判断是否需要扩容,再直接将*_finsh的值赋值为val,然后++_finsh,这里注意,_finsh指向的是最后一个数据的下一个位置,所以可以直接赋值.

		void push_back(const T& x)
		{
			if (_finsh == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity()  * 2);
			}
			*_finsh = x;
			++_finsh;
		}

[]运算符重载

我们需要像数组一样下标访问或修改某一个元素,那么需要对[]进行重载.方法很简单,就是返回第pos个位置的值.

这里有两种,一种是普通的返回,还有一种是const修饰的,以便能适用const所修饰的调用.

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

const修饰的

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

迭代器相关

上面也说过,迭代器内部也是原生指针,所以

typedef T* iterator;

begin()、end()

迭代器中的begin和end就是返回指向数据开始的指针,和数据结尾的指针,如下

		iterator begin()
		{
			return _start;
		}

end()

		iterator end()
		{
			return _finsh;
		}
		

既然有了迭代器,那么也就对应的可以进行范围for遍历了.

for(auto e : v)
{
    cout << e << endl;
}    

范围for其实是一个傻瓜式的替换.

无论类里的迭代器的开始写的什么,最终都会被替换成begin(),所以如果自己实现的迭代器的开始函数名字不是begin(),则会报错,因为范围for被替换成了begin(),而没有begin()这个成员

pop_back()

思路很简单,直接--_finsh就相当于把最后一个元素删除掉了.

		void pop_back()
		{
			assert(_finsh > _start);
			--_finsh;
		}

 insert()&&迭代器失效问题

这里insert是插入一个元素,然后有两个参数,一个是迭代器类型的pos位置,另一个是要插入的值val.

我们的思路是:

1.如果插入后空间足够,我们从pos位置依次向后挪动一个空间,再把空出来的这一个空间插入val.

2.如果插入后空间不足,则需要扩容,扩容之后再进行第一步的操作,但这样会存在迭代器失效问题.

先来看这段存在迭代器失效问题的错误代码

		void insert(iterator pos, const T& x) // 注意迭代器失效
		{
			assert(pos >= _start);
			assert(pos <= _finsh);

            //扩容
			if (_finsh == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);//如果需要扩容,pos位置则会改变,需要更新
			}
            //插入操作
			iterator end = _finsh - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			_finsh++;
		}

然后我们测试一段代码:

void test_vector5()
{
	vector<int> v;
	v.push_back(1);
	v.push_back(2);
	v.push_back(3);
	v.push_back(4);
    //v.push_back(5);

	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;

	//查找3
	auto p = find(v.begin(), v.end(), 3);
	//如果查找到了,则在3的位置插入一个30
	if (p != v.end())
	{
        //在p位置插入数据以后就不要再去p了,因为可能已经失效了.
		v.insert(p, 30);
	}
	for (auto e : v)
	{
		cout << e << " ";
	}
	cout << endl;
}

运行起来,发现程序崩溃了.

这是为什么呢?

我们看insert()代码中,首先插入了4个数据,此时要将新数据val插入其中,此时需要扩容

扩完容之后,_start,_finsh,_end_of_storage都已重新更新了位置,并且将原空间中的数据拷贝到新空间中,释放掉旧空间,但注意此时pos依然还在指向旧空间的位置,这就使pos成为了野指针.

画图来理解一下

所以既然pos没有变化,那我们解决方案就是:在扩完容之后重新更新一下pos即可,具体做法为:先保存pos的长度,扩完容之后,再将_start+pos即可.

void insert(iterator pos, const T& x) // 注意迭代器失效
		{
			assert(pos >= _start);
			assert(pos <= _finsh);

			if (_finsh == _end_of_storage)
			{
				size_t len = pos - _ start;//先保存一下长度
				reserve(capacity() == 0 ? 4 : capacity() * 2);//如果需要扩容,pos位置则会改变,需要更新
				pos = _start + len;
			}

			iterator end = _finsh - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			_finsh++;
		}

在那段测试代码中,我们插入了一个在p位置插入了一个30,但如果此时再插入一个40,便会再次出现问题,因为p的位置插入30时已经失效了,但有人会说我们不是更新了pos吗在扩容之后,我们仔细的看一下,pos只是一份形参!形参的改变不会影响实参,所以p并没有改变.

那可能就有人又说了,直接给pos加上引用不就可以了吗,这样pos更新,p也会跟着更新了。

这么做确实正确的解决了这个问题,但是如果下面这段代码该怎么处理呢?

insert(v.begin(),3);

这句代码是说想在begin()的位置插入一个3,但是由于begin()是传值返回,会形成一份临时拷贝数据,这个数据具有常性,而我们的就是普通的iterator&,权限不可以放大.这样又会造成了问题.

那我们在iterator前面加一个const不就可以了吗,就具有了常性,但加上之后扩容的时候我们该怎么修改这个p呢?这又是个问题.

所以正如以上所说:在p位置插入一个数据后,就不要再去使用这个p了,可能会失效.

erase()&&迭代器失效问题

erase()是删除某个位置的数据,思路也很简单,就是从pos+1位置开始,每次覆盖掉前一个位置.

	    //STL 规定erase返回删除位置的下一个位置的迭代器	
        iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finsh);

			iterator begin = pos + 1;
			while (begin < _finsh)
			{
				*(begin - 1) = = *begin; 
				++begin;
			}
			--_finsh;
            return pos; 
		}

虽然这个代码本身不涉及迭代器失效问题,但看下面一段代码:需要删除所有的偶数.

看代码

 

比如原来的数据是1,2,3,4.

 我们可以看到问题是出现在每次erase之后,它的后一个数据已经移动过来了,但是it依然++,相当于it跳过了一个数据直接访问下一个,所以正确的方法是:

 这样就成功解决了.

这里总结一句:insert/erase pos位置,不要直接访问pos,一定要更新!

直接访问,可能会有各种出乎意料的结果,这就是所谓的迭代器失效.

总代码如下:

#include<iostream>
using namespace std;
namespace hmylq
{
	template<class T>
	class vector
	{
	public:
		//vector的迭代器是一个原生指针
		typedef T* iterator;
		typedef const T* const_iterator;

		iterator begin()
		{
			return _start;
		}

		iterator end()
		{
			return _finsh;
		}

		/Construct and destory//
		vector()
			:_start(nullptr)
			,_finsh(nullptr)
			,_end_of_storage(nullptr)
		{
			;
		}
		vector(int n, const T& val = T())//T()是一个匿名对象,如string,会调用它的构造函数,内置类型如int,也有构造函数,默认为0.
			:_start(nullptr)
			, _finsh(nullptr)
			, _end_of_storage(nullptr) 
		{
			reserve(n);
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}


		template<class Inputlterator>

		//迭代器区间构造
		vector(Inputlterator first, Inputlterator last)
			:_start(nullptr)
			,_finsh(nullptr)
			,_end_of_storage(nullptr)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}


		vector(const vector<T>& v)//拷贝构造
			:_start(nullptr)
			,_finsh(nullptr)
			,_end_of_storage(nullptr)
		{
			//_start = new T[v.size()];//v.capacity也可以
			//memcpy(_start, v._start, sizeof(T) * v.size());
			//_finsh = _start + v.size();
			//_end_of_storage = _start + v.size();
			reserve(v.size());
			for (const auto &e : v)
			{
				push_back(e);
			}
		}
		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finsh, v._finsh);
			std::swap(_end_of_storage, v._end_of_storage);
		}

		//vector(const vector<T>& v)//拷贝构造
		//	:_start(nullptr)
		//	, _finsh(nullptr)
		//	, _end_of_storage(nullptr)
		//{
		//	vector<T> tmp(v.begin(), v.end());
		//	swap(tmp);
		//}

		vector<T>& operator= (vector<T> v)
		{
			swap(v);
			return *this;
		}

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

		///capacity///

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

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

		void reserve(size_t n)
		{
			if (n > capacity())
			{
				size_t sz = size();
				T* tmp = new T[n];
				if (_start)
				{
					memcpy(tmp, _start, sizeof(T) * sz);
					delete[] _start;
				}
 
				_start = tmp;
				_finsh = _start + sz ;
				_end_of_storage = _start + n;
			}
		}

		void resize(size_t n, const T& value = T())
		{
			if (n > capacity())
			{
				reserve(n);
			}
			if (n > size() )
			{
				while (_finsh < _start + n)
				{
					*_finsh = value;
					++_finsh;
				}
			}
			else
			{
				_finsh = _start + n;
			}
		}

		/access///

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

		/modify//	

		void push_back(const T& x)
		{
			if (_finsh == _end_of_storage)
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finsh = x;
			++_finsh;
		}


		void pop_back()
		{
			assert(_finsh > _start);
			--_finsh;
		}

		void insert(iterator pos, const T& x) // 注意迭代器失效
		{
			assert(pos >= _start);
			assert(pos <= _finsh);

			if (_finsh == _end_of_storage)
			{
				size_t len = pos - _start;//先保存一下长度
				reserve(capacity() == 0 ? 4 : capacity() * 2);//如果需要扩容,pos位置则会改变,需要更新
				pos = _start + len;
			}

			iterator end = _finsh - 1;
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x;
			_finsh++;
		}




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

			iterator begin = pos + 1;
			while (begin < _finsh)
			{
				*(begin - 1) == *begin; 
				++begin;
			}
			--_finsh;
		}
	private:
		iterator _start;//指向数据块的开始
		iterator _finsh;//指向有效数据的尾
		iterator _end_of_storage;//指向存储容量的尾
	};


}	

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

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

相关文章

【14】Docker network | bridge | host | none | container

目录 1、Docker 运行的基本流程为: 2、Docker0 3、Docker默认的三种网络模式 4、Docker的网络命令 5、网络模式 6、实例&#xff1a; 7、bridge模式 8、host模式 9、none模式 10、container模式 11、自定义网络 1、Docker 运行的基本流程为: 1.用户是使用 Docker Clie…

UDS诊断系列介绍10-28服务

本文框架1. 系列介绍1.1 28服务概述2. 28服务请求与应答2.1 28服务请求2.2 28服务正响应2.3 否定应答3. Autosar系列文章快速链接1. 系列介绍 UDS&#xff08;Unified Diagnostic Services&#xff09;协议&#xff0c;即统一的诊断服务&#xff0c;是面向整车所有ECU的一种诊…

session利用的小思路

session利用的小思路 前言 做题的时候经常考到session利用&#xff0c;常见的基本就两种&#xff0c;session文件包含和session反序列化&#xff0c;之前没有详细总结过&#xff0c;就写写吧。 session文件包含 php.ini session的相关配置 session.upload_progress.enabl…

15、ThingsBoard-自定义阿里云SMS规则节点

1、概述 一个物联网平台承载着很多设备的连接,当设备出现异常的时候,能够快速的通知到运维管理员是非常重要的,thingsboard提供了自定义配置邮箱,但是它对支持发送短信的不是很友好,都是国外的sms服务商,我反正是不用那个,在国内常见就是阿里、腾讯、华为、七牛常用的s…

CORBA,Common Object Request Broker Architecture 简介

CORBA&#xff0c;Common Object Request Broker Architecture 简介 1. 简介 CORBA&#xff08;Common ObjectRequest Broker Architecture&#xff0c;公共对象请求代理体系结构&#xff09;是由OMG组织&#xff08;OMG组织是一个国际性的非盈利组织&#xff0c;其职责是为应…

【17】Docker | CAdvisor_InfluxDB_Granfana | 成功安装

目录 1、查看目前docker容器的状态 2、三大组件 【1】、CAdvisor 【2】、InfluxDB 【3】、Granfana 3、用docker-compose安装三个组件 4、三大组件的登录 【1】浏览cAdvisor收集服务&#xff0c;http://ip:8080 【2】浏览influxdb存储服务&#xff0c;http://ip:8083 …

python日志处理模块讲解-loguru

说明&#xff1a; 本篇文章主要讲的是python日志模块loguru详解&#xff0c;感兴趣的同学赶快来看一看吧。 背景&#xff1a; 在部署一些定时运行或者长期运行的任务时&#xff0c;为了留存一些导致程序出现异常或错误的信息&#xff0c;通常会采用日志的方式来进行记录这些…

运放指标-压摆率SR

1. 压摆率SR 处理交流信号时&#xff0c;压摆率是运放器件重要的指标。其表示运放输出电压的转换速率。在高频信号时&#xff0c;若压摆率不够&#xff0c;则运放输出的信号会变形&#xff0c;导致不满足要求。其实际就是运放输出信号的分辨率&#xff0c;只有分辨率足…

机器学习中的数学基础(四):概率论

机器学习中的数学基础&#xff08;四&#xff09;&#xff1a;概率论4 概率论4.1 一些概念4.2 二维随机变量4.2.1 离散型4.2.2 连续型4.3 边缘分布4.3.1 离散型边缘分布4.3.2 连续型边缘概率密度4.4 期望4.4.1 一维期望4.4.2 二维期望4.5 马尔可夫不等式4.6 切比雪夫不等式在看…

Vue2.0开发之——组件数据共享-Eventbus(39)

一 概述 兄弟组件之间数据共享的方案—EventBusEventBus的使用步骤EventBus的使用示例 二 兄弟组件之间数据共享的方案—EventBus 在 vue2.x 中&#xff0c;兄弟组件之间数据共享的方案是EventBus。 三 EventBus的使用步骤 创建 eventBus.js 模块&#xff0c;并向外共享一…

个人项目部署在云服务器上以及购买云服务器后如何操作

一. 购买云服务器后简单的操作1.镜像: 镜像可以认为是云服务器的操作系统&#xff0c;选择什么镜像云服务器就安装对应的操作系统。云服务器操作系统主要分为两大类&#xff0c;即Linux和Windows. 本次说明在linux操作系统下进行项目的部署, 那么在选择镜像的时可以选择Linux镜…

linux系统中利用QT实现串口通信的方法

大家好&#xff0c;今天主要和大家分享一下&#xff0c;如何使用QT中的串口通信方法。 目录 第一&#xff1a;资源简介 第二&#xff1a;应用实例的具体实现 第三&#xff1a;程序运行效果 第一&#xff1a;资源简介 在开发板的资源中出厂系统中&#xff0c;默认已经配置了两…

ASP.NET Core 3.1系列(27)——Autofac使用JSON、XML配置文件

1、前言 很多IoC框架都支持以配置文件的形式实现接口和类的注册&#xff0c;Autofac当然也不例外。本文就来介绍一下如何利用JSON、XML等配置文件来实现接口和类的注册。 2、定义接口和类 这里搭建了一个简单的分层项目&#xff0c;如下图所示&#xff1a; Repository层代码…

Verilog HDL

一、基础语法 1. 基础知识 &#xff08;1&#xff09;逻辑值 逻辑0&#xff1a;低电平。 逻辑1&#xff1a;高电平。 逻辑X&#xff1a;未知&#xff0c;可能是高电平&#xff0c;也可能是低电平。 逻辑Z&#xff1a;高阻态&#xff0c;外部没有激励信号&#xff0c;是一…

读书笔记《深度学习与图像识别原理与实践 大白话讲解对小白易懂》2022-8-5

开始 目录前言1. 常见深度学习框架2. 图像分类算法2.1 传统类2.2 机器学习2.2.1 人工神经网络&#xff08;神经元&#xff09;2.2.2 卷积神经网络3. 目标检测算法3.1 分类定位&#xff08;单目标&#xff09;3.2 分类定位&#xff08;多目标&#xff0c;目标检测&#xff09;3.…

近端串扰NEXT和远端串扰的ADS仿真

目录 近端串扰NEXT和远端串扰FEXT 串扰仿真原理图 NEXT特征 减少NEXT的措施 FEXT特征 减少FEXT的措施 本文记录近阶段对近端串扰和远端串扰概念的理解。 经验法则&#xff1a;最大可容许串扰大约是信号摆幅的5%。 近端串扰NEXT和远端串扰FEXT 静态线上的靠近驱动源的一端…

CTF中常用的http知识点总结

目录 前提知识 请求头大全 响应头大全 请求方法大全 常见考点 从某ip访问 从某网站跳转 身份为admin才可以访问 从某某浏览器访问 靶场练习 [极客大挑战 2019]Http Become A Member 前提知识 请求头大全 Header解释示例Accept指定客户端可以接收的内容类型Accep…

逆向分析资料汇总

商务合作 2023年招聘 ​安全业务和软件业务(商务合作) 移动端漏洞或隐私合规检测 APP常见漏洞扫描器 ​移动端APP隐私合规检测 2023年逆向分析资料汇总 移动端漏洞/安全检测与隐私合规解决方案 Frida逆向分析基础 APP基于Frida脱壳 frida hook so导出或未导出函数的方法…

【写作能力提升】写作小白需要避免的五个写作误区和灵魂五问

“ 【写作能力提升】系列文章&#xff1a; 为什么建议你一定要学会写作? 手把手教你快速搞定4个职场写作场景 5种搭建⽂章架构的⽅法”免费赠送! ”一、前言 Hello&#xff0c;我是小木箱&#xff0c;今天主要分享的内容是: 写作小白需要避免的五个写作误区和灵魂五问。 二、 …

E. The Human Equation(前缀和与差分数组)

嘤&#xff0c;总算过了 题目大意&#xff1a;可以从一个序列中按照顺序&#xff08;可间断&#xff09;选出一堆数&#xff0c;选出的这些数可以做以下操作&#xff1a; 奇数位置&#xff0b;1&#xff0b;1&#xff0b;1&#xff0c;偶数位置−1- 1−1偶数位置&#xff0b;1…