【C++】C++ STL 探索:List使用与背后底层逻辑

在这里插入图片描述

C++语法相关知识点可以通过点击以下链接进行学习一起加油!
命名空间缺省参数与函数重载C++相关特性类和对象-上篇类和对象-中篇
类和对象-下篇日期类C/C++内存管理模板初阶String使用
String模拟实现Vector使用及其模拟实现

本文将通过模拟实现List,从多个角度深入剖析其底层机制,详细讲解其内部实现原理和实际应用场景,帮助读者全面理解和掌握List的工作方式。

请添加图片描述
Alt
🌈个人主页:是店小二呀
🌈C语言专栏:C语言
🌈C++专栏: C++
🌈初阶数据结构专栏: 初阶数据结构
🌈高阶数据结构专栏: 高阶数据结构
🌈Linux专栏: Linux

🌈喜欢的诗句:无人扶我青云志 我自踏雪至山巅 请添加图片描述

文章目录

  • 前文:List介绍
  • 一、搭建创建List武器库
    • 1.1 创建节点ListNode
    • 1.2 迭代器ListIterator
      • 1.2.1 自定义类型迭代器
      • 1.2.2 交换语句
      • 1.2.3 迭代器运算符重载
        • 1.2.3.1 前置++与后置++
        • 1.2.3.2 前置--与后缀--
        • 1.2.3.4 *运算符重载
        • 1.2.3.5 ==与!=运算符重载
  • 二、正式模拟实现List
    • 2.1 武器库使用
    • 2.2 获得List开头和结尾迭代器
    • 2.3 关于迭代器失效
    • 2.4 insert
    • 2.5 erase
    • 2.6 复用insert与erase完成插入与删除
    • 2.7 operator->重载
    • 2.8 const_Iterator迭代器
      • 2.8.1 实现const_Iterator迭代器
      • 2.8.2 模板整合类型问题
    • 2.9 打印链表元素
  • 三、常规接口实现
    • 3.1 构造函数
    • 3.2 拷贝构造
    • 3.3 operator= 赋值运算符
    • 3.4 clear 清除
    • 3.5 ~list 析构函数
    • 3.6 size 查看当前链表元素个数
    • 3.7 empty 判空
  • List.h

前文:List介绍

list文档介绍

  • list是可以在常数范围内在任意位置插入和删除的序列式容器,并且该容器可以前后双向迭代
  • list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前面一个元素和后一个元素
  • list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效
  • 与其他的序列式容器相比,list通常在任意位置进行插入,移除元素的执行效率更好
  • 与其他序列式容器相比,list和forward_list最大的缺陷式不支持任意位置的随机访问。

在这里插入图片描述

在模拟实现list过程中,为了避免跟库中list发生冲突,需要创建个命名空间,在命名空间中实现list。

namespace ls
{class list{};
}

list的底层是双向链表结构,而链表是通过每个独立的节点利用指针连接在一起的数据结构。节点是组成链表基本单位。模拟实现带头双向循环链表,为了适应不同的数据类型,选择采用类模板。

一、搭建创建List武器库

1.1 创建节点ListNode

template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;//创建节点,是传数据创建的ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};

具体说明:为了适应不同类型的节点,将创建节点设计成类模板。访问限定符一般选用struct,由于默认访问权限是公开的,节点里面数据都是可以使用。当然使用class也是可以的,只需要将访问限定符设置为public。

节点对象实例化一般是传个数值,但是为了考虑类型和是否传参,可以设置一个全缺省构造函数

1.2 迭代器ListIterator

链表通过每个节点连接在一起,但这不能保证节点间地址是连续的。对此虽然原生指针是天然的迭代器,但是建立在物理空间连续的情况下

1.2.1 自定义类型迭代器

list<T> ::iterator it = lt.begin();
while (it != lt.end())
{*it += 10;cout << *it;it++;
}

由于链表节点间物理空间不连续,使用原生指针作为迭代器不能满足++或–操作。节点间又是通过指针连接在一起的,那么可以将指针封装到类中,通过运算符重载重新定义运算符的行为,达成我们的需求。不采用内置类型迭代器,选择自定义类型迭代器,其中自定义类型迭代器内部是通过指针实现的。

template<class T>struct ListIterator{typedef ListNode<T> Node;typedef  ListIterator<T> Self;Node* _node;ListIterator(Node* node):_node(node){}}

注意事项:

  • 将通过ListIterator类模板模拟实现迭代器,通过采用struct公开迭代器里面的数据
  • ListIterator(Node* node),这里迭代器的实现还是依赖指针,只是通过类封装改变运算符的行为,参数部分是传递指针类型(关键)
  • 其中迭代器是作为指向作用,申请资源不属于迭代器,而属于链表,不需要考虑析构的问题,迭代器就是玩数据
  • 数据公开意味着,内部数据可以被任意修改。没人会去跳过封装,使用内部的数据,没有意义。不同编译器中底层实现是不一样的(实现逻辑、名称),这本身就是一种隐式设置为私有的作用

1.2.2 交换语句

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

1.2.3 迭代器运算符重载

1.2.3.1 前置++与后置++
//前缀++
Self& operator++()
{_node = _node->_next;return *this;
}//后缀++
Self operator++(int)
{//构造一个tmp临时变量Self tmp(*this);_node = _node->_next;return tmp;
}
1.2.3.2 前置–与后缀–
Self& operator--()
{_node = _node->_prev;return *this;
}//后缀--
Self operator--(int)
{//构造一个tmp临时变量Self tmp(*this);_node = _node->_prev;return tmp;
}

具体说明:有拷贝构造就需要考虑深浅拷贝的问题。这里需要使用到浅拷贝,指向同一块空间,并且不需要考虑重复析构的问题,也说明了浅拷贝并都是坏处。临时对象tmp同指向一块空间,调用完临时对象被销毁,指向空间资源保留。这也导致了返回类型是指针还是引用。

1.2.3.4 *运算符重载
T& operator* ()
{return _node->_data;
}
1.2.3.5 ==与!=运算符重载
//通过指针指向的节点去判断是否相等
bool operator==(const Self& it)
{return _node == it._node;
}bool operator!=(const Self& it)
{return _node != it._node;
}

具体说明:

  • 关于形参部分使用const修饰,其一为了提醒对象顺序问题,其二在while(it != lt.end())lt.end()调用返回临时对象具有常性,在参数部分进行接收将权限降低。
  • 迭代器间的比较并不是比较指向数据,而是比较迭代器指向的位置

二、正式模拟实现List

2.1 武器库使用

前面知识是为了我们实现List提供武器库

template<class T>class list{typedef ListNode<T> Node;public:typedef  ListIterator<T> Iterator;private://创建哨兵位Node* _head;size_t _size;};

在这里插入图片描述

武器使用:

  • 关于typedef ListNode<T> Nodetypedef ListIterator<T> Iterator这两个类可以当作武器库,主要是为List类提供服务。那么对于ListIterator类来说ListNode<T> Node也是当作一个武器进行使用。
  • 对于带头双向循环链表,成员对象需要哨兵位和记录个数对象。

2.2 获得List开头和结尾迭代器

Iterator begin()
{//return Iterator(_head->_next);return _head->_next;
}Iterator end()
{//return Iterator(_head);return _head;
}

具体说明:

  • 由于正在使用自定义类型的迭代器,返回类型为Iterator自定义类型。返回类型可以写成Node*类型,单参数的拷贝构造函数支持隐式类型转换。

需要注意:

  • 返回值没有传引用返回,由于该接口功能是返回开头和节点迭代器,如果传引用返回,会影响下一次调用该节点。我们不需要拿到这个位置的迭代器,只需要拿到这个位置的信息。

小插曲:隐式类型转换

list<int> :: Iterator it = (lt._head->_next);

由于_ head属于list类中的私有对象,不能直接的访问私有成员,通过公共接口访问。不同编译器底层实现是不同的,这也体现了封装的重要性。

2.3 关于迭代器失效

关于vector学习,我们知道在扩容或缩容的时候,可能会出现迭代器失效的问题。在list这里insert不会出现迭代器失效,但是erase就会出现迭代器失效。

在这里插入图片描述

关于解决不同编译器下erase导致迭代器失效的问题。方法有两个:迭代器失效以后,不要直接使用,如何要使用按照规则重新更新使用,返回被删除数据的迭代器。

2.4 insert

void  insert(Iterator pos, const T& val)
{Node* newnode = new Node(val);Node* cur = pos._node;Node* prev = cur->_prev;newnode->_next = cur;newnode-> _prev = prev;cur->_prev = newnode;prev->_next = newnode;_size++;
}

这里跟数据结构实现双向链表任意位置插入数据的逻辑是一样的,不同的地方就是这里使用迭代器。

2.5 erase

//这些需要使用迭代器作为返回类型,怕出现迭代器失效
Iterator& erase(Iterator pos)
{Node* cur = pos._node;Node* next = cur->_next;Node* prev = cur->_prev;delete cur;next->_prev = prev;prev->_next = next;_size--;//注意点return Iterator(next);
}

具体说明:这里跟数据结构实现双向链表任意位置删除数据的逻辑是一样的。

需要注意:返回类型需要使用迭代器类型,怕出现迭代器失效,返回下一个位置的迭代器

2.6 复用insert与erase完成插入与删除

void push_back(const T& val)
{insert(end(), val);
}void pop_back()
{erase(--end());
}void push_front(const T& val)
{insert(begin(), val);
}void pop_front()
{erase(begin());
}

具体说明:

  • 在vector实现push_back和pop_back时,通过begin和end得到迭代器指向的位置。返回变量为具有常性的临时变量,不能通过++或–对其修改。
  • 在List中迭代器可以进行++或–操作,由于不是对临时对象本身进行修改,而是在运算符重载中改变了运算符的行为,修改是临时对象指向的内容。在vector中修改是对象本身当然是不信的。

2.7 operator->重载

给出场景:关于之前类模板实例化中模板参数为内置类型,这次将T改为自定义类型,注意A是自定义类型。

struct A
{int a1 = 0, a2 = 0;A(int a1,int a2):a1(1),a2(2){}
};
list<A> lt;
list<A> ::Iterator it = lt.begin();lt.push_back(A(1,2));
lt.push_back({3,3});
while (it != lt.end())
{cout << *it;it++;
}

在进行cout << *it中得到该类对象,并没有得到内部数据,对此有两个解决措施:

  1. (*it).a1直接访问成员
  2. 流插入的运算符重载

我们希望得到的效果是第二种写法,第一种感觉会冗余

  • 第一种:(*it).a1

  • 第二种:it->_a1

重点梳理:

  • 先梳理思路list<A> ::Iterator it代表了it属于Iterator类对象。这里将it看成迭代器,其中*被运算符重载为_node->_data,其中_data类型就是自定义类型A。
  • 那么*it行为就是得到自定义类型A对象,通过A对象.内部成员变量方式进行访问。
  • 对于第二种:也是想通过->运算符重载得到A类型对象,但是很奇怪,得到了A类型对象怎么去访问A类型对象中成员变量呢?对此编译器为了可读性,省略了一个->,实际上是这样子的it.operator->()->_a1,编译器省略之后it->_a1就可以了,提高了可读性。
//返回A类型对象指针,很合理啦
T* operator->()
{return &_node->_data;
}

在这里插入图片描述

2.8 const_Iterator迭代器

2.8.1 实现const_Iterator迭代器

关于迭代器相关接口已经实现完毕,如果我们需要实现个指向内容不可以被修改的迭代器呢?

思考问题:

  1. 我们为什么要用const_Iterator而不是const Iterator呢?
  2. const修饰迭代器,是迭代器本身不能被修改,还是迭代器指向内容不能被修改呢?
const Iterator begin() const
{return _head->_next;
}
const Iterator end() const
{return _head;
}

我们实现const版本迭代器目的就是为了指向内容不被修改。

分析过程

  • Iterator是一个类,如果在前面加const,它表示的意思是这个类对象本身不能被修改,而不是指向内容不能被修改。
  • 实现链表的迭代器不是内置类型,而是自定义类型,在自定义类型前面使用const修饰代表本身不能被修改,会导致++或–运算符之类的运算符重载会失效。
  • 指出问题:迭代器指向内容是否被修改,需要对实现迭代器的类里面解引用运算符重载进行const修饰
template<class T>struct ListConIterator{typedef ListNode<T> Node;typedef  ListConIterator<T> Self;//只针对解引用 修改了迭代器的行为 但是++ --是将迭代器位置改变,迭代器指向的内容不能被修改Node* _node;//迭代器还是依赖指针,通过类来改变运算符的行为//这里是浅拷贝 拷贝构造函数ListIterator(Node* node):_node(node){}Self& operator++(){_node = _node->_next;return *this;}//后缀++Self operator++(int){//构造一个tmp临时变量Self tmp(*this);_node = _node->_next;return tmp;}//这里需要判断空Self& operator--(){_node = _node->_prev;return *this;}////后缀--Self operator--(int) {//构造一个tmp临时变量Self tmp(*this);_node = _node->_prev;return tmp;}const  T& operator* () {return _node->_data;}//通过指针指向的节点去判断是否相等bool operator==(const Self& it){return _node == it._node;}bool operator!=(const Self& it){return _node != it._node;}const T* operator->() {return &_head->_data;}};

具体说明:将解引用操作符重载的返回值用const修饰,保证返回的数据不能被修改。这里没有对++和–的返回值进行修饰,因为++和–并不直接影响迭代器的内容,是针对迭代器(it)本身

2.8.2 模板整合类型问题

不足之处:对于Ierator类与const_Ierator类的解引用运算符重载大体功能相同,主要区别在于不同情况下需要使用返回值类型不同,实现两个类显得有点冗余。如果问题是在于类型上差异导致的,可以将两个封装到同一个类中,使用模板实现。

typedef ListIterator<T, T&, T*> Iterator;
typedef ListIterator<T, const T&, const T*> const_Iterator;template<class T,class Ref, class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef  ListIterator<T,Ref,Ptr> Self;Node* _node;//迭代器还是依赖指针,通过类来改变运算符的行为//这里是浅拷贝 拷贝构造函数ListIterator(Node* node):_node(node){}//Self& operator++()Self& operator++(){_node = _node->_next;return *this;}//后缀++Self operator++(int){//构造一个tmp临时变量Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}//后缀--Self operator--(int){//构造一个tmp临时变量Self tmp(*this);_node = _node->_prev;return tmp;}// T& operator* ()Ref operator* (){return _node->_data;}//通过指针指向的节点去判断是否相等bool operator==(const Self& it){return _node == it._node;}bool operator!=(const Self& it){return _node != it._node;}//T* operator->()Ptr operator->(){return &_head->_data;}};

2.9 打印链表元素

void PrintList(const list<int>& clt)
{list<int> ::Iterator it = clt.begin();while (it != clt.end()){*it += 10;cout << *it << " ";++it;}cout << endl;
}

三、常规接口实现

3.1 构造函数

    void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}

构造和拷贝构造都需要一份节点,那么可以复用一份

这里不需要对_data进行初始化,在new Node时已经完成

    list(){empty_init();}

3.2 拷贝构造

    list(const list<T>& lt){empty_init();for (auto e : lt){push_back(e);}}

3.3 operator= 赋值运算符

    list<T>& operator=(list<T> it){swap(it);			 return *this;}

3.4 clear 清除

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

说明:

  • 这里是简单的资源清除,没有删除哨兵位
  • 同时在使用erase时,需要将下一个位置迭代器返回

3.5 ~list 析构函数

~list()
{clear();				delete _head;_head = nullptr;
}

3.6 size 查看当前链表元素个数

    //获得基本信息size_t size(){return _size;}

3.7 empty 判空

bool empty()
{return _size == 0;
}

List.h

#include <iostream>using namespace std;//链表 任意位置插入 不怕迭代器失效
//链表 任意位置删除 怕迭代器失效 对此需要传下一个位置的迭代器
namespace bit
{//节点template<class T>struct ListNode{ListNode<T>* _next;ListNode<T>* _prev;T _data;//创建节点,是传数据创建的ListNode(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};//template<class T>//template<class T,class Ref, class Ptr>struct ListIterator{typedef ListNode<T> Node;typedef  ListIterator<T,Ref,Ptr> Self;Node* _node;//迭代器还是依赖指针,通过类来改变运算符的行为//这里是浅拷贝 拷贝构造函数ListIterator(Node* node):_node(node){}//Self& operator++()Self& operator++(){_node = _node->_next;return *this;}//后缀++Self operator++(int){//构造一个tmp临时变量Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(){_node = _node->_prev;return *this;}//后缀--Self operator--(int){//构造一个tmp临时变量Self tmp(*this);_node = _node->_prev;return tmp;}// T& operator* ()Ref operator* (){return _node->_data;}//通过指针指向的节点去判断是否相等bool operator==(const Self& it){return _node == it._node;}bool operator!=(const Self& it){return _node != it._node;}//T* operator->()Ptr operator->(){return &_head->_data;}};//template<class T>//struct ListConIterator//{//	typedef ListNode<T> Node;//	typedef  ListConIterator<T> Self;//	//只针对解引用 修改了迭代器的行为 但是++ --是将迭代器位置改变,迭代器指向的内容不能被修改//	Node* _node;//	//迭代器还是依赖指针,通过类来改变运算符的行为//	//这里是浅拷贝 拷贝构造函数//	ListIterator(Node* node)//		:_node(node)//	{}//	Self& operator++()//	{//		_node = _node->_next;//		return *this;//	}//	//后缀++//	Self operator++(int)//	{//		//构造一个tmp临时变量//		Self tmp(*this);//		_node = _node->_next;//		return tmp;//	}//	//这里需要判断空//	Self& operator--()//	{//		_node = _node->_prev;//		return *this;//	}//	////	//	//后缀--//	 Self operator--(int) //	{//		//构造一个tmp临时变量//		Self tmp(*this);//		_node = _node->_prev;//		return tmp;//	}//	 //	 T& operator* () //	{//		return _node->_data;//	}//	//通过指针指向的节点去判断是否相等//	bool operator==(const Self& it)//	{//		return _node == it._node;//	}//	bool operator!=(const Self& it)//	{//		return _node != it._node;//	}//	 T* operator->() //	{//		return &_head->_data;//	}//};template<class T>class list{typedef ListNode<T> Node;public://typedef  ListIterator<T> Iterator;typedef ListIterator<T, T&, T*> Iterator;typedef ListIterator<T, const T&, const T*> const_Iterator;//不要传引用,不要修改这些位置Iterator begin(){//return Iterator(_head->_next);//单参数的拷贝构造函数支持隐式类型转换return _head->_next;}Iterator end(){//return Iterator(_head);return _head;}const Iterator begin() const{return _head->_next;}const Iterator end() const{return _head;}const_Iterator begin() const{//return Iterator(_head->_next);//单参数的拷贝构造函数支持隐式类型转换return _head->_next;}const_Iterator end() const{//return Iterator(_head);return _head;}//构造-->同时拷贝需要也需要一份节点 那么就可以复用同一份void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;//不需要 newndoe->date 在实例化时,data已经是0了_size = 0;}list(){empty_init();}//拷贝构造list(const list<T>& lt){empty_init();for (auto e : lt){push_back(e);}}list<T>& operator=(list<T>& it){swap(it);			 return *this;}void swap(list<T>& it){std::swap(_head, it._head);std::swap(_size, it._size);}//通过迭代器来实现void  insert(Iterator pos, const T& val){Node* newnode = new Node(val);Node* cur = pos._node;Node* prev = cur->_prev;newnode->_next = cur;newnode-> _prev = prev;cur->_prev = newnode;prev->_next = newnode;_size++;}//这些需要使用迭代器作为返回类型,怕出现迭代器失效Iterator& erase(Iterator pos){Node* cur = pos._node;Node* next = cur->_next;Node* prev = cur->_prev;delete cur;next->_prev = prev;prev->_next = next;_size--;//注意点return Iterator(next);}void push_back(const T& val){insert(end(), val);}void pop_back(){erase(--end());}void push_front(const T& val){insert(begin(), val);}void pop_front(){erase(begin());}void clear(){Iterator it = begin();while (it != end()){it = erase(it);}}~list(){clear();				delete _head;_head = nullptr;}//获得基本信息size_t size(){return _size;}bool empty(){return _size == 0;}private://创建哨兵位Node* _head;size_t _size;};struct A{int a1 = 0, a2 = 0;A(int a1 , int a2):a1(1),a2(2){}};void PrintList(const list<int>& clt){list<int> ::Iterator it = clt.begin();while (it != clt.end()){*it += 10;cout << *it << " ";++it;}cout << endl;}void list_test(){//list<int> it;先插入//it.push_back(1);//it.push_back(2);//list<int> :: Iterator lt = it.begin();//A* ptr = &a1// (*ptr).a1;// ptr->_a1//返回的是临时变量 是一份临时拷贝 具有常性list<A> lt;list<A> ::Iterator it = lt.begin();lt.push_back(A(1,2));lt.push_back({3,3});while (it != lt.end()){cout << *it;it++;}}
}


以上就是本篇文章的所有内容,在此感谢大家的观看!这里是店小二呀C++笔记,希望对你在学习C++语言旅途中有所帮助!
请添加图片描述

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

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

相关文章

vue-router + el-menu

1. el-menu的router属性 在el-menu中有一属性&#xff1a;router&#xff0c;默认是false 1.1 使用默认配置&#xff0c;即false 这时候需要自己在点击子菜单的时候进行导航&#xff0c;在el-menu添加方法&#xff0c;里边有三个参数 index: 选中菜单项的 index,indexPath…

本地部署大语言模型详细操作步骤

本地部署大语言模型&#xff0c;尤其是像我这样的基于中文开源项目的大模型&#xff0c;涉及的步骤相对复杂&#xff0c;需要一定的技术背景。下面我将详细阐述整个流程&#xff1a; 环境准备&#xff1a; 硬件准备&#xff1a;大语言模型通常需要高性能的计算资源&#xff0c;…

uni-app实现web-view和App之间的相互通信

双向实时 如果app端部署成网站&#xff0c;则web-view就是iframe&#xff0c;使用也可以双向通讯 https://uniapp.dcloud.net.cn/component/web-view.html APP端代码 index.vue: <template><web-viewid"m-webview":fullscreen"true":src"…

不同vlan之间的通信方法

1.通过路由器的物理接口 1.给PC1,PC2配置IP地址&#xff0c;网关2.进入交换机配置vlan&#xff0c;交换机所有口都配置access口并绑定vlan3.配置路由器&#xff0c;进入路由器的两个接口配置网关IP和掩码缺点&#xff1a;成本高&#xff0c;每增加一个vlan就需要一个物理端口和…

辛巴赔付到账,罗永浩退一赔三:直播带货终于往好方向卷了下…

因为快手顶流辛巴扔出的一颗重磅炸弹「被辛巴架火上烤&#xff0c;带货顶流圈快乱成一锅粥了……」&#xff0c;把直播带货行业藏在深处的淤泥炸出了水面。 原本表面看上去清澈、安静的水面&#xff0c;越来越浑&#xff0c;且还冒着火星子&#xff01;‍‍‍‍‍‍‍ 自从这个…

无人机电调接线

接线方式&#xff1a; 电调的作用是将飞控板的PWM控制信号转变为电流信号 因为电机的电流是很大的&#xff0c;通常每个点击正常工作时都平均有3A左右的电流&#xff0c;如果没有电调的存在&#xff0c;飞控无法承受这么大的电流。 电调的选择&#xff1a;电调上标的电流值是…

六、图结构

文章目录 一、引入二、基本概念三、图的表示四、图的遍历4.1 图的深度优先遍历&#xff08;DFS&#xff09;4.2 图的广度优先遍历&#xff08;BFS&#xff09;4.3 图的深度优先 VS 广度优先 一、引入 二、基本概念 三、图的表示 package com.gyh.grapg;import java.util.ArrayL…

OpenCV结构分析与形状描述符(22)计算图像中某个轮廓或区域的矩函数moments()的使用

操作系统&#xff1a;ubuntu22.04 OpenCV版本&#xff1a;OpenCV4.9 IDE:Visual Studio Code 编程语言&#xff1a;C11 算法描述 计算一个多边形或光栅化形状直到三阶的所有矩。 该函数计算一个向量形状或光栅化形状直到三阶的矩。结果返回在 cv::Moments 结构中。 函数原型…

ATF UFS初始化笔记

1. JESD220 中关于UFS初始化的描述 原文 13.1.3 Initialization and boot code download process The initialization and boot code download process is made up of the following phases: partial initialization, boot transfer and initialization completion. 13.1.3.…

List<Map<String, Object>>汇总统计排序

开发环境&#xff1a;jdk 1.8 需求一&#xff1a; 1、统计每个小时(升序)不同事件的产品产量 2、统计不同事件&#xff08;OK 、NG&#xff09;的总产量 public static void main(String[] args) {//数据源List<Map<String, Object>> list new ArrayList<Map…

云计算实训48——k8s环境搭建(详细版)

1.创建主机、设置ip、设置hostname 2.设置免密登录 # 生成私钥 [rootk8s-master ~]# ssh-keygen Generating public/private rsa key pair. Enter file in which to save the key (/root/.ssh/id_rsa): /root/.ssh/id_rsa already exists. Overwrite (y/n)? y Enter passphr…

vue3 使用swiper制作带缩略图的轮播图

效果图 实现代码 <template><div class"wrap"><!-- 主轮播图 --><swiper :style"{--swiper-navigation-color: #fff,--swiper-pagination-color: #fff,}" :modules"modules" :navigation"true" :thumbs"{ …

计算机网络 第2章 物理层

文章目录 通信基础基本概念信道的极限容量编码与调制常用的编码方法常用的调制方法 传输介质双绞线同轴电缆光纤以太网对有限传输介质的命名规则无线传输介质物理层接口的特性 物理层设备中继器集线器一些特性 物理层任务&#xff1a;实现相邻节点之间比特&#xff08;0或1&…

pptpd配置文件/etc/pptpd.conf详解

正文共&#xff1a;1111 字 2 图&#xff0c;预估阅读时间&#xff1a;1 分钟 如果要在Linux系统配置PPTP&#xff08;Point-to-Point Tunneling Protocol&#xff0c;点到点隧道协议&#xff09;VPN&#xff0c;一般是使用pptpd软件。pptpd命令通常从配置文件/etc/pptpd.conf中…

单片机拍照_将采集的RGB图像封装为BMP格式保存到SD卡

文章目录 一、前言二、BMP文件结构2.1 BMP图片的格式说明 2.2 RGB888与RGB565格式是什么&#xff1f;&#xff08;1&#xff09;RGB565&#xff08;2&#xff09;RGB888&#xff08;3&#xff09;区别&#xff08;4&#xff09;如何构成&#xff08;5&#xff09;示例 三、实现…

【Leetcode56】合并区间(数组 | 排序)

文章目录 一、题目二、思路三、代码 一、题目 二、思路 先将所有子列表按照start_pos进行排序&#xff0c;有利于保持顺序性&#xff0c;每次处理新子列表时&#xff0c;只用和结果列表ans_lst的最后一个子列表对比&#xff0c;如果有重合则合并&#xff0c;然后将合并的新子列…

Java 入门指南:Java 并发编程 —— 同步工具类 Phaser(相位器)

文章目录 同步工具类Phaser主要特点核心方法使用步骤适用场景使用示例 同步工具类 JUC&#xff08;Java.util.concurrent&#xff09;是 Java 提供的用于并发编程的工具类库&#xff0c;其中包含了一些通信工具类&#xff0c;用于在多个线程之间进行协调和通信&#xff0c;特别…

创新实验报告VC++案例开发十二生肖的俄罗斯方块智力游戏完整代码设计方案

一&#xff0e;项目名称 十二生肖俄罗斯方块 二&#xff0e;开发背景&#xff1a; 俄罗斯方块是一个很低古老的一个小游戏&#xff0c;到但今日它还有很大的魅力。 三&#xff0e;技术路线或工作原理&#xff1a; 采用的软件及开发平台 Micosoft Visual 6.0 项目的总体方…

uniapp 原生插件开发 UI

前言&#xff1a; 在集成某些特定 原生SDK的时候&#xff0c;它本身是带UI控件的。当我们使用 uniapp 开发app的时候实是 可以使使用 nvue 页面&#xff0c;以 weex 的方式嵌入原生的UI控件。 我这边的场景是 接入连连app的支付&#xff0c;它有个自己的密码键盘 控件是原生的页…

树形弹窗选择框/vue2/Element/弹框选择

前言 此类选择器根据vueelementUI实现&#xff0c;使用vue3的可以根据此案例稍作改动即可实现&#xff0c;主要功能有弹出选择、搜索过滤、搜索结果高亮等&#xff0c;此选择器只支持单选&#xff0c;如需多选可在此基础进行改造。 效果图 代码实现 使用时&#xff0c;props-…