【C++】解锁<list>的正确姿势
> 🍃 本系列为初阶C++的内容,如果感兴趣,欢迎订阅🚩
> 🎊个人主页:[小编的个人主页])小编的个人主页
> 🎀 🎉欢迎大家点赞👍收藏⭐文章
> ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍
目录
🐼前言
🐼认识list
🐼list的迭代器失效问题⭐️
🐼list的模拟实现
🚩定义链表节点结构
🚩定义list类
🚩正向迭代器实现⭐️
🚩迭代器使用
🚩insert操作
🚩erase操作
🚩增删
🚩list构造
🚩析构函数
🚩反向迭代器实现⭐️
🐼全部源码
🐼总结
🐼前言
👐在之前的容器<string>,<vector>中,我们遇到的底层物理空间都是连续的,在list中,由于底层物理空间不连续,但是逻辑上是连续的,此时底层是如何实现的呢❓迭代器的行为又是什么样呢❓小编这篇文章👇带你从0认识并掌握使用list并了解list的底层结构。
🐼认识list
我们可以借助Cplusplus来查看list类的一些常用接口(list类中的其它接口小伙伴们可以根据我给的链接在需要时进行查询)。
🌻list类的构造:
以及第一个构造空的初始化构造。
🌻list iterator的使用:
这里可以先简单将迭代器理解成一个指针,该指针指向list中的某个节点。在模拟实现时我们可以再谈。
🌻容量操作
🌻访问元素操作
list支持访问头部和尾部元素,不支持随机访问,因为效率太低。但是像vector支持随机访问。List不支持operator[]
🌻增删查改操作
和<vector>不同的是,list支持在头部和尾部操作,因为效率很高,<vector>不支持在头部操作。
其余操作,大家可以查文档。
🐼list的迭代器失效问题⭐️
在<vector>中我们认为insert需要扩容和erase后,原来的迭代器就失效了,不能继续使用。list稍微有一些不同。首先迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器,其他迭代器不会受到影响。本质上还是由于vector物理空间是连续的,扩容等操作需要发生空间搬移,而list物理空间不连续,迭代器指向的那块空间没有发生改变。
举个例子:
void Test_lsg_list08() {int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l1(array, array + sizeof(array) / sizeof(array[0]));auto it = l1.begin();while (it != l1.end()){// erase()函数执行后,it所指向的节点已被删除,因此it无效,在下一次使用it时,必须先给// 其赋值l1.erase(it);++it;}//改正list<int> l2(array, array + sizeof(array) / sizeof(array[0]));auto it = l2.begin();while (it != l2.end()){it = l2.erase(it);//返回下一个元素的迭代器}}
erase删除It迭代器之后,it位置的迭代器失效了,需要重新更新it,才能继续使用。
🐼list的模拟实现
list的底层使用的是双向循环带头链表。如果有不清楚的小伙伴,可以看这篇文章,双向循环带头链表。
🚩定义链表节点结构
//定义节点结构 template<class T> struct List_Node {List_Node<T>* _next;//指向下一个节点的指针List_Node<T>* _prev;//指向前一个节点的指针T _data;List_Node(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){} };
定义链表节点结构,并对每个节点进行构造初始化,避免是垃圾值。
🚩定义list类
我们知道list是双向循环带头链表,在list类拿一个头结点来维护一个list对象,并且我们希望统计list中元素的个数,list类就可以这样定义了:
template<class T> class list {typedef List_Node<T> Node;typedef List_Node<T>* pNode; public:void empty_init(){_head = new Node(-1);_head->_next = _head;_head->_prev = _head;}list(){empty_init();}private:pNode _head;size_t _size; };
✅代码解析:
完成了对空list类对象的初始化,本质是双向循环带头链表。
🚩正向迭代器实现⭐️
下面,我们完成list迭代器的创建工作:
我们知道迭代器目的是不暴露底层结构,不管是<vector><list><tree>等,对于不同的容器遍历的使用方式都是一样的,而迭代器的行为就是像指针一样,有的迭代器就是指针,不需要我们封装,像<vector><string>,而有的迭代器需要我们封装,像<list>等,这正是我们的STL设计迭代器的目的,不暴露底层结构,对于不同容器间一套相同的访问方式。
在list类中,如果我们还希望迭代器能访问双链表中的元素,即*访问到当前节点保存的值,++访问到下一个节点。如果单靠Node*作为迭代器,那解引用是Node,++也访问不到下一个节点,这显而易见没有这么简单。既然迭代器行为是具有像指针一样的东西,那么如果我们就能对迭代器进行封装,可以重载*和++以及更多的迭代器操作。
正向迭代器非const版本实现:
template <class T>struct List_iterator{typedef List_Node<T> Node;typedef List_Node<T>* pNode;pNode _Node;List_iterator(Node* node):_Node(node){}T& operator*(){return _Node->_data;}//前置++List_iterator<T>& operator++(){_Node = _Node->_next;return *this;}//后置++List_iterator<T> operator++(int){List_iterator<T> tmp = *this;_Node = _Node->_next;return tmp;}bool operator!=(const List_iterator<T>& it){return _Node != it._Node;}T* operator->(){return &_Node->_data;}};
✅代码解析:
迭代器支持*,我们就重载一份*操作符来访问元素的值,迭代器支持++,--,我们就重载一份++,--,让迭代器的行为能够支持++,--。这样,不暴露底层结构,我们就能完成一套相同的访问操作。而list迭代器本质还是一个Node*的指针,只不过我们进行了封装。
我们根据上述的思路再实现正向迭代器const版本。
template <class T> struct const_List_iterator {typedef List_Node<T> Node;typedef List_Node<T>* pNode;pNode _Node;const_List_iterator(Node* node):_Node(node){}const T& operator*() const{return _Node->_data;}//前置++const_List_iterator<T>& operator++() {_Node = _Node->_next;return *this;}const_List_iterator<T> operator++(int) {List_iterator<T> tmp = *this;_Node = _Node->_next;return tmp;}bool operator!=(const const_List_iterator<T>& it){return _Node != it._Node;}const T* operator->() const{return &_Node->_data;} };
只需要把权限缩小到const。
但是这样写,代码有点冗余了,因为我们只想控制const和非const在<list>中,那么如果我们能够在迭代器实例化时传参时传入对应的参数,因为他们都是一系列共用的迭代器家族,只是权限上有差异。因此,我们可以用函数模版多加两个参数来避免代码冗余性。
//用模版方法来控制const和非const迭代器 template <class T,class Ref,class Ptr> struct List_iterator {typedef List_Node<T> Node;typedef List_Node<T>* pNode;typedef List_iterator<T, Ref, Ptr> Self;pNode _Node;List_iterator(Node* node):_Node(node){}//迭代器具有像指针一样的行为,可以解引用Ref operator*(){return _Node->_data;}//指针可以通过->访问其所指空间成员,因此迭代器类中必须重载oprator->()Ptr operator->(){return &_Node->_data;}//迭代器可以++//前置++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){return _Node != it._Node;}bool operator==(const Self& it){return _Node == it._Node;}};
这样我们在list实例化时,传入对应的参数,list_iterator就能实例化出不同的迭代器版本。
🚩<list>迭代器使用
//传参来控制const迭代器和非const迭代器 typedef List_iterator<T,T&,T*> iterator; typedef List_iterator<T,const T&,const T*> const_iterator;iterator begin() {return iterator(_head->_next); }iterator end() {return iterator(_head); }const_iterator begin() const {return const_iterator(_head->_next); }const_iterator end() const {return const_iterator(_head); }
这里通过传参来控制const迭代器和非const迭代器,以构造匿名对象的形式来作为返回值,更简洁,也更好。
🚩insert操作
此处在pos位置和双链表中插入元素的逻辑一样,只不过pos此时是用迭代器封装的。
//insert后pos位置迭代器失效· void insert(iterator pos, const T& x) {pNode newnode = new Node(x);//prev newnode curpNode cur = pos._Node;pNode prev = cur->_prev;prev->_next = newnode;newnode->_next = cur;newnode->_prev = prev;cur->_prev = newnode;++_size;//pos = iterator(newnode);//如果想继续使用,更新pos位置的迭代器 }
注意:这里插入操作,pos位置的迭代器并没有失效,只不过逻辑上在下一个位置了,如果需要让pos指向新插入的节点,可以显式地更新它,如 pos = iterator(newnode)
;
iterator(newnode)是构造一个新的迭代器匿名对象,并将其赋值给 pos
。
🚩
erase操作
erase pos位置后,pos位置的迭代器被删除了,即失效了,不能继续使用。不过erase后,返回下一个元素位置的迭代器。
iterator erase(iterator pos){pNode cur = pos._Node;pNode next = cur->_next;pNode prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);//返回下一个元素的迭代器}
✅代码解析:
实现方式和双向带头循环带头链表删除某个pos节点是一样的。
对比一下<vector> <list>insert和erase操作后迭代器失效问题:
<vector>insert操作,数据可能需要扩容,那么指向pos位置的迭代器就失效了;而<list>insert操作,pos位置的迭代器没有删除,只是逻辑上发生了变化,因此没有失效;
<vector><list>erase操作由于pos位置迭代器都删除了,因此都失效了。不过,erase后,都要返回下一个元素位置的迭代器。
🚩增删
有了insert和erase后,头插头删,尾插尾删都很方便。
void push_back(const T& x){insert(end(), x);}void pop_back(){erase(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}
我们需要注意的是,<vector>中,没有对头部的操作,因为要挪动数据,效率很低。
🚩list构造
我们这里实现一下分别实现拷贝构造,赋值运算符重载,以及用一段迭代器区间构造,和initializer_list的构造
void empty_init() {_head = new Node(-1);_head->_next = _head;_head->_prev = _head; }//拷贝构造 //lt2(lt1) list(const list<T>& it) {empty_init();for (auto& e : it){push_back(e);} }void swap(list<T>& it){std::swap(_head, it._head);std::swap(_size, it._size);} //赋值运算符重载list<T>& operator=(list<T> it){swap(it);return *this;}template <class InputIterator> list(InputIterator first, InputIterator last) {empty_init();while (first != last){push_back(*first);first++;} }list(initializer_list<T> il) {empty_init();for (auto& e : il){push_back(e);} }
✅代码解析:
拷贝构造先调用empty_init为*this开辟头结点,再直接尾插。
有了拷贝构造就可以直接写赋值赋值运算符重载。
用一段迭代器区间构造,我们来遍历这段迭代器区间,然后完成尾插工作。
initializer_list调用empty_init为this开辟头结点,再拿il中的元素进行尾插。
🚩析构函数
析构函数是对有资源的对象完成销毁和清理工作.
明确一下,此处有资源的包括每个节点,以及头结点。
void clear() {iterator it = begin();while(it != end()){it = erase(it);//erase后更新迭代器,防止迭代器失效;it++;} }~list() {clear();delete _head;_head = nullptr; }
✅代码解析:
先释放<list>类对象中头结点后的所有元素,最后,释放头结点。
🚩反向迭代器实现⭐️
首先我们来了解一下适配器的概念:
适配器(Adapter)是一个设计模式,用于解决两个不兼容接口之间的兼容性问题。适配器模式允许你通过创建一个适配器类来“转换”一个类的接口,使其能够与另一个类的接口兼容。
简单可以理解成类模版之间的复用
而我们已经实现了正向迭代器,反向迭代器的行为跟正向迭代器没有什么不同,解引用可以取元素,迭代器++,--支持移动。无非就是反向迭代器的++是正向迭代器的--,反向迭代器的--是正向迭代器的++,逻辑上是相反的。
因此我们可以使用正向迭代器作为适配器,来适用于反向迭代器的实现。
我们先简单实现一下👉:
#pragma oncetemplate<class Iterator,class Ref,class Ptr>struct Reverse_iterator{typedef Reverse_iterator<Iterator, Ref, Ptr> Self;Reverse_iterator(Iterator it):_it(it){}//迭代器支持解引用/*Ref operator*(){return *_it;}*///返回前一个元素的值Ref operator*(){Iterator tmp = _it;tmp--;return *tmp;}Ptr operator->(){return &(operator*());}//迭代器支持移动Self& operator++(){--_it;return *this;}Self operator++(int){Self tmp(*this);--_it;return tmp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self tmp(*this);++_it;return tmp;}//迭代器支持比较bool operator==(const Self& it){return _it == it._it;}bool operator!=(const Self& it){return _it != it._it;}Iterator _it; };
反向迭代器的成员变量是<iterator>类型,用<iterator>作为适配器。
调用只需要调用适配器的接口,只需要注意逻辑上方向的问题,反向迭代器的++是正向迭代器的--。
这里有个问题,就是为什么反向迭代器解引用是访问前一个数据❓
这里设计本质是希望对称,即你的end()是我的rbegin(),你的begin()是我的rend()
因此这样从rbegin开始遍历,由于第一个位置是头结点,只能访问前面一个元素,也就是4,然后3,2,1,直到rbegin==rend
因此我们有了反向迭代器,对所有容器都可以使用,前提是只要提供了它的正向迭代器,我们拿它的正向迭代器适配出对应的反向迭代器。
因此在<list>中,我们构造出反向迭代器的rbegin(),rend()const和非const版本。
//反向迭代器 typedef Reverse_iterator< iterator, T&, T*> reverse_iterator; typedef Reverse_iterator< const_iterator, const T&, const T*> const_reverse_iterator;reverse_iterator rbegin() {return reverse_iterator(end()); }reverse_iterator rend() {return reverse_iterator(begin()); }const_reverse_iterator rbegin() const {return const_reverse_iterator(end()); }const_reverse_iterator rend() const {return const_reverse_iterator(begin()); }
<list>中传入模版参数,Reverse_iterator这样实例化。其中第一个参数以iterator作为适配器。
🐼全部源码
list.h👇👇
#pragma once#include<iostream>
#include<list>
#include"iterator.h"
using namespace std;namespace lsg
{//定义节点结构template<class T>struct List_Node{List_Node<T>* _next;//指向下一个节点的指针List_Node<T>* _prev;//指向前一个节点的指针T _data;List_Node(const T& x = T()):_next(nullptr),_prev(nullptr),_data(x){}};两个迭代器版本(高度相似)//template <class T>//struct List_iterator//{// typedef List_Node<T> Node;// typedef List_Node<T>* pNode;// // pNode _Node;// List_iterator(Node* node)// :_Node(node)// {}// T& operator*()// {// return _Node->_data;// }// //前置++// List_iterator<T>& operator++()// {// _Node = _Node->_next;// return *this;// }// //后置++// List_iterator<T> operator++(int)// {// List_iterator<T> tmp = *this;// _Node = _Node->_next;// return tmp;// }// // bool operator!=(const List_iterator<T>& it)// {// return _Node != it._Node;// }// T* operator->()// {// return &_Node->_data;// }//};//template <class T>//struct const_List_iterator//{// typedef List_Node<T> Node;// typedef List_Node<T>* pNode;// pNode _Node;// const_List_iterator(Node* node)// :_Node(node)// {}// const T& operator*() const// {// return _Node->_data;// }// //前置++// const_List_iterator<T>& operator++() // {// _Node = _Node->_next;// return *this;// }// const_List_iterator<T> operator++(int) // {// List_iterator<T> tmp = *this;// _Node = _Node->_next;// return tmp;// }// bool operator!=(const const_List_iterator<T>& it)// {// return _Node != it._Node;// }// const T* operator->() const// {// return &_Node->_data;// }//};//用模版方法来控制const和非const迭代器template <class T,class Ref,class Ptr>struct List_iterator{typedef List_Node<T> Node;typedef List_Node<T>* pNode;typedef List_iterator<T, Ref, Ptr> Self;pNode _Node;List_iterator(Node* node):_Node(node){}//迭代器具有像指针一样的行为,可以解引用Ref operator*(){return _Node->_data;}//指针可以通过->访问其所指空间成员,因此迭代器类中必须重载oprator->()Ptr operator->(){return &_Node->_data;}//迭代器可以++//前置++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){return _Node != it._Node;}bool operator==(const Self& it){return _Node == it._Node;}};template<class T>class list{typedef List_Node<T> Node;typedef List_Node<T>* pNode;public:/* typedef List_iterator<T> iterator;typedef const_List_iterator<T> const_iterator;*///传参来控制const迭代器和非const迭代器typedef List_iterator<T,T&,T*> iterator;typedef List_iterator<T,const T&,const T*> const_iterator;//反向迭代器typedef Reverse_iterator< iterator, T&, T*> reverse_iterator;typedef Reverse_iterator< const_iterator, const T&, const T*> const_reverse_iterator;reverse_iterator rbegin(){return reverse_iterator(end());}reverse_iterator rend(){return reverse_iterator(begin());}const_reverse_iterator rbegin() const{return const_reverse_iterator(end());}const_reverse_iterator rend() const{return const_reverse_iterator(begin());}iterator begin(){return iterator(_head->_next);}iterator end(){return iterator(_head);}const_iterator begin() const{return const_iterator(_head->_next);}const_iterator end() const{return const_iterator(_head);}void empty_init(){_head = new Node(-1);_head->_next = _head;_head->_prev = _head;}list(){empty_init();_size = 0;}void clear(){iterator it = begin();while(it != end()){it = erase(it);//erase后更新迭代器,防止迭代器失效;it++;}}~list(){clear();delete _head;_head = nullptr;}//拷贝构造//lt2(lt1)list(const list<T>& it){empty_init();for (auto& e : it){push_back(e);}}list(initializer_list<T> il){empty_init();for (auto& e : il){push_back(e);}}template <class InputIterator>list(InputIterator first, InputIterator last){empty_init();while (first != last){push_back(*first);first++;}}void swap(list<T>& it){std::swap(_head, it._head);std::swap(_size, it._size);}list<T>& operator=(list<T> it){swap(it);return *this;}/*void push_back(const T& x){pNode newnode = new Node(x);pNode tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;_head->_prev = newnode;newnode->_next = _head;}*/void push_back(const T& x){insert(end(), x);}void pop_back(){erase(--end());}void push_front(const T& x){insert(begin(), x);}void pop_front(){erase(begin());}size_t size() const{return _size;}//insert后pos位置迭代器失效·void insert(iterator pos, const T& x){pNode newnode = new Node(x);//prev newnode curpNode cur = pos._Node;pNode prev = cur->_prev;prev->_next = newnode;newnode->_next = cur;newnode->_prev = prev;cur->_prev = newnode;++_size;//pos = iterator(newnode);//如果想继续使用,更细pos位置的迭代器}iterator erase(iterator pos){pNode cur = pos._Node;pNode next = cur->_next;pNode prev = cur->_prev;prev->_next = next;next->_prev = prev;delete cur;--_size;return iterator(next);//返回下一个元素的迭代器}private:pNode _head;size_t _size;};
}
Reverse_iterator.h👇👇
#pragma oncetemplate<class Iterator,class Ref,class Ptr>struct Reverse_iterator{typedef Reverse_iterator<Iterator, Ref, Ptr> Self;Reverse_iterator(Iterator it):_it(it){}//迭代器支持解引用/*Ref operator*(){return *_it;}*///返回前一个元素的值Ref operator*(){Iterator tmp = _it;tmp--;return *tmp;}Ptr operator->(){return &(operator*());}//迭代器支持移动Self& operator++(){--_it;return *this;}Self operator++(int){Self tmp(*this);--_it;return tmp;}Self& operator--(){++_it;return *this;}Self operator--(int){Self tmp(*this);++_it;return tmp;}//迭代器支持比较bool operator==(const Self& it){return _it == it._it;}bool operator!=(const Self& it){return _it != it._it;}Iterator _it;
};
🐼总结
通过<list>的认识以及模拟实现,加深了我们对迭代器的认识,迭代器支持++.--,比较,解引用,随机访问等等操作,我们知道了迭代器行为是像指针一样的东西,迭代器提供了一种统一的方式来访问容器中的元素,而无需关心容器的具体实现细节,在<list>中我们专门封装了一个iterator类来模拟迭代器的行为。
而介绍了适配器后,我们通过正向迭代器<iterator>来适配出反向迭代器<Reverse_iterator>类,通过类模版之间的复用实现了两个不同接口的兼容性。
我们也更加感受到了函数模版的魅力,通过模版参数,来减少很多逻辑上重复的代码,比如const对象和非const对象迭代器的实例化,我们控制实参,就能实例化出权限不同的迭代器版本。
感谢你耐心地阅读到这里,你的支持是我不断前行的最大动力。如果你觉得这篇文章对你有所启发,哪怕只是一点点,那就请不吝点赞👍,收藏⭐️,关注🚩吧!你的每一个点赞都是对我最大的鼓励,每一次收藏都是对我努力的认可,每一次关注都是对我持续创作的鞭策。希望我的文字能为你带来更多的价值,也希望我们能在这个充满知识与灵感的旅程中,共同成长,一起进步。再次感谢你的陪伴,期待与你在未来的文章中再次相遇!⛅️🌈 ☀️
相关文章:
【C++】解锁<list>的正确姿势
> 🍃 本系列为初阶C的内容,如果感兴趣,欢迎订阅🚩 > 🎊个人主页:[小编的个人主页])小编的个人主页 > 🎀 🎉欢迎大家点赞👍收藏⭐文章 > ✌️ 🤞 …...
【玩转全栈】----Django模板的继承
先赞后看,养成习惯!!! 目录 模板继承的好处 模板继承的语法规则 更新代码 上文中的部门管理页面: 【玩转全栈】----Django制作部门管理页面-CSDN博客 大家会发现,由于定义了多个html文件,多个ht…...
【紫光同创PG2L100H开发板】盘古676系列,盘古100Pro+开发板,MES2L676-100HP
本原创文章由深圳市小眼睛科技有限公司创作,版权归本公司所有,如需转载,需授权并注明出处(www.meyesemi.com) 一:开发系统介绍 开发系统概述 MES2L676-100HP开发板采用紫光创 logos2系列 FPGA,型号&#x…...
Mac下使用brew安装go 以及遇到的问题
首先按照网上找到的命令进行安装 brew install go 打开终端输入go version,查看安装的go版本 go version 配置环境变量 查看go的环境变量配置: go env 事实上安装好后的go已经可以使用了。 在home/go下新建src/hello目录,在该目录中新建…...
活动预告 |【Part 2】Microsoft 安全在线技术公开课:通过扩展检测和响应抵御威胁
课程介绍 通过 Microsoft Learn 免费参加 Microsoft 安全在线技术公开课,掌握创造新机遇所需的技能,加快对 Microsoft Cloud 技术的了解。参加我们举办的“通过扩展检测和响应抵御威胁”技术公开课活动,了解如何更好地在 Microsoft 365 Defen…...
android的DataBinding简介
Android DataBinding 简介 DataBinding 是 Android Jetpack 中的数据绑定库,用于将 UI 组件直接与数据模型绑定,减少模板代码并实现双向数据同步。它通过声明式布局简化 UI 更新逻辑,常用于 MVVM(Model-View-ViewModel࿰…...
基于DeepSeek API和VSCode的自动化网页生成流程
1.创建API key 访问官网DeepSeek ,点击API开放平台。 在开放平台界面左侧点击API keys,进入API keys管理界面,点击创建API key按钮创建API key,名称自定义。 2.下载并安装配置编辑器VSCode 官网Visual Studio Code - Code Editing…...
【DeepSeek】私有化本地部署图文(Win+Mac)
目录 一、DeepSeek本地部署【Windows】 1、安装Ollama 2、配置环境变量 3、下载模型 4、使用示例 a、直接访问 b、chatbox网页访问 二、DeepSeek本地部署【Mac】 1、安装Ollama 2、配置环境变量 3、下载模型 4、使用示例 5、删除已下载的模型 三、DeepSeek其他 …...
vscode预览插件
在左侧列表拓展里搜索 Live Preview 安装,然后在html页面点击右键找到show Preview 结果如下图 然后就可以进行代码开发并实时预览了...
迅为RK3568开发板篇OpenHarmony实操HDF驱动控制LED-编写应用APP
在应用代码中我们实现如下功能: 当应用程序启动后会获取命令行参数。如果命令行没有参数,LED 灯将循环闪烁;如果命令行带有参数,则根据传输的参数控制 LED 灯的开启或关闭。通过 HdfIoServiceBind 绑定 LED灯的 HDF 服务ÿ…...
在大型语言模型(LLM)框架内Transformer架构与混合专家(MoE)策略的概念整合
文章目录 传统的神经网络框架存在的问题一. Transformer架构综述1.1 transformer的输入1.1.1 词向量1.1.2 位置编码(Positional Encoding)1.1.3 编码器与解码器结构1.1.4 多头自注意力机制 二.Transformer分步详解2.1 传统词向量存在的问题2.2 详解编解码…...
tolua[一]框架搭建,运行example
一.安装tolua https://github.com/topameng/tolua 下载LuaFramework_UGUI的zip 将Assets目录拷贝到项目根目录下 提示确认注册,遇到这个对话框点确定即可 生成如下目录 二.LuaFramework->Build Windows Resource 接下来的目标是将这个main场景跑起来 需要先执行…...
大数据项目4:基于spark的智慧交通项目设计与实现
项目概述 项目直达 www.baiyuntu.com 随着交通数据的快速增长,传统的交通管理方式已无法满足现代城市的需求。交通大数据分析系统通过整合各类交通数据,利用大数据技术解决交通瓶颈问题,提升交通管理效率。本项目旨在通过大数据技术&#…...
Vue 响应式渲染 - 条件渲染
Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue响应式渲染 - 条件渲染 目录 条件渲染 v-if v-if-else 模版template 物流状态显示判断 设置数据 不同状态渲染 总结 条件渲染 v-if 使用Vue条件判断显示和 隐藏。 示例如下: <!DOCTYPE html> …...
活动预告 |【Part1】Microsoft Azure 在线技术公开课:AI 基础知识
课程介绍 参加“Azure 在线技术公开课:AI 基础知识”活动,了解 AI 核心概念。参加我们举办的本次免费培训活动,了解组织如何使用 AI 技术克服实际挑战,以及如何借助 Azure AI 服务构建智能应用程序。本次培训适用于任何对 AI 解决…...
OpenSIPS-Dispatcher模块详解:优化SIP流量分发的利器
在 OpenSIPS 中,dispatcher 模块用于实现负载均衡和故障转移。通过 dispatcher 模块,你可以将 SIP 请求分发到一组后端服务器(如媒体服务器、代理服务器等),并根据配置的算法和策略动态调整分发逻辑。 模块功能使用样…...
深入理解linux中的文件(下)
目录 一、语言级缓冲区和内核级缓冲区 二、C语音中的FILE* fp fopen(“./file.txt”,"w"): 四、理解磁盘结构: 物理结构 逻辑结构 五、未被打开的文件: 六、更加深入理解inode编号怎么找到文件: 七、对路径结构进行…...
Deepseek系列从v3到R易背面经版
deepseek v3 base要点 MTP : Multi-Token Prediction 训练时: 1. 把前一个block中input tokens经过embedding layer和transformer block的输出,进入output head之前的内容记为h,与下一个block的input tokens经过embedding layer输出的内容都…...
SQLAlchemy 的内存消耗
为何要研究SQLAlchemy 的内存消耗问题?因为SQLAlchemy在应用中,绝大多数问题体现在应用人员对SQLAlchemy 的内存消耗问题不认知、不重视、不处理,最终造成整个系统的大问题,使SQLAlchemy 的性能大打折扣,最终影响了SQL…...
Centos Ollama + Deepseek-r1+Chatbox运行环境搭建
Centos Ollama Deepseek-r1Chatbox运行环境搭建 内容介绍下载ollama在Ollama运行DeepSeek-r1模型使用chatbox连接ollama api 内容介绍 你好! 这篇文章简单讲述一下如何在linux环境搭建 Ollama Deepseek-r1。并在本地安装的Chatbox中进行远程调用 下载ollama 登…...
vscode和pycharm的区别
VSCode(Visual Studio Code)和 PyCharm 是两款常用的 Python 开发工具,它们在功能和使用体验上有一些关键区别: 1. 核心定位 VSCode:轻量级、多语言支持的代码编辑器,依靠插件扩展 Python 开发能力。PyCh…...
【0403】Postgres内核 检查(procArray )给定 db 是否有其他 backend process 正在运行
文章目录 1. 给定 db 是否有其他 backend 正在运行1.1 获取 allPgXact[] 索引1.1.1 MyProc 中 databaseId 初始化实现1.2 allProcs[] 中各 databaseId 判断1. 给定 db 是否有其他 backend 正在运行 CREATE DATABASE 语句创建用户指定 数据库名(database-name)时候, 会通过 …...
MySQL 数据库的体系架构
MySQL 数据库的体系架构是其高效、灵活和可扩展性的核心基础。其设计采用 分层模块化 结构,主要分为以下四层: 1. 连接层(Connectors & Connection Pool) 功能:负责处理客户端连接、认证和权限管理。 连接管理&a…...
【C++高并发服务器WebServer】-14:Select详解及实现
本文目录 一、BIO模型二、非阻塞NIO忙轮询三、IO多路复用四、Select()多路复用实现 明确一下IO多路复用的概念:IO多路复用能够使得程序同时监听多个文件描述符(文件描述符fd对应的是内核读写缓冲区),能够提升程序的性能。 Linux下…...
apisix网关ip-restriction插件使用说明
ip-restriction插件可以在网关层进行客户端请求ip拦截。 当然了,一般不推荐使用该方法,专业的事专业工具做。建议有条件,还是上防火墙或者waf来做。 官方文档:ip-restriction | Apache APISIX -- Cloud-Native API Gateway whit…...
15vue3实战-----props和emit传值
15vue3实战-----props和emit传值 1.emit的使用2.配合props实现完整父子通信 vue3中props和emit的使用有点不一样,但万变不离其宗。 1.emit的使用 子组件: <script setup lang"ts"> // 定义触发的事件及其数据类型 const emit defineEmits([update, delete]…...
第二节 docker基础之---镜像构建及挂载
查看当前镜像: [rootdocker ~]# docker images REPOSITORY TAG IMAGE ID CREATED SIZE [rootdocker ~]#很明显docker是咱们新搭建的所以目前还没有镜像 1,搜索镜像: [rootdocker ~]# docker search centos 搜索镜像并过滤是官…...
Android性能优化
Android性能优化 如何优化一个包含大量图片加载的Android应用,以提高性能和用户体验? 优化一个包含大量图片加载的Android应用,可以从以下几个方面入手,以提高性能和用户体验: 选择合适的图片加载库 使用成熟的图片…...
scikit-learn简介
scikit-learn 是一个广泛使用的 Python 库,主要用于 机器学习(ML)。它提供了一些简单高效的工具,适用于 数据挖掘 和 数据分析。scikit-learn 基于 NumPy、SciPy 和 matplotlib 等科学计算库,具有 易于使用的接口 和 高…...
查询已经运行的 Docker 容器启动命令
一、导语 使用 get_command_4_run_container 查询 docker 容器的启动命令 获取镜像 docker pull cucker/get_command_4_run_container 查看容器命令 docker run --rm -v /var/run/docker.sock:/var/run/docker.sock cucker/get_command_4_run_container 容器id或容器名 …...
ssti学习笔记(服务器端模板注入)
目录 一,ssti是什么 二,原理 所谓模板引擎(三列,可滑动查看) 三,漏洞复现 1,如何判断其所属的模板引擎? 2,判断清楚后开始注入 (1)Jinja2&a…...
Docker 部署 RabbitMQ | 自带延时队列
一、获取镜像 docker pull farerboy/rabbitmq:3.9.9 二、运行镜像 docker run -d --name rabbitmq \n --hostname rabbitmq \n -p 15672:15672/tcp \n -p 5672:5672/tcp \n -v /wwwroot/opt/docker/rabbitmq:/var/lib/rabbitmq \n farerboy/rabbitmq:3.9.9 备注:…...
如何使用deepseek创建钉钉收费AI助理
1、打开deepseek,简单描述下自己的问题,勾选深度思考 2、deepseek给我生成了一大段提示词模板 3、打开钉钉-应用中心-找AI助理 4、点击立即创作 5、创作 默认创建的是免费上网,需要创建收费的话需要先申请,填一个表单内容和价格&…...
【STM32系列】利用MATLAB配合ARM-DSP库设计FIR数字滤波器(保姆级教程)
ps.源码放在最后面 设计IIR数字滤波器可以看这里:利用MATLAB配合ARM-DSP库设计IIR数字滤波器(保姆级教程) 前言 本篇文章将介绍如何利用MATLAB与STM32的ARM-DSP库相结合,简明易懂地实现FIR低通滤波器的设计与应用。文章重点不在…...
2025年日祭
本文将同步发表于洛谷(暂无法访问)、CSDN 与 Github 个人博客(暂未发布) 本蒟自2025.2.8开始半停课。 以下是题目格式: [题目OJ 题号] [来源(选填)] 名称 …… 题号 - 名称 题目:……...
torch_bmm验算及代码测试
文章目录 1. torch_bmm2. pytorch源码 1. torch_bmm torch.bmm的作用是基于batch_size的矩阵乘法,torch.bmm的作用是对应batch位置的矩阵相乘,比如, mat1的第1个位置和mat2的第1个位置进行矩阵相乘得到mat3的第1个位置mat1的第2个位置和mat2的第2个位置…...
ChatGPT提问技巧:行业热门应用提示词案例-文案写作
ChatGPT 作为强大的 AI 语言模型,已经成为文案写作的得力助手。但要让它写出真正符合你需求的文案,关键在于如何与它“沟通”,也就是如何设计提示词(Prompt)。以下是一些实用的提示词案例,帮助你解锁 ChatG…...
k8s中,一.service发布服务,二.dashboard:基于网页的k8s管理插件,三.资源服务与帐号权限
一.service资源对内发布服务Cluster IP对外发布服务NodePortIngress 二.dashboard:基于网页的k8s管理插件 三.资源服务与帐号权限一.service:用户无法预知pod的ip地址以及所在的节点,多个相同的pod如何访问他们上面的服务功能:1.服务自动感知:pod迁移后访问service的ip,不受影响…...
Python的那些事第十六篇:Python的网络爬虫技术
基于Python的网络爬虫技术研究与应用 摘要 随着互联网的飞速发展,网络爬虫技术在数据采集、信息挖掘等领域发挥着重要作用。本文详细介绍了Python环境下常用的网络爬虫技术,包括Requests库、BeautifulSoup库以及Scrapy框架。通过对这些工具的使用方法、…...
Linux第106步_Linux内核RTC驱动实验
1、了解rtc_device结构体 1)、打开“include/linux/rtc.h” rtc_class_ops是需要用户根据所使用的RTC设备编写的,其结构体如下: struct rtc_class_ops { int (*ioctl)(struct device *, unsigned int, unsigned long);/*函数指针ioctl*/ int (*read_time)(struct device *,…...
《XSS跨站脚本攻击》
一、XSS简介 XSS全称(Cross Site Scripting)跨站脚本攻击,为了避免和CSS层叠样式表名称冲突,所以改为了XSS,是最常见的Web应用程序安全漏洞之一,位于OWASP top 10 2013/2017年度分别为第三名和第七名&…...
康谋方案 | BEV感知技术:多相机数据采集与高精度时间同步方案
随着自动驾驶技术的快速发展,车辆准确感知周围环境的能力变得至关重要。BEV(Birds-Eye-View,鸟瞰图)感知技术,以其独特的视角和强大的数据处理能力,正成为自动驾驶领域的一大研究热点。 一、BEV感知技术概…...
堆详解及C语言实现
堆结构与最大堆详解:从原理到C语言实现 1. 堆结构概述 1.1 定义与性质 堆(Heap)是一种特殊的完全二叉树数据结构,满足以下性质: 结构性:完全二叉树结构有序性:每个结点的值都≥(…...
基于STM32的智能鱼缸水质净化系统设计
🤞🤞大家好,这里是5132单片机毕设设计项目分享,今天给大家分享的是智能鱼缸水质净化系统。 目录 1、设计要求 2、系统功能 3、演示视频和实物 4、系统设计框图 5、软件设计流程图 6、原理图 7、主程序 8、总结 1、设计要求…...
python:csv文件批量导入mysql
1.导入sql文件到数据库中 mysql -u username -p要先创建一个空的数据库 CREATE DATABASE your_database_name;USE your_database_name;导入sql文件 source /path/to/your/file.sql;查看某个表格的结构,为后续数据插入做准备 DESCRIBE table_name;2.插入假数据到对应…...
第三十二周:Informer学习笔记
目录 摘要Abstract1 Informer1.1 预备知识1.2 模型框架1.3 实验分析 总结 摘要 本周学习的主要内容是Informer模型,Informer是一种专为长序列时间序列预测(LSTF) 设计的Transformer模型。相较于传统的Transformer,Informer采用Pr…...
计算机视觉核心任务
1. 计算机视频重要分类 计算机视觉的重要任务可以大致分为以下几类: 1. 图像分类(Image Classification) 识别图像属于哪个类别,例如猫、狗、汽车等。 应用场景:物品识别、人脸识别、医疗影像分类。代表模型&#…...
【python】matplotlib(animation)
文章目录 1、matplotlib.animation1.1、FuncAnimation1.2、修改 matplotlib 背景 2、matplotlib imageio2.1、折线图2.2、条形图2.3、散点图 3、参考 1、matplotlib.animation 1.1、FuncAnimation matplotlib.animation.FuncAnimation 是 Matplotlib 库中用于创建动画的一个…...
【Linux网络编程】之守护进程
【Linux网络编程】之守护进程 进程组进程组的概念组长进程 会话会话的概念会话ID 控制终端控制终端的概念控制终端的作用会话、终端、bash三者的关系 前台进程与后台进程概念特点查看当前终端的后台进程前台进程与后台进程的切换 作业控制相关概念作业状态(一般指后…...
Vue.js如何根据访问路径切换页面
Vue Router 在前端工程中,路由指的是,根据不同的访问路径,展示不同组件的内容。 Vue Router是Vue.js的官方路由。 Vue Router介绍。 要使用vue Router,得先安装 npm install vue-router4这里的4,指的是第4个版本 在s…...