【C++】list容器及其模拟实现

server/2024/11/25 8:22:39/

目录

1. list的介绍及使用

1.1 list的介绍

1.2 list的使用

1.2.1 list的构造

1.2.2 list iterator的使用

1.2.3 list capacity

1.2.4 list element access

1.2.5 list modifiers

1.2.6 list的迭代器失效

2. list的模拟实现

2.1 模拟实现list

2.1.1list节点

2.1.2list常见功能接口

2.1.3list迭代器实现

2.1.4list构造与析构函数

2.1.5完整代码

2.2 list的反向迭代器

3. list与vector的对比


1. list的介绍及使用

1.1 list的介绍

list的文档介绍

在数据结构当中,我们学习过链表的一系列形式,带头、不带头、双向、单向、循环、不循环等形式,其中带头双向链表由于可以轻易找到头尾节点,某一节点前后节点,具有头结点,因此链表为空不需要做特殊处理等优势,作为链表最完美的形式。C++STL中list底层的结构就是采用带头双向循环链表(对list的理解需要建立在对数据结构有一定基础上,对于链表不了解的读者可以先移步学习链表。)

1.2 list的使用

list中的接口比较多,此处类似之前string、vector,只需要掌握如何正确的使用,然后再去深入研究背后的原理,以达到可扩展的能力。以下为list中一些常见的重要接口。

1.2.1 list的构造

构造函数( (constructor))接口说明
list
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的构造
void TestList1()
{list<int> l1;                         // 构造空的l1list<int> l2(4, 100);                 // l2中放4个值为100的元素list<int> l3(l2.begin(), l2.end());  // 用l2的[begin(), end())左闭右开的区间构造l3list<int> l4(l3);                    // 用l3拷贝构造l4// 以数组为迭代器区间构造l5int array[] = { 16,2,77,29 };list<int> l5(array, array + sizeof(array) / sizeof(int));// 列表格式初始化C++11list<int> l6{ 1,2,3,4,5 };// 用迭代器方式打印l5中的元素list<int>::iterator it = l5.begin();while (it != l5.end()){cout << *it << " ";++it;}cout << endl;// C++11范围for的方式遍历for (auto& e : l5)cout << e << " ";cout << endl;
}

1.2.2 list iterator的使用

此处,大家可暂时将迭代器理解成一个指针,该指针指向list中的某个节点。当然由于链表的节点存储并不是一块连续的空间,因此list的迭代器并不是原生指针,而是通过封装实现的。

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


// list迭代器的使用
// 注意:遍历链表只能用迭代器和范围for
void PrintList(const list<int>& l)
{// 注意这里调用的是list的 begin() const,返回list的const_iterator对象for (list<int>::const_iterator it = l.begin(); it != l.end(); ++it){cout << *it << " ";// *it = 10; 编译不通过}cout << endl;
}void TestList2()
{int array[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };list<int> l(array, array + sizeof(array) / sizeof(array[0]));// 使用正向迭代器正向list中的元素// list<int>::iterator it = l.begin();   // C++98中语法auto it = l.begin();                     // C++11之后推荐写法while (it != l.end()){cout << *it << " ";++it;}cout << endl;// 使用反向迭代器逆向打印list中的元素// list<int>::reverse_iterator rit = l.rbegin();auto rit = l.rbegin();while (rit != l.rend()){cout << *rit << " ";++rit;}cout << endl;
}

STL中范围统一都是左闭右开的区间,在list中由于头结点并不存储有效的数据,begin返回的是指向第一个有效数据的迭代器,end()返回最后一个元素的下一个位置,由于是循环链表,最后一个元素的下一个位置指向就是头结点。

list由于底层的链表存储空间不连续,因此list不在支持下标 +[]的随机访问,同样,比起之前string与vector,list的迭代器也发生了变化,list迭代器不再支持+或-某一常数来改变访问的指向。

这里我们需要在拓展补充一下迭代器的相关概念

基于功能上是正向还是反向遍历,迭代器可以分为正向迭代器iterator与反向迭代器reverse_iterator,以及支持const对象的迭代器。

基于底层结构的不同,实现出来的迭代器又有性质上的不同,可以分为单向、双向、随机迭代器(STL底层还抽象出一个最小单位的输入迭代器,我们使用的只有上诉三个迭代器,这个最小单位可以不考虑),不同迭代器支持的迭代操作不同,单向迭代器只支持单向移动,只能++,双向迭代器如我们本次学习list支持++、--两个方向迭代,而随机迭代器支持通过+、-某一个对象跳跃式移动到某一个位置。STL中所实现的一些组件,由于底层实现存在+、-、++、--等操作,对于有的迭代器是不支持的,如上段代码中sort底层实现式快排加堆排存在+、-等操作,因此传入的迭代器必须是随机迭代器。(不同的迭代器之间其实还存在者继承等复杂关系,本文不在深入探讨)

由上图不同迭代器支持的符号,我们可以得出使用范围上随机迭代器>双向迭代器>单向迭代器。所以在日后的使用中,我们需要根据文档查询对应支持的迭代器类型。

【注意】
1. begin与end为正向迭代器,对迭代器执行++操作,迭代器向后移动
2. rbegin(end)与rend(begin)为反向迭代器,对迭代器执行++操作,迭代器向前移动

1.2.3 list capacity

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

1.2.4 list element access

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

1.2.5 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插入和删除
// push_back/pop_back/push_front/pop_front
void TestList3()
{int array[] = { 1, 2, 3 };list<int> L(array, array + sizeof(array) / sizeof(array[0]));// 在list的尾部插入4,头部插入0L.push_back(4);L.push_front(0);PrintList(L);// 删除list尾部节点和头部节点L.pop_back();L.pop_front();PrintList(L);
}// insert /erase 
void TestList4()
{int array1[] = { 1, 2, 3 };list<int> L(array1, array1 + sizeof(array1) / sizeof(array1[0]));// 获取链表中第二个节点auto pos = ++L.begin();cout << *pos << endl;// 在pos前插入值为4的元素L.insert(pos, 4);PrintList(L);// 在pos前插入5个值为5的元素L.insert(pos, 5, 5);PrintList(L);// 在pos前插入[v.begin(), v.end)区间中的元素vector<int> v{ 7, 8, 9 };L.insert(pos, v.begin(), v.end());PrintList(L);// 删除pos位置上的元素L.erase(pos);PrintList(L);// 删除list中[begin, end)区间中的元素,即删除list中的所有元素L.erase(L.begin(), L.end());PrintList(L);
}// resize/swap/clear
void TestList5()
{// 用数组来构造listint array1[] = { 1, 2, 3 };list<int> l1(array1, array1 + sizeof(array1) / sizeof(array1[0]));PrintList(l1);// 交换l1和l2中的元素list<int> l2;l1.swap(l2);PrintList(l1);PrintList(l2);// 将l2中的元素清空l2.clear();cout << l2.size() << endl;
}

list中还有一些操作,需要用到时大家可参阅list的文档说明。

1.2.6 list的迭代器失效

前面说过,此处大家可将迭代器暂时理解成类似于指针,迭代器失效即迭代器所指向的节点的无效,即该节点被删除了。因为list的底层结构为带头结点的双向循环链表,因此在list中进行插入时是不会导致list的迭代器失效的,只有在删除时才会失效,并且失效的只是指向被删除节点的迭代器(该迭代器变为野指针),其他迭代器不会受到影响。

void TestListIterator1()
{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;}
}
// 改正
void TestListIterator()
{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()){l.erase(it++); // it = l.erase(it);erase会返回当前删除节点的下一节点}
}

2. list的模拟实现

2.1 模拟实现list

要模拟实现list,必须要熟悉list的底层结构以及其接口的含义,通过上面的学习,这些内容已基本
掌握,现在我们来模拟实现list。

2.1.1list节点

//链表的节点,由于存储的数据不定,我们这里实现成模板	
template<class T>struct list_node{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data = T()):_data(data),_next(nullptr),_prev(nullptr){}};

2.1.2list常见功能接口

		void clear(){auto it = begin();//挨个释放list中节点资源while (it != end()){it = erase(it);//复用删除功能}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}void push_back(const T& x){/*Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;++_size;*/insert(end(), x);//尾插直接复用插入代码}void push_front(const T& x){insert(begin(), x);//头插直接复用插入代码}iterator insert(iterator pos, const T& x){//插入一个新节点Node* cur = pos._node;//当前位置节点Node* prev = cur->_prev;//前驱节点Node* newnode = new Node(x);// prev newnode curnewnode->_next = cur;//新节点next指向当前位置节点cur->_prev = newnode;//当前节点前驱指针指向新节点newnode->_prev = prev;//新前驱节点的前驱指针指向前驱节点prev->_next = newnode;//前驱节点next指向新节点++_size;//更新list中节点个数return newnode;//返回新节点指针,返回值在根据指针构造出对应迭代器}void pop_back(){erase(--end());//尾删直接复用对应删除代码}void pop_front(){erase(begin());//前删直接复用对应删除代码}iterator erase(iterator pos){assert(pos != end());//不能删除头结点Node* prev = pos._node->_prev;//将删除节点前驱跟后驱节点的指向相连Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;//释放删除节点的资源--_size;return next;//返回删除节点后驱节点指针,返回值在根据指针构造出对应迭代器//这一步返回操作可以更新外部指向删除节点的迭代器的值//防止迭代器失效的问题}size_t size() const{return _size;}bool empty() const{return _size == 0;}		

2.1.3list迭代器实现

由于list的底层是链表,存储空间并不连续,因此,我们需要利用模板进行封装。首先需要说明的是,由于const_iterator指向的对象不能修改,因此如果我们通过重载的函数返回引用或者指针,那么针对const_iterator跟iterator,我们就需要写两份非常相似的代码,非常冗余,对此库里面增加Ref与Ptr两个模板参数用来控制返回值中的引用与指针,传入的参数是const对像,函数返回的引用与指针就是const修饰的,如果是普通对象,就是普通的指针和引用,我们就不再需要写两份非常相似的代码了。

因为list的迭代器,我们无法通过原生指针实现,为了能过还原对应的指针操作,我们需要在迭代器类中有一个成员变量是节点指针,同时由于迭代器的相关操作经常使用,我们这里使用struct定义类。

	template<class T, class Ref, class Ptr>struct list_iterator{typedef list_node<T> Node;typedef list_iterator<T, Ref, Ptr> Self;//通过模板参数控制iterator与const_iterator //Ptr指针,Ref引用//不同返回值Node* _node;list_iterator(Node* node):_node(node){}Ref operator*()//模拟解引用指针,我们需要将迭代器指向节点的数据返回{return _node->_data;}Ptr operator->()//模拟访问节点内部指针,我们需要将迭代器指向节点的内部成员指针返回{return &_node->_data;}Self& operator++()//模拟迭代器++,向后移动{_node = _node->_next;//迭代器内部的节点指针后移return *this;//返回新节点的引用,在根据引用,构造一个新迭代器返回}Self& operator--(){_node = _node->_prev;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};/*template<class T>//如果不通过函模板参数控制,我们就需要因为const_iterator多写一份非常相 //似的代码struct list_const_iterator{typedef list_node<T> Node;typedef list_const_iterator<T> Self;Node* _node;list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_data;}const T* operator->(){return &_node->_data;}Self& operator++(){_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};*/

2.1.4list构造与析构函数

与之前string和vector实现不同,空list中构造出来是有一个头结点,头节点的前驱和后驱指针都指向自己。因此,我们这里要单独实现一个空初始化函数。

	void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}list(initializer_list<T> il){empty_init();for (auto& e : il){push_back(e);}}// lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}// lt1 = lt3 现代写法的赋值重载list<T>& operator=(list<T> lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}

2.1.5完整代码

#pragma once
#include<assert.h>namespace zlr
{template<class T>struct list_node//链表的节点{T _data;list_node<T>* _next;list_node<T>* _prev;list_node(const T& data = T()):_data(data),_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;//通过模板参数控制iterator与const_iterator //不同返回值Node* _node;list_iterator(Node* node):_node(node){}Ref operator*(){return _node->_data;}Ptr operator->(){return &_node->_data;}Self& operator++(){_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};/*template<class T>//如果不通过函模板参数控制,我们就需要因为const_iterator多写一份非常相 //似的代码struct list_const_iterator{typedef list_node<T> Node;typedef list_const_iterator<T> Self;Node* _node;list_const_iterator(Node* node):_node(node){}const T& operator*(){return _node->_data;}const T* operator->(){return &_node->_data;}Self& operator++(){_node = _node->_next;return *this;}Self& operator--(){_node = _node->_prev;return *this;}Self operator++(int){Self tmp(*this);_node = _node->_next;return tmp;}Self& operator--(int){Self tmp(*this);_node = _node->_prev;return tmp;}bool operator!=(const Self& s) const{return _node != s._node;}bool operator==(const Self& s) const{return _node == s._node;}};*/template<class T>class list{typedef list_node<T> Node;public:/*typedef list_iterator<T> iterator;typedef list_const_iterator<T> const_iterator;*/typedef list_iterator<T, T&, T*> iterator;//将类型重新封装,便于使用,统一风格typedef list_iterator<T, const T&, const T*> const_iterator;iterator begin(){/*	iterator it(_head->_next);return it;*///return iterator(_head->_next);return _head->_next;}iterator end(){return _head;}const_iterator begin() const{return _head->_next;}const_iterator end() const{return _head;}void empty_init(){_head = new Node;_head->_next = _head;_head->_prev = _head;_size = 0;}list(){empty_init();}list(initializer_list<T> il){empty_init();for (auto& e : il){push_back(e);}}// lt2(lt1)list(const list<T>& lt){empty_init();for (auto& e : lt){push_back(e);}}// lt1 = lt3list<T>& operator=(list<T> lt){swap(lt);return *this;}~list(){clear();delete _head;_head = nullptr;}void clear(){auto it = begin();while (it != end()){it = erase(it);}}void swap(list<T>& lt){std::swap(_head, lt._head);std::swap(_size, lt._size);}void push_back(const T& x){/*Node* newnode = new Node(x);Node* tail = _head->_prev;tail->_next = newnode;newnode->_prev = tail;newnode->_next = _head;_head->_prev = newnode;++_size;*/insert(end(), x);//直接复用代码}void push_front(const T& x){insert(begin(), x);}iterator insert(iterator pos, const T& x){Node* cur = pos._node;Node* prev = cur->_prev;Node* newnode = new Node(x);// prev newnode curnewnode->_next = cur;cur->_prev = newnode;newnode->_prev = prev;prev->_next = newnode;++_size;return newnode;}void pop_back(){erase(--end());}void pop_front(){erase(begin());}iterator erase(iterator pos){assert(pos != end());Node* prev = pos._node->_prev;Node* next = pos._node->_next;prev->_next = next;next->_prev = prev;delete pos._node;--_size;return next;}size_t size() const{return _size;}bool empty() const{return _size == 0;}private:Node* _head;size_t _size;};struct AA{int _a1 = 1;int _a2 = 1;};// 按需实例化// T* const ptr1// const T* ptr2template<class Container>void print_container(const Container& con){// const iterator -> 迭代器本身不能修改// const_iterator -> 指向内容不能修改typename Container::const_iterator it = con.begin();//auto it = con.begin();while (it != con.end()){//*it += 10;cout << *it << " ";++it;}cout << endl;for (auto e : con){cout << e << " ";}cout << endl;}void test_list1(){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()){*it += 10;cout << *it << " ";++it;}cout << endl;for (auto e : lt){cout << e << " ";}cout << endl;print_container(lt);list<AA> lta;lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());lta.push_back(AA());list<AA>::iterator ita = lta.begin();while (ita != lta.end()){//cout << (*ita)._a1 << ":" << (*ita)._a2 << endl;// 特殊处理,本来应该是两个->才合理,为了可读性,省略了一个->cout << ita->_a1 << ":" << ita->_a2 << endl;cout << ita.operator->()->_a1 << ":" << ita.operator->()->_a2 << endl;++ita;}cout << endl;}void test_list2(){list<int> lt;lt.push_back(1);lt.push_back(2);lt.push_back(3);lt.push_back(4);// insert以后迭代器不失效list<int>::iterator it = lt.begin();lt.insert(it, 10);*it += 100;print_container(lt);// erase以后迭代器失效// 删除所有的偶数it = lt.begin();while (it != lt.end()){if (*it % 2 == 0){it = lt.erase(it);}else{++it;}}print_container(lt);}void test_list3(){list<int> lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);print_container(lt1);print_container(lt2);list<int> lt3;lt3.push_back(10);lt3.push_back(20);lt3.push_back(30);lt3.push_back(40);lt1 = lt3;print_container(lt1);print_container(lt3);}void func(const list<int>& lt){print_container(lt);}void test_list4(){// 直接构造list<int> lt0({ 1,2,3,4,5,6 });// 隐式类型转换list<int> lt1 = { 1,2,3,4,5,6,7,8 };const list<int>& lt3 = { 1,2,3,4,5,6,7,8 };func(lt0);func({ 1,2,3,4,5,6 });print_container(lt1);//auto il = { 10, 20, 30 };/*	initializer_list<int> il = { 10, 20, 30 };cout << typeid(il).name() << endl;cout << sizeof(il) << endl;*/}
}

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

3. list与vector的对比

vector与list都是STL中非常重要的序列式容器,由于两个容器的底层结构不同,导致其特性以及
应用场景不同,其主要不同如下:(vector与list的主要区别还是基于底层顺序表与链表的区别,对区别感兴趣的读者可以移步看笔者【初阶数据结构】顺序表与链表的比较(附题)这篇文章)

vectorlist



动态顺序表,一段连续空间带头结点的双向循环链表


访
支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)




任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)




底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低


原生态指针对原生态指针(节点指针)进行封装




在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使


需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问


http://www.ppmy.cn/server/144747.html

相关文章

什么是 C++ 中的类型别名和 using 声明?如何使用类型别名和 using 声明?

一、类型别名 类型别名是一个现有类型的另一个名称。它可以让代码更加清晰易读&#xff0c;特别是在处理复杂类型时。在 C 中&#xff0c;可以使用typedef关键字或using关键字来定义类型别名。 1. 使用typedef定义类型别名 typedef 类型 别名 typedef int MyInt; MyInt a …

壹肆柒·2025台球展:春季台球行业的璀璨盛会

在台球产业蓬勃发展的浪潮中&#xff0c;2025 中国&#xff08;郑州&#xff09;国际台球产业博览会&#xff08;壹肆柒・台球展&#xff09;正以其独特的魅力和巨大的影响力吸引着全球目光。 其开展时间定在 2025 年 03 月 12 - 14 日&#xff0c;于郑州・中原国际博览中心盛大…

【计算机网络】多路转接之select

系统提供select()来实现多路转接 IO 等 拷贝 -> select()只负责等待&#xff0c;可以一次等待多个fd select()本身没有数据拷贝的能力&#xff0c;拷贝要read()/write()来完成 一、select的使用 int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exc…

vue 的生命周期函数

Vue 生命周期函数&#xff08;生命周期钩子&#xff09;是 Vue 实例从创建到销毁过程中&#xff0c;不同阶段所触发的特定函数。理解这些生命周期函数对于开发 Vue 应用至关重要&#xff0c;因为它们让你在不同的生命周期阶段执行代码&#xff0c;比如数据初始化、DOM 渲染完成…

深度强化学习(RL)介绍

深度强化学习&#xff08;RL&#xff09;介绍 写到了一半&#xff0c;图待后补 一、强化学习概述 &#xff08;一&#xff09;与监督学习对比及定义 强化学习不同于监督学习&#xff0c;在一些任务中数据标注困难&#xff0c;但机器可通过环境反馈知道结果好坏。强化学习是机…

【C++笔记】数据结构进阶之二叉搜索树(BSTree)

【C笔记】数据结构进阶之二叉搜索树(BSTree) &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】数据结构进阶之二叉搜索树(BSTree)前言一.二叉搜索树的概念二.二叉搜索树的性能分析三.二叉搜索树的实现3.1二叉树的中序…

删除maven仓库中的失败文件

删除maven仓库中的失败文件 创建一个 删除maven仓库中的失败文件.bit 文件,将下面内容复制进去就行 @echo off rem create by sunhao(sunhao.java@gmail.com) rem crazy coderrem 这里写你的仓库路径 set REPOSITORY_PATH=C:\Users\Administrator\.m2\repository rem 正在搜索.…

嵌入式硬件电子电路设计(五)MOS管详解(NMOS、PMOS、三极管跟mos管的区别)

引言&#xff1a;在我们的日常使用中&#xff0c;MOS就是个纯粹的电子开关&#xff0c;虽然MOS管也有放大作用&#xff0c;但是几乎用不到&#xff0c;只用它的开关作用&#xff0c;一般的电机驱动&#xff0c;开关电源&#xff0c;逆变器等大功率设备&#xff0c;全部使用MOS管…