C++list模拟实现

list模拟实现

  • 1.链表结点
  • 2.类模板基本框架
  • 3.构造
  • 4.插入+普通迭代器实现
    • 4.1尾插
    • 4.2普通迭代器实现
    • 4.3对比list和vector的iterator
    • 4.4迭代器的价值
    • 4.5insert
    • 4.6尾插头插复用写法
  • 5.删除+erase
    • 5.1erase
    • 5.2尾删头删复用写法
  • 6.析构+empty+size+clear
    • 6.1clear
    • 6.2size
    • 6.3 empty
    • 6.4 析构
  • 7.拷贝构造+赋值运算符重载
    • 7.1拷贝构造写法
    • 7.2赋值运算符重载
  • 8.const迭代器
  • 9.类名与类型的区别
  • 10.对于自定义类型迭代器遍历的功能实现
  • 11.迭代器完整代码
  • 12.vector与list对比
    • 12.1vector优缺点总结
    • 12.2list优缺点总结
  • 13.迭代器失效问题

自我名言只有努力,才能追逐梦想,只有努力,才不会欺骗自己。在这里插入图片描述
喜欢的点赞,收藏,关注一下把!在这里插入图片描述

在这里插入图片描述

库里面的list类模板,实现的是带头双向循环链表。这里我们和库实现的是一样的。

1.链表结点

在这里插入图片描述
这里链表结点实现的是一个模板,因为我们并不知道别人想在结点里放什么数据。

其次库里面结点指针是void*,而我们和库里面有一些细微差别,

//链表结点
template<class T>
struct __list_node
{__list_node* _prev;__list_node* _next;T _date;
};
//struct和class一样,也可以是类关键字,并且里面的成员变量和成员函数默认都是public的。

2.类模板基本框架

template<class T>
class List
{//每次都要写太长了,所以typedef一下typedef __list_node<T> node;
public:private:node* —_head;
};

3.构造

template<class T>
struct __list_node
{__list_node* _prev;__list_node* _next;T _date;__list_node(const T& val):_prev(nullptr),_next(nullptr),_date(val){}
};//构造list(){//new对自定义类型会调用它的构造函数,因此,结点类必须要有一个构造_head = new node(T());_head->_prev = _head;_head->_next = _head;}

4.插入+普通迭代器实现

4.1尾插

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

4.2普通迭代器实现

在这里插入图片描述
这使用库里面的iterator,遍历链表。

注意list遍历只能用迭代器,或者范围for(底层是迭代器),不能用下标+[],因为vector物理结构是数组,而list底层是一个个按需申请的结点。

接下来我们自己实现一个迭代器。遍历链表。
首先来认识认识迭代器。

std::list<int>::iterator it = It.begin();

在这里插入图片描述
链表结点是一个个申请的,而vector和stirng是一次就申请连续的空间。
在这里插入图片描述

由于在前面学过封装,这里我们可以把指针封装起来,并且加上运算符重载(* ,++, !=)来支持,迭代器的行为。

在这里插入图片描述

template<class T>
struct __lsit_iterator
{typedef __list_node<T> node;node* _pnode;T& operator*(){return _pnode->_date;}__lsit_iterator<T>& operator++(){_pnode = _pnode->_next;return *this;}bool operator!=(const __lsit_iterator<T>& It){return _pnode != It._pnode;}
};
//迭代器
iterator begin()
{//这里是匿名构造,但是__list_iterator缺少了构造,所以增加一下return iterator(_head->_next);
}iterator end()
{return iterator(_head);
}

普通迭代器部分模板

template<class T>
struct __lsit_iterator
{typedef __list_node<T> node;node* _pnode;__lsit_iterator(node* _node):_pnode(_node){}T& operator*(){return _pnode->_date;}__lsit_iterator<T>& operator++(){_pnode = _pnode->_next;return *this;}bool operator!=(const __lsit_iterator<T>& It){return _pnode != It._pnode;}
};

在这里插入图片描述
我们使用自己的iterator,结果也是正确的。这里不太懂得,可以调试一下,帮助理解。
在这里插入图片描述

4.3对比list和vector的iterator

iteratorr------->1.内置类型 2.行为像指针一样。
范围【begin(),end())

在这里插入图片描述
list,vector迭代器上层使用都是一样的。但是它们底层实现不一样。list的迭代器是自定义类型(封装+运算符重载),但vector的迭代器并不一定就是内置类型(原生指针)。

就如在linux环境和vs环境下,erase后pos位置失效不失效的问题,在linux环境下pos不失效,而vs环境下pos失效,这是因为在两个不同环境下,iterator底层实现是不一样的。linux下iterator是原生指针,vs对vector的iterator和list的iterator一样,都是指针的封装。所以iterator不一定就是指针

为什么vector和list这里都用" != “,而不是” < "呢?

在这里插入图片描述
这是因为,vector的物理结构(数组)可以满足" < ",但是list的物理结构并不能确定前一个结点与后一个结点位置谁大谁小,所以统一用 " != ",可以满足不同容器的迭代器。

4.4迭代器的价值

1.封装底层实现,不暴露底层的实现细节。
2.提供统一的访问方式,降低使用成本。

迭代器是一种框架,可能代码我们都会写,但是这种框架思维是最重要的。

从物理层面上看,vector和list的iterator物理结构都是指针,请问大小是几个字节呢?
在这里插入图片描述

vector迭代器是一个原生指针,list迭代器是一个指针类模板,类的大小是成员变量的大小+内存对齐,list迭代器我们就放一个node*指针,我们知道32位指针大小为4,64位指针大小为8;所以大小都是4/8字节。

虽然iterator变化都是指针的变化,但是它们的类型是不一样的,vector的iterator是内置类型(原生指针),list的iterator是自定义类型,编译器根据类型的不同所走的方式不同,list的iterator走的是调用函数,如++it,调用it.operator++()再把结构返回来,而vector直接使用,指针直接像后走4个字节。。这就是类型的力量。

4.5insert

在这里插入图片描述

	iterator insert(iterator pos, const T& val){node* newnode = new node(val);node* cur = pos._pnode;node* prev = cur->_prev;prev->_next = newnode;newnode->_prev = prev;newnode->_next = cur;cur->_prev = newnode;return iterator(newnode);}

在这里插入图片描述

4.6尾插头插复用写法

	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;insert(end(), x);}void push_front(const T& x){insert(begin(), x);}

5.删除+erase

5.1erase

在这里插入图片描述

iterator erase(iterator pos){assert(pos != end());node* prev = pos._pnode->_prev;node* next = pos._pnode->_next;prev->_next = next;next->_prev = prev;delete pos._pnode;//返回pos下一个位置return iterator(next);}

5.2尾删头删复用写法

	__List_iterator<T>& operaotr--(){_pnode = _pnode->prev;return *this;}void pop_back(){//iterator没有实现--运算符重载,所以补充一下erase(--end());}void pop_front(){erase(begin());}

6.析构+empty+size+clear

6.1clear

	void clear(){iterator it = begin();while (it != end()){it = erase(it);}}

6.2size

//一般想法就是遍历,统计一下个数,但是我们这里可以增加一个_size成员遍历,来统计个数
//然后再返回这个变量即可
//成员变量增加一个_size,构造都要改,但是我们实现了empty_initialize(),只要修改这里就可以void empty_initialize(){_head = new node(T());_head->_prev = _head;_head->_next = _head;_size = 0;}size_t size() const{return _size;}
//插入和删除也需要相应的把_size加上。

6.3 empty

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

6.4 析构

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

7.拷贝构造+赋值运算符重载

7.1拷贝构造写法

	//拷贝构造传统写法List(const List<T>& it){//由于构造+拷贝构造+赋值都要有这样写法,为了减少代码冗余//专门写一个empty_initialize()//_head = new node(T());//_head->_prev = _head;//_head->_next = _head;empty_initialize();for (auto e : it){push_back(e);}}

但是这样写有一个问题。
list T是泛型, 如果T放的是内置类型还好说,但如果T放的是自定义类型,这里还涉及拷贝构造和析构。因此正确写法如下。

	//拷贝构造传统写法List(const List<T>& it){empty_initialize();//加个引用for (auto& e : it){push_back(e);}}

但是这样写,还是有问题,it是const修饰的对象,范围for变量底层是迭代器,走的是const的迭代器。但是在__List_iterator 还没有实现。这里先把拷贝构造和赋值写完,就说这个问题。

在string和vector模拟实现,拷贝构造和赋值运算符重载最终都用现代写法来实现。现代写法的主要思想:就是自己的事情让别人干,然后再交换一下。

拷贝构造现代写法复用构造函数
在这里插入图片描述
前面实现的是一个无参构造,想要复用构造函数,必须要实现这个一段区间的构造函数。

	template <class InputIterator>List(InputIterator first, InputIterator last){empty_initialize();while (first != last){push_back(*first);++first;}}
	void swap(List<int>& x){std::swap(_head, x._head);std::swap(_size,x._size);}//拷贝构造现代写法List(const List<T>& it){//这里必须要申请带头结点,不然交换就会野指针,tmp析构的是就会报错//也不能直接给_head(nullptr),这样tmp析构也会报错empty_initialize();List<T> tmp(it.begin(), it.end());swap(tmp);}

7.2赋值运算符重载

	//传统写法List<T>& operator=(const List<T>& it){if (this != &it){//this->clear()clear();for (auto& e : it){//this->push_back(e);push_back(e);}}return *this;}

赋值运算符重载现代写法的思想:复用拷贝构造函数

	//赋值现代写法//it这里调用拷贝构造函数List<T>& operator=(List<T> it){swap(it);return *this;}

虽然现在已经实现了拷贝构造和赋值运算符重载,但是迭代器没有实现const iterator 上面代码都会报错。
在这里插入图片描述
接下来实现const迭代器

8.const迭代器

回想一下vector实现的const迭代器

	typedef T* iterator;typedef const T* const_iterator;
	List<int>::iterator it = It.begin();//可能会以为在前面加上const 就是const迭代器了,但这样是不对的const List<int>::iterator it = It.begin();

我们知道const修饰指针有不同位置

//const修饰*p1,*p1内容不能改变,p1指针可以改变const T* p1
//const修饰p2,p2指针不能改变,*p2内容可以改变T* const p2

而const迭代器类似p1的行为,保护指向对象不能改变,迭代器本身可以修改

//不符合迭代器的行为,因为它保护迭代器 本身不能修改,那么就不能++/--迭代器const List<int>::iterator it = It.begin();

所以考虑一下,放在__List_iterator中;

	//iterator it//*it//++itT& operator*(){return _pnode->_date;}//可能还会想这样写,const修饰指针*this//const iterator* const this  这样*this就不会改变了//但是*this 是const iterator it//*it//++it 这样的话,可以解引用,但是不能++itconst T& operator*() const{//引用赋值,必须权限缩小或者平移,所以返回类型是constreturn _pnode->_date;}

因此我们大多数人会再写一个const迭代器类
在这里插入图片描述

template<class T>
struct __List_iterator
{typedef __List_node<T> node;node* _pnode;__List_iterator(node* _node):_pnode(_node){}cosnt T& operator*(){return _pnode->_date;}__List_iterator<T>& operator++(){_pnode = _pnode->_next;return *this;}__List_iterator<T>& operator--(){_pnode = _pnode->_prev;return *this;}bool operator!=(const __List_iterator<T>& It){return _pnode != It._pnode;}
};

但是对比普通迭代器和const迭代器,发现就只有operator*不一样,其他运算符重载都一样。
我们看库里是怎么实现iterator的。
暂时先不用管Ptr参数。
在这里插入图片描述

看到大佬是把普通迭代器和const迭代器实现放在一块,然后用Ref参数来做区分。

我们知道一个模板类,由于类型不同可实现不同的类。
如list < int > , list< char > 虽然用的是统一模板,但是由于类型不同,因此是不同的类。所以大佬方法是非常厉害的。

//typedef __List_iterator<T,T&> iterator;
//typedef __List_iterator<T,const T&> const_iterator;
template<class T,class Ref>
struct __List_iterator
{typedef __List_node<T> node;typedef __List_iterator<T, Ref> self;node* _pnode;__List_iterator(node* _node):_pnode(_node){}Ref operator*(){return _pnode->_date;}//__List_iterator<T>& operator++()//{//	_pnode = _pnode->_next;//	return *this;//}//普通迭代器和const迭代器返回类型不一样,因此typedef一下,比较方便。self& operator++(){_pnode = _pnode->_next;return *this;}//__List_iterator<T>& operator--()//{//	_pnode = _pnode->_prev;//	return *this;//}self& operator--(){_pnode = _pnode->_prev;return *this;}//bool operator!=(const __List_iterator<T>& It)//{//	return _pnode != It._pnode;//}bool operator!=(const self& It){return _pnode != It._pnode;}
};

有了const迭代器,拷贝构造和赋值运算符重载就没问题了。

9.类名与类型的区别

普通类: 类名 等价于 类型
类模板: 类名 不等价于 类型

在这里插入图片描述
注意:在类外面 类名 != 类型 ,类里面 类名 == 类型

这样写是没有报错的,但是不建议这样写。如果别人这样写,能看懂即可。
在这里插入图片描述
就如库里面提供的拷贝构造+赋值就是这样写的
在这里插入图片描述

10.对于自定义类型迭代器遍历的功能实现

list< T >,T可能是内置类型,也有可能是自定义类型,如果是自定义类型,迭代器该如何遍历呢?

struct Pos
{int _row;int _col;Pos(int row = 0, int col = 0):_row(row),_col(col){}
};void test_list5()
{List<Pos> It;Pos p(1,1);It.push_back(p);It.push_back(p);It.push_back(Pos(2,2));It.push_back(Pos(3,3));List<Pos>::iterator it = It.begin();while (it != It.end()){//这里还这样解引用可以拿到想要的数据吗?cout << *it << " ";++it;}cout << endl;
}

解引用有三种访问方式
1.数组------> [ ]
2.普通指针(如int *p)------> *p
3.结构的指针(pos *p)-------> p----->

	List<Pos>::iterator it = It.begin();while (it != It.end()){//cout << *it << " ";//1.cout << (*it)._row << ":" << (*it)._col << endl;//2.cout << it->_row << ":" << it->_col << endl;++it;}cout << endl;

第一种方法,调用it.operator*,返回的是一个Pos对象,

第二种方法,需要调用it.operator->,因此给迭代器增加一个operator->()函数

	T* opearotr->(){//返回的是结点数据的地址return &_pnode->_date;}	

在这里插入图片描述
在这里插入图片描述
对于T是自定义类型,迭代器遍历,推荐it->_row这样的。

再看一个问题

void print_List(const List<Pos>& lt)
{List<Pos>::const_iterator it = lt.begin();while (it != lt.end()){//const迭代器,数据不能修改it->_row++;cout << it->_row << ":" << it->_col << endl;++it;}cout << endl;
}void test_list7()
{List<Pos> It;Pos p(1, 1);It.push_back(p);It.push_back(p);It.push_back(Pos(2, 2));It.push_back(Pos(3, 3));List<Pos>::iterator it = It.begin();while (it != It.end()){//普通迭代器,对于数据可以修改it->_row++;cout << it->_row << ":" << it->_col << endl;++it;}cout << endl;print_List(It);
}

在这里插入图片描述
注意运行结果,const迭代器竟然也能支持it->_row++了。这不符合const迭代器。

因此再次修改一下迭代器,但是我们的普通迭代器和const迭代器实现在一块,所以这里我们给__List_iterator第三个参数Ptr

在这里插入图片描述

在这里插入图片描述

	Ptr operator->(){return &_pnode->_date;}

11.迭代器完整代码

//typedef __List_iterator<T,T&,T*> iterator;
//typedef __List_iterator<T,const T&,const T*> const_iterator;
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* _node):_pnode(_node){}Ref operator*(){return _pnode->_date;}Ptr operator->(){return &_pnode->_date;}//++itself& operator++(){_pnode = _pnode->_next;return *this;}//it++//int是一个占位符,为了把前置++和后置++ 分开self operator++(int){self tmp(*this);_pnode = _pnode->next;return tmp;}//--itself& operator--(){_pnode = _pnode->_prev;return *this;}//it--self operator--(int){self tmp(*this);_pnode = _pnode->_prev;return tmp;}bool operator!=(const self& It){return _pnode != It._pnode;}bool operator==(const self& It){return _pnode == It._pnode;}
};

注意观察我们的迭代器和List模拟实现,成员变量都有node*(指针)。

List我们实现了析构函数释放所有结点,而迭代器我们并没有实现析构函数。那迭代器这里默认生成的析构会不会释放结点呢?

析构函数和构造函数一样,如果不写,默认生成的析构函数,对内置类型不处理,对自定义类型调用它的析构。
在这里插入图片描述
如果我们List我们不写析构,编译器默认生成的析构,敢不敢轻易释放指针所指向的结点呢?
我们的编译器非常尽责,默认生成的析构,对自定义类型调用它的析构,对内置类型不敢轻易处理。所以对于指针不敢轻易释放所指向的结点。

因此对于List,我们需要析构的时候释放所有结点,所以自己写了个析构。而迭代器我们没写析构,那编译器默认生成的析构,对这个指针指向的结点也不会释放。因为这个结点是链表的,迭代器只是借助结点的指针帮助去访问或修改数据。所以也不能自己写一个析构去把这个结点释放。

不是说有指针就需要去析构,一定要具体问题具体分析。

并且iterator我们也没有实现拷贝构造和赋值。使用的是默认生成的属于浅拷贝就够用了,这是因为默认生成的析构并不会释放指针所指向的结点,所以够用。

在类与对象中也总结过,需要写析构,就需要自己写拷贝构造和赋值。

12.vector与list对比

vectorlist
底层结构动态顺序表,一段连续空间带头结点的双向循环链表
随机访问支持随机访问,访问某个元素效率O(1)不支持随机访问,访问某个元素效率O(N)
插入和删除任意位置插入和删除效率低,需要搬移元素,时间复杂度为O(N),插入时有可能需要增容,增容:开辟新空间,拷贝元素,释放旧空间,导致效率更低任意位置插入和删除效率高,不需要搬移元素,时间复杂度为O(1)
空间利用率底层为连续空间,不容易造成内存碎片,空间利用率高,缓存利用率高底层节点动态开辟,小节点容易造成内存碎片,空间利用率低,缓存利用率低
迭代器原生态指针对原生态指针(节点指针)进行封装
迭代器失效在插入元素时,要给所有的迭代器重新赋值,因为插入元素有可能会导致重新扩容,致使原来迭代器失效,删除时,当前迭代器需要重新赋值否则会失效在插入元素不会导致迭代器失效,删除元素时,只会导致当前迭代器失效,其他迭代器不受影响
使用场景需要高效存储,支持随机访问,不关心插入删除效率大量插入和删除操作,不关心随机访问

12.1vector优缺点总结

优点
1.下标随机访问
2.尾插尾删效率高
3.cpu高速缓存命中高(因为vector物理结构是一段连续的数组)

缺点
1.前面部分插入删除效率低O(N)
2.扩容有销毁,还存在一定空间浪费

12.2list优缺点总结

优点
1.按需申请释放,无需扩容
2.任意位置插入删除O(1)

缺点
1.不支持下标随机访问
2.cpu高速缓存命中低(list物理结点是一个个按需申请的结点)

vector与list就像左右手一样,属于互补配合的关系。

13.迭代器失效问题

我们现在已经模拟实现了string,vector,list。
vector----->insert/erase (失效)
list-------->erase(失效)

string似乎从来没提过失效还是不失效的问题。那么string到底失效不失效呢?

stirng底层和vector一样,都是连续的空间。空间不够需要扩容,因此string也失效。

string------>insert/erase(失效),但是一般不关注string的失效。
在这里插入图片描述
vector的insert/erase给的是迭代器,string的insert/erase常用接口更多都是下标支持,迭代器支持用的很少。所以即使扩容或者删除对下标没什么影响。

自此关于list模拟实现内容结束了,有些接口没有实现,有兴趣可以实现一下。

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

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

问题: 视频颜色问题,偏绿

参考 什么是杜比视界&#xff1f; - https://www.youtube.com/watch?vldXDQ6VlC7g 【哈士亓说】07&#xff1a;HDR、杜比视界究竟是个啥&#xff1f;为什么这个视频还不是HDR视频&#xff1f; - https://www.youtube.com/watch?vrgb9Xg3cJns 正文 视频应该是 杜比视界 电…

GEO生信数据挖掘(二)下载基因芯片平台文件及注释

检索到目标数据集后&#xff0c;开始数据挖掘&#xff0c;本文以阿尔兹海默症数据集GSE1297为例 目录 下载平台文件 1.AnnotGPL参数改为TRUE,联网下载芯片平台的soft文件。&#xff08;国内网速奇慢经常中断&#xff09; 2.手工去GEO官网下载 转换芯片探针ID为gene name 拓…

保姆级Anaconda安装教程

一.anaconda下载 建议使用清华大学开源软件镜像站进行下载&#xff0c;使用官网下载速度比较慢。 anaconda清华大学开源软件镜像站 &#xff1a; https://mirrors.tuna.tsinghua.edu.cn/anaconda/archive/ 一路next即可&#xff0c;注意添加环境变量得选项都勾上。 二.验证…

【AI视野·今日Robot 机器人论文速览 第四十六期】Tue, 3 Oct 2023

AI视野今日CS.Robotics 机器人学论文速览 Tue, 3 Oct 2023 Totally 76 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Robotics Papers Generalized Animal Imitator: Agile Locomotion with Versatile Motion Prior Authors Ruihan Yang, Zhuoqun Chen, Jianhan M…

Java 随机数的获得方法(5种)

1. Math.random() 静态方法 产生的随机数是 0 - 1 之间的一个 double&#xff0c;即 0 < random < 1 代码&#xff1a; 结果&#xff1a; 当调用 Math.random() 方法时&#xff0c;自动创建了一个伪随机数生成器&#xff0c;实际上用的是 new java.util.Random()。当接…

【考研英语】2011 年英语(一)排序题思路复盘(费曼学习法)

文章目录 引言一、找语段特征词二、确定位置写在最后 引言 英语一中的新题型之一 —— 排序题&#xff0c;我是看的刘琦老师的方法课&#xff0c;她用的 2011 年的真题来讲解方法。讲完让我们回去用“费曼学习法”复盘以下&#xff0c;我个人感觉是一个不错的方法&#xff0c;…

mysql面试题10:MySQL中有哪几种锁?表级锁、行级锁、页面锁区别和联系?

该文章专注于面试,面试只要回答关键点即可,不需要对框架有非常深入的回答,如果你想应付面试,是足够了,抓住关键点 面试官:Mysql中有哪几种锁? 在MySQL中,主要有以下几种类型的锁: 共享锁(Shared Lock):也称为读锁。多个事务可以同时持有共享锁,可以读取但不能修…

106.从中序与后序遍历序列构造二叉树

力扣题目链接(opens new window) 根据一棵树的中序遍历与后序遍历构造二叉树。 注意: 你可以假设树中没有重复的元素。 例如&#xff0c;给出 中序遍历 inorder [9,3,15,20,7]后序遍历 postorder [9,15,7,20,3] 返回如下的二叉树&#xff1a; class Solution { public:Tr…

国产32位单片机 普冉PY32F002B 适用于LED灯驱,控制器等

PY32F002B 系列单片机采用高性能的 32 位 ARM Cortex-M0内核&#xff0c;宽电压工作范围的 MCU。嵌入了24Kbytes Flash 和 3Kbytes SRAM 存储器&#xff0c;最高工作频率 24MHz。有TSSOP20, QFN20, SOP16, SOP14,MSOP10多种不同封装类型多款产品。 芯片集成了I2C、SPI、USART 等…

新一代网络框架UringNet,基于最新的异步I/O

介绍 在去年的一篇文章中&#xff0c;笔者曾经提到了最新一代的网络I/O框架UringNet。具体内容可以参考Rings’ Power,性能“世界第一”的Web I/O框架。这是基于最新Linux内核的异步I/O组件io_uring开发的网络框架。由于采用了最新的异步框架&#xff0c;因此在同等硬件配置条件…

RabbitMQ的基本介绍

什么是MQ 本质是一个队列&#xff0c;只不过队列中存放的信息是message罢了&#xff0c;还是一种跨进程的通信机制&#xff0c;用于上下游传递信息。在互联网架构中&#xff0c;MQ是一种非常常见的上下游“逻辑解耦物理解耦”的消息通信服务。使用了MQ之后&#xff0c;信息发送…

【简单的留言墙】HTML+CSS+JavaScript

目标&#xff1a;做一个简单的留言墙 1.首先我们用HTML的一些标签&#xff0c;初步构造区域 样式。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>留言墙</title><style>/* ...... */ …

STM32HAL库CRC学习及测试记录

STM32HAL库CRC学习及测试记录 1.CRC的校验原理2.基本原理3.几个基本概念13.1.1 CRC检验码的计算13.1.2 错误检测13.2 STM32中的CRC 4.CRC功能描述5.STM32Cube生成工程6.看官方说如何使用这个驱动程序7.实验现象 1.CRC的校验原理 循环冗余校验(CRC)计算单元是根据固定的生成多项…

pycharm 中的一个非常好使用的智能提示tabnine(大大提高代码的书写效率)

一. pycharm 中的代码智能提示插件 有时候,我们总是在写代码的时候,敲全部的代码,太过于麻烦了,如果有一个软件可以预知你的后续的操作,提前将代码给你写出来,你只需要检查对错,就可以了,这样就大大提高了工作的效率. 上面的一个插件是汉化pycharm的软件包,也是非常好用的一款…

Kafka收发消息核心参数详解

文章目录 1、从基础的客户端说起1.1、消息发送者主流程1.2、消息消费者主流程 2、从客户端属性来梳理客户端工作机制2.1、消费者分组消费机制 1、从基础的客户端说起 Kafka提供了非常简单的客户端API。只需要引入一个Maven依赖即可&#xff1a; <dependency><groupId…

lv7 嵌入式开发-网络编程开发 02OSI七层结构

目录 1 计算机网络体系结构的形成 1.1 提出了不同体系结构 1.2 国际标准&#xff1a;开放系统互连参考模型 OSI/RM 1.3 存在两种国际标准 2 协议与划分层次 2.1 网络协议 2.2 协议的两种形式 2.3 层次式协议结构 2.4 各层完成的主要功能 2.5 计算机网络的体系结构 …

大规模语言模型--训练成本

目前&#xff0c;基于 Transformers 架构的大型语言模型 (LLM)&#xff0c;如 GPT、T5 和 BERT&#xff0c;已经在各种自然语言处理 (NLP) 任务中取得了 SOTA 结果。将预训练好的语言模型(LM) 在下游任务上进行微调已成为处理 NLP 任务的一种 范式。与使用开箱即用的预训练 LLM…

jenkins联动显示或隐藏参数

1. 添加组件 Active Choices Plug-in 如jenkins无法联网&#xff0c;可在以下两个地址中下载插件&#xff0c;然后放到/home/jenkins/.jenkins/plugin下面重启jenkins即可 Active Choices Active Choices | Jenkins plugin 2. 效果如下&#xff1a; sharding为空时&#xf…

GEE15:获取不同遥感指数的时间序列及不同指数间的关系

GEE 1. 不同遥感指数间的时间序列分析2. 不同指数之间的关系 1. 不同遥感指数间的时间序列分析 GPP数据在一定程度上和植被指数&#xff08;如NDVI和EVI&#xff09;有着显著的相关性&#xff0c;那么其相关性如何&#xff1f;如何从时间序列的角度来思考呢&#xff1f;下面我将…

自动驾驶中的感知模型:实现安全与智能驾驶的关键

自动驾驶中的感知模型&#xff1a;实现安全与智能驾驶的关键 文章目录 引言感知模型的作用感知模型的技术安全与挑战结论 2023星火培训【专项营】Apollo开发者社区布道师倾力打造&#xff0c;包含PnC、新感知等的全新专项课程上线了。理论与实践相结合&#xff0c;全新的PnC培训…