当前位置: 首页 > news >正文

C++ - list介绍 和 list的模拟实现

list介绍

 list 是一个支持在常数范围内,任意位置进行插入删除的序列式容器,且这个容器可以前后双向迭代。我们可以把 list 理解为 双向循环链表的结构。

于其他结构的容器相比,list在 任意位置进行插入和函数的效率要高很多;而list 的缺点也很明显,它在随机访问容器当中的数据的时候,它只能从已知位置开始线性寻找,这样寻找相比于其他容器来说有时间上的消耗;而且在存储方面,因为是一个结点一个结点分开存储,所以会多开空间来存储各个结点之间的关系,在存储消耗上也更高。

 list 的使用

 构造函数

构造函数( (constructor))接口说明
list (size_type n, const value_type& val = value_type())构造的list中包含n个值为val的元素
list()构造空的list
list (const list& x)拷贝构造函数
list (InputIterator first, InputIterator last)用[first, last)区间中的元素构造list

list 的构造函数和 string ,vector是类似的,具体可以看以下博客:

C++ string类 迭代器 范围for_c++string迭代器_chihiro1122的博客-CSDN博客

 迭代器

 list 的迭代器不再像是 string 和 vector 当中使用 原生指针 来简单实现,而是使用类和对象来进行包装,这样可以让 指针 不能实现list 当中的 ++ 等等操作,用运算符重载函数来实现(具体迭代器的实现请看 list 的模拟实现)。

函数声明接口说明
begin +
end
返回第一个元素的迭代器+返回最后一个元素下一个位置的迭代器
rbegin +
rend
返回第一个元素的reverse_iterator,即end位置,返回最后一个元素下一个位置的
reverse_iterator,即begin位置

 注意

  • 因为 list 的存储结构和 string 之类的 存储结构不一样,string 存储结构是 一块连续的区间,所以,对于string类的迭代器就支持  类似   str.begin() + 5 ,这样的操作;但是因为 list 是不连续的空间,对于 "+" 这个操作符的代价就比 string要高,所以在list 的迭代器当中就没有实现 operator+ 这个函数!!
  • 迭代器的使用 ,不能用 " < " 的形式来判断迭代器的区间!!因为 list 的各个结点的存储空间不连续,如果直接用 " < " 来比较,比较的是指针存储的地址大小,这样会出大问题,对于迭代器的使用一般是 这样的 :
list<int> L( 10 , 1 );
list::iterator it = L.begin();while(it != L.end())
{cout << *it << " ";++it;
}
cout << endl;

 其他基本操作

 在STL 当中,这些函数基本使用都差不多,集体可以参照之前介绍 string 和 vector 的博客:

C++ string类 迭代器 范围for_c++string迭代器_chihiro1122的博客-CSDN博客

C++ string类-2_chihiro1122的博客-CSDN博客

C++ - 初识vector类 迭代器失效_chihiro1122的博客-CSDN博客

list capacity

函数声明接口说明
empty检测list是否为空,是返回true,否则返回false
size返回list中有效节点的个数

list element access

函数声明接口说明
front返回list的第一个节点中值的引用
back返回list的最后一个节点中值的引用

 list modifiers

函数声明接口说明
push_front在list首元素前插入值为val的元素
pop_front删除list中第一个元素
push_back在list尾部插入值为val的元素
pop_back删除list中最后一个元素
insert在list position 位置中插入值为val的元素
erase删除list position位置的元素
swap交换两个list中的元素
clear清空list中的有效元素

list 当中的迭代器失效

 list 当中的insert()函数就没有迭代器失效了,因为之前在vector 当中出现的 insert()函数迭代器失效,是因为vector 是一段连续的空间,需要扩容操作,而扩容就会导致迭代器失效;但是list 当中的每一次插入数据都是要开辟新空间,并不会影响到list 当中已经存在了的元素。

但是,对于删除的函数来说,比如 erase()函数,还是会存储迭代器失效的问题:

int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
list<int> l(array, array+sizeof(array)/sizeof(array[0]));
auto it = l.begin();
while (it != l.end())
{
// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给
其赋值
l.erase(it);
++it;

如上述例子,就是外部的迭代器失效问题,当it 指向的空间被删除之后,it指向空间也就被释放,那么在while语句当中的  ++it 这个重载运算符函数就找不到下一个了。

改正;

while (it != l.end())
{
l.erase(it++); // it = l.erase(it);
}

 STL当中的迭代器认识

 我们先来看下面三个函数的不同迭代器类型:

 

 上述有三种迭代器类型:

  •  InputIterator:单向迭代器,只能用 ++ 的操作。
  •  BidirectionalIterator:双向迭代器,可以用 ++ / -- 两个操作。
  • RandomAccessIterator:随机迭代器,可以用      ++ / -- / + / -       四个操作。

 不同的容器类型,对于迭代器的使用就有要求:

 比如,单链表就只能用 单向迭代器,双向和随机都不能用,那么对于库当中的双向和随机迭代器实现的函数,单链表也不能使用。

但是这三个迭代器是向上兼容的,就是说 随机 是 双向的 一种特殊情况,所以,使用随机迭代器的容器就可以使用 双向迭代器的函数;同样,双向 是 单向的一种特殊情况,双向的,可以使用单向的。

 list的模拟实现

 大致框架

#pragma oncenamespace My_List
{template<class T>struct List_Node  // 结点指针结构体{
·············};template<class T>struct List_iterator // 非const 迭代器{
··········};template<class T>class List         // List 类{
············private:Node* _head;};
}

 结点的结构体定义 

 在官方的List源代码当中,List容器的结点定义就是定义在 同一命名空间下的 一个结构体当中,因为在C++ 当中结构体已经升级为了类,所以在结构体当中也可以定义构造函数。

所以,因为结点当中有数值域,和指针域,我们就把这个结构体当做是一个构造结点的函数来实现,效果也是一样的,只不过使用的时候,使用new的方式来开空间和定义:

	template<class T>struct List_Node{List_Node<T>* _next;List_Node<T>* _prev;T _val;// List_Node 的构造函数List_Node(const T& val = T()):_next(nullptr),_prev(nullptr),_val(val){}};

当然,为了数值域的复用性,使用模版来对数值域的类型进行模版化。

 构造函数和析构函数

 无参数的构造函数

		List(){_head = new Node;_head->_next = _head;_head->_prev = _head;}

因为List的底层是 带头循环双向链表,所以没有结点的链接方式如上,只有一个头结点。

 析构函数

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

 这里可以直接复用clear()函数。

增删查改

 push_back():

		void push_back(const T& x){Node* tail = _head->_prev;Node* newNode = new Node(x);_head->_prev = newNode;tail->_next = newNode;newNode->_prev = tail;newNode->_next = _head;// 实现insert()函数之后// insert(end() , x);}

直接开空间然后修改链接关系即可。

 insert()函数:

这里的insert()函数的当中传入的 pos 指针不用检查合法性,因为这里是带头结点循环的链表,在头结点的前面和后面删都是可以的。代码:

		iterator insert(iterator pos, const T& x){Node* cur = pos._Node;Node* prev = cur->_prev;Node* newNode = new Node(x);prev->_next = newNode;newNode->_next = cur:cur->_prev = newNode;newNode->_prev = prev;return newNode;}

因为pos位置前插入元素之后,pos迭代器向后移动了以为,我们认为这样也属于迭代器失效,所以要返回新插入的元素的位置,防止外部迭代器失效。 

erase()函数

 erase()函数同样有迭代器失效的问题,所以亚需要返回新的迭代器:

		iterator erase(iterator pos){assert(pos != end());Node* cur = pos._Node;Node* prev = cur->_prev;Node* next = cur->_next;prev->_next = next;next->_prev = prev;delete[] cur; // 这里会有 pos 迭代器失效的问题,所要要返回新的迭代器return next;}

 push_front():

 直接复用insert()函数

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

 pop_back() 和 pop_front(): 

		void pop_back(){erase(--end());}void pop_front(){erase(begin());}

 clear( )和 size():

		size_t size(){return _size;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}_size = 0;}

 赋值运算符重载函数和swap()函数

		void swap(List<T>& L){std::swap(_head, L._head);std::swap(_head, L._size);}List<T>& operator(List<T> L){swap(L);return *this;}

 上述原理参考文章:(1条消息) C++-string类的模拟实现_chihiro1122的博客-CSDN博客当中赋值操作符重载函数(比较大小标题下)的介绍。

其实上述的 赋值重载运算符函数当中的模板类的类型名可以不用 List<T>,我们知道List<T>是类型名,List是类名,对于模版类,类型名不是 List,而是List<T>,但是如果是在类模板当中写,可以写类名也可以写类型名(下述写法也可以):

List& operator=(List L)

迭代器(重点)

 非const迭代器

 在上述介绍List迭代器的时候也介绍了,List当中的迭代器不是原生指针,而是自定义类型,所以在定义的时候有些难度,但是这样的好处是和 普通的原生指针迭代器一样,可以直接 ++ 后移,*解引用来访问元素,等等,因为自定义类型当中有运算符重载函数,这样就可以实现。

 其实List的迭代器本质上还是一个指针,只不过这个指针现在指向的是一个结点空间,其中不仅仅有数据域,还有指针域,那么直接解引用是不能访问到数据域的,这时候就要重载 “ * ”(解引用)运算符,实现也很简单,直接返回结点的数据域即可:

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

 我们再思考,我们在使用迭代器的时候会使用哪一些运算符,如下所示:
 

	while (it != L.end()){cout << *it << " ";++it; // 因为只重载了 前置的++}cout << endl;

 我们要对上述用到的运算都要进行重载,那么这个迭代器才能正常使用:

		List_iterator<T>& operator++ (){_Node = _Node->_next;return *this;}bool operator!= (const List_iterator<T>& L){return _Node != L._Node;}bool operator== (const List_iterator<T>& L){return _Node == L._Node;}

 最后是整个 迭代器结构体的构建,上述也说过,C++把结构体升级为了类,那么就可以使用构造函数来构造这个 迭代器对象。

首先,这个迭代器的成员其实就一个,就是某一个结点的指针,那么这个结构体的构造函数就只用对这一个对象进行初始化就行了,只需要在构造的时候传入这个结点的指针就可以:
 

		typedef List_Node<T> Node;Node* _Node;List_iterator(Node* node):_Node(node){}

 那么整个非const的迭代器就实现了,如下所示:

	template<class T>struct List_iterator{typedef List_Node<T> Node;Node* _Node;List_iterator(Node* node):_Node(node){}T& operator* (){return _Node->_val;}List_iterator<T>& operator++ (){_Node = _Node->_next;return *this;}bool operator!= (const List_iterator<T>& L){return _Node != L._Node;}bool operator== (const List_iterator<T>& L){return _Node == L._Node;}};

 然后,在类当中也需要给出 begin()和end()两个接口,begin()指向头结点 _head 的后一个,end 指向 _head 就行了:

		typedef List_iterator<T> iterator; // 一定要是公有的,不然不能访问iterator begin(){//return _head->_next; // 可以这样写,只有一个参数的构造函数发生 隐式类型的转换// 上下两种都是一样的return iterator(_head->_next);}// 返回值应该是引用,要不然 != 函数会出错 传值返回返回的不是 _head 是 _head 的一个拷贝 // 临时对象具有常性  ······· 1iterator end(){return _head; // 同样发生隐式类型的转换// 上下两种都可以//return iterator(_head);}

 需要注意几个问题: 

  •  上述代码注释当中提到的返回值类型应该是引用的问题(1),其实不用这样做,上述的 begin()和end()函数是在 operator!= 函数 和 构造函数当中使用的,所以只需要把 operator!= 函数的参数修改为 const 即可,上述已经做出了更改。
  • My_List::List<int>::iterator it = L.begin();     这里不是赋值,而是拷贝构造,因为L.begin() 是一个已经存在了的对象赋值给另一个对象需要调用拷贝构造函数,但是迭代器没有实现拷贝构造,这里的编译器自己实现的浅拷贝,这里的浅拷贝没有问题,因为我们这里需要的就是浅拷贝。
  • My_List::List<int>::iterator it = L.begin();     这里的两个指针都指向一个对象,那么为什么编译器没有报错呢?这是因为迭代器类没有实现析构函数,编译器就会自己调用默认析构函数去 释放迭代器指针空间,而迭代器指向的结点空间,并不需要迭代器类来进行释放,在List类的析构函数当中进行释放。

const迭代器

 这里的const 迭代器针对的是 const 对象,如果是const 修饰的对象,是不用普通的迭代器的,因为 从 const 对象 const 的迭代器(指针),返回给非 const迭代器构造函数的时候,从事const 变成了 非const,造成了权限的放大。

所以还是需要单独实现const 迭代器,对于const 迭代器和普通的迭代器功能类似,只不过在 operator* 这个函数当中返回的不是 非const 对象,而是 const 对象:

	template<class T>struct const_List_iterator{typedef List_Node<T> Node;Node* _Node;const_List_iterator(Node* node):_Node(node){}const T& operator* (){return _Node->_val;}const_List_iterator<T>& operator++ (){_Node = _Node->_next;return *this;}bool operator!= (const const_List_iterator<T>& L){return _Node != L._Node;}bool operator== (const const_List_iterator<T>& L){return _Node == L._Node;}};const_iterator begin() const{return _head->_next;}const_iterator end() const{return const_iterator(_head);}

这样做虽然能够实现const迭代器的效果,但是太冗余了,不太好。

所以这个时候我们有了多个模版参数的使用,如下所示,是 普通迭代器类的模版:
 

	template<class T, class Ref>struct List_iterator{······················

此时,在类当中的 typedef 哑鼓这样写:
 

		typedef List_iterator<T , T&> iterator; // 一定要是公有的,不然不能访问typedef List_iterator<T , const T&> const_iterator;

这样就可以在同一个类当中区分出 const类和 非 const 类,那么之间对 opeartor* 函数的修改就可以是这样的了;

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

同样,在类当中不同的地方都要进行修改,完整代码如下所示 :

template<class T, class Ref>struct List_iterator{typedef List_Node<T> Node;typedef List_iterator<T,Ref> selt;Node* _Node;List_iterator(Node* node):_Node(node){}Ref operator* (){return _Node->_val;}selt& operator++ (){_Node = _Node->_next;return *this;}selt operator++ (int){_Node = _Node->_next;return *this;}bool operator!= (const selt& L){return _Node != L._Node;}bool operator== (const selt& L){return _Node == L._Node;}};template<class T>class List{typedef List_Node<T> Node;public:typedef List_iterator<T , T&> iterator; // 一定要是公有的,不然不能访问typedef List_iterator<T , const T&> const_iterator;·········································

这样的话,看似是写了一个类,其实是写了两个类,但是代码的大小就节省了,这就是模版带来的好处。

在迭代器当中,还会使用到 " -> " 这个操作符,所以这个操作符也需要重载:

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

但是,在使用的时候,下面这个场景就有些怪,如下所示:

struct A{A(int a1 = 0, int a2 = 0):_a1(a1), _a2(a2){}int _a1;int _a2;};void test_list2(){list<A> lt;lt.push_back(A(1, 1));lt.push_back(A(2, 2));lt.push_back(A(3, 3));lt.push_back(A(4, 4));list<A>::iterator it = lt.begin();while (it != lt.end()){//cout << (*it)._a1 << " " << (*it)._a2 << endl;cout << it->_a1 << " " << it->_a2 << endl;++it;}cout << endl;}

 上述的 cout 流当中的 it 用法其实应该这样写 :

it ->-> _a2

上面才是正常写法,但是在 operator-> 函数的使用当中却直接 it -> _a2 这样使用了,这是因为运算符重载要求可读性,编译器在这个地方进行特殊处理,省略了一个 " -> " 。

 而上述的 operator-> 这个函数如果是 const 的迭代器当中是实现的话,返回值应该是 const T*,所以这里,对迭代器类模版的参数再加上一个 ptr 来使用

最终迭代器的代码:

	template<class T, class Ref , class ptr>struct List_iterator{typedef List_Node<T> Node;typedef List_iterator<T,Ref, ptr> selt;Node* _Node;List_iterator(Node* node):_Node(node){}Ref operator* (){return _Node->_val;}selt& operator++ (){_Node = _Node->_next;return *this;}selt operator++ (int){_Node = _Node->_next;return *this;}selt& operator-- (){_Node = _Node->_prev;return *this;}selt operator-- (int){_Node = _Node->_prev;return *this;}bool operator!= (const selt& L){return _Node != L._Node;}bool operator== (const selt& L){return _Node == L._Node;}ptr operator-> (){return &_Node->_val;}};template<class T>class List{typedef List_Node<T> Node;public:typedef List_iterator<T , T& , T*> iterator; // 一定要是公有的,不然不能访问typedef List_iterator<T , const T& , const T*> const_iterator;·······················································

list的反向迭代器


通过前面例子知道,反向迭代器的++就是正向迭代器的--,反向迭代器的--就是正向迭代器的++,因此反向迭代器的实现可以借助正向迭代器,即:反向迭代器内部可以包含一个正向迭代器,对正向迭代器的接口进行包装即可。
 

template<class Iterator>
class ReverseListIterator
{// 注意:此处typename的作用是明确告诉编译器,Ref是Iterator类中的类型,而不是静态成员变量// 否则编译器编译时就不知道Ref是Iterator中的类型还是静态成员变量// 因为静态成员变量也是按照 类名::静态成员变量名 的方式访问的
public:typedef typename Iterator::Ref Ref;typedef typename Iterator::Ptr Ptr;typedef ReverseListIterator<Iterator> Self;
public://// 构造ReverseListIterator(Iterator it) : _it(it) {}//// 具有指针类似行为Ref operator*() {Iterator temp(_it);--temp;return *temp;}Ptr operator->() { return &(operator*()); }//// 迭代器支持移动Self& operator++() {--_it;return *this;}Self operator++(int) {Self temp(*this);--_it;return temp;}Self& operator--() {++_it;return *this;}Self operator--(int){Self temp(*this);++_it;return temp;}
//
// 迭代器支持比较
bool operator!=(const Self& l)const { return _it != l._it; }
bool operator==(const Self& l)const { return _it != l._it; }
Iterator _it;
};

list和vector 的比较

 list 就是链表, vector 是顺序表,两者的结构不同,导致两者的使用场景不同,两者也是典型的连续空间存储和链式空间存储不同特性的表现,下表是对两者进行的简单比较:

vector        list
底 层 结 构动态顺序表,是一段连续空间带头结点的双向循环链表
随 机 访 问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素
效率O(N)
插 入 和 删 除任意位置插入和删除效率低,需要搬移元素,时间复杂
度为O(N),插入时有可能需要增容,增容:开辟新空
间,拷贝元素,释放旧空间,导致效率更低
任意位置插入和删除效率高,不
需要搬移元素,时间复杂度为
O(1)
空 间 利 用 率底层为连续空间,不容易造成内存碎片,空间利用率
高,缓存利用率高
底层节点动态开辟,小节点容易
造成内存碎片,空间利用率低,
缓存利用率低
迭 代 器原生态指针对原生态指针(节点指针)进行封装
迭 代 器 失 效在插入元素时,要给所有的迭代器重新赋值,因为插入
元素有可能会导致重新扩容,致使原来迭代器失效,删
除时,当前迭代器需要重新赋值否则会失效
插入元素不会导致迭代器失效,
删除元素时,只会导致当前迭代
器失效,其他迭代器不受影响
使 用 场 景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随
机访问

相关文章:

C++ - list介绍 和 list的模拟实现

list介绍 list 是一个支持在常数范围内&#xff0c;任意位置进行插入删除的序列式容器&#xff0c;且这个容器可以前后双向迭代。我们可以把 list 理解为 双向循环链表的结构。 于其他结构的容器相比&#xff0c;list在 任意位置进行插入和函数的效率要高很多&#xff1b;而li…...

干翻Dubbo系列第四篇:Dubbo3第一个应用程序细节补充

前言 不从恶人的计谋&#xff0c;不站罪人的道路&#xff0c;不坐亵慢人的座位&#xff0c;惟喜爱耶和华的律法&#xff0c;昼夜思想&#xff0c;这人便为有福&#xff01;他要像一棵树栽在溪水旁&#xff0c;按时候结果子&#xff0c;叶子也不枯干。凡他所做的尽都顺利。 如…...

深度学习初探

1.深度学习模型训练过程 1&#xff09;数据&#xff08;数据清洗标注&#xff09; 2&#xff09;模型&#xff08;模型的构建和初始化&#xff09; 3&#xff09;损失&#xff08;前向传播的过程&#xff0c;得到模型的输出和真实标签的差异值&#xff0c;称之为损失&#x…...

nn.BCELoss与nn.CrossEntropyLoss

BCELoss与CrossEntropyLoss都是用于分类问题。可以知道&#xff0c;BCELoss是Binary CrossEntropyLoss的缩写&#xff0c;BCELoss是CrossEntropyLoss的一个特例&#xff0c;只用于二分类问题&#xff0c;而CrossEntropyLoss可以用于二分类&#xff0c;也可以用于多分类&#xf…...

CSDN浏览如何解决

一、对于平时我们苦恼csdn数据不够好看 当面试等各个场合需要我们装*或者秀技术无法拿出亮眼的时候&#xff0c;刚好我闲时间编译的在线模块适合你 二、如何操作&#xff08;虚拟平台我已给大家放到最后直接使用即可&#xff09; 重点&#xff1a;pc端必须拥有python环境 win…...

web前端开发小知识

当今互联网技术迅猛发展&#xff0c;web前端开发也成为了非常热门的职业之一。作为一个web前端开发者&#xff0c;不仅需要掌握各种前端开发技术&#xff0c;还需要了解一些小技巧和小知识。下面&#xff0c;我们将介绍一些web前端开发小知识&#xff0c;希望对你的工作有所帮助…...

Java泛型的简单认识

泛型的认识 自定义泛型&#xff0c;定义了String类型&#xff0c;随后这个泛型就是String类型 于是他的方法都是字符串的类型 泛型接口 泛型方法 所有车可以进行比赛&#xff0c;定义了一个BMW和BENZ两个车类&#xff0c;都继承car&#xff0c;当使用泛型的 如果你顶一个狗对象…...

视频转化为图片或灰度视频

1.视频转化为图片 import cv2video_pathr"D:\Dataset\video/7.mp4" capturecv2.VideoCapture(video_path) # print(capture.get(5))if capture.isOpened():ret,imgcapture.read()index0while ret:if index%200:imgidr"D:\Dataset\image/6/""%07d&quo…...

【动态规划刷题 2】使⽤最⼩花费爬楼梯 解码⽅法

使⽤最⼩花费爬楼梯 746 . 使用最小花费爬楼梯 链接: 746 . 使用最小花费爬楼梯 给你一个整数数组 cost &#xff0c;其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用&#xff0c;即可选择向上爬一个或者两个台阶。 你可以选择从下标为 0 或下标为 …...

Python的基本语法

“有人说&#xff0c;写python就像是坐在一个没有安全带的车上&#xff0c; 我认为这个说法很欠妥当&#xff0c; 应该是一辆没有外壳和座椅&#xff0c; 只有发动机和轮子的车&#xff0c; 并且车上摆满了轮子” python既然是作为一个工具&#xff0c;那么就不需要去深入…...

Kubernetes那点事儿——存储之存储卷

Kubernetes那点事儿——存储之存储卷 前言一、K8s数据卷一、临时存储卷emptyDir二、节点存储卷hostPath三、网络存储NFS 前言 在K8s中用Volume为容器提供了外部的存储能力。 Pod需要设置卷来源&#xff08;spec.volume&#xff09;和挂载点&#xff08;spec.containers.volumeM…...

Go语言中‘String’包中的‘Cut‘函数的实现

Go语言中‘String’包中的’Cut’函数的实现 ​ Cut函数用于在字符串**‘s’中查找子串’sep’&#xff0c;并将字符串’s’在子串 ‘sep’ 第一次出现的位置分割成两部分&#xff1a;before和after** package main import("fmt" "strings" ) func main(…...

【JAVASE】顺序和选择结构

⭐ 作者&#xff1a;小胡_不糊涂 &#x1f331; 作者主页&#xff1a;小胡_不糊涂的个人主页 &#x1f4c0; 收录专栏&#xff1a;浅谈Java &#x1f496; 持续更文&#xff0c;关注博主少走弯路&#xff0c;谢谢大家支持 &#x1f496; 顺序和选择 1. 顺序结构2. 分支结构2.1 …...

Oracle恢复删除的数据

不下心删除了生产库的数据或者不小心删除了一部分数据&#xff0c;如何恢复找回。 Oracle恢复删除数据的方法 方案一 利用oracle提供的闪回方法进行数据恢复&#xff0c;适用于delete删除方式 首先获取删除数据的时间点&#xff1a; select * from v$sql where sql_text l…...

(无人机方向)ros小白之键盘控制无人机(终端方式)

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一&#xff1a;配置pycharm的ros开发环境二&#xff1a;核心代码讲解三 效果演示XTDrone 四 完整代码 前言 ubuntu 18.04 pycharm ros melodic 做一个在终端中…...

【python学习笔记】argparse --- 命令行选项、参数和子命令解析器

argparse 是 Python 的标准库中的一个模块&#xff0c;用于解析命令行参数。它提供了一种简单而灵活的方式来处理命令行输入&#xff0c;并生成易于使用的帮助文档。 使用 argparse 模块可以轻松地定义命令行参数和选项&#xff0c;并自动生成用法帮助和错误消息。示例&#x…...

【Java框架】RPC远程调用

RPC架构 一、RPC概述 RPC&#xff08;Remote Procedure Call&#xff09;叫作远程过程调用&#xff0c;它是利用网络从远程计算机上请求服务&#xff0c;可以理解为把程序的一部分放在其他远程计算机上执行。通过网络通信将调用请求发送至远程计算机后&#xff0c;利用远程计…...

云原生全栈体系(一)

云平台核心 第一章 为什么用云平台 环境统一按需付费即开即用稳定性强 一、国内常见云平台 阿里云、百度云、腾讯云、华为云、青云… 二、国外常见云平台 亚马逊 AWS、微软 Azure … 三、公有云 购买云服务商提供的公共服务器 公有云是最常见的云计算部署类型。公有云资…...

【【51单片机直流电机调速】】

学会电机调速&#xff0c;掌握中国速度 PWM的生成方法 先用户设定一个比较值&#xff0c;然后计数器定时自增。 当计数器<比较值&#xff0c;输出0 当计数器>比较值&#xff0c;输出1 main.c #include <REGX52.H> #include"delay.h" #include"…...

【Spring Boot】

目录 &#x1f36a;1 Spring Boot 的创建 &#x1f382;2 简单 Spring Boot 程序 &#x1f370;3 Spring Boot 配置文件 &#x1f36e;3.1 properties 基本语法 &#x1fad6;3.2 yml 配置文件说明 &#x1f36d;3.2.1 yml 基本语法 &#x1f369;3.3 配置文件里的配置类…...

Xshell远程连接Kali(默认 | 私钥)Note版

前言:xshell远程连接&#xff0c;私钥连接和常规默认连接 任务一 开启ssh服务 service ssh status //查看ssh服务状态 service ssh start //开启ssh服务 update-rc.d ssh enable //开启自启动ssh服务 任务二 修改配置文件 vi /etc/ssh/ssh_config //第一…...

QMC5883L的驱动

简介 本篇文章的代码已经上传到了github上面&#xff0c;开源代码 作为一个电子罗盘模块&#xff0c;我们可以通过I2C从中获取偏航角yaw&#xff0c;相对于六轴陀螺仪的yaw&#xff0c;qmc5883l几乎不会零飘并且成本较低。 参考资料 QMC5883L磁场传感器驱动 QMC5883L磁力计…...

DAY 47

三、通道注意力 3.1 通道注意力的定义 # 新增&#xff1a;通道注意力模块&#xff08;SE模块&#xff09; class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...

全球首个30米分辨率湿地数据集(2000—2022)

数据简介 今天我们分享的数据是全球30米分辨率湿地数据集&#xff0c;包含8种湿地亚类&#xff0c;该数据以0.5X0.5的瓦片存储&#xff0c;我们整理了所有属于中国的瓦片名称与其对应省份&#xff0c;方便大家研究使用。 该数据集作为全球首个30米分辨率、覆盖2000–2022年时间…...

对WWDC 2025 Keynote 内容的预测

借助我们以往对苹果公司发展路径的深入研究经验&#xff0c;以及大语言模型的分析能力&#xff0c;我们系统梳理了多年来苹果 WWDC 主题演讲的规律。在 WWDC 2025 即将揭幕之际&#xff0c;我们让 ChatGPT 对今年的 Keynote 内容进行了一个初步预测&#xff0c;聊作存档。等到明…...

华为OD机试-食堂供餐-二分法

import java.util.Arrays; import java.util.Scanner;public class DemoTest3 {public static void main(String[] args) {Scanner in new Scanner(System.in);// 注意 hasNext 和 hasNextLine 的区别while (in.hasNextLine()) { // 注意 while 处理多个 caseint a in.nextIn…...

佰力博科技与您探讨热释电测量的几种方法

热释电的测量主要涉及热释电系数的测定&#xff0c;这是表征热释电材料性能的重要参数。热释电系数的测量方法主要包括静态法、动态法和积分电荷法。其中&#xff0c;积分电荷法最为常用&#xff0c;其原理是通过测量在电容器上积累的热释电电荷&#xff0c;从而确定热释电系数…...

代码随想录刷题day30

1、零钱兑换II 给你一个整数数组 coins 表示不同面额的硬币&#xff0c;另给一个整数 amount 表示总金额。 请你计算并返回可以凑成总金额的硬币组合数。如果任何硬币组合都无法凑出总金额&#xff0c;返回 0 。 假设每一种面额的硬币有无限个。 题目数据保证结果符合 32 位带…...

使用Spring AI和MCP协议构建图片搜索服务

目录 使用Spring AI和MCP协议构建图片搜索服务 引言 技术栈概览 项目架构设计 架构图 服务端开发 1. 创建Spring Boot项目 2. 实现图片搜索工具 3. 配置传输模式 Stdio模式&#xff08;本地调用&#xff09; SSE模式&#xff08;远程调用&#xff09; 4. 注册工具提…...

push [特殊字符] present

push &#x1f19a; present 前言present和dismiss特点代码演示 push和pop特点代码演示 前言 在 iOS 开发中&#xff0c;push 和 present 是两种不同的视图控制器切换方式&#xff0c;它们有着显著的区别。 present和dismiss 特点 在当前控制器上方新建视图层级需要手动调用…...