C++ vector 模拟实现

vector的底层也是一个动态数组,他与 string 的区别就是,string 是专门用来存储字符类数据的,为了兼容C语言,使用C语言的接口,在string的动态数组内都会都开一块空间用来存 \0  ,而vector则不会。

首先我们要知道的就是,vector是一个类模板,他的内存管理是使用空间配置器,我们就简单点直接使用new和delete来管理内存。其他的一些接口什么的,vector与string差别不大,vector有的string基本都有,实现起来我们也挑一些重点来实现。而对于vector的成员变量,我们则不是采用string的一个指针两个整型的实现,而是使用三个迭代器,_start,_finish,_end_of_storage,这是参考Linux的库的实现,而我们实现vector时迭代器就直接使用的原生的指针。

_start 迭代器是数组的开头,_finish是最后一个数据的下一个位置,_end_of_storage指向的是申请的内存块的下一个位置。

使用三个迭代器的实现,在后续需要挪数据的时候会很方便。

无参构造函数与析构

无参构造不用说,很简单也没什么含量,都初始化为空指针就行了

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

完成了无参构造函数之后,我们先去实现其他的一些基本的函数,最后再来解决其他的三个构造

析构函数就是释放_start指向的空间

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

size与capacity和判空

size与capacity的实现其实很简答,我们之前学过数组中同类型指针相减,得到的是这两个指针之间的该类型的数据的个数,于是我们就可以用这三个迭代器来得到size和capacity

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

		//capacity
		size_t capacity()const
		{
			return _end_of_storage - _start;
		}
        
        //判空
	    bool empty()const
		{
			return _start == _finish;
		}

方括号的重载与at

vector由于还是动态数组实现的,所以我们还是支持直接是用下标访问的方式,所以方括号重载的实现还是有必要的,方括号重载的越界检查我们直接使用assert暴力检查,库里面实现的就是assert。同时at的功能虽然和方括号一样,但是他和方括号的区别在于 : 1 方括号是运算符重载,而at是普通的成员函数。2 越界的检查不一样,方括号的越界检查使用assert,而at是抛异常,我们可使用vs的库试一下

由于我们还没学习异常。也直接用assert判断就行了。同时,由于有普通对象和const对象的访问,返回的引用的权限不一样,所以我们需要实现两个版本

		//普通对象  [ ]
		reference operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}

		//const对象 [ ] 
		const_reference operator[](size_t pos)const
		{
			assert(pos < size());
			return _start[pos];
		}

		//普通对象 at
		reference at(size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}
		//const对象 at
		const_reference at(size_t pos)const
		{
			assert(pos < size());
			return _start[pos];
		}

对于下标为pos位置的数据的访问,我们可以直接使用 _start[pos]来访问,等价于*(_start+pos)

由于vector也就是普通的数组的比较是没有意义的,所以一般只重载[ ] 和=

swap

为什么要实现swap呢?

首先vector的库里面是有swap的,用于交换两个对象的数据,但是算法库里面也有swap,

他们的区别在于,算法库里的swap有三次拷贝构造,如果要交换的是两个自定义类型的对象,代价很大,而我们的类里面的成员函数swap,我们可以使用算法库的swap对对象里的成员变量进行交换,避免了深拷贝。


		//swap
		void swap(rvector<T>& v)
		{
			//要使用算法库里面的swap要指定命名空间域,否则会优先匹配当前命名空间的swap函数
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_end_of_storage, v._end_of_storage);
		}

同时我们实现swap也是为了下面的拷贝构造和赋值重载做准备

赋值重载

vector的赋值重载显然是深拷贝,我们有一种很简洁的代码写法来完成这个深拷贝,就是利用拷贝构造,我们可以传值传参,将我们的this与生成的临时的形参进行交换

而当v2不为空时也是一样的,无非就是将这临时对象与当前对象的空间换了一下,出作用域之后,临时对象指向的空间就被释放了,不会影响外面等号右边的变量。

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

push_back和pop_back

尾插的实现就很简单了,首先检查是否扩容,然后直接在 _finish位置插入数据并且更新 _finish就行了。而尾删的操作则是判断是否为空,然后直接 -- _finish 就行了。

		void push_back(const_reference x)
		{
			if (_finish == _end_of_storage)
			{
				size_t newcapacity = capacity() == 0 ? 4 : 2 * capacity();
				reserve(newcapacity);
			}
			*_finish = x;;
			++_finish;

            //insert(beign(),x);//复用insert
		}

		void pop_back()
		{
			assert(size()>0);
			--_finish;
		}

拷贝构造

我们需要考虑当 T 为需要深拷贝的自定义类型这种情况,要做深层次的深拷贝。

如果数据只是内置类型或者只需要浅拷贝的自定义类型的话,我们就只需要memcpy就行了,但是如果是需要深拷贝的数据的话,比如 vector<vector<int>> ,数据类型是 vector<int> ,需要深拷贝,如果只是简单的memcpy

	//如果只是内置类型或者浅拷贝自定义类型
			//先扩容
			reserve(v.capacity());//函数内部会修改_end_of_storage
			//直接内存拷贝
			memcpy(_start,v._start,v.size()*sizeof(T));
			_finish = _start + v.size();

我们发现这时候两个对象虽然是不同的空间,但是里面的 vector<int> 数据指向的空间是同一块,这其实还没有达到我们想要的深拷贝。

对于我们来说目前也没有什么更好的解决方法,可以利用上面实现的赋值重载,赋值重载去对每一个数据都深拷贝,


		//拷贝构造
		vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			//先扩容
			reserve(v.capacity());
			//或者先扩size,再使用赋值重载进行深拷贝
			_finish = _start + v.size();
			for (size_t i = 0; i < size(); ++i)
			{
				_start[i] = v[i];
			}
		}

同时,拷贝构造完成之后,不管再多层,他们都有对应的拷贝构造,但是可能有人会对这里的赋值重载产生疑惑,认为赋值重载的前提是实现拷贝构造,而我们这里的拷贝构造又用了赋值重载,会不会递归死循环了,其实是不会的,就比如这里的vector<vector<int>> ,会生成 vector<int> 和vector<vector<int>>的模板,而在vector<vector<int>>中拷贝构造的赋值重载是属于 vector<int>实例出来的赋值重载,而这个赋值重载调用的是 int 的拷贝构造,也就是内置类型的传值调用,所以这里的逻辑是没有问题的,拷贝构造和赋值重载的互相调用会在内置类型(或者浅拷贝的自定义类型)停下来,然后回归。总之就是 赋值重载的深拷贝是调用 T 类型的拷贝构造

reserve

reserve函数与string一样,只会扩容,不会缩容,同时不会改变size。但是扩容之后需要解决将原空间的数据拷贝到新空间的问题,我们可以直接使用指针来进行边界控制。

但是同时,我们也要考虑 数据类型是需要深拷贝的自定义类型的情况,比如 vector<vector<int>> ,如果只是单纯的memcpy,也会造成浅拷贝的问题,也就是原空间的 vector<int>对象与新空间的vector<int>对象指向相同的内存空间,当时放掉原空间之后,新空间指向的内存也被释放掉了。

解决方法还是跟上面的深拷贝一样,使用数据自身的赋值重载来拷贝,虽然效率低了点,但是能确保不会出问题。

迭代器相关

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

		iterator end()
		{
			return _finish;
		}

		const iterator cbegin()
		{
			return _start;
		}

		const iterator cend()
		{
			return _finish;
		}

insert和erase

		iterator insert(iterator pos, const_reference x=val_type())//缺省值
		{
			//检查越界
			assert(pos >= _start);
			assert(pos <= _finish);//第一次插入pos==_finish  同时要支持尾插
			
			//检查是否扩容
			if (_finish == _end_of_storage)
			{
				//扩容之后pos就失效了,所以要记录pos的相对位置
				size_t pos_index = pos - _start;
				size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
				reserve(newcapacity);

				//更新pos
				pos = _start + pos_index;
			}
			
			//挪动数据 从后往前,往后挪数据
			iterator end = _finish;
			while (end != pos)
			{
				*end = *(end - 1);
				--end;
			}
			//更新_finish
			++_finish;

			//插入新数据
			*pos = x;

			//返回插入的数据的迭代器
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			//挪数据覆盖
			iterator begin = pos+1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				begin++;
			}
			//返回删除的下一个数据
			return pos;
		}

insert和erase都要注意迭代器失效的问题,比如insert的时候,如果扩容了,那么传过来的pos指向的空间就可能已经被释放了,所以要更新pos指向新空间的该元素的位置,同时,正常来说insert之后,函数外面的pos就不能用了,因为我们是传值的,函数内更新了pos不会影响函数外的pos,这时候如果我们要更新函数外面的pos,可以insert返回心得pos的值。  erase也是一样的,因为erase可能是删除最后一个数据,这时候pos的位置就是_finish 了,虽然pos没有出我们开辟的内存空间,但是在我们看来,它指向finish就已经算是越界了,不能继续使用,同样,要在函数外面更新pos也是用erase的返回值更新。

resize

		//resize
		void resize(size_t n, val_type x = val_type())//缺省参数
		{
			//扩容检查
			if (n > capacity())
			{
				reserve(n);
			}
			//是否需要加数据
			if (n > size())
			{
				while (_finish < _start + n)
				{
					*_finish=x;
					++_finish;
				}
			}
		}

剩下的两个构造函数

一个是迭代器区间来构造初始化。为什么这里的构造函数是模板呢? 因为我们可能不是使用vector<T>的数据结构来初始化我们的对象,也可能是用其他的数据结构比如链表等的数据来初始化vector,这也是可以的,只要传迭代器区间就可以

		template<typename InputIterator>
		vector(InputIterator begin, InputIterator end)
		{
			//push_back就行了
			while (begin != end)
			{
				push_back(*begin);
				++begin;
			}
		}

还有n个val初始化,这里我们需要注意的是,我们不能做到跟库里一样,n的参数类型要用int,为什么呢? 

		//n个val初始化
		vector(int n, T val)
		{
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

比如我们可以这样构造 vector<int> v(10,5) 这时候,如果我们的n的类型是size_t 的话,由于编译器会把 10 和5 识别为 int 类型,这时候传给构造函数的两个参数都是 int ,如果去匹配这个构造函数的话, n 还需要进行整型提升。而如果去匹配上面的迭代器区间的模板,因为模板的只有一个参数类型,两个迭代器区域间都是一样的类型,所以不用进行隐式类型转换,因此他与迭代器区间的构造函数更加匹配,编译器就会去调用迭代器区间的构造函数,这时候就会出问题。 有两种解决方法,一种就是我们上面写的,但是这样做就与库不一致了 。另外一种就是我们不要使用原生的指针作为迭代器的类型,这种方法我们目前还不会用

完整代码


namespace MY_vector
{
	template<typename T>
	class vector
	{
	public:
		typedef T val_type;
		typedef T& reference;
		typedef const T& const_reference;
		typedef T* iterator;

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

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

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

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

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

		//尾插
		void push_back(val_type x)
		{ /*
			//检查扩容
			if (_finish== _finish)
			{
				size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
				reserve(newcapacity);
			}
			//插入数据
			*_finish = x;
			++_finish;*/
			insert(begin(), x);
		}

		//尾删
		void pop_back()
		{
			assert(!empty());
			--_finish;
		}

		iterator insert(iterator pos, const_reference x=val_type())//缺省值
		{
			//检查越界
			assert(pos >= _start);
			assert(pos <= _finish);//第一次插入pos==_finish  同时要支持尾插
			
			//检查是否扩容
			if (_finish == _end_of_storage)
			{
				//扩容之后pos就失效了,所以要记录pos的相对位置
				size_t pos_index = pos - _start;
				size_t newcapacity = (capacity() == 0 ? 4 : 2 * capacity());
				reserve(newcapacity);

				//更新pos
				pos = _start + pos_index;
			}
			
			//挪动数据 从后往前,往后挪数据
			iterator end = _finish;
			while (end != pos)
			{
				*end = *(end - 1);
				--end;
			}
			//更新_finish
			++_finish;

			//插入新数据
			*pos = x;

			//返回插入的数据的迭代器
			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			//挪数据覆盖
			iterator begin = pos+1;
			while (begin < _finish)
			{
				*(begin - 1) = *begin;
				begin++;
			}
			//返回删除的下一个数据
			return pos;
		}

		//[ ] 重载
		reference operator[](size_t pos)
		{
			assert(pos < size());
			return _start[pos];
		}

		const_reference operator[](size_t pos)const
		{
			assert(pos < size());
			return _start[pos];
		}
		
		//at
		reference at(size_t pos)
		{
			assert(pos<size());
			return _start[pos];
		}

		const_reference at(size_t pos)const
		{
			assert(pos < size());
			return _start[pos];
		}

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

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

		//扩容 reserve
		void reserve(size_t n)
		{
			if (n > capacity())
			{
				iterator tmp = new val_type[n];
				//记录原来的数据个数,用来更新_finish
				size_t oldsize = size();
				//判断是否需要挪数据
				if(_start) //_start!=nullptr
				{
					for (size_t i = 0; i < oldsize; ++i)
					{
						tmp[i] = _start[i];
					}
					//释放原空间 
					delete[]_start;
				}
				//更新迭代器
				_start = tmp;
				_finish = _start + oldsize;
				_end_of_storage = _start + n;
			}
		}

		//拷贝构造
		vector(const vector<T>& v)
			:_start(nullptr)
			,_finish(nullptr)
			,_end_of_storage(nullptr)
		{
			//先扩容
			reserve(v.capacity());
			//或者先扩size,再使用赋值重载进行深拷贝
			_finish = _start + v.size();
			for (size_t i = 0; i < size(); ++i)
			{
				_start[i] = v[i];
			}
		}

		//resize
		void resize(size_t n, val_type x = val_type())//缺省参数
		{
			//扩容检查
			if (n > capacity())
			{
				reserve(n);
			}
			//是否需要加数据
			if (n > size())
			{
				while (_finish < _start + n)
				{
					*_finish=x;
					++_finish;
				}
			}
		}

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

		iterator end()
		{
			return _finish;
		}

		const iterator cbegin()
		{
			return _start;
		}

		const iterator cend()
		{
			return _finish;
		}

		template<typename InputIterator>
		vector(InputIterator begin, InputIterator end)
		{
			//push_back就行了
			while (begin != end)
			{
				push_back(*begin);
				++begin;
			}
		}

		//n个val初始化
		vector(int n, T val)
		{
			for (int i = 0; i < n; ++i)
			{
				push_back(val);
			}
		}

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



	//测试一维
	void test1()
	{
		vector<int>v1;
		v1.push_back(1);
		v1.push_back(2);
		v1.push_back(3);
		v1.push_back(4);
		v1.push_back(5);
		vector<int> v2(v1);
		vector<int>v3;
		v3.resize(8);
		v3 = v1;
		v3.reserve(10);
		v2.pop_back();
		v2.pop_back();
		v2.pop_back();
		v2.pop_back();
		//v2.pop_back();
		//v2.pop_back();
		//v2.pop_back();
		v3.insert(v3.begin(), 6);
		v3.insert(v3.begin(), 6);
		v3.insert(v3.end()-1, 6);
		v3.insert(v3.end() - 1, 6);
		v3.insert(v3.end(), 6);
		v3.insert(v3.end(), 6);

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

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

	//测试二维
	void test2()
	{
		vector<int>v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);

		vector<vector<int>> vv1;
		vv1.push_back(v);
		vv1.push_back(v);
		vv1.push_back(v);
		vv1.push_back(v);
		vv1.push_back(v);

		vector<vector<int>> vv2(vv1);
		vv2.pop_back();
		vv2.pop_back();
		
		vector<vector<int>> vv3;
		vv3.resize(2);
		vv3.reserve(6);
		vv3.push_back(v);
		vv3.push_back(v);
		vv3 = vv1;
		vv3.push_back(v);
		vv3.push_back(v);

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

		cout << "vv2" << endl;

		for (auto ev : vv2)
		{
			for (auto e : ev)
			{
				cout << e << " ";
			}
			cout << endl;
		}
		cout << "vv3" << endl;

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



	}
}

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

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

相关文章

文本生成流程图 泰酷啦 Excalidraw Mermaid Obsidian

前言 介绍一个很酷的工具&#xff0c;Mermaid to Excalidraw 。作用是用代码生成流程图。 Mermaid 是一款强大的、轻量级的文本到图表的转换工具&#xff0c;它允许用户使用简单的Markdown风格的语法编写文本描述&#xff0c;然后通过JavaScript引擎将其转换成美观的图表。Mer…

YOLOv5改进 | 注意力机制 | 添加双重注意力机制 DoubleAttention【附代码/涨点能手】

&#x1f4a1;&#x1f4a1;&#x1f4a1;本专栏所有程序均经过测试&#xff0c;可成功执行&#x1f4a1;&#x1f4a1;&#x1f4a1; 在图像识别中&#xff0c;学习捕捉长距离关系是基础。现有的CNN模型通常通过增加深度来建立这种关系&#xff0c;但这种形式效率极低。因此&…

在VSCode 中增加文件与文件夹的可辨识度

今天重新打开VSCode&#xff0c;打算新建一个项目做测试&#xff0c;看到VSCode中的文件与文件夹很不容易辨认&#xff0c;有时候容易导致一些误操作&#xff0c;需要做一些配置来改变。 效果图&#xff1a; 只需要做简单的2步就可以了。 1、安装插件 ⑴ 打开VSCode的扩展搜索并…

服务器的远程桌面无法连接,服务器远程桌面无法连接问题处理教程

服务器的远程桌面无法连接&#xff0c;服务器远程桌面无法连接问题处理教程。 一、问题概述 服务器远程桌面无法连接是日常运维中常见的问题之一。它可能由多种原因造成&#xff0c;如网络问题、服务器配置错误、远程桌面服务未启动等。本教程将指导您逐步排查并解决这些问题。…

yolov10 瑞芯微RKNN、地平线Horizon芯片部署、TensorRT部署,部署工程难度小、模型推理速度快

特别说明&#xff1a;参考官方开源的yolov10代码、瑞芯微官方文档、地平线的官方文档&#xff0c;如有侵权告知删&#xff0c;谢谢。 模型和完整仿真测试代码&#xff0c;放在github上参考链接 模型和代码。 yolov8、v9还没玩热乎&#xff0c;这不yolov10又来了&#xff0c;那么…

定点化和模型量化(三)

量化解决的是训练使用的浮点和运行使用的硬件只支持定点的矛盾。这里介绍一些实际量化中使用到的工具。 SNPE简介 The Snapdragon Neural Processing Engine (SNPE)是高通骁龙为了加速网络模型设计的框架。但它不只支持高通&#xff0c;SNPE还支持多种硬件平台&#xff0c;AR…

软件即服务-SaaS

目录 1. SaaS成熟度模型 2. SaaS应用平台 3. SaaS应用实现层次 4. 多租户技术 5. 可配置性 5.1 业务构件 5.2 数据可配置 5.2.1 定制字段 5.2.2 预分配字段 5.2.3 名称值对 5.3 功能可配置 5.3.1 业务构件设计 5.3.2 功能包设计 5.3.3 销售包设计…

Postman快捷功能-批量断言与快速查询替换

大家好&#xff0c;在我们日常的接口测试工作中&#xff0c;经常需要对接口返回的数据进行断言&#xff0c;以确保接口的正确性。当接口数量较多时&#xff0c;逐个编写断言语句会变得非常繁琐。此外&#xff0c;在接口测试过程中&#xff0c;我们还可能需要频繁地查找和替换某…

遇到软件测试职业瓶颈,如何突破

作为职场人&#xff0c;遇到发展瓶颈是在所难免的&#xff0c;无论是晋升受限、技能升级缓慢&#xff0c;还是工作激情的丢失&#xff0c;这些挑战都可能让人感到挫败。但是&#xff0c;积极应对&#xff0c;你就可能找到那扇通向新机遇的窗。 1. 自我评估 识别问题 是缺乏技能…

内存卡频频提示格式化?数据恢复全攻略

内存卡提示需要格式化 在数字时代&#xff0c;内存卡作为我们存储数据的常用设备&#xff0c;广泛应用于手机、相机、无人机等多种设备中。然而&#xff0c;不少用户在使用过程中会突然遭遇一个令人头疼的问题——内存卡提示需要格式化。这一提示往往伴随着数据的丢失风险&…

MobaXterm下载虚拟机SSH链接超时解决(保姆级踩坑)

文章目录 为啥要用MobaXtermMobaXterm下载打开虚拟机ssh链接ssh连接失败排查linux配置windows配置 到这了&#xff0c;什么都干了&#xff0c;怎么还不成功&#xff1f; 更多相关内容可查看 在一个阳光明媚的下午&#xff0c;开启了无限踩坑的旅程 为啥要用MobaXterm 作为小编…

【LeetCode】37.解数独

解数独 题目描述&#xff1a; 编写一个程序&#xff0c;通过填充空格来解决数独问题。 数独的解法需 遵循如下规则&#xff1a; 数字 1-9 在每一行只能出现一次。数字 1-9 在每一列只能出现一次。数字 1-9 在每一个以粗实线分隔的 3x3 宫内只能出现一次。&#xff08;请参…

gif帧数修改怎么操作?一键掌握GIF帧数修改技巧!

gif帧数修改怎么操作&#xff1f;在数字化信息爆炸的时代&#xff0c;GIF动图因其生动有趣的特性而备受广大网友喜爱。然而&#xff0c;很多时候我们可能会遇到GIF动图帧数过多或过少&#xff0c;导致动画效果不尽如人意的情况。那么&#xff0c;如何对GIF动图的帧数进行修改呢…

前端项目开发,3个HTTP请求工具

这一小节&#xff0c;我们介绍一下前端项目开发中&#xff0c;HTTP请求会用到的3个工具&#xff0c;分别是fetch、axios和js-tool-big-box中的jsonp请求。那么他们都有哪些小区别呢&#xff1f;我们一起来看一下。 目录 1 fetch 2 axios 3 js-tool-big-box 的 jsonp 请求 …

企业网络的“瑞士军刀”:探索“一端多能”设备的多面性

在数字化时代&#xff0c;企业网络需求的复杂性和多样性不断增长&#xff0c;传统的单一功能网络设备已难以满足这些需求。企业需要一种集多种功能于一身的“一端多能”网络设备&#xff0c;以应对各种网络环境和业务需求&#xff0c;就像是一把多功能、灵活、可靠的瑞士军刀&a…

windows上安装miniforge和jupyterlab

1&#xff0c;下载miniforge3 GitHub - conda-forge/miniforge: A conda-forge distribution. 下载下来后傻瓜式安装就可以了 配置环境变量&#xff0c;在系统环境变量的path添加下列就行了&#xff0c;根据自己的路径修改 2&#xff0c;创建虚拟环境 conda create -n test …

红蓝对抗提权篇之一文看懂提权

一、计算机中的权限 1.1 不同的权限系统 权限在不同的应用中有着不同的分类&#xff0c;与安全相关的大致上我们分为&#xff1a; 匿名访问权限 来宾权限 用户权限 管理员权限 系统权限 不同的权限对应的权力各不相同&#xff0c;我们对自己电脑一般是用户权限和管理员权限。…

文件IO(二)

文件IO&#xff08;二&#xff09; 标准IO缓冲类型全缓冲行缓冲不缓冲 打开文件fopen 操作文件按字符读写(fgetc fputc)按行读写&#xff08;fgets fputs&#xff09;按块&#xff08;对象&#xff09;读写&#xff08;fread fwrite&#xff09;按格式化读写&#xff08;fscanf…

【CALayer-时钟练习-旋转 Objective-C语言】

一、好,接下来呢,我们要让它旋转出来, 1.让它先旋转起来啊,这根秒针,让它先转着, 把之前的代码复制粘贴一份,改个名字,叫:07-时钟练习(旋转) 旋转的话,我现在应该让它,一秒钟,旋转一次,一秒钟,旋转一次, 那么,这个时候,我们应该怎么样去做, 我现在这个是…

ARM-V9 RME(Realm Management Extension)系统架构之系统能力的执行隔离

安全之安全(security)博客目录导读 目录 一、执行隔离 1、安全状态 2、安全模型 本博客探讨 RME 所需的系统能力&#xff0c;以保证 Arm CCA 对于 Realms 的安全性和隔离特性。 一、执行隔离 1、安全状态 RME 系统支持以下安全状态&#xff1a; 非安全 (Non-secure)安全…