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

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

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

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

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

相关文章

AI日报:百度发布文心大模型学习机;Open-Sora 1.1可生成21秒视频;Canva可以自动剪辑视频了;超牛ComfyUI节点AnyNode来了

欢迎来到【AI日报】栏目!这里是你每天探索人工智能世界的指南&#xff0c;每天我们为你呈现AI领域的热点内容&#xff0c;聚焦开发者&#xff0c;助你洞悉技术趋势、了解创新AI产品应用。 新鲜AI产品点击了解&#xff1a;AIbase - 智能匹配最适合您的AI产品和网站 1、百度文心…

网络、HTTP、HTTPS、Session、Cookie、UDP、TCP

OSI 七层模型 应用层、表示层、会话层、传输层、网络层、数据链路层、物理层 TCP/IP 五层模型 应用层&#xff1a;为用户的应用进程提供网络通信服务&#xff08;协议&#xff1a;域名系统DNS协议&#xff0c;HTTP协议&#xff0c;SMTP协议&#xff09;传输层&#xff1a;负…

leetcode 530.二叉搜索树的最小绝对差 、501.二叉搜索树中的众数 、236. 二叉树的最近公共祖先

leetcode 530.二叉搜索树的最小绝对差 、501.二叉搜索树中的众数 、236. 二叉树的最近公共祖先 leetcode 530.二叉搜索树的最小绝对差 题目链接&#xff1a;https://leetcode.cn/problems/maximum-binary-tree/description/ 题目&#xff1a; 给你一个二叉搜索树的根节点 r…

【MATLAB源码-第214期】基于matlab的遗传算法GA最短路径路由优化算法仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 在现代网络通信和路径规划领域&#xff0c;最短路径路由优化算法是一项关键技术。它涉及在给定的网络拓扑中寻找从源点到目标点的最短或成本最低的路径。近年来&#xff0c;遗传算法&#xff08;GA&#xff09;因其出色的全局…

跟进2年弄丢1.8亿,你的大客管理错在哪里?

数量并非目的之所在&#xff0c;质量才是根本之道。重视1%的超级用户&#xff0c;才是提高效率的关键所在。 ——凯文凯利 在当今的商业环境中&#xff0c;大客户已成为销售服务型企业最宝贵的资产。他们不仅贡献了企业收入的重要一环&#xff0c;…

pinia持久化未生效

pinia官方文档 https://prazdevs.github.io/pinia-plugin-persistedstate/zh/guide/ pinia持久化未生效的原因很有可能就是在main.js中重复创建了pinia&#xff0c;导致持久化未生效

sql注入利用group_concat函数

1.group_concat函数的作用&#xff1a; 首先根据group by指定的列进行分组&#xff0c;将同一组的列显示出来&#xff0c;并且用分隔符分隔。 2.group_concat运用 这里我使用的是sqllab-less1&#xff0c;通过对数据库的查询&#xff0c;我们发现数据库表名&#xff0c;列名&a…

手写电纸书天花板,阅读办公新体验 | 汉王手写电纸本 N10 2024 版使用评测

手写电纸书天花板&#xff0c;阅读办公新体验 | 汉王手写电纸本 N10 2024 版使用评测 请问如果说到电纸书&#xff0c;你的认知还只是Kindle吗&#xff1f;然而遗憾的是&#xff0c;Kindle亦是过去&#xff0c;智能才是未来。 哈喽小伙伴们好&#xff0c;我是Stark-C~&#x…

蓝桥杯嵌入式国赛笔记(4):多路AD采集

1、前言 蓝桥杯的国赛会遇到多路AD采集的情况&#xff0c;这时候之前的单路采集的方式就不可用了&#xff0c;下面介绍两种多路采集的方式。 以第13届国赛为例 2、方法一&#xff08;配置通道&#xff09; 2.1 使用CubeMx配置 设置IN13与IN17为Single-ended 在Parameter S…

内网安全-隧道搭建穿透上线内网穿透-nps自定义上线内网渗透-Linux上线-cs上线Linux主机

目录 内网安全-隧道搭建&穿透上线内网穿透-nps-自定义-上线NPS工具介绍搭建过程 nps原理介绍MSF上线CS上线 内网渗透-Linux上线-cs上线Linux主机1.下载插件2.导入插件模块3.配置监听器4.服务端配置5.配置C2监听器并生成木马6.执行木马 内网安全-隧道搭建&穿透上线 内网…

2024年新算法-秘书鸟优化算法(SBOA)优化BP神经网络回归预测

2024年新算法-秘书鸟优化算法(SBOA)优化BP神经网络回归预测 亮点&#xff1a; 输出多个评价指标&#xff1a;R2&#xff0c;RMSE&#xff0c;MSE&#xff0c;MAPE和MAE 满足需求&#xff0c;分开运行和对比的都有对应的主函数&#xff1a;main_BP, main_SBOA, main_BPvsBP_SB…

【计算机视觉 Mamba】MambaOut: Do We Really Need Mamba for Vision?

MambaOut: Do We Really Need Mamba for Vision? 在视觉任务上我们需要Mamba吗? 论文地址 代码地址 知乎解读&#xff1a;王牌飞行员申请出战&#xff01; 知乎解读&#xff1a;Mamba 模型解读 (一)&#xff1a;MambaOut&#xff1a;在视觉任务中&#xff0c;我们真的需要 …

Langchain-Chatchat之pdf转markdown格式

文章目录 背景开发环境loader文本解析步骤markdown格式的文本为什么选择markdown格式测试markdown格式提取表格原pdf表格markdown格式的表格 测试markdown格式的知识库运行项目修改文件加载器loader 其他问题运行项目报错查看系统当前的max_user_watches修改sysctl.conf配置 图…

AWS迁移与传输之AMS/MGN

AWS Application Migration Service&#xff08;AWS Application Migration Service简称为AWS MGN&#xff0c;MGN是migration的缩写。&#xff09;是一项全面的迁移服务&#xff0c;旨在帮助企业将其本地服务器和虚拟机迁移到云端&#xff0c;包括AWS和VMware Cloud on AWS。 …

OrangePi AIpro初体验:开启嵌入式开发之旅

概述 随着物联网和智能设备时代的到来&#xff0c;单板电脑因其独特的优势成为创新项目和教育实践的重要工具。在众多单板电脑中&#xff0c;香橙派以其出色的性能和亲民的价格&#xff0c;十分吸引博主这初涉嵌入式开发的新手。博主有幸被CSDN邀请对OrangePi AIpro进行测评。…

vivado设置Vscode为默认编辑器

D:\vscode\Microsoft VS Code\Code.exe -g [file name]:[line number]

鸿蒙ArkUI-X跨平台开发:【资源分类与访问】

资源分类与访问 应用开发过程中&#xff0c;经常需要用到颜色、字体、间距、图片等资源&#xff0c;在不同的设备或配置中&#xff0c;这些资源的值可能不同。 应用资源&#xff1a;借助资源文件能力&#xff0c;开发者在应用中自定义资源&#xff0c;自行管理这些资源在不同…

话术巧妙分隔沟通效果更佳看看这个小技巧

客服回复客户咨询&#xff0c;如果遇到比较复杂的问题&#xff0c;经常会有大段的文字回复&#xff0c;用聊天宝的分段符功能&#xff0c;在需要分段的地方点击右上角的“插入分隔符”&#xff0c;就可以在指定位置分段&#xff0c;实现多段发送的目的。 前言 客服回复客户咨询…

干冰清洗机的清洗原理及应用

干冰清洗机的清洗原理及应用可以详细阐述如下&#xff1a; 一、清洗原理 干冰清洗机的清洗原理主要基于干冰的低温冷冻作用。干冰在常温下会迅速升华&#xff0c;吸收大量的热量&#xff0c;使周围的温度迅速降低。当干冰颗粒通过特殊的干冰清洗机喷射到清洗物体表面时&#…

系统架构设计师【第1章】: 绪论 (核心总结)

文章目录 1.1 系统架构概述1.1.1 系统架构的定义及发展历程1.1.2 软件架构的常用分类及建模方法1.1.3 软件架构的应用场景1.1.4 软件架构的发展未来 1.2 系统架构设计师概述1.2.1 架构设计师的定义、职责和任务1.2.2 架构设计师应具备的专业素质1.2.3 架构设计师的知识…