C++智能指针详解

目录

一.   智能指针初识

1.1   什么是智能指针

1.2   智能指针历史历程

 1.3   为什么需要智能指针

1.3.1   内存泄漏

1.3.2   防止内存泄漏

1.3.3   异常的重新捕获

二.   智能指针的原理与使用

2.1   智能指针的原理

2.2   智能指针的使用

2.3   智能指针的拷贝问题

三.   智能指针的众多版本

3.1   auto_ptr

3.2   unique_ptr

3.3   shared_ptr

3.3.1   基础实现 

3.3.2   shared_ptr的循环引用

四.   定制删除器

4.1   定制删除器的使用

 4.2   定制删除器的模拟实现

4.2.1   按照库的实现 

 4.2.2   不按照库的实现


 一.   智能指针初识

1.1   什么是智能指针

智能指针不是指针,是一个管理指针的类,用来存储指向动态分配对象的指针,负责自动释放动态分配的对象,防止堆内存泄漏和空悬指针等等问题。

动态分配的资源,交给一个类对象去管理,当类对象声明周期结束时,自动调用析构函数释放资源。

1.2   智能指针历史历程

  • C++ 98 中产生了第一个智能指针auto_ptr。
  • C++boost给出了更加实用的scoped_ptr(防止拷贝)shared_ptr(引进引用计数)weak_ptr
  • C++ 11 引入了unquie_ptr shared_ptrweak_ptr .需要注意的是,unique_ptr对应的是boost中的scoped_ptr。并且这些智能指针的实现是参照boost中的实现的。

 

 1.3   为什么需要智能指针

1.3.1   内存泄漏

我们在讲为什么之前先来了解一下什么是内存泄漏。

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。

内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对 该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现 内存泄漏会导致响应越来越慢,最终卡死。

1.3.2   防止内存泄漏

我们来看看这一个代码:

void fxx()
{int* p1 = new int[10];int* p2 = new int[20];int* p3 = new int[30];//...delete[] p1;delete[] p2;delete[] p3;
}

如果指针p2或者p3开辟空间new错误,这里就会导致后面的delete不会被执行,这就导致了指针p1的内存泄漏。这里我们可以用异常来解决,但是很难看:

void fxx()
{int* p1 = new int[10];int* p2, *p3;try{p2 = new int[20];try {p3 = new int[30];}catch (...){delete[] p1;delete[] p2;throw;}}catch (...){delete[] p1;throw;}//...delete[] p1;delete[] p2;delete[] p3;
}

1.3.3   异常的重新捕获

我们之前一个博客(http://t.csdnimg.cn/6jA1U)在异常的描述中说了,在异常的重新抛出与捕获中,可以用智能指针解决。我们来看看:

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Division by zero condition!";}return (double)a / (double)b;
}void fyy() noexcept
{int len, time;cin >> len >> time;cout << Division(len, time) << endl;
}void func()
{//这里可以看到如果发生除0错误抛出异常,下面的array数组就没有得到释放//所以这里捕获异常但是不处理异常,异常还是交给外面处理,这里捕获了再抛出去//就能delete array了int* array = new int[10];try{fyy();}catch (...){//捕获异常不是为了处理异常//是为了释放内存,然后异常再重新抛出cout << "delete[]" << array << endl;delete[] array;throw;//捕到什么抛什么}cout << "delete[]" << array << endl;delete[] array;
}

但是当有很多个变量要new和delete呢?就跟上面一样,会导致代码的繁琐嵌套,所以我们要用智能指针来解决。

二.   智能指针的原理与使用

2.1   智能指针的原理

智能指针的基本原理是利用RAII

RAII:RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在 对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做 法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。
template<class T>
class Smartptr
{
public://RAIISmartptr(T* ptr):_ptr(ptr){}~Smartptr(){cout << "delete:" << _ptr << endl;delete _ptr;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};int main()
{Smartptr<int> sp1(new int(1));Smartptr<int> sp2(new int(2));*sp1 += 10;Smartptr<pair<string, int>> sp3(new pair<string, int>);sp3->first = "apple";sp3->second = 1;return 0;
}

2.2   智能指针的使用

template<class T>
class Smartptr
{
public://RAIISmartptr(T* ptr):_ptr(ptr){}~Smartptr(){cout << "delete:" << _ptr << endl;delete _ptr;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};int div()
{int a, b;cin >> a >> b;if (b == 0){throw invalid_argument("除0错误");}return a / b;
}
void func()
{Smartptr<int> sp1(new int(1));Smartptr<int> sp2(new int(2));*sp1 += 10;cout << div() << endl;
}
int main()
{try{func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

通过SmartPtr对象,无论程序是正常执行结束,还是因为某些中途原因进行返回,或者抛出异常等开始所面临的困境,只要SmartPtr对象的生命周期结束就会自动调用对应的析构函数,不会造成内存泄漏,完成资源释放。

2.3   智能指针的拷贝问题

如果我们用一个智能指针拷贝构造一个智能指针,或者用一个智能指针赋值给另一个智能指针。这样的操作都会导致程序崩溃。

void test()
{SmartPtr<int> sp1(new int);SmartPtr<int> sp2(sp1);//拷贝构造SmartPtr<int> sp3(new int);SmartPtr<int> sp4 = sp3;//赋值
}

因为对于我们的智能指针来说,将sp1拷贝给sp2操作是浅拷贝,是将两个指针的指向统一到一块空间。当sp1和sp2释放时,会导致这块空间释放两次。同样的道理,将sp3赋值给sp4的时候,也只是单纯的将指针的指向指到同一块空间,这样在析构的时候也会导致析构两次。

所以对于如何解决这个问题,智能指针分为了很多版本。

三.   智能指针的众多版本

C++中存在4种智能指针:auto_ptr,unquie_ptr,shared_ptr,weak_ptr,他们各有优缺点,以及对应的实用场景。

3.1   auto_ptr

auto_ptr :管理权转移,被拷贝对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空。

auto_ptr是C++98的,通过管理权转移的方式解决智能指针拷贝问题,保证了一个资源只有一个对象对其进行管理,这时候一个资源就不会被多个释放:

int main()
{yjy::auto_ptr<int> ap1(new int(1));yjy::auto_ptr<int> ap2(ap1);*ap2 += 10;//ap1悬空//*ap1 += 10;return 0;
}

 auto_ptr的模拟实现为:

namespace yjy
{template<class T>class auto_ptr{public://RAIIauto_ptr(T* ptr):_ptr(ptr){}//ap2(ap1)auto_ptr(auto_ptr<T>& ap){_ptr = ap._ptr;ap._ptr = nullptr;}auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}~auto_ptr(){cout << "delete:" << _ptr << endl;delete _ptr;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

构造对象获取资源,析构对象释放资源。对*和->运算符进行重载,使其像指针一样。拷贝构造函数,用传入的对象的资源来构造当前对象,并将传入对象管理资源指针悬空。

3.2   unique_ptr

需要引用memory库来使用。

unique_ptr是C++11中的智能指针,unique_ptr来的更直接:直接防止拷贝的方式解决智能指针的拷贝问题,简单而又粗暴,防止智能指针对象拷贝,保证资源不会被多次释放,但是防止拷贝也不是解决问题的好办法,因为在很多场景下是需要拷贝的。

int main()
{yjy::unique_ptr<int> sp1(new int(1));yjy::unique_ptr<int> sp2(new int(10));sp1 = sp2;return 0;
}

模拟实现如下:

namespace yjy
{template<class T>class unique_ptr{public://RAIIunique_ptr(T* ptr):_ptr(ptr){}//up2(up1)unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;~unique_ptr(){cout << "delete:" << _ptr << endl;delete _ptr;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};
}

 为了禁止拷贝,所以我们C++98的方式是将拷贝构造函数和拷贝赋值函数声明为私有;C++11的方式就直接在这两个函数后面加上=delete

3.3   shared_ptr

3.3.1   基础实现 

shared_ptr是C++11的智能指针,通过引用计数的方式解决智能指针的拷贝问题。

  • shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共 享。
  • 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减 一。如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
  • 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对 象就成野指针了。

 引用计数的方式能够支持多个对象一起管理一个资源,也就支持智能指针的拷贝,只有当资源的引用计数减为0时才会释放,保证了同一个资源不会被多次释放:

int main()
{yjy::shared_ptr<int> sp1(new int(1));cout << sp1.use_count() << endl;yjy::shared_ptr<int> sp2(sp1);cout << sp2.use_count() << endl;*sp2 += 10;*sp1 += 10;yjy::shared_ptr<int> sp3(new int(2));yjy::shared_ptr<int> sp4(sp2);cout << sp3.use_count() << endl;return 0;
}

模拟实现如下:

namespace yjy
{template<class T>class shared_ptr{public:// RAII// 保存资源shared_ptr(T* ptr):_ptr(ptr), _pcount(new int(1)){}// 释放资源~shared_ptr(){Release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){++(*_pcount);}void Release(){if (--(*_pcount) == 0){delete _pcount;delete _ptr;}}//sp1 = sp1;//sp1 = sp2;//sp2如果是sp1的拷贝呢?shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr)//资源地址不一样{Release();_pcount = sp._pcount;_ptr = sp._ptr;++(*_pcount);}return *this;}int use_count(){return *_pcount;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}private:T* _ptr;int* _pcount;};
}
  • 构造函数获取资源时,同时将对应于的引用计数设为1,表示当前一个对象在管理这块资源。
  • 析构函数,将管理资源对应的引用计数--,如果为0就需要进行释放。
  • 拷贝构造函数中,与传入对象一起管理资源,将该资源的引用计数++。
  • 对于拷贝赋值:先将当前对象管理的资源对应的引用计数–,为0时需要释放,然后在传入对象一起管理资源。将该资源对应的引用计数++。

为什么引用计数要用指针?

  • 首先引用计数可不能用int整型来表示,这样的话,每个对象都有一个单独的引用计数。而我们要求当多个对象管理一个资源的时候,应该是引用的同一个引用计数。
  • 其次引用计数也不能设置为静态的,这样的话,结果是同一个类创建的对象都是同一个引用计数,即管理不同资源的对象引用了同一个引用计数。而我们要求的是一个资源对应一个引用计数。

3.3.2   shared_ptr的循环引用

我们来讲一下shared_ptr的美中不足的地方:循环引用

struct ListNode
{int _val;yjy::shared_ptr<ListNode> _next;yjy::shared_ptr<ListNode> _prev;ListNode(int val = 0):_val(val){}~ListNode(){cout << "ListNode" << endl;}
};int main()
{yjy::shared_ptr<ListNode> n1(new ListNode(10));yjy::shared_ptr<ListNode> n2(new ListNode(20));cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_next = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;return 0;
}

我们可以看到定义了两个对象,对象里面的prev和next对应指向另一个对象,这时候我们的shared_ptr就会存在缺陷。

在我们出作用域销毁的时候,会发生下面的情况:

n2对象销毁时-》_prev指针释放-》n1对象销毁-》_next指针释放-》n2对象销毁

可以看看运行情况:

 

可以看见销毁时是出错了的。

可以看到这个销毁的过程是一个互相影响的过程,是一个死循环。这样的结构就是我们的循环引用。该怎么办呢?

这里就要用到我们的weak_ptr:

//不支持RAII,不参与资源管理
template<class T>
class weak_ptr
{
public://RAIIweak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& wp){_ptr = wp.get();}weak_ptr<T>& operator=(const shared_ptr<T>& wp){_ptr = wp.get();return *this;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};

 这里的weak_ptr就不涉及RAII,不参与资源管理,从根源上杜绝了这个问题

struct ListNode
{int _val;yjy::weak_ptr<ListNode> _next;yjy::weak_ptr<ListNode> _prev;ListNode(int val = 0):_val(val){}~ListNode(){cout << "ListNode" << endl;}
};int main()
{yjy::shared_ptr<ListNode> n1(new ListNode(10));yjy::shared_ptr<ListNode> n2(new ListNode(20));cout << n1.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_next = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;return 0;
}

 

这样就好了。

四.   定制删除器

4.1   定制删除器的使用

智能指针该如何辨别我们的资源是用new int开辟的还是new int[]开辟的呢,要知道[]必须与delete[]匹配否则会有未知错误的,这个问题我们就交给定制删除器来解决:

这个del参数就是定制删除器,是一个可调用对象,比如函数指针、仿函数、lambda表达式以及被包装器包装后的可调用对象。

当shared_ptr对象的生命周期结束时就会调用传入的删除器完成资源的释放,调用该删除器时会将shared_ptr管理的资源作为参数进行传入。

所以当资源不是以new的形式开辟的时候,就要传特定的定制删除器。

比如:

template<class T>
struct DeleteArray
{void operator()(T* ptr){delete[] ptr;}
};struct ListNode
{int _val;yjy::weak_ptr<ListNode> _next;yjy::weak_ptr<ListNode> _prev;ListNode(int val = 0):_val(val){}~ListNode(){cout << "ListNode" << endl;}
};int main()
{yjy::shared_ptr<ListNode, DeleteArray<ListNode>> n2(new ListNode[10]);return 0;
}

 4.2   定制删除器的模拟实现

4.2.1   按照库的实现 

这是按照库的实现方法来的。可以看到模板参数定制删除器是在构造函数的。 

template<class T>
class shared_ptr
{
public:template<class D>//为了跟库保持一致,我们在此处定义模板shared_ptr(T* ptr, D del)//定制删除器:_ptr(ptr), _pcount(new int(1)), _del(del){}//RAIIshared_ptr(T* ptr = nullptr):_ptr(ptr), _pcount(new int(1)){}//sp2(sp1)shared_ptr(const shared_ptr<int>& sp){_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}void release(){//说明最后一个管理对象析构了,可以释放资源了if (--(*_pcount) == 0){cout << "delete:" << _ptr << endl;//delete _ptr_del(_ptr);delete _pcount;}}//sp1=sp4//sp4=sp4//sp1=sp2shared_ptr<T>& operator=(const shared_ptr& sp){//if(this!=&sp)if (_ptr != sp._ptr){release();_ptr = sp._ptr;--(*_pcount);_pcount = sp._pcount;//拷贝时++计数++(*_pcount);}return *this;}~shared_ptr(){//析构时,--计数,计数减到0release();}int use_count(){return *_pcount;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}
private:T* _ptr;int* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; };
};int main()
{yjy::shared_ptr<ListNode> p1(new ListNode(10));yjy::shared_ptr<ListNode> p2(new ListNode[10], DeleteArray<ListNode>());yjy::shared_ptr<FILE> p3(fopen("Test.cpp", "r"), [](FILE* ptr) {fclose(ptr); });return 0;
}

nwe[]申请内存空间必须以delete[]方式进行释放,文件指针要fclost进行释放。

 4.2.2   不按照库的实现

下面是不按照库的实现方法:

template <class T>struct Delete{void operator()(T* ptr){delete ptr;}};template<class T,class D=Delete<T>>class shared_ptr{public:shared_ptr(T* ptr):_ptr(ptr),_pcount(new int(1)){}shared_ptr(const shared_ptr<T>& sp){_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}void release(){//说明最后一个管理对象析构了,可以释放资源了if (--(*_pcount) == 0){cout << "delete:" << _ptr << endl;//delete _ptr_del(_ptr);delete _pcount;}}//sp1=sp4//sp4=sp4//sp1=sp2shared_ptr<T>& operator=(const shared_ptr& sp){//if(this!=&sp)if(_ptr!=sp._ptr){release();_ptr = sp._ptr;--(*_pcount);_pcount = sp._pcount;//拷贝时++计数++(*_pcount);}return *this;}~shared_ptr(){//析构时,--计数,计数减到0release();}int use_count(){return *_pcount;}// 像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;int* _pcount;D _del;};

那我们main函数传参的时候就要变换一下:

int main()
{yjy::shared_ptr<ListNode, DeleteArray<ListNode>> n2(new ListNode[10]);return 0;
}

总结:

好了,到这里今天的知识就讲完了,大家有错误一点要在评论指出,我怕我一人搁这瞎bb,没人告诉我错误就寄了。

祝大家越来越好,不用关注我(疯狂暗示)

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

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

相关文章

docker网络和模式

Docker使用Linux桥接&#xff0c;在宿主机虚拟一个Docker容器网桥(docker0)&#xff0c;Docker启动一个容器时会根据Docker网桥的网段分配给容器一个IP地址&#xff0c;称为container-IP&#xff0c;同时Docker网桥是每个容器的默认网关。因为在同一宿主机内的容器都接入同一个…

mars3d开发过程中点击面图层飞行定位,设置俯仰角度后,layer.flyTo({没有生效的排查思路

mars3d开发过程中点击面图层飞行定位&#xff0c;设置俯仰角度后&#xff0c;layer.flyTo({没有生效的排查思路记录&#xff0c;给大家提供一下以后排查定位问题的方向 问题场景相关代码&#xff1a; 1.项目本身代码&#xff1a; 2.精简了关键性代码后&#xff0c;就可以去ge…

【博客经验分享】博客小白在CSDN是如何做到一周内涨粉1800的

&#x1f393;我&#xff08;异构算力老群群-CSDN博客&#xff09;是在今年3月份才开始写博客的&#xff0c;目的是做一个博士&#x1f393;期间的笔录&#xff1b;在CSDN这个技术分享与交流的平台&#x1f310;&#xff0c;我近期实现了一个令人振奋的成就——那就是一周内涨粉…

纯血鸿蒙APP实战开发——评论组件案例实现

介绍 评论组件在目前市面上的短视频app中是一种很常见的场景&#xff0c;本案例使用全局状态保留能力弹窗来实现评论组件。点击评论按钮弹出评论组件&#xff0c;点击空白处隐藏该组件&#xff0c;再次点击评论按钮则会恢复上一次浏览的组件状态。 效果图预览 使用说明 点击…

CUDA的基础知识

文章目录 数据精度CUDA概念线程&线程块&线程网络&计算核心GPU规格参数内存 GPU并行方式数据并行流水并行张量并行混合专家系统 数据精度 FP32 是单精度浮点数&#xff0c;用8bit 表示指数&#xff0c;23bit 表示小数&#xff1b;FP16 是半精度浮点数&#xff0c;用…

L1-041 寻找250

作者 陈越 单位 浙江大学 对方不想和你说话&#xff0c;并向你扔了一串数…… 而你必须从这一串数字中找到“250”这个高大上的感人数字。 输入格式&#xff1a; 输入在一行中给出不知道多少个绝对值不超过1000的整数&#xff0c;其中保证至少存在一个“250”。 输出格式&a…

【进程通信】利用管道创建进程池(结合代码)

文章目录 什么叫进程池进程池的优点 创建进程池代码实现&#xff1a; 什么叫进程池 我们知道&#xff0c;一个进程创建子进程通常是为了让这个子进程去为它完成某个任务。例如我们使用的指令&#xff0c;其实就是bash进程创建子进程让子进程去执行的。但是我们需要考虑这样一个…

【介绍下分布式系统】

&#x1f308;个人主页: 程序员不想敲代码啊 &#x1f3c6;CSDN优质创作者&#xff0c;CSDN实力新星&#xff0c;CSDN博客专家 &#x1f44d;点赞⭐评论⭐收藏 &#x1f91d;希望本文对您有所裨益&#xff0c;如有不足之处&#xff0c;欢迎在评论区提出指正&#xff0c;让我们共…

wegame启动游戏错误代码126,加载x3daudio1_7.dll失败的修复教程

在尝试通过WeGame平台启动某款游戏时&#xff0c;遇到了阻碍&#xff0c;系统反馈了一个特定的错误代码“错误代码126&#xff0c;加载x3daudio1_7.dll失败”。这个错误提示表示游戏无法加载x3daudio17.dll文件&#xff0c;导致游戏无法正常启动。经过一番研究和尝试&#xff0…

vue elementui el-table表格 点击单元格添加选中样式

注意&#xff1a; 1、点击某行单元格添加选中样式&#xff1b; 2、表格第一列数据单独添加样式&#xff0c;比如&#xff1a;加粗&#xff1b; 3、表格表头添加样式&#xff0c;比如&#xff1a;修改背景色&#xff1b; 先上代码&#xff08;效果图在文章末尾&#xff09;&…

python-pytorch 如何使用python库Netron查看模型结构(以pytorch官网模型为例)0.9.2

Netron查看模型结构 参照模型安装Netron写netron代码运行查看结果需要关注的地方 2024年4月27日14:32:30----0.9.2 参照模型 以pytorch官网的tutorial为观察对象&#xff0c;链接是https://pytorch.org/tutorials/intermediate/char_rnn_classification_tutorial.html 模型代…

Ansible自动化

Ansible自动化 自动化的需求&#xff1a; 1. 在什么样的场景下需要自动化&#xff1f; 批量化的工作&#xff1a; 装软件包、配置服务、升级、下发文件… 2. 为什么在自动化工具中选择ansible&#xff1f; 对比shell脚本&#xff1a; 相对于用shell的脚本来实现自动化&#x…

42.接雨水

接雨水是一个非常经典的题目了,我在二刷的时候,终于能独立做了,在记录一下灵神的横着计算的单调栈思想. 法一: 竖着计算 奇思妙想 让我们想想,接到的雨水到底是存储哪里了呢,其实他就是凹陷部分,而什么是凹陷呢,就是从左边看,从右边看都发现不了的地方. …

滑块验证码破解----Java使用opencv后端破解滑块验证

使用技术:Java SpringBootopenCV 在windows上首先需要下载opencv进行安装,先去官网:Releases - OpenCV 下载这个windows版本的安装包 下载后直接安装解压就行,然后需要,然后找到安装位置里的这个文件: 你下载的是什么版本的,这里的数字就是多少,比如我下载4.5.3版本那么这…

永磁同步电机SMO负载转矩观测matlab模型。

永磁同步电机SMO负载转矩观测matlab模型。 负载转矩的有效识别是提高伺服驱动系统抗负载扰动性能的关键之一。现在的传统结构的LTID滑模观测器存在频率抖动大&#xff0c;估计精度差的缺点&#xff0c;限制了其在高性能伺服系统中的应用。 本模型推导分析了传统LTID滑模观测器…

eclipse 如何创建python文件

一、准备 1.平台要求&#xff1a; 电脑除了要安装eclipse软件和Python语言包之外&#xff0c;还需要将Python集成到eclipse软件中&#xff0c;网上有很多的方法&#xff0c;这里就不细细介绍如何集成了。 在下面界面中可以看到自己已经安装了继承插件。具体方法见步骤2&…

AI新篇章:全面解读ChatGPT3.5与GPT4.0的革命性融合

MidTool&#xff08;kk.zlrxjh.top&#xff09;&#xff0c;一个集成了多种先进人工智能技术的助手&#xff0c;融合了ChatGPT3.5、GPT4.0、DALLE 3和Midjourney等多个智能服务&#xff0c;提供多功能体验。下面是对这些技术的简要概述&#xff1a; **ChatGPT3.5**&#xff1a;…

可视化智慧工厂

在科技迅猛发展的今天&#xff0c;制造业正迎来一场深刻的变革——智慧工厂的崛起。可视化智慧工厂作为其中的重要一环&#xff0c;以其直观、高效、智能的特点&#xff0c;正成为制造业转型升级的关键所在。 一、什么是可视化智慧工厂? 传统的制造业生产方式往往依赖于人工…

Typora配置PicGo图床,将图片文件上传到gitee厂库,获取图片链接显示在md文件中

Typora配置PicGo图床&#xff0c;将图片文件上传到gitee厂库&#xff0c;获取图片链接显示在md文件中 创建Gitee创库和配置私人令牌 名字、路径、描述自己随便添&#xff0c;但是必须开源&#xff0c;链接才能可以访问&#xff1a; 进入偏好设置 > 图像 > 选择PicGo-Cor…

找不到mfc140u.dll文件如何处理?这三种方法帮你快速修复mfc140u.dll

当你的电脑出现提示&#xff0c;显示找不到mfc140u.dll文件&#xff0c;从而无法继续执行代码&#xff0c;你需要知道如何应对这种情况。今天我们就来详细说明如何解决mfc140u.dll文件丢失的问题&#xff0c;并对该文件进行详细分析。这个文件是Microsoft Visual Studio的一个重…