C++ 智能指针

C++ 智能指针

  • 为什么需要智能指针?
  • auto_ptr
  • unique_ptr
  • shared_ptr
  • weak_ptr
  • 智能指针的核心实现
    • unique_ptr的简单实现
    • Counter的简单实现
    • share_ptr的简单实现
    • weak_ptr简单实现
  • shared_ptr的线程安全性
  • 多线程无保护读写 shared_ptr 可能出现的问题
  • make_shared()
  • share_ptr/unique_ptr自定义删除器

为什么需要智能指针?

如果指针忘记调用delete,将会造成内存泄漏。当超出了智能指针类的作用域时,智能指针类会自动调用析构函数,析构函数会自动释放资源

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果

C++STL共提供了四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中C++11只支持后三个,C++98支持所有四个。

auto_ptr

auto_ptr采用独占式拥有模式,下面有一个例子:

auto_ptr<string> p1 (new string ("I reigned lonely as a cloud.")); 
auto_ptr<string> p2; 
p2 = p1; //auto_ptr不会报错.

当程序运行时访问p1将会报错,因为所有权从p1转让给了p2,此时p1不再拥有该字符串对象从而变成空指针。

故auto_ptr的缺点是:存在潜在的内存崩溃问题!

unique_ptr

unique_ptr用于替换auto_ptr,实现了独占式拥有概念,保证同一时间内只有一个智能指针可以指向该对象。为此,unique_ptr的拷贝构造和拷贝赋值均被声明为delete。因此无法实施拷贝和赋值操作,但可以移动构造和移动赋值。它对于避免资源泄露(例如“以new创建对象后因为发生异常而忘记调用delete”)特别有用,还是上面那个例子:

unique_ptr<string> p3 (new string ("auto"));   //#4
unique_ptr<string> p4;                       //#5
p4 = p3;//此时会报错!!

编译器认为p4=p3非法,避免了p3不再指向有效数据的问题。尝试复制p3时会编译期出错,而auto_ptr能通过编译期从而在运行期埋下出错的隐患。因此,unique_ptr比auto_ptr更安全。

另外unique_ptr还有更聪明的地方:当程序试图将一个 unique_ptr 赋值给另一个时,如果源 unique_ptr 是个临时右值,编译器允许这么做;如果源 unique_ptr 将存在一段时间,编译器将禁止这么做,比如:

unique_ptr<string> pu1(new string ("hello world")); 
unique_ptr<string> pu2; 
pu2 = pu1;                                      // #1 不允许
unique_ptr<string> pu3; 
pu3 = unique_ptr<string>(new string ("You"));   // #2 允许

其中#1留下悬挂的unique_ptr(pu1),这可能导致危害。而#2不会留下悬挂的unique_ptr,因为它调用 unique_ptr 的构造函数,该构造函数创建的临时对象在其所有权让给 pu3 后就会被销毁。这种随情况而已的行为表明,unique_ptr 优于允许两种赋值的auto_ptr 。

如果确实想执行类似与#1的操作,C++有一个标准库函数std::move(),让你能够将一个unique_ptr赋给另一个。例如:

unique_ptr<string> ps1, ps2;
ps1 = demo("hello");
ps2 = move(ps1);
ps1 = demo("alexia");
cout << *ps2 << *ps1 << endl;

shared_ptr

shared_ptr实现共享式拥有概念。通过引用计数,多个智能指针可以指向相同对象,该对象和其相关资源会在最后一个引用被销毁时候释放

它使用计数机制来表明资源被几个指针共享。可以通过成员函数use_count()来查看资源的所有者个数。除了可以通过new来构造,还可以通过传入auto_ptr, unique_ptr,weak_ptr来构造。

当复制一个shared_ptr,引用计数会+1。当我们调用release()或者当一个shared_ptr离开作用域时,计数减1(普通的指针如果没有delete操作,离开作用域时并不会被释放,只有在进程结束后才会被释放)。当计数等于0时,则delete内存

成员函数:

  • use_count :返回引用计数的个数
  • unique :返回是否是独占所有权( use_count 为 1)
  • swap :交换两个 shared_ptr 对象(即交换所拥有的对象)
  • reset :放弃内部对象的所有权或拥有对象的变更, 会引起原有对象的引用计数的减少
  • get :返回内部对象(指针)

share_ptr的简单例子:

int main()
{string *s1 = new string("s1");shared_ptr<string> ps1(s1);shared_ptr<string> ps2;ps2 = ps1;cout << ps1.use_count()<<endl;	//2cout<<ps2.use_count()<<endl;	//2cout << ps1.unique()<<endl;	//0string *s3 = new string("s3");shared_ptr<string> ps3(s3);cout << (ps1.get()) << endl;	//033AEB48cout << ps3.get() << endl;	//033B2C50swap(ps1, ps3);	//交换所拥有的对象cout << (ps1.get())<<endl;	//033B2C50cout << ps3.get() << endl;	//033AEB48cout << ps1.use_count()<<endl;	//1cout << ps2.use_count() << endl;	//2ps2 = ps1;cout << ps1.use_count()<<endl;	//2cout << ps2.use_count() << endl;	//2ps1.reset();	//放弃ps1的拥有权,引用计数的减少cout << ps1.use_count()<<endl;	//0cout << ps2.use_count()<<endl;	//1
}

share_ptr的缺点为:当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏,此时需要使用weak_ptr。

weak_ptr

weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放;它是对对象的一种弱引用,不会增加对象的引用计数;它和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。

class B;	//声明
class A
{
public:shared_ptr<B> pb_;~A(){cout << "A delete\n";}
};class B
{
public:shared_ptr<A> pa_;~B(){cout << "B delete\n";}
};void fun()
{shared_ptr<B> pb(new B());shared_ptr<A> pa(new A());cout << pb.use_count() << endl;	//1cout << pa.use_count() << endl;	//1pb->pa_ = pa;pa->pb_ = pb;cout << pb.use_count() << endl;	//2cout << pa.use_count() << endl;	//2
}int main()
{fun();return 0;
}

可以看到fun函数中pa ,pb之间互相引用,两个资源的引用计数为2,当要跳出函数时,智能指针pa,pb析构时两个资源引用计数会减1,但是两者引用计数还是为1,导致跳出函数时资源没有被释放(A、B的析构函数没有被调用)运行结果没有输出析构函数的内容,造成内存泄露。如果把其中一个改为weak_ptr就可以了,我们把类A里面的shared_ptr pb_,改为weak_ptr pb_ ,运行结果如下:

1
1
1
2
B delete
A delete

这样的话,资源B的引用开始就只有1,当pb析构时,B的计数变为0,B得到释放,B释放的同时也会使A的计数减1,同时pa析构时使A的计数减1,那么A的计数为0,A得到释放。

注意:我们不能通过weak_ptr直接访问对象的方法,比如B对象中有一个方法print(),我们不能这样访问,pa->pb_->print(),因为pb_是一个weak_ptr,应该先把它转化为shared_ptr,如:

shared_ptr<B> p = pa->pb_.lock();
p->print();

weak_ptr 没有重载*和->运算符,但可以使用 lock 获得一个可用的 shared_ptr 对象. 注意, weak_ptr 在使用前需要检查合法性.

成员函数:

  • expired :用于检测所管理的对象是否已经释放, 如果已经释放, 返回 true; 否则返回 false.
  • lock :用于获取所管理的对象的强引用(shared_ptr). 如果 expired 为 true, 返回一个空的 - - - shared_ptr; 否则返回一个 shared_ptr, 其内部对象指向与 weak_ptr 相同.
  • use_count :返回与 shared_ptr 共享的对象的引用计数.
  • reset :将 weak_ptr 置空.
  • weak_ptr :支持拷贝或赋值, 但不会影响对应的 shared_ptr 内部对象的计数.

智能指针的核心实现

unique_ptr的简单实现

简单的实现了unique_ptr,包括如下成员函数:

  • 构造函数
  • 析构函数
  • 拷贝构造函数,禁用,不支持
  • 拷贝赋值函数,禁用,不支持
  • reset():释放源资源,指向新资源
  • release():返回资源,放弃对资源的管理
  • get():返回资源,只是供外部使用,依然管理资源
  • operator bool (): 是否持有资源
  • operator * ()
  • operator -> ()
template<typename T>
class UniquePtr
{
public:UniquePtr(T *pResource = NULL): m_pResource(pResource){}~UniquePtr(){del();}public:void reset(T* pResource) // 先释放资源(如果持有), 再持有资源{del();m_pResource = pResource;}T* release() // 返回资源,资源的释放由调用方处理{T* pTemp = m_pResource;m_pResource = nullptr;return pTemp;}T* get() // 获取资源,调用方应该只使用不释放,否则会两次delete资源{return m_pResource;}public:operator bool() const // 是否持有资源{return m_pResource != nullptr;}T& operator * (){return *m_pResource;}T* operator -> (){return m_pResource;}private:void del(){if (nullptr == m_pResource) return;delete m_pResource;m_pResource = nullptr;}private:UniquePtr(const UniquePtr &) = delete; // 禁用拷贝构造UniquePtr& operator = (const UniquePtr &) = delete; // 禁用拷贝赋值private:T *m_pResource;
};

Counter的简单实现

为了实现weak_ptr和share_ptr的引用计数,首先先实现一个Counter计数器类。

Counter对象的目地就是用来申请一个块内存来存引用基数,s是share_ptr的引用计数,w是weak_ptr的引用计数,当w为0时,删除Counter对象。

class Counter
{
public:Counter() : s(0), w(0){};int s;	//share_ptr的引用计数int w;	//weak_ptr的引用计数
};

share_ptr的简单实现

share_ptr的给出的函数接口为:构造,拷贝构造,赋值,解引用,通过release来在引用计数为0的时候删除_ptr和cnt的内存,各个函数中计数器的变化如下:

  1. 构造函数中计数初始化为1;
  2. 拷贝构造函数中计数值加1;
  3. 赋值运算符中,左边的对象引用计数减1,右边的对象引用计数加1;
  4. 析构函数中引用计数减1;
  5. 在赋值运算符和析构函数中,如果减1后为0,则调用delete释放对象。
template<class T>
class Weak_ptr;//先引用 template<class T>
class Share_ptr
{T * ptr;//管理的指针 Count * cnt;//计数 public:Share_ptr(T * p = 0):ptr(p)//构造函数 {ptr = p;cnt = new Count();}Share_ptr(Share_ptr<T> const& s)//拷贝构造函数, {ptr=s.ptr;//对象切换 cnt=s.cnt;//值切换 cnt->s++;//share计数增加 }Share_ptr(Weak_ptr<T> const& w){ptr = w.ptr;cnt = w.cnt;cnt->s++; //share计数增加 }~Share_ptr()//离开生命周期时调用 {release();//清空内存 }Share_ptr<T> &operator=(Share_ptr<T> const& s)//赋值运算 {if(this != &s)//判断是否是相等的指针如果不是 {release();//释放原有的 ptr=s.ptr;//修改对象指针 cnt=s.cnt;//修改计数器 cnt->s++;//计数器增加 }return *this;//返回 }T& operator*(){*ptr;//解引用 }T* operator->(){return ptr;//返回原指针 } friend class Weak_ptr<T>;//方便Weak指针操作本类 protected:void release()//释放操作 {cnt->s--;//share计数减1 if(cnt->s<1)//如果小于一了,说明没有指向原指针的share指针 {delete ptr;//删除ptr,调用ptr的析构函数 if(cnt->w<1)//如果weak引用小于1了再去删除cnt {delete cnt;cnt=NULL;}}}};

weak_ptr简单实现

weak_ptr的作为弱引用指针,其实现依赖于counter的计数器类和share_ptr的赋值。

weak_ptr一般通过share_ptr来构造,通过expired函数检查原始指针是否为空,lock来转化为share_ptr。

template<class T>
class Weak_ptr
{T * ptr;Count * cnt;public:Weak_ptr()//构造函数 {cnt=0;ptr=0;}Weak_ptr(Weak_ptr<T> &w):ptr(w.ptr),cnt(w.cnt)//同上 {cnt->w++;}Weak_ptr(Share_ptr<T> &s):ptr(s.ptr),cnt(s.cnt)//同上 {cnt->w++;}Weak_ptr<T>& operator=(Weak_ptr<T> &w)//同上 {if(this!=&w){release();cnt=w.cnt;ptr=w.ptr;cnt->w++;}return *this;}Weak_ptr<T>& operator=(Share_ptr<T> &s)//同上 {release();cnt=s.cnt;ptr=s.ptr;cnt->w++;return *this;}Share_ptr<T> lock()//强转 {return static_cast<Share_ptr<T>>(*this);}~Weak_ptr()//释放 {release();}friend class Share_ptr<T>;protected:void release(){cnt->w--;if(cnt->s<1&&cnt->w<1){//这里的cnt不用删除,因为在share中已经被删了 //delete cnt;cnt=NULL;	}	}	
};

shared_ptr的线程安全性

  • 引用计数增加是安全的,引用计数在堆上。
  • 不同线程同时操作同一个shared_ptr的引用是不安全的 。
  • 不同的shared_ptr指向同一块内存,操作同一个内存也是不安全的(即shared_ptr指向对象的读写不是线程安全的)。

shared_ptr 是引用计数型智能指针,几乎所有的实现都采用在堆上放个计数值的办法。具体来说,shared_ptr 包含两个成员,一个是指向 Foo 的指针 ptr,另一个是 ref_count 指针,指向堆上的 ref_count 对象。ref_count 对象有多个成员,具体的数据结构如图 1 所示,其中 deleter 和 allocator 是可选的。

在这里插入图片描述

图 1:shared_ptr 的数据结构。

为了简化并突出重点,后文只画出 use_count:

在这里插入图片描述

以上是 shared_ptr x(new Foo); 对应的内存数据结构。

如果再执行 shared_ptr y = x; 那么对应的数据结构如下:
在这里插入图片描述

但是 y=x 涉及两个成员的复制,这两步拷贝不会同时(原子)发生。

中间步骤 1,复制 ptr 指针:

在这里插入图片描述

中间步骤 2,复制 ref_count 指针,导致引用计数加 1:

在这里插入图片描述

步骤1和步骤2的先后顺序跟实现相关(因此步骤 2 里没有画出 y.ptr 的指向),我见过的都是先1后2。

既然 y=x 有两个步骤,如果没有 mutex 保护,那么在多线程里就有 race condition。

多线程无保护读写 shared_ptr 可能出现的问题

考虑一个简单的场景,有 3 个 shared_ptr 对象 x、g、n:

shared_ptr<Foo> g(new Foo); // 线程之间共享的 shared_ptr
shared_ptr<Foo> x; // 线程 A 的局部变量
shared_ptr<Foo> n(new Foo); // 线程 B 的局部变量

一开始,各安其事。

在这里插入图片描述

线程 A 执行 x = g; (即 read g),以下完成了步骤 1,还没来及执行步骤 2。这时切换到了 B 线程。

在这里插入图片描述

同时编程 B 执行 g = n; (即 write G),两个步骤一起完成了。

先是步骤 1:

在这里插入图片描述

再是步骤 2:

在这里插入图片描述

这时 Foo1 对象已经销毁,x.ptr 成了空悬指针!

最后回到线程 A,完成步骤 2:
在这里插入图片描述

多线程无保护地读写 g,造成了“x.ptr 是空悬指针”的后果。这正是多线程读写同一个 shared_ptr 必须加锁的原因。

make_shared()

智能指针shared_ptr有两种初始化的方式:

shared_ptr<int> sp1 (new int(10));            //通过new构造数据对象,调用了share_ptr的构造函数
shared_ptr<int> sp2 = make_shared<int>(10);  //通过make_shared构造数据对象

之前看过一些相关的文档,描述了这两种方式的不同,主要的区别是说通过new构造的时候:

  • 通过new构造,涉及到两次内存分配,第一次是通过new为数据对象分配内存,即上方的new int(10),第二次是构造一个shared_ptr的管理对象,管理对象记录了强引用(shared_ptr)计数,弱引用(weak_ptr)计数,以及数据对象(new int(10))的地址。当管理对象发现强引用计数为0时,释放数据对象的内存,当管理对象发现弱引用计数为0时,释放管理对象的内存
    在这里插入图片描述

  • 通过make_shared构造,只分配一次内存,这一块内存里既包括管理对象,也包括数据对象。由于是在一块内存里,所以即使强引用计数已被清零,但如果弱引用计数还没有清零,那么也无法释放这一块内存,直到弱引用计数清零时,这一块内存(包括管理对象和数据对象)才能被释放
    在这里插入图片描述

注意:构造函数是保护或私有时,无法使用 make_shared()

share_ptr销毁了,但还有weak_ptr指向那个对象,weak_ptr怎么知道这个对象已销毁?
share_ptr销毁的时候,只是把指向的对象销毁了,而计数器Counter还没被销毁,Counter里面记录了share_ptr的引用计数以及weak_ptr的引用计数,weak_ptr可以通过查询Counter里面的值来知道对象已被销毁。当weak_ptr也小于1时,Counter才会被销毁。

share_ptr/unique_ptr自定义删除器

默认情况下,智能指针使用 delete 释放其管理的资源,有时候,可能要修改默认使用 delete 释放资源的行为,除此之外,我们也可以自定义删除器。

Connection 是一个管理连接类,在释放 Connection 之前,我们需要调用 close 函数来关闭连接。观察如下代码:

#include <iostream>
#include <memory>
#include <string>using namespace std;class Connection{
public:explicit Connection(string name):_name(name){}string get_name() const {return _name;}
private:string _name;
};void close(Connection* connection){cout <<string ("关闭")+connection->get_name ()+"管理的连接中..." << endl;// 关闭连接的代码// .....cout << "关闭完成。" << endl;
}int main(){// 新建管理连接 Connection 的智能指针shared_ptr<Connection> sp(new Connection);unique_ptr<Connection> up(new Connection);
}

执行上述代码,发现并没有办法调用 close 函数,因为控制权完全在 shared_ptr/unique_ptr 中。你可能会说,在退出作用域之前我调用 close (sp.get ()) 先关闭连接,这样不就可以了嘛?实际上,这种做法对于 shared_ptr 并不安全,手动 close 之后,不能确保 sp 管理的 Connection 只有一份拷贝(即 sp 中的计数器多于 1)。因此,需要使用自定义的删除器。

删除函数定义类似于:

void Deleter(Connection *connection){close(connection);delete connection;
}

当删除器的指针 Deleter 传给 shared_ptr/unique_ptr 时,shared_ptr/unique_ptr 不会使用默认的 delete val 来释放其管理的资源,而是使用 Deleter (val) 来释放资源,这样就调用了 Deleter 来释放管理的资源。后面的各种方式的原理也是如此。

int main(){// 新建管理连接 Connection 的智能指针shared_ptr<Connection> sp(new Connection("shared_ptr"), Deleter);unique_ptr<Connection, decltype(Deleter)*> up(new Connection("unique_ptr"), Deleter);
}

shared_ptr 在使用的时候,只需要把函数式删除器的指针传给构造函数就行;而 unique_ptr 还用增加一个模板参数 decltype (Deleter)*,这是 shared_ptr 和 unique_ptr 的不同点之一。

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

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

相关文章

卸载本机已安装的node.js(v.16.13.0版本)

因为要用多版本的node&#xff0c;准备安装一个nvm管理&#xff0c;所以需要先卸载掉原来安装的v.16.13.0版本。 记录一下卸载过程 1、在系统设置-应用里卸载node 妈蛋这样卸载报错。。找了下根本没有这个路径 那就只能最简单的方法了&#xff0c;全部删掉 1、删除node的安装…

IDEA用Gradle构建项目时,lombok插件无效的解决办法

Lombok 可用来帮助开发人员消除 Java 的重复代码&#xff0c;尤其是对于简单的 Java 对象&#xff08;POJO&#xff09;&#xff0c;比如说getter/setter/toString等方法的编写。它通过注解实现这一目的。 正确使用姿势 一、安装Lombok插件 菜单栏File -> Settings ->…

通过MySQL删除Hive元数据信息

之前遇到过一个问题&#xff0c;在进行Hive的元数据采集时&#xff0c;因为Hive表的文件已经被删除了&#xff0c;当时是无法删除表&#xff0c;导致元数据采集也发生了问题&#xff0c;所以希望通过删除Hive表的元数据解决上述问题。 之前安装时&#xff0c;经过特定的配置后…

Qt实现自定义QDoubleSpinBox软键盘

在Qt应用程序开发中&#xff0c;经常会遇到需要自定义输入控件的需求。其中&#xff0c;对于QDoubleSpinBox控件&#xff0c;如果希望在点击时弹出一个自定义的软键盘&#xff0c;以便用户输入数值&#xff0c;并将输入的值设置给QDoubleSpinBox&#xff0c;该如何实现呢&#…

【MySQL】MySQL数据类型

文章目录 一、数据类型的分类二、tinyint类型2.1 创建有符号数值2.2 创建无符号数值 三、bit类型三、浮点类型3.1 float3.2 decimal类型 四、字符串类型4.1 char类型4.2 varchar类型 五、日期和时间类型六、枚举和集合类型6.1 enum的枚举值和set的位图结构6.2 查询集合find_in_…

大数据技术之Clickhouse---入门篇---SQL操作、副本

星光下的赶路人star的个人主页 积一勺以成江河&#xff0c;累微尘以崇峻极 文章目录 1、SQL操作1.1 Insert1.2 Update 和 Delete1.3 查询操作1.4 alter操作1.5 导出数据 2、副本2.1 副本写入流程2.2 配置步骤 1、SQL操作 基本上来说传统关系型数据库&#xff08;以 MySQL 为例…

Java 使用 Google Guava 实现接口限流

一、引入依赖 <dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>30.0-jre</version> </dependency>二、自定义注解及限流拦截器 自定义注解&#xff1a;Limiter package com.haita…

linux 常用命令

防火墙 1.查看下防火墙的状态&#xff1a;systemctl status firewalld systemctl stop firewalld 关闭 systemctl disable firewalld 开机不启永久关闭 2.查看已开放的端口firewall-cmd --zonepublic --list-ports firewall-cmd --permanent --zonepublic --…

【Android Framework系列】第9章 AMS之Hook实现登录页跳转

1 前言 前面章节我们学习了【Android Framework系列】第5章 AMS启动流程和【Android Framework系列】第6章 AMS原理之Launcher启动流程&#xff0c;大概了解了AMS的原理及启动流程&#xff0c;这一章节我们通过反射和动态代理对不同Android版本下的AMS进行Hook&#xff0c;实现…

SpringCloudAlibaba:服务网关之Gateway的cors跨域问题

目录 一&#xff1a;解决问题 二&#xff1a;什么是跨域 三&#xff1a;cors跨域是什么&#xff1f; 一&#xff1a;解决问题 遇到错误&#xff1a; 前端请求时报错 解决&#xff1a; 网关中添加配置文件&#xff0c;注意springboot版本&#xff0c;添加配置。 springboo…

【FPGA IP系列】FIFO的通俗理解

FPGA厂商提供了丰富的IP核&#xff0c;基础性IP核都是可以直接免费调用的&#xff0c;比如FIFO、RAM等等。 本文主要介绍FIFO的一些基础知识&#xff0c;帮助大家能够理解FIFO的基础概念。 一、FIFO介绍 FIFO全称是First In First Out&#xff0c;即先进先出。 FIFO是一个数…

Go学习第三天

map的三种声明定义方式 声明map后&#xff0c;一定要make开辟空间&#xff0c;否则会报越界且不能使用 package mainimport "fmt"func main() {// 第一种声明方式// 声明myMap1是一种map类型 key是string value是stringvar myMap1 map[string]string// 判断一下map在…

ad+硬件每日学习十个知识点(20)23.7.31 (芯片和天线间的巴伦电路)

文章目录 1.什么是前端电路&#xff1f;2.什么是巴伦电路&#xff1f;3.巴伦电路的性能参数4.LC巴伦电路5.ADS是干什么的&#xff1f;6.HFSS是干什么的&#xff1f;7.ANSYS有限元软件8.常用的电路仿真软件都有什么&#xff1f;9.巴伦电路的复端阻抗LC10.微带巴伦&#xff08;不…

数据可视化(七)常用图表的绘制

1. #seaborn绘制常用图表 #折线图 #replot&#xff08;x&#xff0c;y&#xff0c;kind&#xff0c;data&#xff09; #lineplot&#xff08;x&#xff0c;y&#xff0c;data&#xff09; #直方图 #displot&#xff08;data&#xff0c;rug&#xff09; #条形图 #barplot&…

【雕爷学编程】 MicroPython动手做(35)——体验小游戏

知识点&#xff1a;什么是掌控板&#xff1f; 掌控板是一块普及STEAM创客教育、人工智能教育、机器人编程教育的开源智能硬件。它集成ESP-32高性能双核芯片&#xff0c;支持WiFi和蓝牙双模通信&#xff0c;可作为物联网节点&#xff0c;实现物联网应用。同时掌控板上集成了OLED…

机器学习笔记之优化算法(九)收敛速度的简单认识

机器学习笔记之优化算法——收敛速度的简单认识 引言收敛速度的判别标准 Q \mathcal Q Q-收敛速度 R \mathcal R R-收敛速度关于算法复杂度与收敛速度 引言 本节对收敛速度简单介绍。 收敛速度的判别标准 我们之前几节介绍了线搜索方法 ( Line Search Method ) (\text{Line …

bash的特性(二)IO重定向与管道

bash的I/O重定向及管道 一、概述 在shell中&#xff0c;最常使用的fd(file descriptor)有三个&#xff0c;标准输入&#xff0c;标准输出&#xff0c;错误输出。进程用文件描述符来管理打开的文件。 名称 文件描述符 标准输入&#xff08;stdin) 0 键盘&#xff0c;也可以…

【BEV感知】3-BEV开源数据集

3-BEV开源数据集 1 KITTI1.1 KITTI数据怎么采集?1.2 KITTI数据规模有多大?1.3 KITTI标注了哪些目标?1.4 转换矩阵1.5 标签文件 2 nuScenes2.1 nuScenes Vs KITTI2.2 标注文件 1 KITTI KITTI 1.1 KITTI数据怎么采集? 通过车载相机、激光雷达等传感器采集。 只提供了相机正…

【BEV感知】1-BEV感知算法介绍

1-BEV感知算法介绍 1 什么是BEV感知算法&#xff1f;1.1 什么是BEV&#xff1f;1.2 什么是感知&#xff1f;1.3 什么是算法&#xff1f;1.4 什么是BEV感知&#xff1f; 1 什么是BEV感知算法&#xff1f; 1.1 什么是BEV&#xff1f; Bird’s-Eye-View&#xff0c;尺度变化小、…

优化供应链和库存管理:PDM系统的物料控制之道

在现代制造业中&#xff0c;优化供应链和库存管理是企业实现高效运营和降低成本的重要目标。PDM系统作为一款强大的数字化工具&#xff0c;扮演着物料控制之道的角色&#xff0c;帮助企业实现优化供应链和库存管理的目标。让我们一同深入探讨&#xff0c;看看PDM系统是如何通过…