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

【C++】list的使用方法和模拟实现

❤️欢迎来到我的博客❤️

前言

  1. list是可以在常数范围内在任意位置进行插入和删除的序列式容器,并且该容器可以前后双向迭代
  2. list的底层是双向链表结构,双向链表中每个元素存储在互不相关的独立节点中,在节点中通过指针指向其前一个元素和后一个元素
  3. list与forward_list非常相似:最主要的不同在于forward_list是单链表,只能朝前迭代,已让其更简单高效
  4. 与其他的序列式容器相比(array,vector,deque),list通常在任意位置进行插入、移除元素的执行效率更好
  5. 与其他序列式容器相比,list和forward_list最大的缺陷是不支持任意位置的随机访问,比如:要访问list的第6个元素,必须从已知的位置(比如头部或者尾部)迭代到该位置,在这段位置上迭代需要线性的时间开销;list还需要一些额外的空间,以保存每个节点的相关联信息(对于存储类型较小元素的大list来说这可能是一个重要的因素)

list相比于vector在insert和erase上有很大的区别
vector想在第五个位置插入数据可以直接+5

int main()
{vector<int> v1;v1.push_back(1);v1.push_back(2);v1.push_back(3);v1.push_back(4);v1.push_back(5);v1.push_back(7);v1.insert(v1.begin() + 5, 6);for (auto e : v1){cout << e << " ";}cout << endl;//输出结果为:1 2 3 4 5 6 7return 0;
}

而list想在第五个位置插入数据只能使用迭代器,不可以直接+5
在这里插入图片描述

int main()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);lt.push_back(5);lt.push_back(7);//lt.insert(lt.begin() + 5, 10);auto it = lt.begin();for (size_t i = 0; i < 5; i++){++it;}lt.insert(it, 6);for (auto e : lt){cout << e << " ";}cout << endl;//输出结果为:1 2 3 4 5 6 7return 0;
}

list使用insert之后迭代器不会失效,因为结点的位置没有发生改变
但list使用erase之后迭代器就会失效,因为结点位置发生了改变(结点已经被删除)


排序相关

在这里插入图片描述
可以看到list单独提供了sort库里明明有一个sort为什么list还要单独提供呢?
我们来试试:
在这里插入图片描述
可以看到,当我们使用库里的sort的时候编译报错了
在这里插入图片描述

我们转到库定义可以看到,库里的sort用了一个last-first,原理是快排,需要三数取中,但在链表中就不适合三数取中的场景

迭代器从功能的角度是会分类的,他分为:单向、双向和随机

单向可以进行 ++
双向可以进行 ++/–
随机可以进行 ++/–/+/-
单向迭代器有:forward_list / unordered_map / unordered_set
双向迭代器有:list / map / set
随机迭代器有:vector / string / deque

在形参的名字中就暗示了我们适合用哪一种算法,比如reverse就适合用双向,find适合用单向,sort适合用随机

在这里插入图片描述

InputIterator(find):只写迭代器(他在单向迭代器的上面)也就是说单向 / 双向 / 随机都可以使用
双向迭代器(reverse):双向 / 随机可以使用
随机迭代器(sort):只有随机能用

容器的迭代器类型在文档中是有说明的:
list:
在这里插入图片描述
vector:
在这里插入图片描述
set:
在这里插入图片描述
forward_list:
在这里插入图片描述


在数据量大的情况下,我们可以把list拷贝到vector中,使用库里的排序,再把数据拷贝回去效率更高,我们来对比一下:
数据个数为一千万

int main()
{srand(time(0));const int N = 10000000;vector<int> v;v.reserve(N);list<int> lt1;list<int> lt2;for (int i = 0; i < N; i++){auto e = rand();lt2.push_back(e);lt1.push_back(e);}//拷贝到vectorint begin1 = clock();//拷贝for (auto e : lt1){v.push_back(e);}//排序sort(v.begin(), v.end());//拷贝回去size_t i = 0;for (auto& e : lt1){e = v[i++];}int end1 = clock();//直接使用list排序int begin2 = clock();lt2.sort();int end2 = clock();cout << "使用vector排序:" << end1 - begin1 << endl;cout << "直接使用list排序:" << end2 - begin2 << endl;return 0;
}

在这里插入图片描述
可以看到效率差了近十倍,所以在数据量特别大的时候就不要直接使用list排序了,排少量数据则可以直接使用


merge

在这里插入图片描述
可以将两个链表归并(前提是链表有序)


unique

在这里插入图片描述

去重(前提是链表有序)


remove

在这里插入图片描述

remove就是find+erase,如果要删除的值不存在则不进行任何操作


splice

在这里插入图片描述
可以把一个链表的内容转移到另一个链表(直接把结点拿走)

转移全部

int main()
{list<int> list1, list2;list<int>::iterator it;for (int i = 1; i <= 4; ++i){list1.push_back(i);			//list1:1 2 3 4}for (int i = 1; i <= 3; ++i){list2.push_back(i * 10);	//list2:10 20 30}cout << "list1转移前:";for (auto e : list1){cout << e << " ";}cout << endl;cout << "list2转移前:";for (auto e : list2){cout << e << " ";}cout << endl;it = list1.begin();++it;//2位置//把list2全部转移到list1中2之前的位置list1.splice(it, list2);cout << "list1转移后:";for (auto e : list1){cout << e << " ";}cout << endl;cout << "list2转移后:";for (auto e : list2){cout << e << " ";}cout << endl;return 0;
}

在这里插入图片描述

转移某一个结点

//转移某一个结点
list1.splice(it, list2,++list2.begin());

在这里插入图片描述

部分转移

//部分转移
list1.splice(it, list2,++list2.begin(),list2.end());

在这里插入图片描述

自己转移自己

//把第二位置转移到第一个位置的前面
list1.splice(list1.begin(),list1,++list1.begin());

在这里插入图片描述

注意:转移重叠位置会造成死循环


模拟实现list

基本框架

namespace List
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;//构造函数list_node(const T& val = T())	//缺省值不能给0,因为T不一定是内置类型:_next(nullptr),_prev(nullptr),_val(val){}};template<class T>class list{typedef list_node<T> Node;public:list(){_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;}//尾插void push_back(const T& x){Node* tail = _head->_prev;//找尾Node* newnode = new Node(x);//调用构造函数tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;}private:Node* _head;};void test1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);}
}

尾插

//尾插
void push_back(const T& x)
{Node* tail = _head->_prev;//找尾Node* newnode = new Node(x);//调用构造函数tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;
}

在这里插入图片描述

迭代器

由于结点的地址不是连续的,那我们的迭代器该如何设置呢,这时候就要用到运算符重载
我们先来看迭代器的使用:

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

那么我们的begin应该返回的是第一个数据的位置,end在最后一个数据的下一个位置
在这里插入图片描述

迭代器是一个自定义类型,这个自定义类型是一个结点的指针,所以在list中他的迭代器需要自己设计而不是像vector或string那样使用原生指针(不同平台下实现方式不同,个别平台也可能对其进行二次封装,也可能不是用原生指针实现)作为迭代器,原因如下:

  1. list 的元素在内存中不是连续存储的,而是通过指针链接起来的。这意味着你不能简单地使用原生指针作为迭代器,因为原生指针只能访问连续内存块中的元素
  2. list 的迭代器需要提供额外的功能,如在链表中前进或后退,这些操作涉及到修改指针以跳转到下一个或上一个元素。原生指针不支持这些操作,因此需要自定义迭代器来实现
  3. 自定义迭代器可以针对链表的特性进行优化。例如,链表迭代器在插入或删除操作时,只需要调整相邻元素的指针,而不需要移动大量元素,这使得链表在某些操作上比数组更高效
  4. list中,插入或删除元素通常不会使迭代器失效(除非删除的是迭代器指向的元素),这与vector不同。自定义迭代器可以确保这些操作的正确性,而原生指针无法提供这种保证
  5. 自定义迭代器可以提供更好的封装性,隐藏链表的内部实现细节,这样,即使链表的实现发生变化,只要迭代器的接口保持不变,使用迭代器的代码就不需要修改

begin和end

template<class T>
class list
{typedef list_node<T> Node;
public:typedef __list_iterator<T> iterator;iterator begin(){//单参数的构造函数支持隐式类型转换//return _head->_next;return iterator(_head->_next);}iterator end(){//return _head;return iterator(_head);}list(){_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;}private:Node* _head;
};

* / ++ / – / != / ==

template<class T>
struct __list_iterator
{typedef list_node<T> Node;Node* _node;//用一个结点的指针构造一个迭代器__list_iterator(Node* node):_node(node){}T& operator*(){//*it默认解引用是结点,我们不想要结点,我们要的是数据return _node->_val;}//迭代器++返回的还是迭代器__list_iterator<T>& operator++(){//我们想++做的是让他走向下一个结点//(原生指针是加一个结点的大小,这样并不能到下一个结点的位置)_node = _node->_next;return *this;}//后置++__list_iterator<T> operator++(int){__list_iterator<T> 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;}bool operator!=(const __list_iterator<T>& it){//使用结点的指针来进行比较return _node != it._node;}bool operator==(const __list_iterator<T>& it){//使用结点的指针来进行比较return _node == it._node;}
};

这样我们的迭代器就可以正常使用了

void test1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);list<int>::iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;
}int main()
{List::test1();return 0;
}

运行效果:
在这里插入图片描述

const迭代器

迭代器模拟的是指针的行为,指针有2种const指针:
1:const T* ptr1; (指针指向的内容不能修改)
2:T* const ptr2;(指针本身不能修改)
const迭代器模拟的是第一种指针的行为

我们可以通过一个类型来控制返回值,给不同的模板参数不同的实例化,他们就是不同的类

template<class T,class Ref>//添加一个class Ref参数
struct __list_iterator
{Ref operator*()//返回值改为Ref{return _node->_val;}
}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;//const迭代器//提供const接口const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}
};

->

T* operator->()
{return &_node->_val;
}
struct A
{A(int a1 = 0,int a2 = 0):_a1(a1),_a2(a2){}int _a1;int _a2;
};void test1()
{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;++it;}cout << endl;
}

运行效果
在这里插入图片描述

在这里插入图片描述
const迭代器,也需要添加一个模板参数

//typedef __list_iterator<T,T&,T*> iterator;//普通迭代器
//typedef __list_iterator<T, const T&,const T*> const_iterator;//const迭代器
template<class T,class Ref,class Ptr>

insert

insert默认都是在pos位置之前插入(任意位置都可以),所以insert不需要做检查
erase则需要检查(因为哨兵位的结点不可删)

//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;
}

库里面的insert返回的是新插入位置的迭代器,所以我们实现时和库里保持一致

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;return next;	//返回下一个位置
}

注意:这里会存在迭代器失效的问题,因为pos位置的结点已经被我们释放掉了,所以我们需要返回下一个位置的迭代器,而不是void

代码复用

当我们实现完insert和erase之后,尾插、头插、尾删、头删都可以复用

尾插

//尾插
void push_back(const T& x)
{//Node* tail = _head->_prev;//找尾//Node* newnode = new Node(x);//调用构造函数//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _head;//_head->_prev = newnode;//复用//尾插 - 在哨兵位的前面插入insert(end(), x);
}

头插

void push_front(const T& x)
{//头插//在第一个位置插入,也就是begin的前面insert(begin(), x);
}

尾删

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

头删

void pop_front()
{//头删erase(begin());
}

size

size有两种实现方法

方法一:

size_t size()
{size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;
}

方法二:
增加一个_size成员初始化为0,每次插入++_size,每次删除–_size

size_t size()
{return _size;
}private:Node* _head;size_t _size;//增加成员

clear和析构

void clear()
{//clear - 清除所有数据,不清哨兵位iterator it = begin();while (it != end()){//这里erase之后就不能进行++了,因为他失效了//所以得接收返回值(返回值是下一个结点)it = erase(it);}
}

析构可以直接复用clear

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

测试一下:

void test1()
{list<int> lt;lt.push_back(1);lt.push_back(2);lt.insert(lt.end(), 3);lt.push_back(4);lt.push_back(5);lt.erase(lt.begin());for (auto e : lt){cout << e << " ";}cout << endl;lt.push_front(1);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_front();lt.pop_back();for (auto e : lt){cout << e << " ";}cout << endl;lt.clear();lt.push_back(10);lt.push_back(20);for (auto e : lt){cout << e << " ";}cout << endl;cout << "size=" << lt.size() << endl;
}

运行效果

在这里插入图片描述

拷贝构造和empty_init

拷贝构造

// lt2(lt1)
list(const list<T>& lt)
{//初始化_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;//T对象不确定,所以最好加上引用//遍历lt1,把lt1的数据插入到lt2for (auto& e : lt){push_back(e);}
}

empty_init

void empty_init()
{_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;
}

复用

list()
{empty_init();
}// lt2(lt1)
list(const list<T>& lt)
{empty_init();//T对象不确定,所以最好加上引用//遍历lt1,把lt1的数据插入到lt2for (auto& e : lt){push_back(e);}
}

赋值和swap

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

赋值

list<T>& operator=(list<T> lt)
{//现代写法swap(lt);return *this;
}

测试一下

void test2()
{list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);for (auto e : lt2){cout << e << " ";}cout << endl;list<int> lt3;lt3.push_back(10);lt3.push_back(20);lt3.push_back(30);lt3.push_back(40);lt1 = lt3;for (auto e : lt1){cout << e << " ";}cout << endl;
}

运行效果
在这里插入图片描述


完整代码

#pragma once
#include<iostream>
#include<assert.h>using namespace std;namespace List
{template<class T>struct list_node{list_node<T>* _next;list_node<T>* _prev;T _val;//构造函数list_node(const T& val = T())	//缺省值不能给0,因为T不一定是内置类型:_next(nullptr),_prev(nullptr),_val(val){}};template<class T,class Ref,class Ptr>struct __list_iterator{typedef list_node<T> Node;typedef __list_iterator<T, Ref,Ptr> self;Node* _node;//用一个结点的指针构造一个迭代器__list_iterator(Node* node):_node(node){}Ref operator*(){//*it默认解引用是结点,我们不想要结点,我们要的是数据return _node->_val;}Ptr operator->(){return &_node->_val;}//迭代器++返回的还是迭代器self& operator++(){//我们想++做的是让他走向下一个结点//(原生指针是加一个结点的大小,这样并不能到下一个结点的位置)_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;}bool operator!=(const self& it) const{//使用结点的指针来进行比较return _node != it._node;}bool operator==(const self& it) const{//使用结点的指针来进行比较return _node == it._node;}};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;iterator begin(){//单参数的构造函数支持隐式类型转换//return _head->_next;return iterator(_head->_next);}iterator end(){//return _head;return iterator(_head);}const_iterator begin() const{//单参数的构造函数支持隐式类型转换//return _head->_next;return const_iterator(_head->_next);}const_iterator end() const{//return _head;return const_iterator(_head);}void empty_init(){_head = new Node;//哨兵位指向自己_head->_prev = _head;_head->_next = _head;}list(){empty_init();}// lt2(lt1)list(const list<T>& lt){empty_init();//T对象不确定,所以最好加上引用//遍历lt1,把lt1的数据插入到lt2for (auto& e : lt){push_back(e);}}void swap(list<T>& lt){std::swap(_head, lt._head);}list<T>& operator=(list<T> lt){//现代写法swap(lt);return *this;}~list(){//析构clear();delete _head;_head = nullptr;}void clear(){//clear - 清除所有数据,不清哨兵位iterator it = begin();while (it != end()){//这里erase之后就不能进行++了,因为他失效了//所以得接收返回值(返回值是下一个结点)it = erase(it);}}//尾插void push_back(const T& x){//Node* tail = _head->_prev;//找尾//Node* newnode = new Node(x);//调用构造函数//tail->_next = newnode;//newnode->_prev = tail;//newnode->_next = _head;//_head->_prev = newnode;//复用//尾插 - 在哨兵位的前面插入insert(end(), x);}void push_front(const T& x){//头插//在第一个位置插入,也就是begin的前面insert(begin(), x);}void pop_back(){//尾删erase(--end());}void pop_front(){//头删erase(begin());}//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;}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;return next;}size_t size(){size_t sz = 0;iterator it = begin();while (it != end()){++sz;++it;}return sz;}private:Node* _head;};void Print(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){//(*it) += 1;cout << *it << " ";++it;}cout << endl;}struct A{A(int a1 = 0,int a2 = 0):_a1(a1),_a2(a2){}int _a1;int _a2;};void test1(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.insert(lt.end(), 3);lt.push_back(4);lt.push_back(5);lt.erase(lt.begin());for (auto e : lt){cout << e << " ";}cout << endl;lt.push_front(1);for (auto e : lt){cout << e << " ";}cout << endl;lt.pop_front();lt.pop_back();for (auto e : lt){cout << e << " ";}cout << endl;lt.clear();lt.push_back(10);lt.push_back(20);for (auto e : lt){cout << e << " ";}cout << endl;cout << "size=" << lt.size() << endl;}void test2(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);for (auto e : lt2){cout << e << " ";}cout << endl;list<int> lt3;lt3.push_back(10);lt3.push_back(20);lt3.push_back(30);lt3.push_back(40);lt1 = lt3;for (auto e : lt1){cout << e << " ";}cout << endl;}
}

以上就是本篇文章的全部内容了,希望大家看完能有所收获

❤️创作不易,点个赞吧❤️

相关文章:

【C++】list的使用方法和模拟实现

❤️欢迎来到我的博客❤️ 前言 list是可以在常数范围内在任意位置进行插入和删除的序列式容器&#xff0c;并且该容器可以前后双向迭代list的底层是双向链表结构&#xff0c;双向链表中每个元素存储在互不相关的独立节点中&#xff0c;在节点中通过指针指向其前一个元素和后…...

【物联网实战项目】STM32C8T6+esp8266/mqtt+dht11+onenet+uniapp

一、实物图 前端uniapp效果图&#xff08;实现与onenet同步更新数据&#xff09; 首先要确定接线图和接线顺序&#xff1a; 1、stm32c8t6开发板连接stlinkv2下载线 ST-LINK V2STM323.3V3.3VSWDIOSWIOSWCLKSWCLKGNDGND 2、ch340串口连接底座&#xff08;注意RXD和TXD的连接方式…...

Pyhton 二叉树层级遍历

class TreeNode:def __init__(self, val0, leftNone, rightNone):self.val valself.left leftself.right rightclass Solution:def levelOrder(self, root: Optional[TreeNode]) -> List[List[int]]:res []# 空节点&#xff0c;直接返回if not root:return resque [roo…...

Flutter 中的 FadeTransition 小部件:全面指南

Flutter 中的 FadeTransition 小部件&#xff1a;全面指南 在 Flutter 中&#xff0c;动画是一种吸引用户注意力并提供流畅用户体验的强大工具。FadeTransition 是 Flutter 提供的一个动画小部件&#xff0c;它允许子组件在不透明度上进行渐变&#xff0c;从而实现淡入和淡出效…...

缓存存储器:性能提升的关键

目录 基本原理 主存与缓存的地址映射 主存的替换策略 缓存的写操作策略 Pentium 4 的缓存组织 使用多级缓存减少缺失损失 结论 在计算机系统中&#xff0c;缓存存储器&#xff08;Cache Memory&#xff09;发挥着至关重要的作用。它充当处理器和主存之间的高速缓冲区&am…...

『大模型笔记』工程师的LLMs简介!

💡工程师的LLMs简介 ! 文章目录 1. Embeddings Conceptually(嵌入的概念)1.1. One-hot Encodings(独热编码)1.2. Embeddings(嵌入)2. LLM Basics(LLM 基础知识)3. Autoregressive LLMs(自回归LLMs)4. Where to go from here(何去何从?)5. 参考文献https://devo…...

Vue中的常用指令

Vue 会根据不同的【指令】&#xff0c;针对标签实现不同的【功能】 概念&#xff1a;指令&#xff08;Directives&#xff09;是 Vue 提供的带有 v- 前缀 的 特殊 标签属性。 为啥要学&#xff1a;提高程序员操作 DOM 的效率。 vue 中的指令按照不同的用途可以分为如下 6 大…...

百度页面奔跑的白熊html、css

一、相关知识-动画 1.基本使用&#xff1a;先定义再调用 2. 调用动画 用keyframes定义动画&#xff08;类似定义类选择器&#xff09; keyframes动画名称{ 0%{ width:100px&#xff1b; } 100%{ width:200px; } } 使用动画 div { width:200px; height:200px; background-…...

Day-02面向对象

一、匿名函数 和函数的作用一致&#xff0c;都是进行代码逻辑的封装&#xff0c; 区别1 在定义时&#xff0c;匿名函数可以不指定函数的名字 区别2 匿名函数执行实现简单的计算 区别3 匿名函数会自动将计算的结果返回 定义格式 lambda 参数1,参数2...:计算逻辑(参数的处理逻辑…...

Sentinel-2 哨兵二号数据介绍及下载

1 Sentinel-2简介 SENTINEL-2 is a European wide-swath, high-resolution, multi-spectral imaging mission. Sentinel-2 是高分辨率多光谱成像卫星&#xff0c;一颗卫星的重访周期为10天&#xff0c;两颗互补&#xff0c;重访周期为5天。分为2A和2B两颗卫星。2A于2015年6月…...

阿里智能信息数据挖掘复盘

&#xff08;嘻嘻——不嘻嘻&#xff09; 挫败呜呜呜&#xff0c;钉钉忘装&#xff0c;还要手机登录&#xff0c;迟到三分钟。 一上来就问项目&#xff0c;没有自我介绍。 第一篇的重要特征是什么直接忘记&#xff0c;正负样本比&#xff0c;过拟合的判断标准&#xff0c;特…...

Flutter中图片是怎么在flutter上呈现出来的?

在Flutter中&#xff0c;图片的呈现是通过使用Image组件来实现的。Image组件是一个用于加载和显示图片的Widget&#xff0c;可以从本地文件、网络URL或内存中加载图片&#xff0c;并在应用界面上进行渲染。 图片在Flutter中的呈现过程如下&#xff1a; 加载图片&#xff1a; 使…...

使用 CNN 训练自己的数据集

CNN&#xff08;练习数据集&#xff09; 1.导包&#xff1a;2.导入数据集&#xff1a;3. 使用image_dataset_from_directory()将数据加载tf.data.Dataset中&#xff1a;4. 查看数据集中的一部分图像&#xff0c;以及它们对应的标签&#xff1a;5.迭代数据集 train_ds&#xff0…...

自动控制: 最小二乘估计(LSE)、加权最小二乘估计(WLS)和线性最小方差估计

自动控制&#xff1a; 最小二乘估计&#xff08;LSE&#xff09;、加权最小二乘估计&#xff08;WLS&#xff09;和线性最小方差估计 在数据分析和机器学习中&#xff0c;参数估计是一个关键步骤。最小二乘估计&#xff08;LSE&#xff09;、加权最小二乘估计&#xff08;WLS&…...

基于VMware安装Linux虚拟机

1.准备Linux环境 首先&#xff0c;我们要准备一个Linux的系统&#xff0c;成本最低的方式就是在本地安装一台虚拟机。为了统一学习环境&#xff0c;不管是使用MacOS还是Windows系统的同学&#xff0c;都建议安装一台虚拟机。 windows采用VMware&#xff0c;Mac则采用Fusion …...

6、phpjm混淆解密和php反序列化

题目&#xff1a;青少年雏形系统 1、打开链接也是一个登入面板 2、尝试了sqlmap没头绪 3、尝试御剑&#xff0c;发现一个www.zip 4、下载打开&#xff0c;有一个php文件打开有一段phpjm混淆加密 5、使用手工解混淆 具体解法链接&#xff1a;奇安信攻防社区-phpjm混淆解密浅谈…...

Codeforces Round 909 (Div. 3) E. Queue Sort(模拟 + 贪心之找到了一个边界点)

弗拉德找到了一个由 n 个整数组成的数组 a &#xff0c;并决定按不递减的顺序排序。 为此&#xff0c;弗拉德可以多次执行下面的操作&#xff1a; 提取数组的第一个元素并将其插入末尾&#xff1b; 将个元素与前一个元素对调&#xff0c;直到它变成第一个元素或严格大于前一个…...

设计模式基础——设计原则介绍

1.概述 ​ 对于面向对象软件系统的设计而言&#xff0c;如何同时提高一个软件系统的可维护性、可复用性、可拓展性是面向对象设计需要解决的核心问题之一。面向对象设计原则应运而生&#xff0c;这些原则你会在设计模式中找到它们的影子&#xff0c;也是设计模式的基础。往往判…...

【校园网网络维修】当前用户使用的IP与设备重定向地址中IP不一致,请重新认证

出现的网络问题&#xff1a;当前用户使用的IP与设备重定向地址中IP不一致&#xff0c;请重新认证 可能的原因&#xff1a; 把之前登录的网页收藏到浏览器&#xff0c;然后直接通过这个链接进行登录认证。可能是收藏网址导致的ip地址请求参数不一致。 解决方法&#xff1a; 方法…...

如何找到docker的run(启动命令)

使用python三方库进行 需要安装python解释器 安装runlike安装包 pip3 install runlike 运行命令 runlike -p <container_name> # 后面可以是容器名和容器id&#xff0c;-p参数是显示自动换行实验 使用docker启动一个jenkins 启动命令为 docker run -d \ -p 9002:80…...

网络编程(Modbus进阶)

思维导图 Modbus RTU&#xff08;先学一点理论&#xff09; 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议&#xff0c;由 Modicon 公司&#xff08;现施耐德电气&#xff09;于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…...

【kafka】Golang实现分布式Masscan任务调度系统

要求&#xff1a; 输出两个程序&#xff0c;一个命令行程序&#xff08;命令行参数用flag&#xff09;和一个服务端程序。 命令行程序支持通过命令行参数配置下发IP或IP段、端口、扫描带宽&#xff0c;然后将消息推送到kafka里面。 服务端程序&#xff1a; 从kafka消费者接收…...

1688商品列表API与其他数据源的对接思路

将1688商品列表API与其他数据源对接时&#xff0c;需结合业务场景设计数据流转链路&#xff0c;重点关注数据格式兼容性、接口调用频率控制及数据一致性维护。以下是具体对接思路及关键技术点&#xff1a; 一、核心对接场景与目标 商品数据同步 场景&#xff1a;将1688商品信息…...

postgresql|数据库|只读用户的创建和删除(备忘)

CREATE USER read_only WITH PASSWORD 密码 -- 连接到xxx数据库 \c xxx -- 授予对xxx数据库的只读权限 GRANT CONNECT ON DATABASE xxx TO read_only; GRANT USAGE ON SCHEMA public TO read_only; GRANT SELECT ON ALL TABLES IN SCHEMA public TO read_only; GRANT EXECUTE O…...

鱼香ros docker配置镜像报错:https://registry-1.docker.io/v2/

使用鱼香ros一件安装docker时的https://registry-1.docker.io/v2/问题 一键安装指令 wget http://fishros.com/install -O fishros && . fishros出现问题&#xff1a;docker pull 失败 网络不同&#xff0c;需要使用镜像源 按照如下步骤操作 sudo vi /etc/docker/dae…...

.Net Framework 4/C# 关键字(非常用,持续更新...)

一、is 关键字 is 关键字用于检查对象是否于给定类型兼容,如果兼容将返回 true,如果不兼容则返回 false,在进行类型转换前,可以先使用 is 关键字判断对象是否与指定类型兼容,如果兼容才进行转换,这样的转换是安全的。 例如有:首先创建一个字符串对象,然后将字符串对象隐…...

Maven 概述、安装、配置、仓库、私服详解

目录 1、Maven 概述 1.1 Maven 的定义 1.2 Maven 解决的问题 1.3 Maven 的核心特性与优势 2、Maven 安装 2.1 下载 Maven 2.2 安装配置 Maven 2.3 测试安装 2.4 修改 Maven 本地仓库的默认路径 3、Maven 配置 3.1 配置本地仓库 3.2 配置 JDK 3.3 IDEA 配置本地 Ma…...

代理篇12|深入理解 Vite中的Proxy接口代理配置

在前端开发中,常常会遇到 跨域请求接口 的情况。为了解决这个问题,Vite 和 Webpack 都提供了 proxy 代理功能,用于将本地开发请求转发到后端服务器。 什么是代理(proxy)? 代理是在开发过程中,前端项目通过开发服务器,将指定的请求“转发”到真实的后端服务器,从而绕…...

return this;返回的是谁

一个审批系统的示例来演示责任链模式的实现。假设公司需要处理不同金额的采购申请&#xff0c;不同级别的经理有不同的审批权限&#xff1a; // 抽象处理者&#xff1a;审批者 abstract class Approver {protected Approver successor; // 下一个处理者// 设置下一个处理者pub…...

人机融合智能 | “人智交互”跨学科新领域

本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...