【STL】vector模拟实现

news2024/11/24 20:39:08

vector模拟实现

  • 一、vector函数接口总览
  • 二、vector当中的成员介绍
  • 三、list模拟实现
    • 1、默认成员函数
      • (1)构造函数1
      • (2)构造函数2
      • (3)构造函数3
    • 2、拷贝构造函数
      • (1)写法一:老式写法
      • (2)写法二:现代写法
    • 3、赋值运算符重载函数
      • (1)写法一:传统写法
      • (2)写法二:现代写法
    • 4、析构函数
  • 四、迭代器相关函数
    • 1、begin和end
    • 2、const对象的begin和end
    • 3、使用演示
  • 五、容器和大小相关函数
    • 1、size和capacity
    • 2、reserve
      • 细节问题1、在进行操作之前需要提前记录当前容器当中有效数据的个数。
      • 细节问题2、拷贝容器当中的数据时,不能使用memcpy函数进行拷贝。
    • 3、resize
    • 4、empty
  • 六、修改容器内的相关函数
    • 1、push_back
    • 2、pop_back
    • 3、insert
    • 4、erase
    • 5、swap
  • 七、访问容器相关函数
    • 1、operator[]
    • 2、const operator[]


一、vector函数接口总览

namespace JRH
{
	// vector
	template<class T>
	class vector
	{
	public:
		// 1、默认的函数
		vector(); // 构造函数1
		vector(size_t n, const T& val); // 构造函数2
		template<class InputIterator>
		vector(InputIterator first, InputIterator last); // 构造函数3
		vector(const vector<T>& vt); // 拷贝构造
		vector<T>& operator=(const vector<T>& vt); // 运算符赋值重载函数
		~vector(); // 析构函数

		// 2、迭代器相关函数
		iterator begin(); // 头
		iterator end(); // 尾
		const_iterator begin() const; // const头
		const_iterator end() const; // const尾

		// 3、容器相关的函数
		size_t size() const; // 大小
		size_t capacity() const; // 容量
		void reserve(size_t n); // 保持容量
		void resize(size_t n, const T& val = T()); // 扩容
		bool empty() const; // 判空

		// 4、修改容器内容相关函数
		void push_back(const T& x);
		void pop_back();
		void insert(iterator pos, const T& x);
		iterator erase(iterator pos);
		void swap(vector<T>& v);

		// 5、访问容器相关函数
		T& operator[](size_t i);
		const T& operator[](size_t i)const;
	private:
		iterator _start;        //指向容器的头
		iterator _finish;       //指向有效数据的尾
		iterator _endofstorage; //指向容器的尾
	};
}

二、vector当中的成员介绍

在vector当中有三个成员变量_start、_finish、_endofstorage。

在这里插入图片描述

_start指向容器的头,_finish指向容器当中有效数据的尾,_endofstorage指向整个容器的尾。

三、list模拟实现

1、默认成员函数

(1)构造函数1

首先支持一个无参的构造函数,我们只需要将其三个变量都定为nullptr即可。

		vector() // 构造函数1
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorge(nullptr)
		{}

(2)构造函数2

vector还支持构造这样一种容器,该容器当中含有n个值为val的数据。对于该构造函数,我们可以先使用reserve函数将容器容量先设置为n,然后使用push_back函数尾插n个值为val的数据到容器当中即可。

		vector(size_t n, const T& val) // 构造函数2
			:_start(nullptr)
			,_finish(nullptr)
			,_endofstorage(nullptr)
		{
			reserve(n); // 调用下面写的reverse函数将其空间先定义为n个
			// 尾插n个val值进入到vector中
			for (size_t i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

注意

  1. 这个reverse函数有大用,我们要开好刚好的n个的空间,因为避免调用push_back函数时需要增容多次,导致效率降低。
  2. 该构造函数还需要实现两个重载函数(int和long)。
		vector(int n, const T& val) // 构造函数2 -- 重载int
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n); // 调用下面写的reverse函数将其空间先定义为n个
			// 尾插n个val值进入到vector中
			for (int i = 0; i < n; i++)
			{
				push_back(val);
			}
		}
		vector(long n, const T& val) // 构造函数2 -- 重载long
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(n); // 调用下面写的reverse函数将其空间先定义为n个
			// 尾插n个val值进入到vector中
			for (long i = 0; i < n; i++)
			{
				push_back(val);
			}
		}

(3)构造函数3

vector还支持使用一段迭代器区间进行对象的构造。因为该迭代器区间可以是其他容器的迭代器区间,也就是说该函数接收到的迭代器的类型是不确定的,所以我们这里需要将该构造函数设计为一个函数模板,在函数体内将该迭代器区间的数据一个个尾插到容器当中即可

		template<class InputIterator>
		vector(InputIterator first, InputIterator last) // 构造函数3
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			// 将迭代器中的所有区间的值全部都放进vector中
			while (first != last)
			{
				// this->push_back(*first);
				push_back(*first);
				first++;
			}
		}

2、拷贝构造函数

(1)写法一:老式写法

先开辟一块与该容器大小相同的空间,然后将该容器当中的数据一个个拷贝过来即可,最后更新_finish和_endofstorage的值即可。

		vector(const vector<T>& vt) // 拷贝构造
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			// 先开一块相同大小的空间
			_start = new T[vt.capacity()];
			for (size_t i = 0; i < vt.size(); i++) // 一个个拷贝过去
			{
				_start[i] = vt[i];
			}
			_finish = _start + vt.size(); // 有效数据的尾
			_endofstorage = _start + vt.capacity(); // 整个数组的尾
		}

注意: 将容器当中的数据一个个拷贝过来时不能使用memcpy函数,当vector存储的数据是内置类型或无需进行深拷贝的自定义类型时,使用memcpy函数是没什么问题的,但当vector存储的数据是需要进行深拷贝的自定义类型时,使用memcpy函数的弊端就体现出来了。例如,当vector存储的数据是string类的时候。

在这里插入图片描述
并且vector当中存储的每一个string都指向自己所存储的字符串。
在这里插入图片描述

如果此时我们使用的是memcpy函数进行拷贝构造的话,那么拷贝构造出来的vector当中存储的每个string的成员变量的值,将与被拷贝的vector当中存储的每个string的成员变量的值相同,即两个vector当中的每个对应的string成员都指向同一个字符串空间。

在这里插入图片描述

这结果是肯定错误的,因为都指向同一块空间了,假如销毁原字符串用memcpy拷贝构造的函数也销毁了,那么我们看一下代码是怎么实现的:
在这里插入图片描述

代码中看似是使用普通的“=”将容器当中的数据一个个拷贝过来,实际上是调用了所存元素的赋值运算符重载函数,而string类的赋值运算符重载函数就是深拷贝,所以拷贝结果是这样的:

在这里插入图片描述

  • 如果vector当中存储的元素类型是内置类型(int)或浅拷贝的自定义类型(Date),使用memcpy函数进行进行拷贝构造是没问题的,但如果vector当中存储的元素类型是深拷贝的自定义类型(string),则使用memcpy函数将不能达到我们想要的效果。

(2)写法二:现代写法

现代写法比较简单且暴力,直接一个for循环将其一个个放到this指针的push_back中即可,也就是一个个尾插过来即可。
在使用范围for对容器v进行遍历的过程中,变量e就是每一个数据的拷贝,然后将e尾插到构造出来的容器当中。就算容器v当中存储的数据是string类,在e拷贝时也会自动调用string的拷贝构造(深拷贝),所以也能够避免出现与使用memcpy时类似的问题。

		vector(const vector<T>& vt) // 拷贝构造 -- 现代写法
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			reserve(vt.capacity()); // 开辟一段vt等大的空间
			for (auto e : vt)
			{
				push_back(e); // 将容器vt中的数据一个个尾插进来
			}
		}

3、赋值运算符重载函数

(1)写法一:传统写法

首先判断是否是给自己赋值,若是给自己赋值则无需进行操作。若不是给自己赋值,则先开辟一块和容器v大小相同的空间,然后将容器v当中的数据一个个拷贝过来,最后更新_finish和_endofstorage的值即可
同样也不能用memcpy函数进行拷贝,其原理与上面的拷贝构造函数是一样的。

		vector<T>& operator=(const vector<T>& vt) // 运算符赋值重载函数 -- 传统写法
		{
			if (this != &vt) // 不能自己给自己赋值
			{
				delete[] _start; // 释放原来的空间
				_start = new T[vt.capacity()]; // 开辟一块与vt等大的空间
				for (size_t i = 0; i < vt.size(); i++)
				{
					_start[i] = vt[i]; // 将vt里面的数据一个个拷贝过来
				}
				_finish = _start + vt.size(); // 有效数据的大小
				_endofstorage = _start + vt.capacity(); // 整个数组的大小
			}
			return *this; // 支持连续赋值
		}

(2)写法二:现代写法

首先在右值传参时并没有使用引用传参,因为这样可以间接调用vector的拷贝构造函数,然后将这个拷贝构造出来的容器v与左值进行交换,此时就相当于完成了赋值操作,而容器v会在该函数调用结束时自动析构。

赋值运算符重载的现代写法也是进行的深拷贝,只不过是调用的vector的拷贝构造函数进行的深拷贝,在赋值运算符重载函数当中仅仅是将深拷贝出来的对象与左值进行了交换而已。

		vector<T>& operator=(const vector<T> vt) // 运算符赋值重载函数 -- 现代写法
		{
			std::swap(vt); // 交换这两个值
			return *this; // 支持连续赋值
		}

4、析构函数

析构函数很简单,但有细节,是析构函数首先要判断是不是空指针,不可释放空指针,非空指针后先释放原空间,再将三个变量置空即可。

		~vector() // 析构函数
		{
			if (_start != nullptr) // 避免对空指针进行曹组
			{
				delete[] _start; // 释放空间
				_start = nullptr; 
				_finish = nullptr;
				_endofstorage = nullptr;
			}
		}

四、迭代器相关函数

我们的迭代器就是容器当中所存储数据类型的指针,我们首先定义两个迭代器,一个是iterator,另一个是const_iterator,我们看一下下面的定义:

		typedef T* iterator;
		typedef const T* const_iterator;

1、begin和end

begin()就是返回该vector的首元素的地址,end()返回容器当中有效数据的下一个数据的地址。我们看一下下面的代码:

		iterator begin() // 头
		{
			return _start; // 返回头元素地址
		}
		iterator end() // 尾
		{
			return _finish; // 返回有效数据的下一个元素地址
		}

2、const对象的begin和end

const对象的begin和end函数是和上面一样的,只不过重载了上面的两个函数,我们直接显示代码即可:

		const_iterator begin() const // const头
		{
			return _start; // 返回头元素地址
		}
		const_iterator end() const // const尾
		{
			return _finish; // 返回有效数据的下一个元素地址
		}

3、使用演示

第一种我们可以用迭代器iterator来进行演示:

		for (size_t i = 0; i < v.size(); ++i)
		{
			std::cout << v[i] << " ";
		}
		std::cout << std::endl;

第二种我们用for循环来进行演示,因为for循环可以用的情况下也就是底层的是迭代器也可以进行使用。

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

五、容器和大小相关函数

1、size和capacity

vector当中三个成员遍历各自的指向,我们可以很容易得出当前容器中的有效数据个数和最大容量。
在这里插入图片描述
size()就是已用空间的大小,也就是_finish-_start;capacity()就是总空间的大小,也就是_endofstorage-_start。

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

2、reserve

  1. 当n大于对象当前的capacity时,将capacity扩大到n或大于n。
  2. 当n小于对象当前的capacity时,什么也不做。

reserve函数的实现思路也是很简单的,先判断所给n是否大于当前容器的最大容量(否则无需进行任何操作),操作时直接开辟一块可以容纳n个数据的空间,然后将原容器当中的有效数据拷贝到该空间,之后将原容器存储数据的空间释放,并将新开辟的空间交给该容器维护,最好更新容器当中各个成员变量的值即可。

		void reserve(size_t n) // 保持容量
		{
			if (n > capacity()) // 判断是否需要进行操作
			{
				size_t sz = size(); // 保存一下有效数据的个数
				T* tmp = new T[n]; // 开辟一个可以容纳n个空间的tmp
				if (_start)
				{
					for (size_t i = 0; i < sz; i++)
					{
						tmp[i] = _start[i]; // 将原本容器中的所有数据都拷贝到tmp临时空间中
					}
					delete[] _start; // 释放原空间
				}
				_start = tmp; // 将tmp临时空间中的数据全都放到对象容器当中
				_finish = _start + sz; // 有效数据个数
				_endofstorage = _start + n; // 总容量大小
			}
		}

细节问题1、在进行操作之前需要提前记录当前容器当中有效数据的个数。

我们在进入循环记录了一下有效数据的大小sz,这是因为我们最后需要更新_finish指针的指向,而_finish指针的指向就等于_start指针加容器当中有效数据的个数,当_start指针的指向改变后我们再调用size函数通过_finish - _start计算出的有效数据的个数就是一个随机值了。

在这里插入图片描述

细节问题2、拷贝容器当中的数据时,不能使用memcpy函数进行拷贝。

可能你会想,当vector当中存储的是string的时候,虽然使用memcpy函数reserve出来的容器与原容器当中每个对应的string成员都指向同一个字符串空间,但是原容器存储数据的空间不是已经被释放了,相当于现在只有一个容器维护这这些字符串空间,这还有什么影响。
但是不要忘了,当你释放原容器空间的时候,原容器当中存储的每个string在释放时会调用string的析构函数,将其指向的字符串也进行释放,所以使用memcpy函数reserve出来的容器当中的每一个string所指向的字符串实际上是一块已经被释放的空间,访问该容器时就是对内存空间进行非法访问。
在这里插入图片描述

所以还是用for循环和=将其一个个赋值过来才是王道:
在这里插入图片描述

3、resize

  1. 当n大于当前的size时,将size扩大到n,扩大的数据为val,若val未给出,则默认为容器所存储类型的默认构造函数所构造出来的值。
  2. 当n小于当前的size时,将size缩小到n。

根据resize函数的规则,进入函数我们可以先判断所给n是否小于容器当前的size,若小于,则通过改变_finish的指向,直接将容器的size缩小到n即可,否则先判断该容器是否需要增容,然后再将扩大的数据赋值为val即可。

		void resize(size_t n, const T& val = T()// 模板缺省值) // 扩容
		{
			if (n < size()) // 当n小于当前的size时
			{
				_finish = _start + n; // 将size缩小到n
			}
			else // 当n大于当前的size时
			{
				if (n > capacity()) // 判断是否需要增容
				{
					reserve(n);
				}
				while (_finish < _start + n) // 将size扩大到n
				{
					*_finish = val;
					_finish++;
				}
			}
		}

4、empty

只需要比较_start和_finish的位置是否相同即可,相同则空了,不相同则非空。

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

六、修改容器内的相关函数

1、push_back

简单,判断是否容器已经满了,满了就扩容,然后将数据尾插到_finish指向的位置,再将_finish++即可。

		void push_back(const T& x)
		{
			if (_finish == _endofstorage) // 到容量底了
			{
				reserve(capacity() == 0 ? 4 : 2 * capacity());
			}
			*_finish = x;
			_finish++;
		}

2、pop_back

简单,先进行判空,这里用assert断言即可,方便快捷,非空则直接将_finish--即可。

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

3、insert

insert函数可以在所给迭代器pos位置插入数据,在插入数据前先判断是否需要增容,然后将pos位置及其之后的数据统一向后挪动一位,以留出pos位置进行插入,最后将数据插入到pos位置即可。

		void insert(iterator pos, const T& x)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			if (_finish == _endofstorage)
			{
				size_t len = pos - _start; // 记录一下相差大小
				// 扩容
				reserve(capacity() == 0 ? 4 : capacity() * 2);
				pos = _start + len;
			}

			iterator end = _finish - 1; // 将pos位置及其之后的数据统一向后挪动一位,以留出pos位置进行插入
			while (end >= pos)
			{
				*(end + 1) = *end;
				--end;
			}
			*pos = x; // 插入 
			++_finish;  // 往后移一位
		}

4、erase

erase函数可以删除所给迭代器pos位置的数据,在删除数据前需要判断容器释放为空,若为空则需做断言处理,删除数据时直接将pos位置之后的数据统一向前挪动一位,将pos位置的数据覆盖即可。

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos < _finish);
			iterator start = pos + 1;
			// 将pos位置之后的数据统一向前挪动一位,以覆盖pos位置的数据
			while (start != _finish)
			{
				*(start - 1) = *start;
				++start;
			}
			--_finish;
		}

5、swap

直接用std库中的swap函数即可,用其进行交换三个变量,这里的std::域作用限定符告诉编译器这里优先在全局范围寻找swap函数,否则编译器会认为你调用的就是你正在实现的swap函数(就近原则)。

		void swap(vector<T>& v)
		{
			std::swap(_start, v._start);
			std::swap(_finish, v._finish);
			std::swap(_endofstorage, v._endofstorage);
		}

七、访问容器相关函数

只有一个operator[]来进行访问,实现时直接返回对应位置的数据即可。

1、operator[]

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

2、const operator[]

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

这里的权限只需要读,不能写。

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

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

相关文章

最新酒桌小游戏喝酒小程序源码,带流量主,附带搭建教程

喝酒神器&#xff0c;增加了广告位&#xff0c;根据文档直接替换即可&#xff0c;原版本没有广告位 直接上传源码到开发者端即可 通过后改广告代码&#xff0c;然后关闭广告展示提交&#xff0c;通过后打开即可 搜索adunit-848e5f13d1ff237a替换为你的Banner 搜索adunit-597…

Linux操作系统基础(十一):RPM软件包管理器

文章目录 RPM软件包管理器 一、rpm包的卸载 二、rpm包的安装 RPM软件包管理器 rpm&#xff08;英文全拼&#xff1a;redhat package manager&#xff09; 原本是 Red Hat Linux 发行版专门用来管理 Linux 各项软件包的程序&#xff0c;由于它遵循GPL规则且功能强大方便&…

Python中使用opencv-python进行人脸检测

Python中使用opencv-python进行人脸检测 之前写过一篇VC中使用OpenCV进行人脸检测的博客。以数字图像处理中经常使用的lena图像为例&#xff0c;如下图所示&#xff1a; 使用OpenCV进行人脸检测十分简单&#xff0c;OpenCV官网给了一个Python人脸检测的示例程序&#xff0c;…

sympy斐波那契数列

文章目录 fibonaccitribonacci对比 fibonacci 斐波那契数列的递推公式大家都很熟悉了&#xff0c;是 F n F n − 1 F n − 2 F_nF_{n-1}F_{n-2} Fn​Fn−1​Fn−2​&#xff0c;其在复数域的的解析延拓可表示为 F z ϕ z − cos ⁡ ( π z ) ϕ − z 5 , ϕ 5 − 1 2 F_z\…

3D高斯溅射:面向三维场景的实时渲染技术

1. 前言 高斯溅射技术【1】一经推出&#xff0c;立刻引起学术界和工业界的广泛关注。相比传统的隐式神经散射场渲染技术&#xff0c;高斯溅射依托椭球空间&#xff0c;显性地表示多目图像的三维空间关系&#xff0c;其计算效率和综合性能均有较大的提升&#xff0c;且更容易理…

电动汽车上哪些部位用到了电机?

一、背景 电动汽车中除了主驱动电机之外的其他电机的控制复杂度因电机的种类和功能而异。 一般来说&#xff0c;助力转向电机、空调风扇电机、冷却水泵电机等辅助电机的控制相对较为简单。这些电机通常只需要进行简单的开/关控制或速度调节&#xff0c;以满足车辆的基本需求。…

第三百一十七回

文章目录 1. 概念介绍2. 实现方法2.1 hintText2.2 labelText2.3 controller 3. 示例代码4. 内容总结 我们在上一章回中介绍了"如何在输入框中处理光标"相关的内容&#xff0c;本章回中将介绍如何添加输入框默认值.闲话休提&#xff0c;让我们一起Talk Flutter吧。 1.…

《Linux 简易速速上手小册》第6章: 磁盘管理与文件系统(2024 最新版)

文章目录 6.1 磁盘分区与格式化6.1.1 重点基础知识6.1.2 重点案例&#xff1a;为新硬盘配置分区和文件系统6.1.3 拓展案例 1&#xff1a;创建交换分区6.1.4 拓展案例 2&#xff1a;使用 LVM 管理分区 6.2 挂载与卸载文件系统6.2.1 重点基础知识6.2.2 重点案例&#xff1a;挂载新…

ChatGPT高效提问—prompt常见用法(续篇)

ChatGPT高效提问—prompt常见用法&#xff08;续篇&#xff09; ​ 对话式prompt适用于模拟各种交流情境。若我们意图探索在特殊场合下可能出现的对话情景&#xff0c;或者模拟一段对话流程&#xff0c;可以采用这种方法&#xff0c;通过精准的prompt指令&#xff0c;引导Chat…

windows配置开机自启动软件或脚本

文章目录 windows配置开机自启动软件或脚本配置自启动目录开机运行的脚本调试开机自启动脚本配置守护进程(包装成自启动服务)使用任务计划程序FAQ 开机自动运行脚本示例 windows配置开机自启动软件或脚本 配置自启动目录 在Windows中添加开机自动运行的软件&#xff0c;可以按…

CF1870F - Lazy Numbers 一道Trie树思路应用的题目

C F 1870 F − L a z y N u m b e r s \mathrm{CF1870F - Lazy\ Numbers} CF1870F−Lazy Numbers D e s c r i p t i o n Description Description 对于给定的 n n n 和 k k k&#xff0c;求解出 1 ∼ n 1\sim n 1∼n 的每一个数在 k k k 进制下字典序排列的顺序&#xff…

Matlab图像处理——图像边缘检测方法(算子)

1.edge函数语法 BW edge(I) BW edge(I,method) BW edge(I,method,threshold) BW edge(I,method,threshold,direction) BW edge(___,"nothinning") BW edge(I,method,threshold,sigma) BW edge(I,method,threshold,h) BW edge(I) 返回二值图像 BW&#xff0…

分享89个时间日期JS特效,总有一款适合您

分享89个时间日期JS特效&#xff0c;总有一款适合您 89个时间日期JS特效下载链接&#xff1a;https://pan.baidu.com/s/127_keimOfy_AKrCNT4TQNA?pwd8888 提取码&#xff1a;8888 Python采集代码下载链接&#xff1a;采集代码.zip - 蓝奏云 学习知识费力气&#xff0c;…

Java安全 CC链1分析(Lazymap类)

Java安全 CC链1分析 前言CC链分析CC链1核心LazyMap类AnnotationInvocationHandler类 完整exp&#xff1a; 前言 在看这篇文章前&#xff0c;可以看下我的上一篇文章&#xff0c;了解下cc链1的核心与环境配置 Java安全 CC链1分析 前面我们已经讲过了CC链1的核心ChainedTransf…

【开源】SpringBoot框架开发木马文件检测系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 数据中心模块2.2 木马分类模块2.3 木马软件模块2.4 安全资讯模块2.5 脆弱点模块2.6 软件检测模块 三、系统设计3.1 用例设计3.2 数据库设计3.2.1 木马分类表3.2.2 木马软件表3.2.3 资讯表3.2.4 脆弱点表3.2.5 软件检测表…

GEE数据集——巴西年度土地覆被和利用地图

巴西年度土地覆被和利用地图 巴西年度土地利用和土地覆被制图项目是一个由生物群落、土地利用、遥感、地理信息系统和计算机科学专家组成的合作网络&#xff0c;依靠谷歌地球引擎平台及其云处理和自动分类功能生成巴西年度土地利用和土地覆被时间序列。MapBiomas 项目--是一项多…

C++入门学习(二十七)跳转语句—break语句

1、与switch语句联合使用 C入门学习&#xff08;二十三&#xff09;选择结构-switch语句-CSDN博客 #include <iostream> #include <string> using namespace std;int main() { int number;cout<<"请为《斗萝大路》打星(1~5※)&#xff1a;" &…

python统计分析——两样本t检验

参考资料&#xff1a;用python动手学统计学 1、导入库 # 导入库 # 用于数值计算的库 import numpy as np import pandas as pd import scipy as sp from scipy import stats # 用于绘图的库 from matplotlib import pyplot as plt import seaborn as sns sns.set() 2、准备数…

导数的几何意义【高数笔记】

1. 高数中的导数几何意义&#xff0c;与中学中斜率的联系 2. 导函数与导数的区别和联系又是什么 3. 导数的几何意义的题型是什么 4. 这些题型又有哪些区别 5. 点在曲线外和点在曲线上&#xff0c;需要注意什么 6. 法线和切线有什么关系 7. 法线是什么

JavaScript DOM 变动观察器(Mutation observer)

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 DOM 变动观察 是 web 开发中的一个重要概念&#xff0c;指的是监视 …