【C++】STL中vector常见功能的模拟实现

前言:在上一篇中我们讲到了Vector的一些常见功能的使用方式,今天为了进一步的去学习Vector和能够更深度的去理解Vector的一些底层的原理。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:高质量C++学习 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
在这里插入图片描述


目录标题

  • Vector的大体框架
  • Vector中的构造函数
    • 无参构造
    • 拷贝构造(传统写法)
    • 析构函数
    • swap函数
    • 赋值重载operator=
  • Vector容器中的遍历方式
    • operator[]遍历容器
    • 迭代器的遍历和范围for
    • 打印函数print_vector(const vector<T>& v)
  • Vector容器中相关的增删查改的函数
    • 查看容器中有效元素的个数- size()
    • 查看容器中国的容量的大小- capacity()
    • 调整和检查容量的大小- reserve(size_t n)
    • 调整向量的大小-resize(size_t n, const T& value = T())
    • 尾插数据- push_back(const T& val)
    • 尾删数据-pop_back()
    • 插入数据-insert(iterator pos,const T& val)
    • 删除数据- erase(iterator pos)
    • 查看容器中的数据是否为空Empty()
  • vector中构造函数的一点延伸
    • 拷贝构造的现代写法
    • 区间构造函数
    • 列表初始化构造(c++11以上的写法)
  • 总体代码

Vector的大体框架

在vector中本质是由三个指针变量组成的,且vector中可以存储各种类型的对象,因此我们使用模板在这里插入图片描述

namespace bit
{
	template<class T>//因为在使用的时候Vector中可以是任意类型所以我们调用模板
	class vector
	{
	public:
		typedef T* iterator;//迭代器
		typedef const T* const_iterator;
	private:
		iterator _start = nullptr; //容器的起始位置  // T* _start
		iterator _finish = nullptr;//容器中所使用的元素的最后一个位置
		iterator _endofstorage = nullptr;//容器中所有元素的最后一个位置
	};
}

Vector中的构造函数

无参构造

我们也可以直接在成员变量中对其直接给初始值,不写这个无参构造函数也是可以的。

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

拷贝构造(传统写法)

在拷贝构造中有两种写法,一个传统写法一个现代写法,由于传统写法要调用的函数我们得到后面才会去实现,所以现代写法作者就先放在后面了,这里先写的传统写法.

  1. 如果我们不自己实现,让编译器去实现,就是浅拷贝,会出现问题,故需我们自己实现深拷贝,v2(v1),即只要让v2拷贝一份新的数据即可(使v2和v1不是指向同一份数据)。
  2. 因此我们需要先开辟一块和需要拷贝的对象的同样大的空间,让新的对象指向这一块空间,在把值依次拷贝过去。(代码如下)
vector(const vector<T>& V) 
{
	//传统写法
	_start = new T[V.capacity()];
	_finish = _start;//让_finish指向现在的位置,然后依次进行赋值
	_endofstorage = _start + V.capacity();
	for (size_t i = 0; i < V.size(); i++)
	{
		*_finish = V[i];
		_finish++;
	}
	//此时finish指向的就是最后一个有效的元素的后一个位置
}

析构函数

关于析构函数这里就不过多的介绍了,就是把容器给销毁,然后置空即可。

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

swap函数

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

赋值重载operator=

代码思路:

  1. 传过来的对象会产生一个临时变量,临时变量会有一块独立的空间,这块空间和原本需要的值进行交换即达到了深拷贝的效果,然后返回*this即可达到连续赋值的效果。
vector<T>& operator=(vector<T> v)//现代写法
{
	swap(v);
	return *this;
}

Vector容器中的遍历方式

operator[]遍历容器

1.我们通过运算符重载可以写出两个operator的容器的遍历方式,一种是可以对其进行修改和操作,一种是只可以对其进行操作不可以修改

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

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

迭代器的遍历和范围for

当然了,迭代器也有可读和可写的两个版本(如下所示),且我们在前面的学习中知道,范围for的本质也就是迭代器进行遍历,只要有迭代器我们就能实现范围for了。

iterator begin()//返回起始位置
{
	return _start;
}

iterator end()//结束位置
{
	return _finish;
}

const_iterator begin() const
{
	return _start;
}

const_iterator end()const
{
	return _finish;
}
void test2()
{
	vector<int> v1;
	v1.push_back(1);
	v1.push_back(1);
	v1.push_back(1);
	v1.push_back(1);
	
	cout << "迭代器遍历" << endl;
	vector<int>::iterator it = v1.begin();
	while (it != v1.end())
	{
		cout << *it << " ";
		it++;
	}
	cout << endl;
	cout << "范围for遍历" << endl;
	for (auto e : v1)
		cout << e << " ";
	cout << endl;
}

在这里插入图片描述


打印函数print_vector(const vector& v)

void print_vector(const vector<T>& v)
{
	for (size_t i = 0; i < v.size(); i++)
	{
		cout << v[i] << " ";
	}
	cout << endl;
}

Vector容器中相关的增删查改的函数

查看容器中有效元素的个数- size()

代码思路:_finsh指向最后一个元素的后一个元素,_start指向第一个元素,两个指针相减就是元素的个数。

size_t size()const //元素个数
{
	return _finish - _start;
}

查看容器中国的容量的大小- capacity()

这里和上面的size()函数同理就不过多的解释了。

size_t capacity()const //总的容量
{
	return _endofstorage - _start;
}

调整和检查容量的大小- reserve(size_t n)

代码思路:

  1. 我们首先需要看传过来的大小是否比原本的容量还大,如果比原本的容量还大我们就需要对其进行扩容,比他小我们就不需要处理,因为通常都不会进行缩容的操作。
  2. 在前面string类的模拟实现的时候我们知道在C++中是没有realloc这样的函数的,因此我们只能开辟一块新的空间,然后将原本的值拷贝过去,在将旧空间的指针指向新空间,在释放掉旧的空间。
  3. 这里我们需要注意的是,我们需要将原本旧的空间的size记录下来,因为在前面的size函数中是元素的个数是_finish - _start,此时_start指向了一块新的空间,如果直接对他们相减就会导致越界等问题的发生(如下图所示)。
    在这里插入图片描述
void reserve(size_t n)//调整和检查容量
{
	if (capacity() < n)
	{
		T* tmp = new T[n];
		size_t old_size = size();//因原本的_start在后面会改变,放在野指针的出现我们需要对old_size进行存储
		for (size_t i = 0; i < old_size; i++)
		{
			tmp[i] = _start[i];
		}
		delete[] _start;
		_start = tmp;
		_finish = tmp + old_size;//用之前记录的size来更新_finsh
		_endofstorage = tmp + n;
	}
}

调整向量的大小-resize(size_t n, const T& value = T())

代码思路:
1.这里同理,我们需要看传过来的容量是否比原本的大,大的话我们就进行扩容的操作,如果使用者指定了value的值,就将扩容的空间的值全部指定成使用者指定的值,没有指定就使用默认值,小的话对原本的_finish进行更新即可。
2. 这里很多人不懂的是为什么使用匿名对象T(),在前面我们说过自定义类型会调用它的默认构造函数去初始化他的匿名对象,但是T是内置类型的话也会有自己的默认构造函数,如下图所示。
在这里插入图片描述

void resize(size_t n, const T& value = T())//使用半缺省,扩容的话就把剩余的部分全部扩容成value
{
	if (n > size())
	{
		reserve(n);
		while (_finish < _start + n)
		{
			*_finish = value;
			_finish++;
		}
	}
	else
	{
		_finish = _start + n;
	}
}


尾插数据- push_back(const T& val)

代码思路:

  1. 同理们在插入数据的时候需要查看空间的大小是否足够,如果不够扩容即可。
  2. 直接在原本的_finish进行赋值,再让其往后更新一位即可。
void push_back(const T& val)
{
	//insert(end(), val);
	if (_endofstorage == _finish)//判断容量是否是满的
	{
		reserve(capacity() == 0 ? 4 : capacity() * 2);
	}
	*_finish = val;
	_finish++;

}

尾删数据-pop_back()

代码思路:

  1. 我们需要判断是否还有数据,如果没有数据就不能进行删除
  2. 我们直接和之前顺序表一样的玩法,让其尾部往前移动一位即可。
void pop_back()
{
	assert(_finish > _start);
	--_finish;
}

插入数据-insert(iterator pos,const T& val)

代码思路:

  1. 同理我们需要判断是否需要扩容,如果需要扩容的话我们需要注意的是在调用reserve函数时候会对底层的_start和_finish进行修改,所以我们需要对原本的长度进行记录,否则就无法知道原本的长度了。
  2. 此时我们要插入的位置,就是新的_start+len此时的位置了。
  3. 这里我们找到尾部的位置让pos后的位置依次往后移动一位即可,然后再将需要插入的值赋值给pos的位置即可完成插入。
void insert(iterator pos,const T& val)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	if (_finish == _endofstorage)//判断是否需要扩容
	{
		size_t len = pos - _start;//同理需要记录原本的长度,因为在reserver后会对原本的_start进行修改
		reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容了的话原本的_start
		//如果扩容了就要更新pos
		pos = len + _start;
	}
	iterator it = _finish - 1;
	while (it >= pos)
	{
		*(it + 1) = *it;
		--it;
	}
	*pos = val;
	++_finish;
}

讲到这里很多朋友就会发现,那么我们的push_back函数也可以直接调用insert函数即可,两个写成一个就行,代码如下:

void push_back(const T& val)
{
	insert(end(), val);//直接尾插即可
}

删除数据- erase(iterator pos)

代码思路:

  1. 我们这里需要判断删除的数据是否在容器有效数据的区间内
  2. 同理我们只需要将删除数据位置的区间全部往前移动一个覆盖掉删除的数据即可,然后更新_finish即可完成删除。
iterator erase(iterator pos)
{
	assert(pos >= _start);
	assert(pos <= _finish);
	iterator it = pos + 1;
	while (it < _finish)
	{
		*(it - 1) = *it;
		++it;
	}

	--_finish;
	return pos;//返回被删除数据的下一个位置,那就是还是原位置,原位置的数据已经被删除了
}

查看容器中的数据是否为空Empty()

bool Empty()
{
	return _start == _finish;
}

vector中构造函数的一点延伸

拷贝构造的现代写法

代码思路:

  1. 我们直接用reserve开辟一个一样大的空间,相当于开辟了一块新的空间,达到了深拷贝的效果,然后在对其每个数据进行尾插即可。
vector(const vector<T>& V) 
{
	reserve(V.capacity());//直接开辟一个一样大的空间,然后每个数据进行尾插
	for (auto& e : V)
	{
		push_back(e);
	}
}

区间构造函数

代码思路:
1.这里直接将该区间的数据依次插入到*this中的容器中即可

template <class InputIterator>
vector(InputIterator first, InputIterator last)
{
	while (first != last)
	{
		push_back(*first);
		++first;
	}
}

列表初始化构造(c++11以上的写法)

代码思路:

  1. initializer_list是C++11引入的一种数据结构,用于表示一个初始化列表(initializer list)。它是一个模板类,定义在<initializer_list>头文件中。initializer_list可以用于在函数参数中传递一组值,也可以用于对象的初始化。它的语法类似于数组,使用花括号{}括起一组值,并以逗号分隔(这里就不过多的讲解了,后面会单独写一篇博客进行讲解)。
  2. 剩下的同理检查内存容量,然后对这里直接对其每个数据进行尾插就行。
vector(initializer_list<T> il)
{
	reserve(il.size());
	for (auto& e : il)
	{
		push_back(e);
	}
}

总体代码

using namespace std;
#include <iostream>
#include <assert.h>
namespace bit
{
	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;
		}

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

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

		//V2(v1) v2是对v1的值拷贝,叫浅拷贝,v1生命周期结束,析构v1,v2生命周期结束释放V2,,
		vector(const vector<T>& V) 
		{
			reserve(V.capacity());
			for (auto& e : V)
			{
				push_back(e);
			}

			//传统写法
			//_start = new T[V.capacity()];
			//_finish = _start;//让_finish指向现在的位置,然后依次进行赋值
			//_endofstorage = _start + V.capacity();
			//for (size_t i = 0; i < V.size(); i++)
			//{
			//	*_finish = V[i];
			//	_finish++;
			//}
		}

		vector(initializer_list<T> il)
		{
			reserve(il.size());
			for (auto& e : il)
			{
				push_back(e);
			}
		}

		// 类模板的成员函数可以是函数模板
		template <class InputIterator>
		vector(InputIterator first, InputIterator last)
		{
			while (first != last)
			{
				push_back(*first);
				++first;
			}
		}

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

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

		size_t size()const //元素个数
		{
			return _finish - _start;
		}

		size_t capacity()const //总的容量
		{
			return _endofstorage - _start;
		}

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

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

		void reserve(size_t n)//调整和检查容量
		{
			if (capacity() < n)
			{
				T* tmp = new T[n];
				size_t old_size = size();//因原本的_start在后面会改变,放在野指针的出现我们需要对old_size进行村粗
				for (size_t i = 0; i < old_size; i++)
				{
					tmp[i] = _start[i];
				}
				delete[] _start;
				_start = tmp;
				_finish = tmp + old_size;
				_endofstorage = tmp + n;
			}
		}

		void resize(size_t n, const T& value = T())//使用半缺省,扩容的话就把剩余的部分全部扩容成value
		{
			if (n > size())
			{
				reserve(n);
				while (_finish < _start + n)
				{
					*_finish = value;
					_finish++;
				}
			}
			else
			{
				_finish = _start + n;
			}
		}

		void insert(iterator pos,const T& val)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			if (_finish == _endofstorage)
			{
				size_t len = pos - _start;
				reserve(capacity() == 0 ? 4 : capacity() * 2);//扩容了的话原本的_start
				//如果扩容了就要更新pos
				pos = len + _start;
			}
			iterator it = _finish - 1;
			while (it >= pos)
			{
				*(it + 1) = *it;
				--it;
			}
			*pos = val;
			++_finish;
		}

		void push_back(const T& val)
		{
			//insert(end(), val);
			if (_endofstorage == _finish)//判断容量是否是满的
			{
				reserve(capacity() == 0 ? 4 : capacity() * 2);
			}
			*_finish = val;
			_finish++;
		}

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

		iterator erase(iterator pos)
		{
			assert(pos >= _start);
			assert(pos <= _finish);
			iterator it = pos + 1;
			while (it < _finish)
			{
				*(it - 1) = *it;
				++it;
			}

			--_finish;
			return pos;//返回被删除数据的下一个位置,那就是还是原位置,原位置的数据已经被删除了
		}

		bool Empty()
		{
			return _start == _finish;
		}

	private:
		iterator _start = nullptr; //容器的起始位置  // T* _start
		iterator _finish = nullptr;//容器中所使用的元素的最后一个位置
		iterator _endofstorage = nullptr;//容器中所有元素的最后一个位置
	};

	template<class T>
	void print_vector(const vector<T>& v)
	{
		for (size_t i = 0; i < v.size(); i++)
		{
			cout << v[i] << " ";
		}
		cout << endl;
	}

	void test1()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(1);
		v1.push_back(1);
		v1.push_back(1);
		//print_vector(v1);
		//vector<int> v2 = { 1,2,3,4,5 };
		//print_vector(v2);
		vector <int>v3(v1);
		print_vector(v3);
		for (auto e : v1)
		{
			cout << e << " ";
		}
	}
	void test2()
	{
		vector<int> v1;
		v1.push_back(1);
		v1.push_back(1);
		v1.push_back(1);
		v1.push_back(1);
		
		cout << "迭代器遍历" << endl;
		vector<int>::iterator it = v1.begin();
		while (it != v1.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
		cout << "范围for遍历" << endl;
		for (auto e : v1)
			cout << e << " ";
		cout << endl;
	}
	
	void test3()
	{
		vector<int> v1 = { 1,3,4,6 };
		v1.insert(v1.begin()+2, 10);
		print_vector(v1);
	}
}

好啦,今天的内容就到这里啦,下期内容预告stl中list的使用,博主前段时间有点事情,后面这段时间会加班加点的更新!


结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。


🌏🗺️ 这里祝各位接下来的每一天好运连连 💞💞

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

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

相关文章

自定义类型:结构体类型

在学习完指针相关的知识后将进入到c语言中又一大重点——自定义类型&#xff0c;在之前学习操作符以及指针时我们对自定义类型中的结构体类型有了初步的了解&#xff0c;学习了结构体类型的创建以及如何创建结构体变量&#xff0c;还有结构体成员操作符的使用&#xff0c;现在我…

[数据集][目标检测][数据集][目标检测]智能手机检测数据集VOC格式5447张

数据集格式&#xff1a;Pascal VOC格式(不包含分割的txt文件&#xff0c;仅仅包含jpg图片和对应的xml) 图片数量(jpg文件个数)&#xff1a;5447 标注数量(xml文件个数)&#xff1a;5447 标注类别数&#xff1a;1 标注类别名称:["phone"] 每个类别标注的框数&#xff…

WPF -> MVVM

1.1安装MVV MLight 打开 Visual Studio 2022。 在顶部菜单栏中选择“工具” -> “NuGet 包管理器” -> “程序包管理器控制台”。 在控制台中输入以下命令&#xff0c;并按回车键运行&#xff1a; Install-Package MvvmLightLibsStd104.等待安装完成后&#xff0c;你就…

man命令的作用

man命令是Linux操作系统中一个非常实用的命令&#xff0c;它用于查看命令的手册页面&#xff0c;帮助用户了解特定命令的用法、选项和参数。这不仅对新用户在学习如何使用新命令时很有帮助&#xff0c;也方便了经验丰富的用户快速查找命令的详细信息。以下是具体介绍&#xff1…

基于java18多端展示+ idea hbuilder+ mysql家政预约上门服务系统,源码交付,支持二次开发

基于java18多端展示 idea hbuilder mysql家政预约上门服务系统&#xff0c;源码交付&#xff0c;支持二次开发 家政预约上门系统是一种通过互联网或移动应用平台&#xff0c;为用户提供在线预约、下单、支付和评价家政服务的系统。该系统整合了家政服务资源&#xff0c;使用户能…

LeetCode 算法:无重复字符的最长子串c++

原题链接&#x1f517;&#xff1a;无重复字符的最长子串 难度&#xff1a;中等⭐️⭐️ 题目 给定一个字符串 s &#xff0c;请你找出其中不含有重复字符的最长子串的长度。 示例 1: 输入: s “abcabcbb” 输出: 3 解释: 因为无重复字符的最长子串是 “abc”&#xff0c;所…

谷歌浏览器的平替,内置开挂神器,我已爱不释手!

油猴浏览器正式版是一款基于谷歌Chromium源码开发的浏览器&#xff0c;它集成了集成了强大的油猴扩展&#xff08;Tampermonkey&#xff09;&#xff0c;使得用户可以轻松安装各种脚本&#xff0c;从而增强网页浏览体验。提供了一个更加个性化和高效的浏览体验。 油猴扩展&…

【Python网络爬虫】详解python爬虫中URL资源抓取

&#x1f517; 运行环境&#xff1a;PYTHON &#x1f6a9; 撰写作者&#xff1a;左手の明天 &#x1f947; 精选专栏&#xff1a;《python》 &#x1f525; 推荐专栏&#xff1a;《算法研究》 #### 防伪水印——左手の明天 #### &#x1f497; 大家好&#x1f917;&#x1f91…

CS和msf的权限传递,利用mimikatz抓取win10明文密码

一、Cobaltstrike的安装 http://t.csdnimg.cn/yhZin 安装CobaltStrike&#xff0c;浏览博主的上篇文章即可&#xff01;&#xff01;&#xff01; 这里我在自己的本机win11上执行了Client去连接kali中的Server端&#xff0c;直接执行.cmd文件即可&#xff01;&#xff01;&…

AI智能语音机器人系统如何对接科大讯飞接口

关于AI语音机器人的介绍有很多&#xff0c;但是由于商业化&#xff0c;没有一个能真正说明白的&#xff0c;当然&#xff0c;我们搭建的AI智能机器人系统也是商业化的&#xff0c;毕竟业务是做这方面的&#xff0c;但是价格绝对是公道的&#xff0c;废话不多说了&#xff0c;我…

C++vector及其实现

第一个参数是类型(可以是自定义也可以是内置类型) 相当于生成一个该类型的数组 allocator是空间配置器 遍历 1.下标遍历 2.迭代器遍历 3.范围for 对象访问 有名对象访问 匿名对象访问 隐式类型转换 成员函数 sort 使用sort需要包含头文件algorithm eg. sort的使用非…

QA 未能打开位于 D:/Computer999/Computer999.vbox 的虚拟电脑

前言 未能打开位于 xxx/Computer999.vbox 的虚拟电脑&#xff0c;并提示E_INVALIDARG (0X80070057)&#xff0c;是最常见的一个错误&#xff0c;下面是解决办法。 内容 1、提示下面的错误&#xff0c;注册Computer999失败&#xff1a; 未能打开位于 D:/Computer999/Compute…

【刷题(15】普通数组

一 普通数组基础 首先&#xff0c;我们根据下图先了解一下什么是前缀和。 既然我们明白了前缀和是怎么回事&#xff0c;那我们就来看一下我们该怎么输入 先给出答案&#xff0c;然后再给出分析。 答案&#xff1a; for (int i 1; i < n; i ){cin >> a[i];s[i] s…

JVM之垃圾回收面试总结

文章目录 1.GC概述1.1 什么是垃圾1.2 为什么需要GC&#xff1f;1.3 早期垃圾回收1.4 Java垃圾回收机制1.5 评估GC的性能指标 2.垃圾回收相关算法2.1 垃圾标记阶段的算法2.1.1 引用计数算法(Java没有使用)2.1.2 可达性分析算法 2.2 垃圾清除阶段的算法2.2.1 标记-清除(Mark-Swee…

今在推特发一个推特立马推特账户被删除了

咨询 Google Contacts 是如何 获取我的苹果手机通讯录的电话号码清单的&#xff1f;不到一分钟&#xff0c;我的账户之间被删除了&#xff0c;比停用、冻结还令人可怕。 立马推特账户被删除了。

阿里云搭建物联网平台+MQTT.fx接入阿里云

文章目录 本篇介绍一、阿里云物联网平台搭建二 、MQTT客户端接入阿里云物联网平台总结 本篇介绍 本篇搭建了阿里云物联网平台&#xff0c;使用MQTT.fx接入阿里云&#xff0c;上传温湿度数据 使用到的软件&#xff1a;阿里云、MQTT.fx 一、阿里云物联网平台搭建 首先创建一个物…

Codeforces Round 949 (Div. 2)(A,B题解)

这场真是给我打的汗流浃背了&#xff0c;这场真的巨难&#xff08;可能是因为我二进制根本就没学好的原因吧&#xff09; 反正总共就搞了两道题&#xff0c;第一道题注重于思维&#xff0c;第二道题纯二进制&#xff0c;第三道题看着也是二进制&#xff08;最后时间不够了&…

【Microelectronic Systems】

1 background&introduction 2 analog to Digital Conversion 3 Digital to Anolog Conversion 4 introduction to CMOS outlook ! introduction to semiconductors siemens,西门子 properties of semiconductors types of semiconductors $ PN junction 5 Mathematic s…

Llama(二):Open WebUI作为前端界面,使用本机的llama3

目录 背景 Open WebUI是什么 工程能力特性 产品功能特性 用户体验特性 Open WebUI安装并使用 背景 Mac M1芯片&#xff0c;16G 内存 llama3 8B的部署参考Llama&#xff08;一&#xff09;&#xff1a;Mac M1芯片运行Llama3-CSDN博客在Mac M1 16G内存环境中&#xff0c;…

【数据结构】二叉树的层序遍历~动画超详解

目录 1 什么是层序遍历2 二叉树层序遍历的基本思路3 二叉树层序遍历的实现 1 什么是层序遍历 我们从字面意思就明白,所谓层序,就是一层一层按顺序去遍历一个二叉树,这和我们之前了解的按前中后序遍历方式完全不同 比方说这颗二叉树: 前序遍历: 层序遍历: 2 二叉树层序遍历的…