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

list底层的简单实现(万字长文详解!)

list底层的简单实现

文章目录

  • list底层的简单实现
    • list_node的实现!
      • list_node的构造函数
    • list的迭代器!——重点!
      • list迭代器的成员变量
      • 迭代器的构造函数
      • * 重载
      • 前置++ 重载
      • 后置++ 重载
      • 前置-- 重载
      • 后置-- 重载
      • != 重载
      • == 重载
      • -- 重载
    • list的const迭代器——重点
    • 迭代器之-> 重载——重点
    • list的成员变量
    • list的构造函数(无参)
    • insert
    • begin
    • end
    • const版本的begin
    • const版本的end
    • push_back
      • 原始的写法
      • 复用的写法
    • push_front
    • swap
    • 构造函数(带参)
    • 拷贝构造
      • 传统写法
      • 现代写法
    • 赋值重载
      • 传统写法
      • 现代写法
    • erase
    • pop_front
    • pop_back
    • clear
    • 析构函数
    • size
    • empty
  • 代码展示

list_node的实现!

要实现链表我们首先就要实现节点的结构体!

struct list_node
{list_node* _next;list_node* _prev;T _data;
};

list_node的构造函数

list_node(const T& x):_data(x)_next(nullptr),_prev(nullptr)
{
}

list的迭代器!——重点!

任何一个容器的迭代器都是内嵌类型!都要受到域的影响!

list的迭代器和string和vector不一样,并不是一个原生的指针!

typedef node* iterator;

为什么我们不可以像上面怎么写呢?

因为我们要求迭代器行为要类似指针!——1.解引用得到数据 2.支持++/–

像是我们解引用,我们就要得到这个节点的数据!但是如果我们将上面的这个迭代器解引用了我们其实得到的是一个节点!我们自己还得去x.data用来获得数据!,这就不符合迭代器的定义了!

因为节点本身不是连续的!所以上面的迭代器也不支持++/–

以前string和vector能使用原生指针的原因是因为存储的物理结构都是连续的!所以可以直接支持原生指针能完成迭代器行为!

所以list的迭代器是通过类的封装+函数重载实现的!

用函数重载来实现相似的行为!

list迭代器的成员变量

template<class T>
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T> self;//方便添加模板参数node* _pnode;
}

迭代器的构造函数

__list_iterator(node* p):_pnode(p)
{}

使用一个节点用来构造一个迭代器

* 重载

T& operator*()
{return _pnode->_data;
}

因为我们一般都是解引用后可以修改数据的值!

所以我们要引用返回

前置++ 重载

self& operator++()
{_pnode = _pnode->_next;return *this;
}

这个是迭代器++ 要返回的是这个迭代器的类型__list_iterator!

不要忘记加上 否则就是类型不完全!也不是node*

还是一个前置++ ,返回引用可以减少拷贝构造

后置++ 重载

self operator++(int)
{node* cur = _pnode;_pnode = _pnode->_next;return self(cur);
}

后置的定义为 先用后 ++

本质就是一个先将原来的节点构造成一个迭代器然后返回

本身再移动到下一个节点!

实际上使用的是我们返回后的新创建的迭代器!

而不是原来的迭代器

前置-- 重载

self& operator--()
{_pnode = _pnode->_prev;return *this;
}

后置-- 重载

self operator--(int)
{node* cur = _pnode;_pnode = _pnode->_prev;return self(cur);
}

!= 重载

bool operator!=(const self& it)const
{return _pnode != it._pnode;
}

虽然传的参数类型是__list_iterator

但是实际上比较的是他们里面封装的pnode

== 重载

bool operator==(const self& it)const
{return _pnode == it._pnode;
}

– 重载

self& operator--()
{_pnode = _pnode->_prev;return *this;
}

list的const迭代器——重点

像是string和vector因为其迭代器的本质就是原生指针!所以const迭代器其实也是十分的简单的!但是list不一样!它不是一个原生的指针!是以一个内部类!这就导致了一些问题

如果我们仅仅在普通的迭代器前面加上const

const list<int>iterator cit;

这个const保护的其实是cit本身!

而不是cit指向的内容!

const T* p1;
T* const p2;

上面的行为要类似于p1,而不是p2

p1是本身可以修改!但是p1指向的内容却是不可修改的!

为了能够实现我们预想的行为我们真正要对修改的是返回值!

像是对于 *的重载我们原先返回的是 它的引用

现在我们应该返回的是 它的const T& 从而来进行权限的缩写!

那么我们是不是可以写一个如下的代码呢

const T operator*()const
{return _pnode->data;
}

让const调用这个const重载 让非const调用非const重载

不行!我们不能对解引用进行重载!

为什么?答案是虽然上面的都可行了但是 我们会发现const的对象无法去调用++了

const的对象无法去调用非const的成员函数

那我们如果实现一个const版本的++呢?

self& operator++()const
{_pnode = _pnode->_next;return *this;
}

可行吗?当然是不可行的!因为这个const修饰的是this指针!this指针指向的内容就无法修改了!就没办法做到修改pnode了!

那如何解决这个问题呢?——就是不要让迭代器本身const!

方法1 ——重新写一个类这两个类的唯一区别就是他们的解引用是不一样的!

一个返回引用,一个返回常引用 其他都是完全一致的!

//迭代器
template<class T>
struct __list_const_iterator
{typedef list_node<T> node;node* _pnode;__list_iterator(node* p):_pnode(p){}const T operator*()const{return _pnode->data;}//唯一的区别返回常引用__list_iterator<T>& operator++();__list_iterator<T> operator++(int);__list_iterator<T>& operator--();__list_iterator<T> operator--(int);bool operator!=(const __list_iterator& it);bool operator==(const __list_iterator& it);Ptr operator->()
};

然后使用这个类去实现const版本的list函数调用接口例如begin和end

typedef __list_const_iterator const_iterator
const_iterator begin()const
{return const_iterator(_head->_next);
}
const_iterator end()const
{return const_iterator(_head);
}

然后我们就可以通过这个类来实现const的迭代器!使用的时候const和非const调用的就是两个完全不同的类了!

但是这样子实在是太繁琐了!为了一个不同而写一个类

所以有了方法二 ——使用类模板来实现!即使是同一个类模板 但是使用的参数不一样的时候也就是完不同的类!

vector<int>;
vector<string>;
vector<vector<string>>:

虽然都是同一个类模板,这三种都是不同的类!

所以我们可以添加一个模板参数!

template<class T, class Ref>//Ref 引用
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T, Ref> self;//方便添加模板参数node* _pnode;__list_iterator(node* p):_pnode(p){}Ref operator*()//使用第二个模板参数{return _pnode->_data;}self& operator++();self operator++(int);self& operator--();self operator--(int);bool operator!=(const self& it)const;bool operator==(const self& it)const;Ptr operator->()
};
class list
{typedef list_node<T> node;
private:node* _head;
public:typedef __list_iterator<T, T&>  iterator;typedef __list_iterator<T, const T&> const_iterator;
}
typedef __list_iterator<T,T&>  iterator;//迭代器
typedef __list_iterator<T, const T&> const_iterator;//const 迭代器

这样子我们就通过复用来简洁的完成const的迭代器!

迭代器之-> 重载——重点

template<class T, class Ref,class Ptr>//多引入一个模板参数
struct __list_iterator
{typedef list_node<T> node;typedef __list_iterator<T, Ref,Ptr> self;Ptr operator->(){return &_pnode->_data;}
}

为什么我们需要 重载 -> 呢?

当我遇到如下的如下的结构

struct pos
{int _row;int _col;pos(int row = 0, int col = 0){_row = row;_col = col;}
};

当我们把它插入链表

void test_list()
{list<pos> lt;lt.push_back(pos(1, 2));lt.push_back(pos(2, 3));lt.push_back(pos(3, 4));lt.push_back(pos(3, 5));list<pos>::iterator it = lt.begin();while (it != lt.end()){cout << (*it)._col << " " << (*it)._row << endl;++it;}
}

如果我们想要访问那么就不得不先解引用!

但是我们一般在使用指针的时候都是使用 - > 更加符合我们的使用习惯!

但是当看到重载的时候或许会觉得很奇怪会什么要返回的是数据的指针呢?

it->_col;
//转换成函数调用
it.operator->();//返回值是一个地址一个pos* 类型的指针

那为什么是 pos* 类型的指针后面可以直接跟上成员变量呢?

其实这是因为编译器给我们进行了简化处理

真实的情况 应该是 it->->_col转换成函数调用就是it.operator->()- > _col这样的情况但是这样子又不符合我们的日常使用的习惯和为了更好的可读性!所以编译器就简化了一个 ->

it->->_col;//当然了我们也不可以怎么使用就是了
it.operator->()->_col//但是如果显示的去调用的话我们也可以怎么使用

如果我们不引入第三个模板参数会怎么样

T* operator->()
{return &_pnode->_data;
}

看起来好像也毫无问题啊!

但是当我们遇到const对象的时候!

struct pos
{int _row;int _col;pos(int row = 0, int col = 0){_row = row;_col = col;}
};
void print(const list<pos>& lt)
{list<pos>::const_iterator it = lt.begin();while (it != lt.end()){++it->_col;cout << it->_col << " " << it->_row << endl;++it;}}
void test_list()
{list<pos> lt;lt.push_back(pos(1, 2));lt.push_back(pos(2, 3));lt.push_back(pos(3, 4));lt.push_back(pos(3, 5));
}

image-20221214211747498.png

我们发现const对象下仍然可以通过-> 来修改数据!这就和 我们上面const迭代器讲的 * 的重载是一个道理!

对于const迭代器 - > 的返回值应该是 一个 const指针!用来保护指针指向的内容!

所以我们才要引入第三个模板参数来给 - > 使用!或者就是再写一个类!

 typedef __list_iterator<T,T&,T>  iterator;//迭代器typedef __list_iterator<T, const T&,const T*> const_iterator;//const 迭代器

这样子迭代器才算是真正的完成了!

list的成员变量

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;//const 迭代器
private:node* _head;size_t _size;
}

因为写list_node 不方便所以我们一般会进行重命名

list的构造函数(无参)

void empty_initialize()
{_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;
}
list()
{empty_initialize();
}

链表的底层是一个带头双向循环的链表

开始的头是一个哨兵位所以要new一个!

因为node 的构造函数是带参的构造,没有默认构造!

所以我们要给一个值进行初始化!不可以填0 什么的!

因为我们无法保证这个数据类型是一个内置类型!

所以我们要用T() 来进行初始化!如果是一个自定义类型那么T()就是一个匿名对象,就会去调用这个类的默认构造来初始化!

insert

iterator insert(iterator pos, const T& x = T())
{node* newnode = new node(x);node* cur = pos._pnode;//取到了这个节点的指针!node* prev = cur->_prev;newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;++_size;return iterator(cur);//返回当前这个新插入的元素的迭代器
}

image-20221213154238842.png

begin

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

iterator是一个内部类!

假如有无参构造 那么iterator() 就是一个匿名对象!

iterator(_head-> _next) 就是用 _head-> _next初始化这个对象! begin() 是指向第一个元素的迭代器!

_head是哨兵位

也可以先创建一个对象然后返回!

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

end

iterator end()
{return iterator(_head);
}

end指向的是最后一个元素的下一个元素!那么最后一个元素的下一个元素就是哨兵位!

const版本的begin

const_iterator begin()const
{return const_iterator(_head->_next);
}

const版本的end

const_iterator end()const
{return const_iterator(_head);
}

push_back

原始的写法

void push_back(const T& x)
{node* newnode = new node(x);node* tail = _head->_prev;tail->_next = newnode;newnode->_next = _head;newnode->_prev = tail;_head->_prev = newnode;++_size;
}

要改变 四个指针

newnode的prev和next

tail的next 和 head的prev!

其中head的prev要在要保存成tail才能能修改!否则newnode的prev就找不到原来链表的最后一个元素了了!

如果不保存tail就要最后修改_head->prev!

image-20221129142505132.png

复用的写法

void push_back(const T& x)
{insert(end(), x);//在end的前面插入就是尾插
}

push_front

void push_front(const T& x)
{insert(begin(), x);//在begin的前面插入就是头插!
}

swap

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

交换两个链表对象!

只要交换头结点和大小

构造函数(带参)

template<class InputIterator>
list(InputIterator first, InputIterator last)
{empty_initialize();while (first != last){push_back(*first);++first;}
}

可以复用pushbak来完成一个带参的构造函数

拷贝构造

传统写法

list(const list<T>& lt)
{empty_initialize();//先初始化!创造哨兵位for (const auto& e : lt)//使用使用范围for之前要先实现const迭代器{push_back(e); //进行深拷贝}//范围for就是会自动的从lt里面取 其迭代器然后解引用得到T类型的数据//如果不使用auto&  一旦T是一个自定义类型 可能会多次发生深拷贝!极大的影响效率!
}

现代写法

list(const list<T>& lt)
{empty_initialize();list<T> temp(lt.begin(), lt.end());swap(temp);
}

现代写法是利用带参的构造来完成深拷贝!然后交换彼此的头结点来完成一次拷贝构造!

但是开始一定要完成一次初始化!

否则开始的我们我们要初始化的这个头结点是一个随机值,temp一旦出了作用域后就会立刻调用析构函数,析构一个野指针会导致程序崩溃!

或者我们能不能不能在初始化列表里面将_head初始化成nullptr呢?也不行!因为我们写的析构函数是默认有哨兵位的情况下进行析构的!所以必须进行初始化!

赋值重载

传统写法

//传统写法
iterator& operator=(const list<T>& lt)
{if (this != &lt){clear();//每次赋值前都去清除掉原理的for (const auto& e : lt){push_back(e);}}return *this;
}

现代写法

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

这个写法是利用了拷贝构造! 传值传参会调用拷贝构造构造一个新的对象!lt

然后我们可以直接利用swap交换头结点!

而且出了作用域后lt也会调用析构函数来释放原来对象的资源!

erase

iterator erase(iterator pos)
{assert(pos != end());//不能等于哨兵位!node* cur = pos._pnode;node* next = cur->_next;node* prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);//返回删除的节点的下一个节点的迭代器
}

image-20221213160033939.png

先把前后两个节点保存下来

然后改变前一个节点的next

改变后一个节点的prev

最后释放掉原来的节点!

pop_front

void pop_front()
{erase(begin());//删除掉头结点
}

pop_back

void pop_back()
{erase(--end());//end()是最后一个节点的下一个!所以要-- 是其指向最后一个节点!
}

clear

void clear()
{iterator it = begin();while (it != end()){it = erase(it);//erase之后不能++ 因为已经发生了迭代器失效!}
}

clear和析构的区别在于 clear不清头结点!

析构函数

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

析构函数记得要清楚头结点!

size

size_t size()const
{return _size;
}

关于list的size,因为有的源码可能不提供_size这个成员变量 使用的是遍历一遍然后返回!时间复杂度为O(N)

所以使用list的size需要谨慎!

empty

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

也可以使用

bool empty()const
{return _head->_next == _head->_prev;
}

来判断是否为空

代码展示

#pragma once
#include<iostream>
#include<assert.h>
namespace My_STL
{template<class T>struct list_node{list_node* _next;list_node* _prev;T _data;list_node(const T& x):_data(x),_next(nullptr),_prev(nullptr){}};template<class T,class Ref,class Ptr>struct __list_iterator{typedef list_node<T> node;typedef __list_iterator<T, Ref, Ptr> self;node* _pnode;__list_iterator(node* p):_pnode(p){}Ref operator*(){return _pnode->_data;}Ptr operator->(){return &_pnode->_data;}self& operator++(){_pnode = _pnode->_next;return *this;}self operator++(int){node* cur = _pnode;_pnode = _pnode->_next;return self(cur);}self& operator--(){_pnode = _pnode->_prev;return *this;}self operator--(int){node* cur = _pnode;_pnode = _pnode->_prev;return self(cur);}bool operator!=(const self& it)const{return _pnode != it._pnode;}bool operator==(const self& it)const{return _pnode == it._pnode;}};template<class T>class list{typedef list_node<T> node;private:node* _head;size_t _size;public:typedef __list_iterator<T,T&,T>  iterator;typedef __list_iterator<T, const T&,const T*> const_iterator;void empty_initialize(){_head = new node(T());_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_initialize();}template<class InputIterator>list(InputIterator first, InputIterator last){empty_initialize();while (first != last){push_back(*first);++first;}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}list(const list<T>& lt){empty_initialize();list<T> temp(lt.begin(), lt.end());swap(temp);}~list(){clear();delete _head;_head = nullptr;}iterator& operator=(list<T> lt){swap(lt);return *this;}void clear(){iterator it = begin();while (it != end()){it = erase(it);}}iterator insert(iterator pos, const T& x = T()){node* newnode = new node(x);node* cur = pos._pnode;node* prev = cur->_prev;newnode->_next = cur;newnode->_prev = prev;prev->_next = newnode;cur->_prev = newnode;++_size;return iterator(cur);}iterator erase(iterator pos){assert(pos != end());node* cur = pos._pnode;node* next = cur->_next;node* prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);}void push_back(const T& x){insert(end(), x);}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}void pop_back(){erase(--end());}iterator begin(){return iterator(_head->_next);}const_iterator begin()const{return const_iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator end()const{return const_iterator(_head);}size_t size()const{return _size;}bool empty()const{		//return _head->_next == _head->_prev;return _size == 0;}};};

相关文章:

list底层的简单实现(万字长文详解!)

list底层的简单实现 文章目录list底层的简单实现list_node的实现&#xff01;list_node的构造函数list的迭代器&#xff01;——重点&#xff01;list迭代器的成员变量迭代器的构造函数* 重载前置 重载后置 重载前置-- 重载后置-- 重载! 重载 重载-- 重载list的const迭代器——…...

学习Linux只要学会这个命令就够了!

大家好&#xff0c;我是良许。 这段时间又是搬家&#xff0c;又是找新办公室&#xff0c;现在终于安顿下来了&#xff0c;有时间给大家分享干货了。 今天给大家介绍一个 Linux 超级实用命令&#xff0c;有了这个命令&#xff0c;你就可以愉快使用 Linux 上几乎所有常用命令了…...

javascript基础

javascript基础 1概述&#xff1a; JavaScript是目前web开发中不可缺少的脚本语言&#xff0c;js不需要编译即可运行&#xff0c;运行在客户端&#xff0c;需要通过浏览器来解析执行JavaScript代码。 诞生于1995年&#xff0c;当时的主要目的是验证表单的数据是否合法。 JavaS…...

【游戏逆向】某游戏技能库分析

技能库的分析大多是从技能名字入手的&#xff0c;然后再通过传入职业或者ID等信息去到库中去取当前角色的可用技能。下面我们来对《**明月刀》中的技能库进行分析。 首先通过CE对技能名字进行搜索&#xff0c;得到较少的结果&#xff0c;分别对结果进行修改&#xff0c;并再次…...

Pytorch深度学习常用预训练网络模型的下载地址

Resnet:model_urls {‘resnet18’: ‘https://download.pytorch.org/models/resnet18-5c106cde.pth‘,‘resnet34’: ‘https://download.pytorch.org/models/resnet34-333f7ec4.pth‘,‘resnet50’: ‘https://download.pytorch.org/models/resnet50-19c8e357.pth‘,‘resnet…...

毕业设计 基于51单片机自动智能浇花系统设计

基于51单片机自动智能浇花系统设计1、毕业设计选题原则说明&#xff08;重点&#xff09;2、项目资料2.1 系统框架2.2 系统功能3、部分电路设计3.1 STC89C52单片机最小系统电路设计3.2 按键电路设计3.3 水泵控制电路设计4、部分代码展示4.1 数码管位选程序4.2 ad0832数据读取程…...

熟悉常用的 Linux 操作和 Hadoop 操作

文章目录前言一、常用命令集合1、cd命令&#xff1a;切换目录1、切换到目录/usr/local2、切换回上级目录3、切换到当前登录Linux系统的用户的自己的文件夹2、ls命令&#xff1a;查看文件与目录3、mkdir命令&#xff1a;创建目录4、rmdir命令&#xff1a;删除空的目录5、cp 命令…...

Vue2项目总结-电商后台管理系统

Vue2项目总结-电商后台管理系统 去年做的项目&#xff0c;拖了很久&#xff0c;总算是打起精力去做这个项目的总结&#xff0c;并对Vue2的相关知识进行回顾与复习 各个功能模块如果有过多重复冗杂的部分&#xff0c;将会抽取部分值得记录复习的地方进行记录 一&#xff1a;项目…...

【二】一起算法---队列:STL queue、手写循环队列、双端队列和单调队列、优先队列

纸上得来终觉浅&#xff0c;绝知此事要躬行。大家好&#xff01;我是霜淮子&#xff0c;欢迎订阅我的专栏《算法系列》。 学习经典算法和经典代码&#xff0c;建立算法思维&#xff1b;大量编码让代码成为我们大脑的一部分。 ⭐️已更系列 1、基础数据结构 1.1、链表➡传送门 1…...

<Linux>环境变量

环境变量 文章目录环境变量一、基本概念二、常见环境变量三、查看环境变量的方法四、测试PATH五、测试HOME六、测试SHELL七、环境变量相关的命令八、环境变量的组织方式九、命令行参数十、通过代码获得环境变量十一、通过系统调用获取环境变量十二、环境变量通常是具有全局属性…...

【MySQL】下载(超详细教程)

目录 First-下载 Second-安装 Third-检测是否安装 Last-总结 First-下载 首先 &#xff0c;我们一步一步跟着我的操作来&#xff0c;不能越步骤&#xff0c;很容易报错&#xff0c;就芭比Q了。 第一步直接进入这个网址&#xff1a;MySQL &#xff1a;&#xff1a; MySQL 社…...

再探pytorch的Dataset和DataLoader

本文从分类、检测、分割三大任务的角度来剖析pytorch得dataset和dataloader源码&#xff0c;可以让初学者深刻理解每个参数的由来和使用&#xff0c;并轻松自定义dataset。思考&#xff1a;在探究Dataset和DataLoader之前&#xff0c;需要明白一个事情&#xff0c;就是当我们不…...

【2023.3.18 美团校招】

文章目录1. 小美剪彩带2. 最多修改两个字符&#xff0c;生成字典序最小的回文串1. 小美剪彩带 题意&#xff1a;找出区间内不超过k种数字子数组的最大长度 使用双指针的方式&#xff0c;用哈希表来统计每个数出现次数。在双指针移动的过程中&#xff0c;动态的维护区间内不同数…...

程序员必须知道的HTML常用代码有哪些?

HTML 即超文本标记语言&#xff0c;是目前应用最为广泛的语言之一&#xff0c;是组成一个网页的主要语言。在现今这个 HTML5 华丽丽地占领了整个互联网的时候&#xff0c;如果想要通过网页抓住浏览者的眼球光靠因循守旧是不行的&#xff0c;程序猿们需要掌握一些必须知道的 HTM…...

多目标家庭行为检测--人脸识别模块构建

文章目录前言原理项目结构编码配置主控函数人脸采集模块特征提取识别测试前言 2023-3-18 天小雨&#xff0c;午觉舒适程度5颗星。任务完成指数2颗星。续接上文&#xff1a;《MidiaPipe stgcn&#xff08;时空图卷积网络&#xff09;实现人体姿态判断&#xff08;单目标&#x…...

RocketMQ重复消费问题的原因

文章目录 概览消息发送异常时重复发送消费消息抛出异常消费者提交offset失败服务端持久化offset失败主从同步offset失败重平衡清理长时间消费的消息总结概览 消息发送异常时重复发送 首先,我们来瞅瞅RocketMQ发送消息和消费消息的基本原理。 如图,简单说一下上图中的概念: …...

proxy详细介绍与使用

proxy详细介绍与使用 proxy 对象用于创建一个对象的代理&#xff0c;是在目标对象之前架设一个拦截&#xff0c;外界对该对象的访问&#xff0c;都必须先通过这个拦截。通过这种机制&#xff0c;就可以对外界的访问进行过滤和改写。 ES6 原生提供 Proxy 构造函数&#xff0c;…...

基于YOLOv5的舰船检测与识别系统(Python+清新界面+数据集)

摘要&#xff1a;基于YOLOv5的舰船检测与识别系统用于识别包括渔船、游轮等多种海上船只类型&#xff0c;检测船舰目标并进行识别计数&#xff0c;以提供海洋船只的自动化监测和管理。本文详细介绍船舰类型识别系统&#xff0c;在介绍算法原理的同时&#xff0c;给出Python的实…...

【C#】List数据去重

系列文章 【C#】单号生成器&#xff08;定义编号规则、流水号、产生业务单号&#xff09; 本文链接&#xff1a;https://blog.csdn.net/youcheng_ge/article/details/129129787 【C#】日期范围生成&#xff08;构建本周开始、结束日期&#xff09; 本文链接&#xff1a;https…...

避免踩坑,教给你VSCode中最常用到的6项功能

这里为程序员介绍VSCode中包含的许多令人兴奋的Tips。 1. 插件市场中免费下载使用CodeGeeX插件 AI辅助编程工具CodeGeeX&#xff0c;是完全免费&#xff0c;开源开放给所有开发者使用。程序员普遍反应使用这个插件后&#xff0c;代码编写效率提升2倍以上。 CodeGeeX插件拥有…...

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…...

Cursor实现用excel数据填充word模版的方法

cursor主页&#xff1a;https://www.cursor.com/ 任务目标&#xff1a;把excel格式的数据里的单元格&#xff0c;按照某一个固定模版填充到word中 文章目录 注意事项逐步生成程序1. 确定格式2. 调试程序 注意事项 直接给一个excel文件和最终呈现的word文件的示例&#xff0c;…...

《从零掌握MIPI CSI-2: 协议精解与FPGA摄像头开发实战》-- CSI-2 协议详细解析 (一)

CSI-2 协议详细解析 (一&#xff09; 1. CSI-2层定义&#xff08;CSI-2 Layer Definitions&#xff09; 分层结构 &#xff1a;CSI-2协议分为6层&#xff1a; 物理层&#xff08;PHY Layer&#xff09; &#xff1a; 定义电气特性、时钟机制和传输介质&#xff08;导线&#…...

解锁数据库简洁之道:FastAPI与SQLModel实战指南

在构建现代Web应用程序时&#xff0c;与数据库的交互无疑是核心环节。虽然传统的数据库操作方式&#xff08;如直接编写SQL语句与psycopg2交互&#xff09;赋予了我们精细的控制权&#xff0c;但在面对日益复杂的业务逻辑和快速迭代的需求时&#xff0c;这种方式的开发效率和可…...

macOS多出来了:Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用

文章目录 问题现象问题原因解决办法 问题现象 macOS启动台&#xff08;Launchpad&#xff09;多出来了&#xff1a;Google云端硬盘、YouTube、表格、幻灯片、Gmail、Google文档等应用。 问题原因 很明显&#xff0c;都是Google家的办公全家桶。这些应用并不是通过独立安装的…...

cf2117E

原题链接&#xff1a;https://codeforces.com/contest/2117/problem/E 题目背景&#xff1a; 给定两个数组a,b&#xff0c;可以执行多次以下操作&#xff1a;选择 i (1 < i < n - 1)&#xff0c;并设置 或&#xff0c;也可以在执行上述操作前执行一次删除任意 和 。求…...

【论文阅读28】-CNN-BiLSTM-Attention-(2024)

本文把滑坡位移序列拆开、筛优质因子&#xff0c;再用 CNN-BiLSTM-Attention 来动态预测每个子序列&#xff0c;最后重构出总位移&#xff0c;预测效果超越传统模型。 文章目录 1 引言2 方法2.1 位移时间序列加性模型2.2 变分模态分解 (VMD) 具体步骤2.3.1 样本熵&#xff08;S…...

在WSL2的Ubuntu镜像中安装Docker

Docker官网链接: https://docs.docker.com/engine/install/ubuntu/ 1、运行以下命令卸载所有冲突的软件包&#xff1a; for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done2、设置Docker…...

laravel8+vue3.0+element-plus搭建方法

创建 laravel8 项目 composer create-project --prefer-dist laravel/laravel laravel8 8.* 安装 laravel/ui composer require laravel/ui 修改 package.json 文件 "devDependencies": {"vue/compiler-sfc": "^3.0.7","axios": …...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...