【C++】list的模拟实现【完整理解版】

目录

一、list的概念引入

1、vector与list的对比

 2、关于struct和class的使用

3、list的迭代器失效问题 

 二、list的模拟实现

1、list三个基本函数类

2、list的结点类的实现

3、list的迭代器类的实现 

3.1 基本框架

3.2构造函数 

3.3 operator*

3.4 operator->

3.5 operator前置++和--与后置++和-- 

3.5 operator==与operator!=

4、list类的实现

 4.1 基本框架

4.2 构造函数 

4.2 begin()和end()

4.3 拷贝构造

4.4  clear

4.5 operator=

4.6  析构函数

 4.7 insert

4.8 push_back 和 push_front

4.9 erase

4.10 pop_back 和 pop_front

三、完整源代码:

list.h:

 test.cpp:


一、list的概念引入

1、vector与list的对比

 两者的区别十分类似于顺序表和链表的区别


 2、关于struct和class的使用

C++中的structclass的唯一区别在于默认访问限定符(即你不写public、private这种访问限定符)不同,struct默认为公有(public),而class默认为私有(private),一般情况,成员部分私有,部分共有,就用class,所有成员都开放或共有,就用struct

所以下文写的节点和迭代器类都用struct是因为,struct中的成员函数默认为公有了,也不用写public了,但是你用class就要写个public


3、list的迭代器失效问题 

 list本质:带头双向循环链表 

支持操作接口的角度分迭代器的类型:单向(forward_list)、双向(list)、随机(vector)

从使用场景的角度分迭代器的类型:(正向迭代器,反向迭代器)+const迭代器

 迭代器失效本质内存空间的问题,失效原因:

1、增容完还指向旧空间

2、节点已经被释放(list里面)

list的erase迭代器失效

注意:删除不会报错,迭代器失效也不会报错,是删除以后迭代器失效了,是去访问失效的迭代器才会报错

插入知识点(库里面的find实现,可不看


 二、list的模拟实现

1、list三个基本函数类

list本质是一个带头双向循环链表:

模拟实现list,要实现下列三个类

①、模拟实现结点类
②、模拟实现迭代器的类
③、模拟list主要功能的类
list的类的模拟实现其基本功能(增删等操作)要建立在迭代器类和结点类均已实现好的情况下才得以完成。

2、list的结点类的实现

因为list的本质为带头双向循环链表,所以其每个结点都要确保有下列成员:

  1. 前驱指针
  2. 后继指针
  3. data值存放数据

而结点类的内部只需要实现一个构造函数即可。

//1、结点类
template<class T>
struct __list_node//前面加__是命名习惯,一般这么写表示是给别人用的
{//前驱指针和后驱指针__list_node<T>* _next;//C++中可不写struct,直接用名定义,但注意这里还要加类型__list_node<T>* _prev;T _data;//存节点的值//构造函数__list_node(const T& val = T())//给一个缺省值T():_next(nullptr), _prev(nullptr), _data(val){}
};

①、为什么是__list_node<T>?

首先,C++中用struct定义时可不加struct,重点是这里用了一个类模板,类模板的类名不是真正的类型,即__list_node不是真正的类型,定义变量时__list_node<T>这种才是真正的类型,也就是用类模板定义变量时必须 指定对应的类型 


3、list的迭代器类的实现 

因为list其本质是带头双向循环链表,而链表的物理空间是不连续的,是通过结点的指针顺次链接,我们不能像先前的string和vector一样直接解引用去访问其数据,结点的指针解引用还是结点,结点指针++还是结点指针,而string和vector的物理空间是连续的,所以这俩不需要实现迭代器类,可以直接使用。

为了能让list像vector一样解引用后访问对应节点中的值,++访问到下一个数据,我们需要单独写一个迭代器类的接口实现,在其内部进行封装补齐相应的功能,而这就要借助运算符重载来完成。 

注:迭代器封装后是想模拟指针的行为 

3.1 基本框架

template<class T,class Ref,class Ptr>
struct __list_iterator
{typedef __list_node<T> Node;typedef __list_iterator<T, Ref, Ptr> Self;Node* _node;
}

①、迭代器类模板为什么要三个参数?

若只有普通迭代器的话,一个class T参数就够了,但因为有const迭代器原因,需要加两个参数,两个参数名Ref(reference:引用)和Ptr(pointer:指针),名字怎么起都行,但这种有意义的名字是很推荐的,即这两个参数一个让你传引用,一个让你传指针,具体等下文讲到const迭代器再说

②、迭代器类到底是什么?

迭代器类就一个节点的指针变量_node,但是因为我们要运算符重载等一系列操作,不得不把list的迭代器写成类,完成那些操作,list的迭代器才能正确的++到下一位置,解引用访问节点的值

③、节点指针和迭代器的区别?


3.2构造函数 

	//迭代器的构造函数只需要一个指针构造__list_iterator (Node* node):_node(node){ }

3.3 operator*

//*it(调用的是函数,返回节点中的值)Ref operator*(){//出了作用域还在,引用返回return _node->_data;}

①、 返回值为什么是Ref?

Ref是模板参数,因为迭代器类的模板参数Ref传入的要么是T&要么是const T&,就是为了const迭代器和普通迭代器的同时实现,底层就是这么实现的,意义就是一个只读,一个可读可写 

注:比如之前讲的vector的迭代器,*it(假设it是迭代器变量)就是拿到对应的值,那么list的迭代器也要同理,解引用迭代器就是为了访问对应位置的值,那么list只要通过迭代器返回对应节点的值就好了(*it,我们是就想要对应的值)


3.4 operator->

Ptr operator->(){return &_node->_data;}

①、为什么需要operator->?

本质因为自定义类型需要,那需从list存的类型是个自定义类型说起,以Date类型为例

 若list存了个自定义类型的Date类,程序错误,因为我们并没有重载Date类的operator<<,若是内置类型的话才可以正常输出,那写一个operator<<重载吗?不,因为你无法确定要用哪些类,也不能每个类都写operator<<,那怎么办?我们访问Date类本质是想访问它内置类型(int)的_year、_month和_day吧,那我们不妨写个专属于自定义类型的operator->(因为内置类型只需要*it就可以直接输出了,但自定义类型不可以直接输出),利用operator->直接访问类的成员变量,而内置类型可以直接输出

从根源上解决问题: 在迭代器中实现个operator->:

(Ptr是迭代器的模板参数,我们用来作为T*或const T*的)


3.5 operator前置++和--与后置++和-- 

//++it;(迭代器++本质就是指针往后移,加完后还应是个迭代器)Self& operator++(){_node = _node->_next;return *this;}//it++;Self operator++(int)//加参数以便于区分前置++{Self tmp(*this);//拷贝构造tmp_node = _node->_next;//直接让自己指向下一个结点即可实现++return tmp;//注意返回tmp,才是后置++}//--it;Self& operator--(){_node = _node->_prev;//让_node指向上一个结点即可实现--return *this;}//it--;Self operator--(int)//记得传缺省值以区分前置--{Self tmp(*this);//拷贝构造tmp_node = _node->_prev;return tmp;}

①、迭代器++对于list是什么意思?

迭代器++的意思就是想让其指向下一个节点,--正好相反,为了区分前置和后置++(--),我们会用函数重载,也就是多一个“没用的”参数:int,这个参数没什么用,只是为了区分++与--


3.5 operator==与operator!=

//it != end() bool operator!=(const Self& it){//迭代器是否相等只要判断对应节点地址是否相等即可return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}

①、两个迭代器怎么比较的?

迭代器中就一个成员变量_node,节点指针,只要比较当前的节点指针是否相同即可,个人认为这个操作意义不大


4、list类的实现

 在结点类和迭代器类都实现的前提下,就可实现list主要功能:增删等操作的实现

 4.1 基本框架

//3、链表类
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;private:Node* _head;//头结点
}

①、const_iterator(const迭代器的介绍)

我们知道const_iterator在begin()和end()中的返回值是需要用到的,其主要作用就是当迭代器只读时使用, 因为普通迭代器和const迭代器的实现区别仅仅在于内部成员函数的返回值不同,难道重写一遍吗?不用,我们模板参数多两个就好了,一个是引用class Ref(T&或const T&),一个是指针class Ptr(T*或const T*),当Ref时const T&就是const迭代器的调用,当Ref时T& 时就是普通迭代器的调用,这就利用模板实现了两个迭代器的同时实现


4.2 构造函数 

//带头双向循环链表的构造函数
list()
{_head = new Node;_head->_next = _head;_head->_prev = _head;
}

解释:我们开辟一个头结点,然后使其处于一个对应的初始状态即可


4.2 begin()和end()

iterator begin()
{//第一个位置应该是头结点的下一个节点return iterator(_head->_next);//用匿名对象构造iterator类型的
}
iterator end()
{//最后一个数据的下一个位置应该是第一个节点,即头结点return iterator(_head);
}const_iterator begin()const
{return const_iterator(_head->_next);
}
const_iterator end()const
{return const_iterator(_head);
}

①、关于匿名构造的理解

比如 iterator(_head->_next); iterator是个类模板类型(它被typedef过的),那不应该实例化一个对象再构造吗?这里没有用是因为这里是匿名对象的构造,这里这么用比较方便


4.3 拷贝构造

//拷贝构造:传统写法
list(const list<T>& lt)
{_head = new Node;//开辟一样的头结点_head->_next = _head;_head->_prev = _head;//1、用迭代器遍历/*const_iterator it = lt.begin();while (it != lt.end()){push_back(*it);++it;}*///2、范围for遍历//遍历lt1,把lt1的元素push_back到lt2里头for (auto e : lt){push_back(e);//自动开辟新空间,完成深拷贝}
}

 解释:list的拷贝构造跟vector不同,它的拷贝是拷贝一个个节点(因为不连续的物理空间), 那么我们可以用迭代器拿到list的一个个节点

①、为什么有的拷贝构造需初始化,operator=不需要?

以string的模拟实现为例

这里为什么s2要初始化?

是因为string的模拟实现有交换操作,而list的传统写法无需交换,开辟一个头结点即可

因为s2是要被拷贝构造出来的,没被拷贝构造前还没存在,然后s2要跟tmp交换,如果也就是tmp得到s2的数据,如果s2之前没初始化,析构销毁就出问题了,因为没初始化是随机值

但是赋值不一样,赋值是两个对象都存在,不存在随机值问题,所以不用一上来就初始化

还有一种现代写法先不介绍


4.4  clear

void clear()
{//clear不删除头结点,因为万一删除了头结点你还想插入数据怎么办iterator it = begin();while (it != end()){it = erase(it);}
}

①、 it = erase(it)什么意思?

防止迭代器失效,因为erase返回的是被删除位置的下一位置,比如删除pos位置的,pos位置被删除后,it都不用动,erase算自动指向下一位置了,故用it接收,若不接收,迭代器失效


4.5 operator=

        //赋值运算符重载(传统写法)//lt1 = lt3//list<T>& operator=(const list<T>& lt)//{//	if (this != &lt)//	{//		clear();//先释放lt1//		for (auto e : lt)//			push_back(e);//	}//	return *this;//}//赋值运算符重载(现代写法)//lt1 = lt3list<T>& operator=(list<T> lt)//套用传值传参去拷贝构造完成深拷贝{swap(_head,lt._head);//交换两个list的头结点即可//lt出了作用域,析构函数销毁lt1原来的链表,一举两得//swap(lt);return *this;}

注:传统写法要先把被赋值的对象先释放,然后利用push_bak尾插,push_back在下文说明


4.6  析构函数

//析构函数~list(){clear();//删除除头结点以外的节点delete _head;//删去哨兵位头结点_head = nullptr;}

 4.7 insert

//insert,插入pos位置之前
iterator insert(iterator pos, const T& x)
{Node* newnode = new Node(x);//创建新的结点Node* cur = pos._node; //迭代器pos处的结点指针Node* prev = cur->_prev;//prev newnode cur//链接prev和newnodeprev->_next = newnode;newnode->_prev = prev;//链接newnode和curnewnode->_next = cur;cur->_prev = newnode;//返回新插入元素的迭代器位置return iterator(newnode);
}

①、返回参数为什么是iterator?

本质是为了防止迭代器失效问题

注:insert指的是插入到指定位置的前面


4.8 push_back 和 push_front

//尾插
void push_back(const T& x)
{Node* tail = _head->_prev;//找尾Node* newnode = new Node(x);//创建一个新的结点//_head  tail  newnode//使tail和newnode构成循环tail->_next = newnode;newnode->_prev = tail;//使newnode和头结点_head构成循环newnode->_next = _head;_head->_prev = newnode;}
//头插
void push_front(const T& x)
{insert(begin(), x);
}

4.9 erase

//erase
iterator erase(iterator pos)
{assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;//prev cur next//链接prev和nextprev->_next = next;next->_prev = prev;//delete删去结点,因为每一个节点都是动态开辟出来的delete cur;//返回被删除元素后一个元素的迭代器位置//return next;return iterator(next);
}

①、 返回值问题

erase的返回值,返回的是被删除位置的下一位置


4.10 pop_back 和 pop_front

//尾删
void pop_back()
{erase(--end());//erase(iterator(_head->prev));//构造个匿名对象
}
//头删
void pop_front()
{erase(begin());
}

最后, list的排序意义不大,因为实际生活中我们都是对数组等排序

三、完整源代码:

list.h:

#pragma oncenamespace mz
{//1、结点类template<class T>struct __list_node//前面加__是命名习惯,一般这么写表示是给别人用的{//前驱指针和后驱指针__list_node<T>* _next;//C++中可不写struct,直接用名定义,但注意这里还要加类型__list_node<T>* _prev;T _data;//存节点的值//构造函数__list_node(const T& val = T())//给一个缺省值T():_next(nullptr), _prev(nullptr), _data(val){}};//2、迭代器类//__list_iterator<T,T&,T*>              -> iterator//__list_iterator<T,const T&,const T*>  -> const_iteratortemplate<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){ }//*it(调用的是函数,返回节点中的值)Ref operator*(){//出了作用域还在,引用返回return _node->_data;}Ptr operator->(){return &_node->_data;}//++it;(迭代器++本质就是指针往后移,加完后还应是个迭代器)Self& operator++(){_node = _node->_next;return *this;}//it++;Self operator++(int)//加参数以便于区分前置++{Self tmp(*this);//拷贝构造tmp_node = _node->_next;//直接让自己指向下一个结点即可实现++return tmp;//注意返回tmp,才是后置++}//--it;Self& operator--(){_node = _node->_prev;//让_node指向上一个结点即可实现--return *this;}//it--;Self operator--(int)//记得传缺省值以区分前置--{Self tmp(*this);//拷贝构造tmp_node = _node->_prev;return tmp;}//it != end() bool operator!=(const Self& it){//迭代器是否相等只要判断对应节点地址是否相等即可return _node != it._node;}bool operator==(const Self& it){return _node == it._node;}};//3、链表类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 iterator(_head->_next);//用匿名对象构造iterator类型的}iterator end(){//最后一个数据的下一个位置应该是第一个节点,即头结点return iterator(_head);}const_iterator begin()const{return const_iterator(_head->_next);}const_iterator end()const{return const_iterator(_head);}//带头双向循环链表的构造函数list(){_head = new Node;_head->_next = _head;_head->_prev = _head;}//拷贝构造:传统写法list(const list<T>& lt){_head = new Node;//开辟一样的头结点_head->_next = _head;_head->_prev = _head;//1、用迭代器遍历/*const_iterator it = lt.begin();while (it != lt.end()){push_back(*it);++it;}*///2、范围for遍历//遍历lt1,把lt1的元素push_back到lt2里头for (auto e : lt){push_back(e);//自动开辟新空间,完成深拷贝}}//赋值运算符重载(传统写法)//lt1 = lt3//list<T>& operator=(const list<T>& lt)//{//	if (this != &lt)//	{//		clear();//先释放lt1//		for (auto e : lt)//			push_back(e);//	}//	return *this;//}//赋值运算符重载(现代写法)//lt1 = lt3list<T>& operator=(list<T> lt)//套用传值传参去拷贝构造完成深拷贝{swap(_head,lt._head);//交换两个list的头结点即可//lt出了作用域,析构函数销毁lt1原来的链表,一举两得//swap(lt);return *this;}//析构函数~list(){clear();//删除除头结点以外的节点delete _head;//删去哨兵位头结点_head = nullptr;}void clear(){//clear不删除头结点,因为万一删除了头结点你还想插入数据怎么办iterator it = begin();while (it != end()){it = erase(it);}}//尾插void push_back(const T& x){Node* tail = _head->_prev;//找尾Node* newnode = new Node(x);//创建一个新的结点//_head  tail  newnode//使tail和newnode构成循环tail->_next = newnode;newnode->_prev = tail;//使newnode和头结点_head构成循环newnode->_next = _head;_head->_prev = newnode;}//头插void push_front(const T& x){insert(begin(), x);}//insert,插入pos位置之前iterator insert(iterator pos, const T& x){Node* newnode = new Node(x);//创建新的结点Node* cur = pos._node; //迭代器pos处的结点指针Node* prev = cur->_prev;//prev newnode cur//链接prev和newnodeprev->_next = newnode;newnode->_prev = prev;//链接newnode和curnewnode->_next = cur;cur->_prev = newnode;//返回新插入元素的迭代器位置return iterator(newnode);}//eraseiterator erase(iterator pos){assert(pos != end());Node* cur = pos._node;Node* prev = cur->_prev;Node* next = cur->_next;//prev cur next//链接prev和nextprev->_next = next;next->_prev = prev;//delete删去结点,因为每一个节点都是动态开辟出来的delete cur;//返回被删除元素后一个元素的迭代器位置//return next;return iterator(next);}//尾删void pop_back(){erase(--end());//erase(iterator(_head->prev));//构造个匿名对象}//头删void pop_front(){erase(begin());}private:Node* _head;//头结点};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;}struct Date{int _year = 0;int _month = 1;int _day = 1;};void test2(){Date* p2 = new Date;*p2;//取到的是Datep2->_year;//取到的是Date类中的成员变量list<Date>lt;lt.push_back(Date());lt.push_back(Date());//list存了个日期类(自定义类型)的类型list<Date>::iterator it = lt.begin();while (it != lt.end()){//cout << *it << " ";cout << it->_year << "-" << it->_month << "-" << it->_day << endl;++it;}cout << endl;}void print_list(const list<int>& lt){list<int>::const_iterator it = lt.begin();while (it != lt.end()){cout << *it << " ";++it;}cout << endl;}void test3(){list<int>lt1;lt1.push_back(1);lt1.push_back(2);lt1.push_back(3);lt1.push_back(4);list<int> lt2(lt1);print_list(lt2);list<int>lt3;lt3.push_back(10);lt3.push_back(20);lt3.push_back(30);lt3.push_back(40);lt1 = lt3;print_list(lt1);}
}

 test.cpp:

#include<iostream>
#include<assert.h>
using namespace std;
#include"list.h"int main()
{//mz::test1();mz::test3();return 0;
}

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

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

相关文章

bug总结问题集和知识点集(一)

目录 一 bug问题集1. 端口被占用 二 oracle1. oracle查看版本怎么操作2. oracle数据库&#xff1a;参数个数无效![在这里插入图片描述](https://img-blog.csdnimg.cn/6a2eebc164f9406c81525371893bbd11.png)3. ORACLE数据库如何完整卸载? 三 mybatis1. mybatis用注解如何实现模…

学习Node js:raw-body模块源码解析

raw-body是什么 raw-body的主要功能是处理HTTP请求体的原始数据。它提供了以下核心功能&#xff1a; 解析请求体&#xff1a;可以从HTTP请求中提取原始数据&#xff0c;包括文本和二进制数据。配置选项&#xff1a;通过配置项&#xff0c;可以设置请求体的大小限制、编码方式…

【LeetCode-简单题KMP】232. 用栈实现队列

文章目录 题目方法一&#xff1a;用输入栈和输出栈模拟队列 题目 方法一&#xff1a;用输入栈和输出栈模拟队列 只有输出栈为空的时候才能将输入栈的元素补充到输出栈&#xff0c;否则输出栈不为空&#xff0c;如果再从输入栈往输出栈填充元素&#xff0c;就会弄乱队列的先进先…

【SpringMVC】拦截器JSR303的使用

【SpringMVC】拦截器&JSR303的使用 1.1 什么是JSR3031.2 为什么使用JSR3031.3 常用注解1.4 Validated与Valid区别1.5 JSR快速入门1.5.2 配置校验规则# 1.5.3 入门案例二、拦截器2.1 什么是拦截器2.2 拦截器与过滤器2.3 应用场景2.4 拦截器快速入门2.5.拦截器链2.6登录案列权…

接口测试——接口协议抓包分析与mock_L1

目录&#xff1a; 接口测试价值与体系常见的接口协议接口测试用例设计postman基础使用postman实战练习 1.接口测试价值与体系 接口测试概念 接口&#xff1a;不同的系统之间相互连接的部分&#xff0c;是一个传递数据的通道接口测试&#xff1a;检查数据的交换、传递和控制…

设计模式之职责链模式

职责链模式:使多个对象都有机会处理请求&#xff0c;从而避免请求的发送者和接收者之间的耦合关系。将这个对象连成一条链&#xff0c;并沿着这条链传递该请求&#xff0c;直到有一个对象处理它为止。 这里发出这个请求的客户端并不知道这当中的哪一个对象最终处理这个请求&am…

TCP详解之三次握手和四次挥手

TCP详解之三次握手和四次挥手 1. TCP基本认识 1.1 什么是 TCP TCP是面向连接的、可靠的、基于字节流的传输层通信协议。 1.2 TCP协议段格式 我们先来看看TCP首部协议的格式 我们先来介绍一些与本文关联比较大的字段&#xff0c;其他字段不做详细阐述。 序列号&#xff1a…

2023面试知识点一

1、新生代和老年代的比例 默认的&#xff0c;新生代 ( Young ) 与老年代 ( Old ) 的比例的值为 1:2 ( 该值可以通过参数 –XX:NewRatio 来指定 )&#xff0c;即&#xff1a;新生代 ( Young ) 1/3 的堆空间大小。老年代 ( Old ) 2/3 的堆空间大小。其中&#xff0c;新生代 ( …

213. 打家劫舍 II

文章目录 Tag题目来源题目解读解题思路方法一&#xff1a;动态规划 写在最后 Tag 【动态规划】【数组】 题目来源 213. 打家劫舍 II 题目解读 你是一个专业的小偷&#xff0c;现在要偷一排屋子&#xff0c;但是你不能偷相邻的两间屋子&#xff08;这一排房子的首尾是相连的&…

什么是性能调优?方法有哪些?流程是怎样的?

一、性能调优的含义 性能调优通俗来讲就是对计算机硬件、操作系统和应用有相当深入的了解&#xff0c;调节三者之间的关系&#xff0c;实现整个系统&#xff08;包括硬件、操作系统、应用&#xff09;的性能最大化&#xff0c;并能不断的满足现有的业务需求。 在判定软件存在…

Hadoop-Hbase

1. Hbase安装 1.1 安装zookeeper、 hbase 解压至/opt/soft&#xff0c;并分别改名 配置环境变量并source生效 #ZK export ZOOKEEPER_HOME/opt/soft/zk345 export PATH$ZOOKEEPER_HOME/bin:$PATH #HBASE_HOME export HBASE_HOME/opt/soft/hbase235 export PATH$HBASE_HOME/b…

浅显易懂理解傅里叶变换

说起电子硬件专业&#xff0c;那不得不提的就是傅里叶变换了。 大学课程中应该吓倒了很多人&#xff0c;谈傅里叶色变了。 本次就来重新认识一下电子硬件中的傅里叶变化。 首先理解之前&#xff0c;当然是需要先知道傅里叶这位大牛的人物百科啦。 傅里叶是法国数学家&#xff0…

【集成学习】对已训练好的模型进行投票

在不同的数据预处理情况下训练得到了三个SVM模型&#xff0c;结果都差不多&#xff0c;对这三个模型的分类结果进行投票 1、三个模型的model_path # 最终model的path self.model_path log_path/model_name_model.gz self.time_log log_path/model_name_time_log.csv# 模型1…

无涯教程-JavaScript - EXP函数

描述 EXP函数返回e升至数字的幂。常数e等于自然对数的底数2.71828182845904。 语法 EXP (number)争论 Argument描述Required/OptionalNumberThe exponent applied to the base e.Required Notes 要计算其他碱基的幂,请使用幂运算符(^) EXP是LN的倒数,LN是数字的自然对数…

免费:CAD批量转PDF工具,附下载地址

分享一款CAD 批量转PDF、打印的工具插件。能自动识别图框大小、自动识别比例、自动编号命名。重点&#xff01;重点&#xff01;重点&#xff01;自动将CAD的多张图纸一次性地、批量地转为PDF&#xff0c;或者打印。效果看下图&#xff1a; 适用环境&#xff1a; 32位系统 Auto…

5-2 Pytorch中的模型层layers

深度学习模型一般由各种模型层组合而成。 torch.nn中内置了非常丰富的各种模型层。它们都属于nn.Module的子类&#xff0c;具备参数管理功能。 例如&#xff1a; nn.Linear, nn.Flatten, nn.Dropout, nn.BatchNorm2d, nn.Embedding nn.Conv2d,nn.AvgPool2d,nn.Conv1d,nn.ConvTr…

layui框架学习(45: 工具集模块)

layui的工具集模块util支持固定条、倒计时等组件&#xff0c;同时提供辅助函数处理时间数据、字符转义、批量事件处理等操作。   util模块中的fixbar函数支持设置固定条&#xff08;2.7版本的帮助文档中叫固定块&#xff09;&#xff0c;是指固定在页面一侧的工具条元素&…

小程序引入vant-Weapp保姆级教程及安装过程的问题解决

小知识&#xff0c;大挑战&#xff01;本文正在参与“程序员必备小知识”创作活动。 本文同时参与 「掘力星计划」&#xff0c;赢取创作大礼包&#xff0c;挑战创作激励金 当你想在小程序里引入vant时&#xff0c;第一步&#xff1a;打开官方文档&#xff0c;第二步&#xff…

RK3399平台开发系列讲解(入门篇)VIM的基础命令

🚀返回专栏总目录 文章目录 一、Vim 命令速查二、其他命令三、Vim模式沉淀、分享、成长,让自己和他人都能有所收获!😄 📢 本篇将介绍Vim相关命令。 一、Vim 命令速查 简单说明一下,这张图上展示了一个键盘。图中的“•”表示,单个字母不是完整的命令,必须再有进一步…

elasticsearch5-RestAPI操作

个人名片&#xff1a; 博主&#xff1a;酒徒ᝰ. 个人简介&#xff1a;沉醉在酒中&#xff0c;借着一股酒劲&#xff0c;去拼搏一个未来。 本篇励志&#xff1a;三人行&#xff0c;必有我师焉。 本项目基于B站黑马程序员Java《SpringCloud微服务技术栈》&#xff0c;SpringCloud…