【C++】stl---vector的模拟实现(超级详细,万字详解)

news2025/2/25 17:23:13

文章目录

  • 前言
  • vector的成员属性
  • 构造函数
  • size函数
  • cacpcity函数
  • begin和end函数
  • reserve函数
  • insert函数
  • push_back函数
  • []操作符重载
  • 析构函数
  • 拷贝构造函数
  • 赋值操作符重载
  • erase函数
  • pop_back
  • 反向迭代器
    • 反向迭代器模板
    • 反向迭代器的构造函数
    • ++运算符重载
    • - -运算符重载
    • *引用操作符重载
    • !=操作符重载
    • ==操作符重载
    • rbegin函数
    • rend函数
    • ->重载
  • 总结


前言

vector 是一个顺序容器,简单来说就是一个可以扩容的数组。它的数据在内存中是连续存储的。这也就意味着它支持随机访问(下标访问),而这篇文章将会带着大家模拟实现一个vector的容器。


vector的成员属性

vector是一个顺序容器,那么我们需要给它分配空间。那么我们可以用三个迭代器来指向,_start迭代器指向第一个位置,_finish指向有效数据的下一个位置,而_endofstorage指向开辟空间大小的最后一个位置。

代码:

template<class T>
class vector
{
public:
	//普通迭代器
	typedef T* iterator;
	//const迭代器
	typedef const T* const_iterator;
private:
	iterator _start;
	iterator _finish;
	iterator _endofstorage;
	
};

构造函数

接下来我们就要给vector初始化,这里用2种初始化方式,一种是普通初始化。而一种是迭代器区间初始化。
普通初始化:什么也不干,迭代器初始化成空指针。
迭代器区间初始化:把迭代器区间的所有值一一加到当前的vector对象种。

普通初始化代码:

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

迭代器区间初始化:

	//迭代器区间初始化
	template <class InputIterator>
	vector(InputIterator first, InputIterator last)
		:_start(nullptr)
		,_finish(nullptr)
		,_endofstorage(nullptr)
	{
		while (first != last)
		{
			//把迭代器数据依次插入当前对象
			push_back(*first);//尾部插入数据的函数,在下面实现
			++first;
		}
	}

size函数

size函数返回vector的长度,也就是已经存放的数据个数,我们直接返回_finish - _start的值即可。

	size_t size()
	{
		return _finish - _start;
	}

如果是const对象,我们也要返回一个长度

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

cacpcity函数

cacpcity函数返回vector的容量,也就是能存放的数据的最大个数,我们直接返回_endofstorage - _start的值即可。

	size_t cacpcity()
	{
		return _endofstorage - _start;
	}

如果是const对象,我们也要返回

	size_t cacpcity()const
	{
		return _endofstorage - _start;
	}

begin和end函数

begin 获取 迭代器开始位置,end 获取迭代器结束位置。

直接返回_start 和 _finish

		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}

当我们的容器是const的时候,我们需要返回const迭代器。所以还需要对begin和end函数进行函数重载。

		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}

		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}


reserve函数

reserve函数是用来改变容量的,但是对于缩容我们不做处理,reserve只做增容处理。

我们可以分以下步骤:

  1. 开辟一块指定大小的空间(只增容)。
  2. 把原_start空间的数据拷贝到新空间。
  3. 更新_finish。
  4. 释放原来的_start。
  5. 更新_start 和 _endofstorage

代码:

void reserve(size_t n)
	{
		//n比当前容量大
		if (cacpcity() < n)
		{
			//开辟n个空间
			T* tmp = new T[n];
			//更新_finish
			_finish = tmp + size();
			//销毁旧空间
			delete[] _start;
			//更新
			_start = tmp;
			_endofstorage = _start + n;
		}
	}


insert函数

insert函数是在容器指定位置插入一个数据,而实现insert后我们可以复用insert实现push_back尾插函数。
而这个指定位置,我们用迭代器来表示。插入完后,我们返回插入数据的迭代器位置,方便迭代。

			指定位置插入
		iterator insert(iterator pos, const T& val)
		{
			//pos必须在 _start  - _finish之间
			assert(pos >= _start && pos <= _finish);
			//判断空间是否足够
			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;
				//空间不够,增容,是0增容4,不是0增容2倍
				reserve(cacpcity() == 0 ? 4 : 2 * cacpcity());
				pos = _start + len;
			}
			//把pos - finish 的位置都往后移动
			iterator end = _finish;
			while (end > pos)
			{
				*(end) = *(end - 1);
				--end;
			}
			*pos = val;
			++_finish;
			//返回pos迭代器
			return pos;
		}

push_back函数

push_back是一个尾插函数,而我们前面实现过insert,所以可以直接复用。


		void push_back(const T& val)
		{
			//复用insert
			insert(end(), val);
		}

[]操作符重载

我们要支持下标访问,可以重载[]操作符,给定一个下标,返回_start+下标的解引用的引用即可。

		T& operator[](const T& val)
		{
			return *(_start + val);;
		}

如果是const对象,那么我们不允许修改[]的值。


		const T& operator[](const T& val)const
		{
			return *(_start + val);;
		}

析构函数

直接释放_start的空间即可。

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

拷贝构造函数

我们可以用 被拷贝对象的迭代区间实例化一个对象,再把当前对象与这个对象进行交换。
然后出了函数,实例化的对象会被自动析构。

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

		vector(const vector<T>& v)
		{
			vector<T> tmp(v.begin(), v.end());
			//交换
			swap(tmp);
		}

赋值操作符重载

直接用拷贝构造构造一个,然后和当前对象交换。

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

erase函数

eraser函数是在指定位置删除,我们依旧通过迭代器的方式删除。删除后,返回原来下标的迭代器。

代码:


		iterator erase(iterator pos)
		{
			//pos必须在start - finish区间,且 size不能为空
			assert(size() > 0);
			assert(pos >= _start && pos < _finish);
			int len = pos - _start;
			//从pos的下一个位置开始,往前覆盖
			iterator begin = pos + 1;
			while (begin != _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}
			_finish--;

			return pos;
		}

pop_back

pop_back是一个尾删函数,也就是删除最后一个数据,我们可以复用erase

		void pop_back()
		{
			erase(end() - 1);
		}

end是指向最后一个数据的下一个位置,所以 -1 就是最后一个数据。

反向迭代器

反向迭代器我们需要再有一个类来封装它,目的是为了让它具有复用性。不仅让它可以在 vector中使用,在list里也可以复用 。

反向迭代器模板

我们可以用模板来接收,目的是为了让它既能生成普通反向迭代器,也可以生成带有const的反向迭代器。iterator是传过来的普通迭代器,Ref是返回解引用后的值,Ptr是返回指针。

template<class iterator,class Ref,class Ptr>
class _reverse_iterator
{
	typedef _reverse_iterator<iterator, Ref, Ptr> self;
public:	
private:
	iterator reverse_iterator;
};

反向迭代器的构造函数

反向迭代器其实就是正向迭代器封装,只是++操作变–,–操作变++

	_reverse_iterator(iterator it)
		:_it(it)
	{}

++运算符重载

反向迭代器的++是普通迭代器的- -

	//前置++
	self& operator++()
	{
		--_it;
		return *this;
	}
	//后置++
	self operator++(int)
	{
		self tmp(*this);
		--_it;
		return tmp;
	}

- -运算符重载

反向迭代器的- - 是正向迭代器的++

//前置--
	self& operator--()
	{
		++_it;
		return *this;
	}
	//后置--
	self operator--(int)
	{
		self tmp(*this);
		++_it;
		return tmp;
	}

*引用操作符重载

根据官方的stl,解引用返回的其实是它反向迭代器的前一个位置。
因为起始位置是在最后一个数据的下一个位置。
在这里插入图片描述
所以返回它的前一个位置即可

	Ref operator*()
	{
		iterator tmp = (*this)._it;
		return *(--tmp);
	}

然后我们在主类里面加上模板。

typedef _reverse_iterator<iterator, T&, T*> reverse_iterator;
typedef _reverse_iterator<const_iterator, const T&,const T*> const_reverse_iterator;

在这里插入图片描述

!=操作符重载

比较地址相不相等,直接返回结果即可。

	bool operator!=(const self& it)
	{
		return _it != it._it;
	}

	bool operator!=(const self& it)const
	{
		return _it != it._it;
	}

==操作符重载

一样,直接返回结果即可。

	bool operator==(const self& it)
	{
		return _it == it._it;
	}

	bool operator==(const self& it)const
	{
		return _it == it._it;
	}

rbegin函数

rbegin函数返回最后一个数据的下一个位置,也就是_finish,那我们就用_finish实例化一个反向迭代器。

		reverse_iterator rbegin()
		{
			return reverse_iterator(_finish);
		}

对于const容器,我们返回const的反向迭代器

		const_reverse_iterator rbegin()const
		{
			return const_reverse_iterator(_finish);
		}

rend函数

rend函数就是第一个元素,我们用_start实例化反向迭代器然后返回。

		reverse_iterator rend()
		{
			return reverse_iterator(_start);
		}

const 容器返回const的反向迭代器


		const_reverse_iterator rend()const
		{
			return const_reverse_iterator(_start);
		}

->重载

如果我们vector存的是一个对象类型,我们还需要支持->访问。所以重载->操作符。

Ptr operator->()
	{
		Ptr tmp = (*this)._it;
		return --tmp;
	}

我们->也要返回前一个地址,反向迭代器reverse_iterator是对普通迭代器iterator的封装,而普通迭代器实际上就是T*,所以我们返回当前的迭代器的前一个位置即可。


全部代码:
vector.h全部代码:

#pragma once
#include<assert.h>
#include<iostream>
#include"reverse_iterator.h"
using namespace std;
namespace wyl
{
	template<class T>
	class vector
	{
	public:
		//普通迭代器
		typedef T* iterator;
		//const迭代器
		typedef const T* const_iterator;
		//反向迭代器
		typedef _reverse_iterator<iterator, T&, T*> reverse_iterator;
		typedef _reverse_iterator<const_iterator, const T&,const T*> const_reverse_iterator;

		vector()
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
		}
		//迭代器区间初始化
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
			:_start(nullptr)
			, _finish(nullptr)
			, _endofstorage(nullptr)
		{
			while (first != last)
			{
				//把迭代器数据依次插入当前对象
				push_back(*first);
				++first;
			}
		}

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

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

		vector(const vector<T>& v)
		{
			vector<T> tmp(v.begin(), v.end());
			//交换
			swap(tmp);
		}

		T& operator[](const T& val)
		{
			return *(_start + val);
		}

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

		const T& operator[](const T& val)const
		{
			return *(_start + val);;
		}

		size_t size()
		{
			return _finish - _start;
		}

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


		size_t cacpcity()
		{
			return _endofstorage - _start;
		}
		size_t cacpcity()const
		{
			return _endofstorage - _start;
		}

		void reserve(size_t n)
		{
			//n比当前容量大
			if (cacpcity() < n)
			{
				//开辟n个空间
				T* tmp = new T[n];
				//拷贝
				for (int i = 0; i < size(); i++)
				{
					*(tmp + i) = *(_start + i);
				}
				//更新_finish
				_finish = tmp + size();
				//销毁旧空间
				delete[] _start;
				//更新
				_start = tmp;
				_endofstorage = _start + n;
			}
		}

		指定位置插入
		iterator insert(iterator pos, const T& val)
		{
			//pos必须在 _start  - _finish之间
			assert(pos >= _start && pos <= _finish);
			//判断空间是否足够
			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;
				//空间不够,增容,是0增容4,不是0增容2倍
				reserve(cacpcity() == 0 ? 4 : 2 * cacpcity());
				pos = _start + len;
			}
			//把pos - finish 的位置都往后移动
			iterator end = _finish;
			while (end > pos)
			{
				*(end) = *(end - 1);
				--end;
			}
			*pos = val;
			++_finish;
			//返回pos迭代器
			return pos;
		}

		iterator erase(iterator pos)
		{
			//pos必须在start - finish区间,且 size不能为空
			assert(size() > 0);
			assert(pos >= _start && pos < _finish);
			int len = pos - _start;
			//从pos的下一个位置开始,往前覆盖
			iterator begin = pos + 1;
			while (begin != _finish)
			{
				*(begin - 1) = *begin;
				++begin;
			}
			_finish--;

			return pos;
		}

		void pop_back()
		{
			erase(end() - 1);
		}

		iterator begin()
		{
			return _start;
		}
		iterator end()
		{
			return _finish;
		}

		const_iterator begin()const
		{
			return _start;
		}
		const_iterator end()const
		{
			return _finish;
		}
		//反向迭代器
		reverse_iterator rbegin()
		{
			return reverse_iterator(_finish);
		}

		const_reverse_iterator rbegin()const
		{
			return const_reverse_iterator(_finish);
		}

		reverse_iterator rend()
		{
			return reverse_iterator(_start);
		}

		const_reverse_iterator rend()const
		{
			return const_reverse_iterator(_start);
		}


		void push_back(const T& val)
		{
			//复用insert
			insert(end(), val);
		}

	private:
		iterator _start;
		iterator _finish;
		iterator _endofstorage;

	};


	void a(const vector<int>& v)
	{
		for (int i = 0; i < v.size(); i++)
		{
			//v[i] = 10;
			cout << v[i] << " ";
		}
	}
	void test1()
	{
		vector<int> v;
		v.push_back(1);
		cout << v.size() << "," << v.cacpcity() << endl;
		v.push_back(2);
		cout << v.size() << "," << v.cacpcity() << endl;
		v.push_back(3);
		cout << v.size() << "," << v.cacpcity() << endl;
		v.push_back(4);
		v.push_back(5);
		cout << v.size() << "," << v.cacpcity() << endl;
		
		vector<int>::iterator it = v.begin();
		//while (it != v.end())
		//{
		//	
		//	cout << *it << " ";
		//	++it;
		//}
		for (int i = 0; i < v.size(); i++)
		{
			v[i] = 10;
			cout << v[i] << " ";
		}
		a(v);

	}

	void test2()
	{
		vector<int> v;
		v.push_back(1);
		v.push_back(2);
		v.push_back(3);
		v.push_back(4);
		v.push_back(5);
		//v.erase(v.begin());
		//v.erase(v.end()-1);


		vector<int> v2 = v;
		vector<int>::reverse_iterator rit = v2.rbegin();
		while (rit != v2.rend())
		{
			
			cout << *rit << " ";
			++rit;
		}
	}
	struct Date
	{
		int _year;
		int _month;
		int _day;
		Date(int year = 1,int month = 1,int day = 1)
			:_year(year)
			,_month(month)
			,_day(day)
		{}
	};

	void b(const vector<Date>& v)
	{
		vector<Date>::const_reverse_iterator rit = v.rbegin();
		while (rit != v.rend())
		{
			cout << rit->_year << " " << rit->_month << " " << rit->_day << endl;
			++rit;
		}
	}

	void test3()
	{
		vector<Date> v;
		v.push_back(Date(2022, 1, 1));
		v.push_back(Date(2022, 2, 1));
		v.push_back(Date(2022, 3, 1));
		b(v);

	}
}

反向迭代器封装的类的全部代码
reverse_iterator.h

#pragma once

template<class iterator,class Ref,class Ptr>
class _reverse_iterator
{
	typedef _reverse_iterator<iterator, Ref, Ptr> self;
public:	
	_reverse_iterator(iterator it)
		:_it(it)
	{}
	//前置++
	self& operator++()
	{
		--_it;
		return *this;
	}
	//后置++
	self operator++(int)
	{
		self tmp(*this);
		--_it;
		return tmp;
	}

	//前置--
	self& operator--()
	{
		++_it;
		return *this;
	}
	//后置--
	self operator--(int)
	{
		self tmp(*this);
		++_it;
		return tmp;
	}

	Ref operator*()
	{
		iterator tmp = (*this)._it;
		return *(--tmp);
	}

	Ptr operator->()
	{
		Ptr tmp = (*this)._it;
		return --tmp;
	}

	bool operator!=(const self& it)
	{
		return _it != it._it;
	}

	bool operator!=(const self& it)const
	{
		return _it != it._it;
	}

	bool operator==(const self& it)
	{
		return _it == it._it;
	}

	bool operator==(const self& it)const
	{
		return _it == it._it;
	}


private:
	iterator _it;
};

主main函数文件代码
test.cpp

#include"vector.h"

void vectorTest()
{
	//wyl::test1();
	wyl::test3();

}

int main()
{
	vectorTest();
}

总结

代码是跟着博客边写,边测,没问题才写上博客的。而后续还会为大家更新更多关于stl的知识,以及一些容器的模拟实现。而反向迭代器具有复用性,也就是这次实现了,下次模拟实现其他容器时可以直接带进去用。实际中vector的使用频率也很高,手动实现一次vector对学习stl的帮助也非常大。后续会持续为大家更新关于C/C++,数据结构以及linux操作系统等知识,如果觉得讲的还可以,可以三连关注一下。如果哪里有写的不对的地方,也欢迎大家指出。

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

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

相关文章

Spring AOP 企业级应用 - 统一功能处理

1.统一用户登录权限效验统一用户登录权限效验使用传统的 AOP 能否解决问题呢 ? Component Aspect // 标识当前类为一个切面 public class LoginAOP {// 定义切点 (拦截的规则) - 拦截 UserController 中的所有方法Pointcut("execution(* com.example.demo.controller.Tes…

React Hooks 基础、实现、原理

React Hooks 基础、实现、原理题外话为什么要有Hooks&#xff1f;但是Class Component 的用法也有缺陷&#xff1a;1.组件复用变的困难2.JavaScript本身的缺陷函数式React HooksuseStateuseEffectuseCallback、useMemouseReducer最后题外话 2023了&#xff0c;新年快乐&#x…

【javascript】DOM 案例

点击查看密码 &#xff1a;就是把type等于password改为text即可&#xff1a; <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta http-equiv"X-UA-Compatible" content"IEedge"><…

电力系统强大的Gurobi 求解器的学习(PythonMatlab)

到底有多强大&#xff0c;看看就知道&#xff0c;必须&#x1f44d;&#x1f44d;&#x1f44d;&#xff1a; 目录 1 概述 2 算例理解【Python】 2.1 算例1——详细入门 2.2 算例2——一般线性规划问题 2.3 算例3——非凸问题 3 算例升级【Matlab】 3.1 模型 3.2 电力系统…

Python2.x 与 3​​.x 版本到底有啥区别?

嗨害大家好鸭&#xff01;我是小熊猫~ 今天给大家带来一点小干货~ 很多人对于python的版本有些许疑问&#xff0c; 今天就来给大家说说看~ Python学习资料电子书点击此处跳转文末名片 Python 的 3​​.0 版本&#xff0c;常被称为 Python 3000&#xff0c;或简称 Py3k。 相对…

Mybatis-Plus“读-批量写-读”数据不一致的问题分享

在日常开发过程中&#xff0c;时常会遇到一个如下场景&#xff1a; 根据条件x&#xff0c;读取表A&#xff0c;得到多行数据&#xff1b;遍历读取到的数据&#xff0c;对条件x以外的字段进行修改&#xff0c;并进行保存&#xff1b;&#xff08;重点&#xff09;修改后&#x…

基础算法(七)——离散化

离散化 介绍 这里的离散化&#xff0c;特指整数的、保序的离散化 有些题目可能需要以数据作为下标来操作&#xff0c;但题目给出的数据的值比较大&#xff0c;但是数据个数比较小。此时就需要将数据映射到和数据个数数量级相同的区间&#xff0c;这就是离散化&#xff0c;即…

基于imx6ull第一个Linux驱动

在编译第一个驱动之前&#xff0c;需要把基本的环境准备好&#xff0c;可以参照这两篇文章&#xff1a;https://wlink.blog.csdn.net/article/details/128590747https://wlink.blog.csdn.net/article/details/128591216我们之前写过一个基于ubuntu最基本的字符设备驱动&#xf…

关于固态硬盘冷数据掉速问题解决方案

20230107 By wdhuag 前言&#xff1a; 我有一个西数蓝盘500G固态&#xff0c;系统盘&#xff0c;一年没开机&#xff0c;这个月开机后发现系统很卡&#xff0c;持续读取假死严重。测试没有坏块&#xff0c;网上说的是冷数据掉速问题。 参考&#xff1a; 如何看待西数/闪迪多…

排序算法:插入、希尔、选择、冒泡

目录 一.插入排序 1.算法描述&#xff1a; 2.实现思路&#xff1a; 3.时间复杂度&#xff1a; 代码如下&#xff1a; 二.希尔排序 &#xff08;插入排序的优化升级&#xff09; 1.算法描述&#xff1a; 2.实现思路&#xff1a; 3.时间复杂度&#xff1a; 代码如下&a…

【算法笔记】最近公共祖先(LCA)问题求解——倍增算法

0. 前言 最近公共祖先简称 LCA&#xff08;Lowest Common Ancestor&#xff09;。两个节点的最近公共祖先&#xff0c;就是这两个点的公共祖先里面&#xff0c;离根最远的那个。 这种算法应用很广泛&#xff0c;可以很容易解决树上最短路等问题。 为了方便&#xff0c;我们记…

星光不负赶路人|2022年终总结

时间真快&#xff0c;转眼又是年末。整理一篇文章来给自己好好做一次年终盘点&#xff0c;本着陈述事实&#xff0c;提炼精华&#xff0c;总结不足的思路&#xff0c;给自己这一年的工作、生活、成长画个句号。 工作 &#x1f3e2; 从经海路到中关村 去年换了工作&#xff0c…

Java设计模式中的创建者模式/单例模式是啥?单例模式其中的饿汉式与懒汉式又是啥?又可以用在哪些地方

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 4. 创建者模式 4.1 特点 使用者不需要知道对象的创建细节 4.2 单例模式 4.2.1使用场景 单例类&#xff1a;且仅能创建一个实例类访问类&#xff1a;使用单例类…

七、Gtk4-Defining a final class

1 定义一个最终类 1.1 一个非常简单的编辑器 在上一节中&#xff0c;我们创建了一个非常简单的文件查看器。现在我们继续重写它&#xff0c;并将其转换为非常简单的编辑器。它的源文件是tfe目录下的tfe1.c(文本文件编辑器1)。 GtkTextView是一个多行编辑器。因此&#xff0c…

java学习day71(乐友商城)购物车实现

今日目标&#xff1a; 1.实现未登录状态的购物车 2.实现登陆状态下的购物车 1.搭建购物车服务 1.1.创建module 1.2.pom依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi&…

软件测试~测试分类

目录 1.按照是否查看代码划分 ① 黑盒测试(Black-box Testing) ② 白盒测试(White-box Testing) ③ 灰盒测试(Gray-Box Testing) 2.按照开发阶段划分 ① 单元测试(Unit Testing) ② 集成测试(Integration Testing) ③ 系统测试(System Testing) ④ 验收测试(Acceptance…

kNN分类

一、 概述 kNN(k nearest neighbor,k近邻)是一种基础分类算法&#xff0c;基于“物以类聚”的思想&#xff0c;将一个样本的类别归于它的邻近样本。 ![在这里插入图片描述] 二、算法描述 1.基本原理 给定训练数据集 T{(x1,y1),(x2,y2),...,(xN,yN)}T\left\{ \left( x_1,y_1 …

17. XML

文章目录一、XML概念二、XML语法1、基础语法2、快速入门3、组成部分4、约束1. 约束概述2. 分类3. DTD4. Schema三、XML解析1、操作xml文档2、 解析xml的方式1. DOM2. SAX3. xml常见的解析器&#xff08;工具包&#xff09;4. Jsoup&#xff08;1&#xff09;快速入门&#xff0…

VUE3 学习笔记(一):环境配置、项目创建

一、首先需要安装node.jsnodejs官网&#xff1a;Node.js (nodejs.org)下载安装包&#xff1a;下载稳定版本即可&#xff0c;目前&#xff08;2023-01-07&#xff09;是18.13.0版本c. 检查当前版本&#xff08;CMD&#xff09;&#xff1a;至此&#xff0c;nodejs已经安装成功&a…

电力系统机组组合(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️❤️&#x1f4a5;&#x1f4a5;&#x1f4a5;&#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清…