【C++】—— list 模拟实现

news2024/11/14 14:10:28

【C++】—— list 模拟实现

  • 1 list 基础结构
  • 2 默认构造
  • 3 迭代器
    • 3.1 整体框架
    • 3.2 成员函数
    • 3.3 begin() 与 end() 的实现
    • 3.4 operator-> 的实现
    • 3.5 const 迭代器
      • 3.5.1 const 迭代器为什么命名 const_iterator
      • 3.5.2 const 迭代器的实现
      • 3.5.3 合并两个迭代器
  • 4 源码

1 list 基础结构

   l i s t list list 的底层就是我们之前学过的双向链表,它由一个哨兵位头结点 _pHead记录链表长度_size 组成。
  而链表中的每个节点都是由两个自身类型指针一个存储数据的变量组成的。因此,我们不仅要定义链表的类,还要定义节点的类

  下面是 l i s t list list 的成员变量和整体框架

namespace my_list
{
	template<class T>
	struct ListNode
	{
		ListNode* _prev;
		ListNode* _next;
		T _val;
		
		//默认构造
		ListNode(const T& val = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_val(val)
		{}
	};


	template<class T>
	class list
	{
		typedef ListNode<T> Node;//给节点重命名
		//成员函数···
		
	private:
		Node* _pHead;//哨兵位头结点指针
		size_t _size;
	}
}

  因为我们要频繁访问节点,因此我们直接用 s t r u c t struct struct 定义节点,其成员变量默认全公有
  
  

2 默认构造

  我们先写一个简单的无参默认构造出来。

  默认构造可以直接这样写吗?

list()
	:_pHead(nullptr)
	,_size(0)
{}

  不可以的,因为双向链表有个哨兵位即使链表中没有任何数据,头节点指针也是指向哨兵位,而不是空,所以我们应创建哨兵位,并将其初始化
  而哨兵位的前驱指针 _ p r e v prev prev 和后继指针 _ n e x t next next,因为整个链表只有它自己,它的前一个和后一个节点都是自己,因此 _ p r e v prev prev 和 _ n e x t next next 都是指向哨兵位自己

list()
{
	_pHead = new Node;
	_pHead->_next = _pHead;
	_pHead->_prev = _pHead;
	_size = 0}

  其是不仅仅是无参的构造,所有的构造函数第一步都是初始化哨兵位,因此我们不妨单独写一个函数出来

list()
{
	CreateHead()}

void CreateHead()
{
	_pHead = new Node;
	_pHead->_next = _pHead;
	_pHead->_prev = _pHead;
	_size = 0;
}

  
  这里有个问题就是CreateHead()是非 c o n s t const const 成员函数,那定义 c o n s t const const 成员是否还能来调用呢?答案自然是可以的因为 c o n s t const const 变量在定义的时候是不具有 c o n s t const const 属性的,定义完成之后才有。比如说:

//如果在定义之前就具有const属性,那么n就无法赋值
//const变量只有在定义时可以被赋值
const int n = 10;
const list<int> l1;

  
  

3 迭代器

  要模拟实现 l i s t list list迭代器的实现是其中的重中之重。
  前面我们模拟实现 s t r i n g string string v e c t o r vector vector,他们的迭代器都是原生指针,他们的原生指针完美符合迭代器的所有要求。其本质是因为他们底层物理空间是连续的。
  但 l i s t list list 不像 s t r i n g string string v e c t o r vector vector 那样天生丽质,它的底层结构是一个一个节点,并不连续,无法满足迭代器的要求(比如 ++,我们希望的是迭代器跳到下一个节点,如果使用原生指针,因为不是连续的物理空间,当前节点 ++ 大概率是个野指针)。
  但没关系, l i s t list list 可以通过封装,通过运算符重载,来满足迭代器的要求。
  

3.1 整体框架

  迭代器其本身就是模拟指针的行为,既然节点的原生指针无法满足迭代器的要求,我们对节点指针进行封装,通过运算符重载让其满足迭代器的需求

template<class T>
struct ListIterator
{
	typedef ListIterator<T> Self;//给自身类(迭代器)重命名,短一点方便
	typedef ListNode<T> Node;//节点重命名

	//成员变量:节点的指针
	Node* pNode;

	//成员函数
	//··· 

};

  因为待会链表中要大量访问成员变量,我们直接用默认全公有的 s t r u c t struct struct
  

到现在,我们一共实现了三个类,为什么要实现三个类呢?我们先把每个类的作用过一遍:

  • class list:链表这个类是链表的基本结构,指向哨兵位的头结点,管理这整个链表
  • struct ListNode:节点这个类是因为链表中每个节点都是自定义类型,每个数据都是存在一个独立的结构体里面
  • struct ListIterator:迭代器这个类,遍历整个链表本来是用节点的指针,但是节点的指针是不符合我们的预期,我们希望有一个迭代器统一的方式进行遍历,因此我们用一个结构去封装节点的指针,封装以后通过重载运算符使节点的指针能达到迭代器那样的行为

  
  

3.2 成员函数

  有了迭代器这个类,我们就可以运用运算符重载满足迭代器的行为啦

template<class T>
struct ListIterator
{
	typedef ListIterator<T> Self;
	typedef ListNode<T> Node;

	//成员变量
	Node* pNode;
	
	//解引用
	T& operator*()
	{
		return pNode->_val;
	}
	
	//前置++
	Self& operator++()
	{
		pNode = pNode->_next;
		return *this;
	}
	
	//后置++
	Self& operator++(int)
	{
		Self tmp = *this;
		pNode = pNode->_next;
		return tmp;
	}
	
	//前置--
	Self& operator--()
	{
		pNode = pNode->_prev;
		return *this;
	}
	
	//后置--
	Self& operator--(int)
	{
		Self tmp = *this;
		pNode = pNode->_prev;
		return tmp;
	}
	
	//不等于
	bool operator!=(const Self& x)
	{
		return pNode != x.pNode;
	}
	
	//等于
	bool operator==(const Self& x)
	{
		return pNode == x.pNode;
	}
}

  
  

3.3 begin() 与 end() 的实现

  迭代器的基本行为实现了,我们也可以在list类中实现begin()end()等函数了

  begin()函数是返回第一个迭代器,我们要构造第一个位置的迭代器,那怎么构造呢?我们用第一个节点的指针,即_pHead->_next,就能构造第一个迭代器,但现在ListIterator类中还缺少一个构造函数

//默认构造
ListIterator(Node* p = nullptr)
	:pNode(p)
{}

//拷贝构造
ListIterator(const ListIterator& x)
{
	pNode = x.pNode;
}

  其实上述拷贝构造可以不用实现,编译器会自己生成一个拷贝构造,完成浅拷贝,指向链表的节点。
  这里不要认为有指针指向资源就要自己实现深拷贝,而是看指针所指向的资源是不是属于自己的。像 s t r i n g string string v e c t o r vector vector 那些就需要自己实现深拷贝,但迭代器指向的是链表的资源,并不是迭代器自己的,并且迭代器本身的目标就是指向链表的节点,以此来访问遍历链表,因此浅拷贝即可。
  所以赋值重载析构与不需要写
  
  现在,万事具备只欠东风,我们还要在list类中将ListIterator类 t y p e d e f typedef typedef i t e r a t o r iterator iterator

template<class T>
class list
{
	typedef ListNode<T> Node;
	typedef ListIterator<T> iterator;//重命名为iterator
	//成员函数···
	
private:
	Node* _pHead;
	size_t _size;
}

  下面是begin()的实现

iterator begin()
{
	iterator it(_pHead->_next);
	return it;
}

  上述是有名对象的写法,我们可以用匿名对象的写法

iterator begin()
{	
	return iterator(_pHead->_next);
}

  
  甚至我们可以用隐式类型转换,直接传指针就好啦

  迭代器本身就是节点的指针,指针节点指针本身不满足迭代器的那些需求,所以我们才用一个类把他封装一层,用重载运算符使其达到迭代器的要求。

  

iterator begin()
{
	return _pHead->_next;
}

  end()最后一个数据的下一个位置,就是哨兵位的头结点

iterator end()
{
	return _pHead;
}

  
  

3.4 operator-> 的实现

  既然迭代器模拟的是指针的行为,那它还要实现 operator->
  什么情况下用到 -> 运算符呢?

  现在我们定义一个类型 AA,链表中存储的数据是 AA 类型,我们想依次遍历链表,打印每个节点 AA 中的两个成员变量

struct AA
{
	int _a1 = 1;
	int _a2 = 2;
}

void test1()
{
	list<AA> lta;
	lta.push_back(AA());
	lta.push_back(AA());
	lta.push_back(AA());

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

  上述(*it)._a1it->_a1的写法是等价的,但现在还没重载->运算符

  下面是operator->的实现方式

T* operator->()
{
	return &(pNode->_val);
}

  operator->的实现方式非常奇怪,返回的是T*
  
  其实这里省略了一个 ->,因为太难看了,为了可读性省略了一个 ->

cout << it->_a1 << " " << it->_a2 << endl;

//本质是这样的
cout << it->->_a1 << " " << it->->_a2 << endl;

  
  第一个->是运算符重载出来的,第二个则是普通的->
  本质是这样的:

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

cout << &(pNode->_val)->_a1 << " " << &(pNode->_val)->_a2 << endl;

  
  

3.5 const 迭代器

3.5.1 const 迭代器为什么命名 const_iterator

  首先问大家一个问题: c o n s t const const 迭代器为什么是const_iterator,而不是const iterator

   c o n s t const const 迭代器是自身不能修改还是指向的内容不能修改呢?
  就和指针一样,指针的 c o n s t const const 有两个,一个在 * 之前,一个在 * 之后

T* const ptr1//指针本身不能修改
const T* ptr2//指向的内容不能修改

  
  如果是 const iterator ,const 直接修饰一个变量,就是这个变量本身不能修改,即迭代器本身不能修改,而我们 c o n s t const const 迭代器是要指向的内容不能修改,所以const_iterator更合适
  
  

3.5.2 const 迭代器的实现

  那我们如何让迭代器指向的内容不能修改呢?

  迭代器修改我们指向的内容是怎么修改的?通过operator*operator->,那我们在其返回值上加上 c o n s t const const 就不能修改了

const T& operator*()
{
	return pNode->_val;
}

const T* operator->()
{
	return &(pNode->_val);
}

  那我们就再自己实现一个 c o n s t const const 迭代器的封装吧

template<class T>
struct const_ListIterator
{
	typedef const_ListIterator<T> Self;
	typedef ListNode<T> Node;

	Node* pNode;

	const_ListIterator(Node* p = nullptr)
		:pNode(p)
	{}
	
	const T& operator*()
	{
		return pNode->_val;
	}

	const T* operator->()
	{
		return &(pNode->_val);
	}

	Self& operator++()
	{
		pNode = pNode->_next;
		return *this;
	}

	Self& operator++(int)
	{
		Self tmp = *this;
		pNode = pNode->_next;
		return tmp;
	}

	Self& operator--()
	{
		pNode = pNode->_prev;
		return *this;
	}

	Self& operator--(int)
	{
		Self tmp = *this;
		pNode = pNode->_prev;
		return tmp;
	}

	bool operator!=(const Self& x)
	{
		return pNode != x.pNode;
	}

	bool operator==(const Self& x)
	{
		return pNode == x.pNode;
	}
};

  

3.5.3 合并两个迭代器

  上面我们再重新封装了一个const_iterator,基本满足了需求,但是代码太冗余了,除了operator*operator-> 的返回值类型不一样,其他代码全是一样的,有什么办法将他们合二为一呢?

  我们来看一下库中是怎么实现的

在这里插入图片描述

  库中的__list_iterator类,用了三个模板参数,新增了 RefPtr 两个参数。

  在 l i s t list list 类中,将__list_iterator<T, T&, T*> t y p e d e f typedef typedef i t e r a t o r iterator iterator,将__list_iterator<T, const T&, const T*> t y p e d e f typedef typedef c o n s t const const_ i t e r a t o r iterator iterator
  也就是说 i t e r a t o r iterator iterator R e f Ref Ref 参数即 T& P t r Ptr Ptr 即参数 T* c o n s t const const_ i t e r a t o r iterator iterator R e f Ref Ref参数即 c o n s t const const T& P t r Ptr Ptr 参数即 c o n s t const const T*

operator* 举例:
  T& 传给RefRefreferenceoperator*的返回值是reference,替换过来operator*的返回值就是 T&
c o n s t const const_ i t e r a t o r iterator iterator
   c o n s t const const T& 传给 R e f Ref Ref,替换过来operator*的返回值就是 c o n s t const const T&

  其实,这种写法与上面我们自己实现两个类模板并没有本质区别,他们实例化出来都是两个不同的类。不同的是第一种写法是我们自己写了两个不同的类,而第二种是我们通过控制模板参数让编译器实例化出两个不同的类,把我们干的活交给了编译器
  
代码如下:

template<class T, class Ref, class Ptr>
struct ListIterator
{
	typedef ListIterator<T, Ref, Ptr> Self;
	typedef ListNode<T> Node;

	Node* pNode;

	ListIterator(Node* p = nullptr)
		:pNode(p)
	{}
	
	Ref operator*()
	{
		return pNode->_val;
	}

	Ptr operator->()
	{
		return &(pNode->_val);
	}

	Self& operator++()
	{
		pNode = pNode->_next;
		return *this;
	}

	Self& operator++(int)
	{
		Self tmp = *this;
		pNode = pNode->_next;
		return tmp;
	}

	Self& operator--()
	{
		pNode = pNode->_prev;
		return *this;
	}

	Self& operator--(int)
	{
		Self tmp = *this;
		pNode = pNode->_prev;
		return tmp;
	}

	bool operator!=(const Self& x)
	{
		return pNode != x.pNode;
	}

	bool operator==(const Self& x)
	{
		return pNode == x.pNode;
	}
};

  
  

4 源码

对于 l i s t list list 的其他成员函数,与前面的 s t r i n g string string v e c t o r vector vector 实现起来都大同小异,这里就不再赘述了,我们直接看源码

#pragma once
#include<iostream>
#include<assert.h>

using namespace std;

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

		ListNode(const T& val = T())
			:_prev(nullptr)
			,_next(nullptr)
			,_val(val)
		{}

	};

	template<class T, class Ref, class Ptr>
	struct ListIterator
	{
		typedef ListIterator<T, Ref, Ptr> Self;
		typedef ListNode<T> Node;

		Node* pNode;

		ListIterator(Node* p = nullptr)
			:pNode(p)
		{}
		

		Ref operator*()
		{
			return pNode->_val;
		}

		Ptr operator->()
		{
			return &(pNode->_val);
		}

		Self& operator++()
		{
			pNode = pNode->_next;
			return *this;
		}

		Self& operator++(int)
		{
			Self tmp = *this;
			pNode = pNode->_next;
			return tmp;
		}

		Self& operator--()
		{
			pNode = pNode->_prev;
			return *this;
		}

		Self& operator--(int)
		{
			Self tmp = *this;
			pNode = pNode->_prev;
			return tmp;
		}

		bool operator!=(const Self& x)
		{
			return pNode != x.pNode;
		}

		bool operator==(const Self& x)
		{
			return pNode == x.pNode;
		}
	};

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


		list()
			:_pHead(nullptr)
			, _size(0)
		{
			CreateHead();
		}

		list(int n, const T& value = T())
		{
			CreateHead();

			while (n--)
			{
				push_back(value);
			}
		}

		template <class Iterator>
		list(Iterator first, Iterator last)
		{
			CreateHead();
			Iterator it = first;
			while (it != last)
			{
				push_back(*it);
				++it;
			}
		}

		list(const list<T>& l)
		{
			CreateHead();
			const_iterator it = l.begin();
			while (it != l.end())
			{
				push_back(*it);
				++it;
			}
		}
		list<T>& operator=(list<T> l)
		{
			swap(l);
			return *this;
		}
		~list()
		{
			clear();
			delete _pHead;
			_pHead = nullptr;
		}

		iterator begin()
		{
			return _pHead->_next;
		}
		iterator end()
		{
			return _pHead;
		}
		const_iterator begin() const
		{
			return _pHead->_next;
		}
		const_iterator end() const
		{
			return _pHead;
		}

		size_t size()const
		{
			return _size;
		}
		bool empty()const
		{
			return _size == 0;
		}

		T& front()                                                                                                
		{
			return _pHead->_next->_val;
		}

		T& back()
		{
			return _pHead->_prev->_val;
		}

		const T& front() const
		{
			return _pHead->_next->_val;
		}

		const T& back() const
		{
			return _pHead->_prev->_val;
		}

		void push_back(const T& val)
		{
			Node* p = new Node(val);

			p->_next = _pHead;
			_pHead->_prev->_next = p;
			p->_prev = _pHead->_prev;
			_pHead->_prev = p;
			++_size;
		}
		void pop_back()
		{
			Node* p = _pHead->_prev;

			p->_prev->_next = _pHead;
			_pHead->_prev = p->_prev;
			delete p;
			--_size;
		}
		void push_front(const T& val)
		{
			Node* p = new Node(val);

			p->_next = _pHead->_next;
			p->_prev = _pHead;
			_pHead->_next = p;
			p->_next->_prev = p;
			++_size;
		}
		void pop_front()
		{
			Node* p = _pHead->_next;

			_pHead->_next = p->_next;
			p->_next->_prev = _pHead;
			delete p;
			--_size;
		}

		iterator insert(iterator pos, const T& val)
		{
			Node* p = new Node(val);

			p->_next = pos.pNode;
			p->_prev = pos.pNode->_prev;
			pos.pNode->_prev->_next = p;
			pos.pNode->_prev = p;

			++_size;

			return pos;
		}

		iterator erase(iterator pos)
		{
			assert(pos != end());

			iterator ret = pos.pNode->_next;
			
			pos.pNode->_prev->_next = pos.pNode->_next;
			pos.pNode->_next->_prev = pos.pNode->_prev;
			delete pos.pNode;

			--_size;

			return ret;
		
		}

		void clear()
		{
			iterator it = begin();

			while (it != end())
			{
				iterator cur = it++;
				delete cur.pNode;
			}
			_pHead->_next = _pHead;
			_pHead->_prev = _pHead;

			_size = 0;
		}

		void swap(list<T>& l)
		{
			std::swap(_pHead, l._pHead);
			std::swap(_size, l._size);
		}


	private:
		Node* _pHead;
		size_t _size;

		void CreateHead()
		{
			_pHead = new Node;
			_pHead->_next = _pHead;
			_pHead->_prev = _pHead;
			_size = 0;
		}
	};
}

template<class Container>
void print_container(const Container& v)
{
	auto it = v.begin();
	while (it != v.end())
	{
		cout << *it << " ";
		++it;
	}
	cout << endl;
}

  
  
  
  
  


  好啦,本期关于 l i s t list list 的知识就介绍到这里啦,希望本期博客能对你有所帮助。同时,如果有错误的地方请多多指正,让我们在 C++ 的学习路上一起进步!

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

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

相关文章

“RISCV+AI”

概述 设计方案 主要有两种设计方案。 RISCV核ASIC RISCV核是标准的基于RISCV指令集的CPU设计&#xff0c;ASIC部分通常是基于RISCV自带的向量扩展指令集构建的向量处理器&#xff0c;或是自定义的矩阵计算单元。 根据CPUAI ASIC部件的接口可以分为紧耦合和松耦合的设计1。 …

OpenCVHaar级联器实现人脸捕捉和微笑检测

概念 Haar 级联分类器是由多个简单分类器组成的复杂分类器&#xff0c;每个简单分类器都由 Haar 特征训练得到。Haar 级联器因其简单和快速而被应用于某些场景。OpenCV 提供多种预训练的 Haar 特征级联分类器&#xff0c;其已经在大量图像上进行了训练&#xff0c;并且针对特定…

【Ubuntu】虚拟机安装USB摄像头ROS驱动 usb_cam(最新方法)

写在前面&#xff1a; &#x1f31f; 欢迎光临 清流君 的博客小天地&#xff0c;这里是我分享技术与心得的温馨角落。&#x1f4dd; 个人主页&#xff1a;清流君_CSDN博客&#xff0c;期待与您一同探索 移动机器人 领域的无限可能。 &#x1f50d; 本文系 清流君 原创之作&…

宝塔Linux部署 Vue + Spring Boot + MySQL + Redis

服务器安装宝塔 不同的服务器操作系统对应着不同的安装命令。这里我用的是centos8.6&#xff0c;所以安装命令是 yum install -y wget && wget -O install.sh http://download.bt.cn/install/install_6.0.sh && sh install.sh 注意安装宝塔面板时&#xff0…

如何在uni-app中使用原子化 CSS——UnoCSS

原文地址&#xff1a;原文链接 一、前言 UnoCSS是一个即时的原子化 CSS 引擎&#xff0c;旨在灵活和可扩展。核心是不拘一格的&#xff0c;所有的 CSS 工具类都是通过预设提供的。 那么&#xff0c;UnoCSS 与其他框架的有何不同之处呢&#xff1f; UnoCSS 由 Windi CSS 团队…

奥维互动地图经纬度导入,再导出ovjsn再转化为kml格式

一、使用python将excel表中的经纬度换算成小数格式。 在文件上看到的经纬度是东经 1165′27.78″&#xff0c;北纬 2310′57.18″&#xff0c;要转化为116.09105,23.182550000000003 格式。如果要用vba编写函数&#xff0c;可能比较麻烦&#xff0c;为此我使用python来转化 i…

4.提升客户服务体验:ChatGPT在客服中的应用(4/10)

本文大纲旨在指导撰写一篇全面探讨ChatGPT如何通过优化客户服务流程、提供实际应用案例和用户反馈&#xff0c;以提升客户服务体验的深入博客文章。 引言 在当今竞争激烈的商业环境中&#xff0c;客户服务已成为企业成功的关键因素。优质的客户服务不仅能够增强客户满意度和忠…

编程辅助工具下一个热门应用场景是什么?(二)

&#x1f381;&#x1f449;点击进入文心快码 Baidu Comate 官网&#xff0c;体验智能编码之旅&#xff0c;还有超多福利&#xff01;&#x1f381; 本系列视频来自百度工程效能部的前端研发经理杨经纬&#xff0c;她在由开源中国主办的“AI编程革新研发效能”OSC源创会杭州站1…

DFS:深搜+回溯+剪枝实战解决OJ问题

✨✨✨学习的道路很枯燥&#xff0c;希望我们能并肩走下来! 文章目录 目录 文章目录 前言 一 排列、子集问题 1.1 全排列I 1.2 子集I 1.3 找出所有子集的异或总和 1.4 全排列II 1.5 字母大小写全排列 1.6 优美的排列 二 组合问题 2.1 电话号码的数字组合 …

Qt | AI+Qt6.5.3+ubuntu20.04+FFmpeg实现音视频编解码(播放一个中秋节快乐视频为例)

点击上方"蓝字"关注我们 01、下载 >>> FFmpeg下载官网:https://ffmpeg.org// 本次选择下载linux版本的 环境准备Qt6.5.3ubuntu+虚拟机FFmpeg

解决Visual Studio中OpenCV链接错误:LNK2019无法解析的外部符号

创作不易&#xff0c;您的打赏、关注、点赞、收藏和转发是我坚持下去的动力&#xff01; 原因分析 错误提示 LNK2019: 无法解析的外部符号 表示在编译过程中&#xff0c;链接器找不到 OpenCV 的相关函数实现。这通常是由于以下原因引起的&#xff1a; 没有正确链接 OpenCV 库&…

[SaaS] FacyTech

Sora还没开源,但这家国产AIGC视频公司已经靠还原现实赚钱了我们找到了朱啸虎说“很酷”的公司https://mp.weixin.qq.com/s/rm_dylLhf4FP01c_hdU3Lw1.tryon 这图ootdiffusion+comfyui工作流吗?lora+controlnet openpose+ipa

数据结构—栈

栈 概念 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In First Out&#xff09;的原则。 压栈&#xff1a;栈…

兰花种类识别系统源码分享

兰花种类识别检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer Vis…

打印图案,输入一个整数表示输出的行数,也表示组成x的长度

//打印图案&#xff0c;输入一个整数表示输出的行数&#xff0c;也表示组成x的长度 //打印图案&#xff0c;输入一个整数表示输出的行数&#xff0c;也表示组成x的长度 //5 //*___* //_*_*_ //__* //-*-*- //*---*- #include<stdio.h> int main() {int i 3;int j 0,…

anaconda下载安装教程

anaconda是python的包管理器&#xff0c;通过它来安装python库比较方便快捷&#xff0c;可以使用conda或者pip命令进行安装。 微智启软件工作室最常用的是Anaconda3-2021.11-Windows-x86_64.exe这一个版本&#xff0c;当然如果你使用其他版本也可以&#xff0c;其他版本特别是最…

激光雷达点云处理—学习随记

一、激光雷达基本概念 激光雷达&#xff08;Light Detection and Ranging&#xff0c;LiDAR&#xff09;&#xff0c;是一种发射激光&#xff08;可见光-近红外&#xff09;于被瞄准物体表面并记录反射光被信号接收器接收到的时间以测定距离的方法。激光雷达通过以下公式确定物…

Redis学习以及SpringBoot集成使用Redis

目录 一、Redis概述 二、Linux下使用Docker安装Redis 三、SpringBoot集成使用Redis 3.1 添加redis依赖 3.2 配置连接redis 3.3 实现序列化 3.4 注入RedisTemplate 3.5 测试 四、Redis数据结构 一、Redis概述 什么是redis&#xff1f; redis 是一个高性能的&#xf…

电子电气架构---智能汽车应该是怎么样的架构?

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗你的人和事&#xff0c;多看一眼都是你的不…

rust GTK4 窗口创建与 wayland Subsurface (vulkan 渲染窗口初始化 (Linux) 上篇)

rust 有封装好的 GTK4 库 (gtk4-rs), 有封装好的 wayland 库 (wayland-rs), 有封装好的 vulkan 库 (vulkano), 单独使用其中的每一个, 都很简单. 但是, 把这些一起使用, 崩 !! 大坑出现了 ! 这个问题的难度超出了事先的预计 (所以原计划一篇文章分成了两篇), 而类似的事情在编…