秒懂C++之List

news2024/12/29 9:46:06

fe594ea5bf754ddbb223a54d8fb1e7bc.gif

目录

前言

一.常用接口展示

二.模拟常用接口

1.1 准备阶段

1.2  push_back 尾插

1.3 insert 插入

 1.4 头插

1.5 erase 删除

1.6 clear 清理 + 析构

1.7 拷贝构造

1.8 赋值拷贝

1.9 反向迭代器

1.10 ->运算符重载

三.全部代码


前言

List其实就是我们前面数据结构学的带头双向循环链表并且List容器的使用跟前面的string以及vector是差不多的,所以我们直接跳过使用接口函数,直接模拟实现一些常用的接口。

建议可以看一下前面的文章了解一下接口功能,另外本章内容很‘杂’,很有必要复习前面的内容

秒懂C++之vector(下)-CSDN博客

秒懂C++之string类(下)-CSDN博客

一.常用接口展示

二.模拟常用接口

1.1 准备阶段

namespace lj
{
	template <class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;

		T _Data;
		
		//构造初始化 利用匿名对象调用该类型的构造函数
		ListNode<T>(const T& x = T())
		{
			_next = nullptr;
			_prev = nullptr;
			_Data = x;
		}
	};

	template <class T>
	class list
	{
		typedef ListNode<T> Node;
	public:
		//构造函数
		list()
		{
			init();
		}
		void init()
		{
			_head = new Node;//创建带头节点
			_head->_next = _head;//构成双向循序
			_head->_prev = _head;
		}

	private:
		Node* _head;//带头节点:哨兵位
	};

}

不过这样还不够,因为在链表中的物理空间是不连续的,这意味我们平时使用的迭代器将会失效~

迭代器可以看作是指针,而我们遍历链表的过程需要通过++指针来进行遍历,但是链表物理空间是不连续的,这意味我们需要重载运算符++,改变指针下一步的指向逻辑。

可是Node*是指针,代表内置类型是无法进行运行符重载的~所以我们需要特意对该指针进行封装~

封装到一个自定义的类中,然后就可以修改这个类的运算符重载,达到指针能指向下一个位置的目的。

为此我们在命名空间里又封装了一份关于指针运算符重载的结构体~

template <class T>
	struct _list_iterator
	{
		typedef ListNode<T> Node;
		typedef _list_iterator<T> self;
        //针对该类型的指针
		Node* _node;
		//构造函数,初始化_node
		_list_iterator(Node* node)
		{
			_node = node;
		}
		//前置++
		self& operator++()
		{
			//不再是单纯的自加1,而是指向下一位置
			_node = _node->_next;
			return *this;
		}
		//后置++
		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		//前置--
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		//后置--
		self operator--(int)
		{
			//拷贝一份--前的数据
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
		//解引用
		T& opeartor* ()
		{
			return _node->_Data;
		}
		//指针与指针的对比!= 因为我们规定了这个指针的条件,所以对比的类型应该用self
		bool operator!=(const self& x)
		{
			return _node != x._node;
		}
        //指针与指针的对比==
		bool operator==(const self& x)
		{
			return _node == x._node;
		}

	};

1.2  push_back 尾插

//尾插
		void push_back(const T& x)
		{
			//先创建新节点
			Node* newnode = new Node(x);
			//先找到尾节点
			Node* tail = _head->_prev;
			//开始链接
			tail->_next = newnode;
			newnode->_prev = tail;
			newnode->_next = _head;
			_head->_prev = newnode;
		}
		//指向第一个有效节点
		iterator begin()
		{	//单参数隐式类型转换
			//return iterator(_head->_next);
			return _head->_next;
		}
		//指向头节点:哨兵位
		iterator end()
		{
			//return iterator(_head);
			return _head;
		}

1.3 insert 插入

iterator insert(iterator pos,const T& x)
		{
			//创建插入节点
			Node* newnode = new Node(x);
			//记录插入位置
			Node* node = pos._node;
			Node* nodeprev = node->_prev;
            //开始链接
			nodeprev->_next = newnode;
			newnode->_prev = nodeprev;
			newnode->_next = node;
			node->_prev = newnode;

			//return iterator(newnode);
			return newnode;

		}

注意:虽然在list容器中insert不会迭代器失效,但为了代码的一致性,一般都要加上返回值~ 

写了insert我们就可以在尾插中进行复用啦~

 1.4 头插

继续复用Insert

//头插
		void push_front(const T& x)
		{
			insert(begin(), x);
		}

1.5 erase 删除

//erase 删除
		iterator earse(iterator pos)
		{
			//记录位置
			Node* node = pos._node;
			Node* nodenext = node->_next;
			Node* nodeprev = node->_prev;
			//开始链接
			nodeprev->_next = nodenext;
			nodenext->_prev = nodeprev;

			delete node;
			return nodenext;
		}

有了erase我们就可以复用它写头删与尾删了

        //pop_back 尾删
		void pop_back()
		{
			erase(--end());
		}
		//pop_front 头删
		void pop_front()
		{
			erase(begin());
		}

关于迭代器失效这一点,erase就会有失效的问题,当你把指向的节点删除的时候你已经是一个野指针了,所以必须得有返回值让你在下一次使用的时候能够有所指向~而下面的clear函数就很好地体现了这一点~

1.6 clear 清理 + 析构

void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
			}
		}

		~list()
		{
			clear();

			delete _head;
			_head = nullptr;
		}

1.7 拷贝构造

//list l2(l1) 拷贝构造
		list(list<T>& lt)
		{
			empty_init();
			for (const auto& e : lt)
			{
				push_back(e);
			}
		}

如果不写拷贝构造的话编译器会调用默认的拷贝构造,但是该拷贝构造是浅拷贝,会调用两次析构函数导致报错~ 

1.8 赋值拷贝

//赋值拷贝 传统写法
		list<T>& operator=(list<T>& lt)
		{
			if (*this != lt)
			{
				clear();
				for (const auto& e : lt)
				{
					push_back(e);
				}
			}
			return *this;
		}
		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}
		//赋值拷贝 现代写法
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

1.9 反向迭代器

我们拿这个函数来举例,lt这个对象是被const所修饰的,那么便无法调用begin(),因为与隐藏this的类型对不上,权限放大。所以我们需要其对应的const成员函数~

那么问题就来了,我们的iterator都是自定义出来的,那么是否还得再自定义一个关于const_iterator的类出来?里面就是指针被const成员函数赋值的内容~原先的就是没有被const成员函数赋值的内容~

不需要,因为我们通过观察发现针对指针所重载的一系列运算符中只有解引用(*)运算符需要加上const成员函数,其他都是涉及到自身的修改是不需要加上const成员函数的。那么const_iterator的类与iterator的类就只有这一个函数的区别了~

所以在这里我们用类模板来让这两个类进行统一合并~ 如果it需要接收的是被const修饰过的对象,那么我们就通过const_iterator去获取被const成员函数修饰过的函数,如end,begin,*  ~

非常之精彩的一步呢~ 

1.10 ->运算符重载

struct AA
	{
		int _a1;
		int _a2;

		AA(int a1 = 1, int a2 = 1)
			:_a1(a1)
			, _a2(a2)
		{}
	};
	void test2()
	{
		list<AA> lt;
		AA aa1;
		lt.push_back(aa1);

		lt.push_back(AA());

		AA aa2(2, 2);
		lt.push_back(aa2);

		lt.push_back(AA(2, 2));

		list<AA>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << (*it)._a1 << ":" << (*it)._a2 << endl;
			cout << it.operator*()._a1 << ":" << it.operator*()._a2 << endl;

			cout << it->_a1 << ":" << it->_a2 << endl;
			cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;

			++it;
		}
		cout << endl;
	}

我们来拿这段代码来演示->的必要性 

当我们想要访问结构体内的成员_a1与_a2的时候,有两种方式:

  • 一是让指向结构体的结构体指针进行解引用进入内部,然后用.访问成员
  • 另外一个是让指向结构体的结构体指针直接用->访问成员

所以我们的目标很明确,首先得先得到一个能指向AA结构体的结构体指针!

*作用,直接让该指针获取list类里面的成员。怎么个获取法呢?直接让指向结构体的结构体指针直接用->访问成员,毕竟本质还是内置类型,只不过是为了重载++才进行封装的。然后访问的成员是一个结构体,那就直接用.访问结构体里面的成员_a1

->作用,返回一个指向该结构体的结构体指针,让指向结构体的结构体指针直接用->访问成员

巧了,->也可以被const成员函数修饰,毕竟不涉及到自身修改~所以我们再扩充一下我们的类模板~

三.全部代码

//list.h
#pragma once
#include <assert.h>
using namespace std;
namespace lj
{
	template <class T>
	struct ListNode
	{
		ListNode<T>* _next;
		ListNode<T>* _prev;

		T _Data;
		
		//构造初始化 利用匿名对象调用该类型的构造函数
		ListNode<T>(const T& x = T())
		{
			_next = nullptr;
			_prev = nullptr;
			_Data = x;
		}
	};
	template <class T,class Ref,class Ptr>
	struct _list_iterator
	{
		typedef ListNode<T> Node;
		typedef _list_iterator<T,Ref,Ptr> self;

		Node* _node;
		//构造函数
		_list_iterator(Node* node)
		{
			_node = node;
		}
		//前置++
		self& operator++()
		{
			//不再是单纯的自加1,而是指向下一位置
			_node = _node->_next;
			return *this;
		}
		//后置++
		self operator++(int)
		{
			self tmp(*this);
			_node = _node->_next;
			return tmp;
		}
		//前置--
		self& operator--()
		{
			_node = _node->_prev;
			return *this;
		}
		//前置--
		self operator--(int)
		{
			//拷贝一份--前的数据
			self tmp(*this);
			_node = _node->_prev;
			return tmp;
		}
		//解引用
		Ref operator*()
		{
			return _node->_Data;
		}
		//指针与指针的对比!=
		bool operator!=(const self& x)
		{
			return _node != x._node;
		}
		//指针与指针的对比==
		bool operator==(const self& x)
		{
			return _node == x._node;
		}
		Ptr operator->()
		{
			return &_node->_data;
		}

	};
	template <class T>
	class list
	{
		typedef ListNode<T> Node;
		
	public:
		typedef _list_iterator<T,T&,T*> iterator;
		typedef _list_iterator<T,const T&,T*> const_iterator;

		//构造函数
		list()
		{
			init();
		}
		void init()
		{
			_head = new Node;//创建带头节点
			_head->_next = _head;//构成双向循序
			_head->_prev = _head;
		}
		//尾插
		void push_back(const T& x)
		{
			insert(end(), x);
			先创建新节点
			//Node* newnode = new Node(x);
			先找到尾节点
			//Node* tail = _head->_prev;
			开始链接
			//tail->_next = newnode;
			//newnode->_prev = tail;
			//newnode->_next = _head;
			//_head->_prev = newnode;
		}
		//指向第一个有效节点
		iterator begin()
		{	//单参数隐式类型转换
			//return iterator(_head->_next);
			return _head->_next;
		}
		const_iterator begin()const
		{	//单参数隐式类型转换
			//return iterator(_head->_next);
			return _head->_next;
		}
		//指向头节点:哨兵位
		iterator end()
		{
			//return iterator(_head);
			return _head;
		}
		//指向头节点:哨兵位
		const_iterator end()const
		{
			//return iterator(_head);
			return _head;
		}
		//插入
		iterator insert(iterator pos,const T& x)
		{
			//创建插入节点
			Node* newnode = new Node(x);
			//记录插入位置
			Node* node = pos._node;
			Node* nodeprev = node->_prev;
			nodeprev->_next = newnode;
			newnode->_prev = nodeprev;
			newnode->_next = node;
			node->_prev = newnode;

			//return iterator(newnode);
			return newnode;

		}
		//头插
		void push_front(const T& x)
		{
			insert(begin(), x);
		}
		//erase 删除
		iterator erase(iterator pos)
		{
			//记录位置
			Node* node = pos._node;
			Node* nodenext = node->_next;
			Node* nodeprev = node->_prev;
			//开始链接
			nodeprev->_next = nodenext;
			nodenext->_prev = nodeprev;

			delete node;
			return nodenext;
		}
		//pop_back 尾删
		void pop_back()
		{
			erase(--end());
		}
		//pop_front 头删
		void pop_front()
		{
			erase(begin());
		}

		//clear 清理
		void clear()
		{
			iterator it = begin();
			while (it != end())
			{
				it = erase(it);
				//erase(it);
			}
		}
		~list()
		{
			clear();
			delete _head;
			_head = nullptr;
		}
		//list l2(l1) 拷贝构造
		list(list<T>& lt)
		{
			init();
			for (const auto& e : lt)
			{
				push_back(e);
			}
		}

		//赋值拷贝 传统写法
		/*list<T>& operator=(list<T>& lt)
		{
			if (*this != lt)
			{
				clear();
				for (const auto& e : lt)
				{
					push_back(e);
				}
			}
			return *this;
		}*/
		void swap(list<T>& tmp)
		{
			std::swap(_head, tmp._head);
		}
		//赋值拷贝 现代写法
		list<T>& operator=(list<T> lt)
		{
			swap(lt);
			return *this;
		}

		

		

	private:
		Node* _head;//带头节点:哨兵位
	};

	void test()
	{
		/*list<int> l1;
		l1.push_back(1);
		l1.push_back(2);
		l1.push_back(3);
		l1.push_back(4);
		l1.push_back(5);

		list<int>::iterator it = l1.begin();
		while (it != l1.end())
		{
			cout << *it << " ";
			it++;
		}
		cout << endl;
		
		l1.insert(l1.begin(), 0);
		l1.insert(l1.end(), 6);
		for (auto e : l1)
		{
			cout << e << " ";
		}
		//cout << endl;*/
		//list<int> l1;
		//l1.push_front(1);
		//l1.push_front(2);
		//l1.push_front(3);
		//l1.push_front(4);
		//l1.push_front(5);

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

		//list<int>::iterator it = l1.begin();

		/*l1.pop_front();
		l1.pop_back();*/

		//it = l1.erase(l1.begin());
		//it = l1.erase(l1.begin());
		//l1.erase(--l1.end());

		//l1.erase(l1.end());

		/*list<int> l1;
		l1.push_front(1);
		l1.push_front(2);
		l1.push_front(3);
		l1.push_front(4);
		l1.push_front(5);

		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;
		
		l1.clear();
		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;*/
		
		/*list<int> l1;
		l1.push_front(1);
		l1.push_front(2);
		l1.push_front(3);
		l1.push_front(4);
		l1.push_front(5);
		list<int> l2;
		l2.push_front(5);
		l2.push_front(5);
		l2.push_front(5);
		l2.push_front(5);
		l2 = l1;
		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;
		for (auto e : l2)
		{
			cout << e << " ";
		}
		cout << endl;*/
		//list<int> l1;
		//l1.push_front(1);
		//l1.push_front(2);
		//l1.push_front(3);
		//l1.push_front(4);
		//l1.push_front(5);
		//l1.erase(l1.begin());
		//l1.erase(l1.begin());
		//l1.erase(l1.begin());
		//l1.erase(l1.begin());
		//l1.erase(l1.begin());


		//list<int>::iterator it = l1.begin();
		//while (it != l1.end())
		//{
		//	cout << *it << " ";
		//	it++;
		//}
		//cout << endl;
		//for (auto e : l1)
		//{
		//	cout << e << " ";
		//}
		//cout << endl;
		//l1.clear();
		//for (auto e : l1)
		//{
		//	cout << e << " ";
		//}
		//cout << endl;
		list<int> l1;
		l1.push_front(1);
		l1.push_front(2);
		l1.push_front(3);
		l1.push_front(4);
		l1.push_front(5);
		l1.clear();
		for (auto e : l1)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	void print_list(const list<int>& lt)
	{
		list<int>::const_iterator it = lt.begin();
		while (it != lt.end())
		{
			//*it += 10;

			cout << *it << " ";
			++it;
		}
		cout << endl;

		for (auto e : lt)
		{
			cout << e << " ";
		}
		cout << endl;
	}
	struct AA
	{
		int _a1;
		int _a2;

		AA(int a1 = 1, int a2 = 1)
			:_a1(a1)
			, _a2(a2)
		{}
	};
	void test2()
	{
		list<AA> lt;
		AA aa1;
		lt.push_back(aa1);

		lt.push_back(AA());

		AA aa2(2, 2);
		lt.push_back(aa2);

		lt.push_back(AA(2, 2));

		list<AA>::iterator it = lt.begin();
		while (it != lt.end())
		{
			cout << (*it)._a1 << ":" << (*it)._a2 << endl;
			cout << it.operator*()._a1 << ":" << it.operator*()._a2 << endl;

			cout << it->_a1 << ":" << it->_a2 << endl;
			cout << it.operator->()->_a1 << ":" << it.operator->()->_a2 << endl;

			++it;
		}
		cout << endl;
	}
}

//test.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include <iostream>
using namespace std;
#include "List.h"


int main()
{

	lj::test();

	return 0;
}

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

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

相关文章

AUTOSAR之AUTOSAR OS(上)

1、OSEK OS 1.1 OSEK OS介绍 AUTOSAR OS是基于 OSEK OS发展而来&#xff0c;向下兼容OSEK OS&#xff0c;所以了解AUTOSAR OS之前我们了解一下OSEK OS。 OSEK操作系统&#xff08;OS&#xff09;是一个为分布式嵌入式系统所定义的单核操作系统。为适应汽车电子可靠性、实时性、…

【视觉SLAM】 十四讲ch7习题

简介 本文主要内容是《视觉SLAM十四讲》&#xff08;第二版&#xff09;第7章的习题解答&#xff0c;并介绍了在解答习题中的一下思考和总结的经验。本文代码部分参考了&#xff1a;HW-of-SLAMBOOK2 1、除了本书介绍的ORB特征点&#xff0c;你还能找到哪些特征点&#xff1f;…

Java聚合快递对接云洋系统小程序源码

&#x1f680;【物流新纪元】聚合快递如何无缝对接云洋系统&#xff0c;效率飙升秘籍大公开&#xff01;✨ &#x1f50d; 开篇揭秘&#xff1a;聚合快递的魅力所在 Hey小伙伴们&#xff0c;你是否还在为多家快递公司账号管理繁琐、订单处理效率低下而头疼&#xff1f;&#…

做不好PPT的原因

新手制作PPT长犯的10个错误 1.Word搬家 为了节约时间&#xff0c;直接把Word素材复制粘贴到PPT上&#xff0c;没有提炼 2.堆积图表 每个页面上堆积了大量的图表&#xff0c;却没有说明数据反映了什么趋势 3.图表业余 想用图表达自己的逻辑&#xff0c;但没有专业的模板&a…

【海洋生态环境】十大数据集合集,速看!

本文将为您介绍10个经典、热门的数据集&#xff0c;希望对您在选择适合的数据集时有所帮助。 1 MAI (Multi-scene Aerial Image) 发布方&#xff1a; 不列颠哥伦比亚大学德国航空航天中心慕尼黑大学 发布时间&#xff1a; 2021 简介&#xff1a; MAI 是用于在单个航拍图像中…

【原创】下载RealEstate10K数据集原始视频的方法

前言&#xff1a;目前互联网上能搜到下载RealEstate10K数据集原始视频的方法都已经不能用了&#xff0c;这篇博客介绍一种目前可用的下载RealEstate10K数据集原始视频的方法&#xff0c;并给出自动化的脚本代码。 目录 RealEstate10K简介 RealEstate10K标注文本下载 RealEst…

WhatsApp接不到验证码?快来看这个新功能,绑定邮箱

很多外贸朋友经常会因为WhatsApp接不到验证码来加我问这个解决方案&#xff0c;基本上我会的我都会帮一下忙。最近WhatsApp更新了一个非常实用的功能&#xff0c;绑定电子邮箱的功能&#xff0c;这个功能可以协助进行WhatsApp验证码的接收&#xff0c;下面来看下如何进行绑定吧…

【Linux-Platform】

目录 1. Linux驱动的分离与分层1.1 为什么要进行Linux驱动的分离与分层1.2 Linux驱动的分层 2. 驱动-总线-设备2.1 总线2.2 驱动2.3 设备 3. platform平台设备总线3.1 platform总线注册3.2 platform驱动3.3 platform设备 1. Linux驱动的分离与分层 1.1 为什么要进行Linux驱动的…

【人工智能专栏】基于人类反馈对语言模型进行强化学习 (RLHF)

Reinforcement Learning from Human Feedback (RLHF) 技术分解 字面上说,RLHF就是基于人类反馈(Human Feedback)对语言模型进行强化学习(Reinforcement Learning),和一般的fine-tune过程乃至prompt tuning自然也不同。RLHF 是一项涉及多个模型和不同训练阶段的复杂概念…

C++ 基础入门篇

文章目录 命名空间输入与输出缺省参数函数重载引用和const引用inline&#xff08;内联函数&#xff09; 命名空间 定义&#xff1a;命名空间需要用到namespace关键字&#xff0c;其后跟着命名空间的名字&#xff08;自定义&#xff09;&#xff0c;再接着就是一对花括号&#x…

气膜建筑在工业仓储厂房中的应用优势—轻空间

随着工业生产的快速发展&#xff0c;对仓储厂房的需求日益增长。气膜建筑作为一种新型的建筑形式&#xff0c;因其独特的优势在工业仓储领域逐渐受到青睐。以下是气膜建筑在工业仓储厂房中的主要应用优势。 快速建设与灵活布局 气膜建筑的一个显著优势是其建设速度快&#xff0…

免费分享:2021-2100中国多情景逐年干燥度模拟数据(附下载方法)

AI是表征一个地区干湿程度的指标&#xff0c;一般来说&#xff0c;根据AI分类可以概括地把区域分为湿润&#xff08;AI<1&#xff0c;相当于森林&#xff09;、半湿润&#xff08;AI在1-1.5&#xff0c;相当于森林草原&#xff09;、半干旱&#xff08;AI在1.5-4&#xff0c…

听,LLM在“说话“:智慧农场开启农业知识传播新范式

&#xff08; 于景鑫 国家农业信息化工程技术研究中心&#xff09;设施农业是现代农业的旗舰,集成了环境调控、水肥管理、植保防疫等多项先进技术。据统计,目前全国现代设施种植面积达到4000万亩&#xff0c;效率高、产出高、效益高的特点明显。北方地区每亩蔬菜日光温室年均纯…

十大免费录屏软件推荐:轻松录制高清视频教程

现在视频教程已经成为知识分享、教学演示以及内容创作的重要形式&#xff0c;无论是在线教育、游戏直播还是软件操作指导&#xff0c;高清、流畅的录屏软件都是我们不可获取的工具之一。 但目前市面上有很多录屏软件&#xff0c;哪款才是适合我们的呢&#xff1f;今天就给大家…

前端(vue3)和后端(django)的交互

vue3中&#xff1a; <template><div><h2>注册页面</h2><form submit.prevent"submitForm"><label for"username">用户名&#xff1a;</label><input type"text" id"username" v-model…

C++威力强大的助手 --- const

Welcome to 9ilks Code World (๑•́ ₃ •̀๑) 个人主页: 9ilk (๑•́ ₃ •̀๑) 文章专栏&#xff1a; C之旅 const是个奇妙且非比寻常的东西&#xff0c;博主从《Effective C》一书中认识到关于const更深层次的理解&#xff0c;写此博客进行巩固。 &#x…

无人机之运输的优势

无人机在进行运输任务时使用的是电力驱动&#xff0c;从而可以减少对环境的污染和碳排放&#xff0c;对于改善大气质量和减少碳足迹具有积极的意义。 无人机运输可以避免人为错误和事故的发生&#xff0c;通过预先设定的飞行路线&#xff0c;进行精确点投放。此外&#xff0c;还…

零基础小白备考PMP需要多长时间?

PMP考试在中国大陆&#xff0c;平均每三个月安排一次考试。报名缴费一般在考试前两个月&#xff0c;报完名后开始进入备考&#xff0c;所以基本上是2-3个月的时间。 PMP考试备考不是越久越好&#xff0c;把备考战线拉得太长 &#xff0c;我们的精力都是有限的&#xff0c;后期…

学习HTTP2中的HPACK算法

文章目录 HPACK动态表中的数据插入点两种基本数据类型 Integer String HPACK 专业术语&#xff1a; Header Field&#xff08;头部字段&#xff09;&#xff1a;指的是一个由name - value组成的键值对。名称和值都被视为不透明的字节序列。Dynamic Table&#xff08;动态表&a…

哪个软件可以识别字幕并生成文本?5款最佳工具分享

你是否曾在深夜&#xff0c;抱着手机或电脑&#xff0c;对着那些充满异国情调却无从下手的外语视频感到束手无策&#xff1f; 那些或激昂、或深情、或幽默的对话&#xff0c;因为语言的隔阂而变得遥不可及&#xff0c;让你的观看体验大打折扣。 别急&#xff0c;今天我来告诉你…