【C++】详解智能指针

目录

  • 一、智能指针的作用
  • 二、内存泄露
    • 1、什么是内存泄露
    • 2、内存泄漏分类
    • 3、如何避免内存泄露
  • 三、智能指针的使用及原理
    • 1、RAII
    • 2、智能指针的原理
    • 3、std::auto_ptr
    • 4、std::unique_ptr
    • 5、std::shared_ptr
      • 1、std::shared_ptr原理
      • 2、std::shared_ptr的线程安全问题
      • 4、std::shared_ptr的循环引用
  • 四、C++11和boost中智能指针的关系

一、智能指针的作用

我们先来看看下面一串代码:

#include <iostream>
#include <vector>
using namespace std;
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{// 1、如果p1这里new 抛异常会如何?// 2、如果p2这里new 抛异常会如何?// 3、如果div调用这里又会抛异常会如何?int* p1 = new int;int* p2 = nullptr;try{p2 = new int;}catch (...){delete p1;throw;}try{cout << div() << endl;}catch (...){delete p1;delete p2;throw;}delete p1;delete p2;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

我们会发现,p1抛异常,无影响,但是如果p2抛异常,我们就要释放p1,div抛异常,我们就要释放p1和p2。
如果还有p3和p4…呢?
由于动态内存的使用是很容易出问题的,因为无法确保在正确时间内释放内存,有时也会忘记释放内存,所以就会造成内存泄露,而且有时在还有指针引用内存的情况下我们就释放了它,就会造成野指针的问题。
为了更安全和更容易的使用动态内存,就提供了智能指针来管理动态对象。

二、内存泄露

1、什么是内存泄露

内存泄露:内存泄露是因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄露并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。
内存泄露的危害:长期运行的程序出现内存泄露,影响很大,如操作系统、后台服务等等,出现内存泄露会导致响应越来越慢,最终卡死。

void MemoryLeaks()
{// 1.内存申请了忘记释放int* p1 = (int*)malloc(sizeof(int));int* p2 = new int;// 2.异常安全问题int* p3 = new int[10];Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.delete[] p3;
}

2、内存泄漏分类

  • 堆内存泄漏:堆内存指的是程序执行中依据须要分配通过 malloc / calloc / realloc / new 等从堆中分配的一块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生堆内存泄漏。
  • 系统资源泄露:指程序使用系统分配的资源,如,套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能效能减少,系统执行不稳定。

检测内存泄露的方法:使用第三方工具进行检测

3、如何避免内存泄露

  1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间要时刻记住匹配的去释放。当然这是理想的状态,但是如果碰上异常时,就算释放了,还是可能会出现问题,所以就需要智能指针来管理才有保证。
  2. 采用RAII思想或者智能指针来管理资源。
  3. 如果出问题了,使用内存泄露工具检测

内存泄露非常常见,解决方案分为两种:1、事前预防型,如,智能指针等。2、事后查错型,如,泄露检测工具。

三、智能指针的使用及原理

1、RAII

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

  • 不需要显式地释放资源
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。
// 使用RAII思想设计的SmartPtr类
#include <iostream>
using namespace std;template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if (_ptr)delete _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);SmartPtr<int> sp2(new int);cout << div() << endl;
}
int main()
{try {Func();}catch (const exception& e){cout << e.what() << endl;}return 0;
}

其中对象sp1和sp2就是把申请的空间交给智能指针去管理,局部对象出了作用域会调用析构函数。当然这还不能被称为智能指针,因为它还不具有指针的行为。

2、智能指针的原理

指针可以解引用,也可以通过->去访问所指空间中的内容,因此类中还需要将 * 、-> 重载下,才可让其像指针一样去使用。

#include <iostream>
using namespace std;template<class T>
class SmartPtr 
{
public:SmartPtr(T* ptr = nullptr): _ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}T& operator*() {return *_ptr;}T* operator->() { return _ptr; }
private:T* _ptr;
};
struct Date
{int _year;int _month;int _day;
};
int main()
{SmartPtr<int> sp1(new int);*sp1 = 10;cout << *sp1 << endl;SmartPtr<Date> sparray(new Date);// 需要注意的是这里应该是sparray.operator->()->_year = 2018;// 本来应该是sparray->->_year这里语法上为了可读性,省略了一个->sparray.operator->()->_year = 2024;sparray->_month = 1;sparray->_day = 1;cout << sparray->_year << "-" << sparray->_month << "-" << sparray->_day << endl;return 0;
}

总结一下智能指针的原理:

  1. RAII特性
  2. 重载operator*和opertaor->,具有像指针一样的行为

3、std::auto_ptr

C++98版本的库中就提供了auto_ptr的智能指针。下面演示的auto_ptr的使用及问题
auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份auto_ptr来了解它的原理

#include <iostream>
using namespace std;template<class T>
class my_auto_ptr
{
public:my_auto_ptr(T* ptr):_ptr(ptr){}my_auto_ptr(auto_ptr<T>& sp):_ptr(sp._ptr){// 管理权转移sp._ptr = nullptr;}my_auto_ptr<T>& operator=(auto_ptr<T>& ap){// 检测是否为自己给自己赋值if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;// 转移ap中资源到当前对象中_ptr = ap._ptr;ap._ptr = NULL;}return *this;}~my_auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};int main()
{my_auto_ptr<int> sp1(new int);my_auto_ptr<int> sp2(sp1); // 管理权转移// sp1悬空*sp2 = 10;cout << *sp2 << endl;cout << *sp1 << endl;// 管理权转移以后导致sp1悬空,不能访问return 0;
}

结论:auto_ptr是一个失败设计,在拷贝的时候会发生管理权转移问题,导致对象悬空,不能访问

4、std::unique_ptr

C++11中开始提供更靠谱的unique_ptr
unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理

#include <iostream>
using namespace std;template<class T>
class my_unique_ptr
{
public:my_unique_ptr(T* ptr):_ptr(ptr){}~my_unique_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}my_unique_ptr(const unique_ptr<T>&sp) = delete;my_unique_ptr<T>& operator=(const unique_ptr<T>&sp) = delete;
private:T* _ptr;
};int main()
{my_unique_ptr<int> sp1(new int);//my_unique_ptr<int> sp2(sp1);std::unique_ptr<int> sp3(new int);//std::unique_ptr<int> sp4(sp3);return 0;
}

在这里插入图片描述

C++11出来之前,boost早出了更好用的scoped_ptr/shared_ptr/weak_ptr,C++11将boost库中智能指针精华部分吸收了过来。
C++11中出了unique_ptr/shared_ptr/weak_ptr。

unique_ptr / scoped_ptr的原理就是简单粗暴的防拷贝,在不用拷贝的场景下,就可以使用unique_ptr

5、std::shared_ptr

1、std::shared_ptr原理

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr
shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享
  2. 对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减一
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对象就成野指针了
// 引用计数支持多个拷贝管理同一个资源,最后一个析构对象释放资源
#include <iostream>
#include <mutex>
using namespace std;template<class T>
class my_shared_ptr
{
public:my_shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex){}my_shared_ptr(const my_shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx){AddRef();}void Release(){_pmtx->lock();bool flag = false;if (--(*_pRefCount) == 0 && _ptr){cout << "delete:" << _ptr << endl;delete _ptr;delete _pRefCount;flag = true;}_pmtx->unlock();if (flag == true){delete _pmtx;}}void AddRef(){_pmtx->lock();++(*_pRefCount);_pmtx->unlock();}my_shared_ptr<T>& operator=(const my_shared_ptr<T>& sp){//if (this != &sp)if (_ptr != sp._ptr){Release(); _ptr = sp._ptr;_pRefCount = sp._pRefCount;_pmtx = sp._pmtx;AddRef();}return *this;}int use_count(){return *_pRefCount;}~my_shared_ptr(){Release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}
private:T* _ptr;int* _pRefCount;mutex* _pmtx;
};// 简化版本的weak_ptr实现
template<class T>
class weak_ptr
{
public:weak_ptr():_ptr(nullptr){}weak_ptr(const shared_ptr<T>& sp):_ptr(sp.get()){}weak_ptr<T>& operator=(const shared_ptr<T>& sp){_ptr = sp.get();return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;
};int main()
{my_shared_ptr<int> sp1(new int);my_shared_ptr<int> sp2(sp1);my_shared_ptr<int> sp3(sp1);my_shared_ptr<int> sp4(new int);my_shared_ptr<int> sp5(sp4);sp1 = sp1;sp1 = sp2;sp1 = sp4;sp2 = sp4;sp3 = sp4;*sp1 = 2;*sp2 = 3;return 0;
}

在这里插入图片描述

shared_ptr智能指针是线程安全的吗?
shared_ptr本身是线程安全的,因为引用计数的加减是加锁保护的。
shared_ptr管理的对象是否是线程安全?指向资源不是线程安全的

指向堆上资源的线程安全问题是访问的人处理的,智能指针不管,也管不了
引用计数的线程安全问题,是智能指针要处理的

2、std::shared_ptr的线程安全问题

通过下面的程序我们来测试shared_ptr的线程安全问题。需要注意的是shared_ptr的线程安全分
为两方面:

  1. 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的,引用计数原来是1,++了两次,可能还是2。这样引用计数就错乱了。会导致资源未释放或者程序崩溃的问题。所以只能指针中引用计数++、–是需要加锁的,也就是说引用计数的操作是线程安全的。
  2. 智能指针管理的对象存放在堆上,两个线程中同时去访问,会导致线程安全问题

演示引用计数线程安全问题,就把AddRefCount和SubRefCount中的锁去掉
演示可能不出现线程安全问题,因为线程安全问题是偶现性问题,main函数的n改大一些概率就变大了,就容易出现了。

#include <iostream>
#include <mutex>
#include <thread>
#include <memory>
using namespace std;template<class T>
class my_shared_ptr
{
public:my_shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex){}my_shared_ptr(const my_shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx){AddRef();}void Release(){_pmtx->lock();bool flag = false;if (--(*_pRefCount) == 0 && _ptr){cout << "delete:" << _ptr << endl;delete _ptr;delete _pRefCount;flag = true;}_pmtx->unlock();if (flag == true){delete _pmtx;}}void AddRef(){_pmtx->lock();++(*_pRefCount);_pmtx->unlock();}my_shared_ptr<T>& operator=(const my_shared_ptr<T>& sp){//if (this != &sp)if (_ptr != sp._ptr){Release(); _ptr = sp._ptr;_pRefCount = sp._pRefCount;_pmtx = sp._pmtx;AddRef();}return *this;}int use_count(){return *_pRefCount;}~my_shared_ptr(){Release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}
private:T* _ptr;int* _pRefCount;mutex* _pmtx;
};struct Date
{int _year = 0;int _month = 0;int _day = 0;
};
void SharePtrFunc(my_shared_ptr<Date>& sp, size_t n, mutex& mtx)
{cout << sp.get() << endl;for (size_t i = 0; i < n; ++i){// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。my_shared_ptr<Date> copy(sp);// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最终看到的结果,并一定是加了2n{unique_lock<mutex> lk(mtx);copy->_year++;copy->_month++;copy->_day++;}}
}
int main()
{my_shared_ptr<Date> p(new Date);cout << p.get() << endl;const size_t n = 1000000;mutex mtx;thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx));thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx));t1.join();t2.join();cout << p->_year << endl;cout << p->_month << endl;cout << p->_day << endl;cout << p.use_count() << endl;return 0;
}

有锁的结果:
在这里插入图片描述
无锁的结果:
在这里插入图片描述
无锁状态下,结果是随机的,是不安全的

可以验证库中的shared_ptr,会发现结论是一样的:

struct Date
{int _year = 0;int _month = 0;int _day = 0;
};
void SharePtrFunc(std::shared_ptr<Date>& sp, size_t n, mutex& mtx)
{cout << sp.get() << endl;for (size_t i = 0; i < n; ++i){// 这里智能指针拷贝会++计数,智能指针析构会--计数,这里是线程安全的。std::shared_ptr<Date> copy(sp);// 这里智能指针访问管理的资源,不是线程安全的。所以我们看看这些值两个线程++了2n次,但是最终看到的结果,并一定是加了2n{unique_lock<mutex> lk(mtx);copy->_year++;copy->_month++;copy->_day++;}}
}
int main()
{std::shared_ptr<Date> p(new Date);cout << p.get() << endl;const size_t n = 1000000;mutex mtx;thread t1(SharePtrFunc, std::ref(p), n, std::ref(mtx));thread t2(SharePtrFunc, std::ref(p), n, std::ref(mtx));t1.join();t2.join();cout << p->_year << endl;cout << p->_month << endl;cout << p->_day << endl;cout << p.use_count() << endl;return 0;
}

结果:
在这里插入图片描述

4、std::shared_ptr的循环引用

#include <iostream>
using namespace std;struct ListNode
{int _data; shared_ptr<ListNode> _prev;shared_ptr<ListNode> _next;~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);cout << node1.use_count() << endl;cout << node2.use_count() << endl;node1->_next = node2;node2->_prev = node1;cout << node1.use_count() << endl;cout << node2.use_count() << endl;return 0;
}

结果:
在这里插入图片描述

循环引用分析:

  1. node1和node2两个智能指针对象指向两个节点,引用计数变成1,我们不需要手动delete。
  2. node1的_next指向node2,node2的_prev指向node1,引用计数变成2。
  3. node1和node2析构,引用计数减到1,但是_next还指向下一个节点。但是_prev还指向上一个节点。
  4. 也就是说_next析构了,node2就释放了。
  5. 也就是说_prev析构了,node1就释放了。
  6. 但是_next属于node的成员,node1释放了,_next才会析构,而node1由_prev管理,_prev属于node2成员,所以这就叫循环引用,谁也不会释放。

在这里插入图片描述

解决方案:在引用计数的场景下,把节点中的_prev和_next改成weak_ptr就可以了
原理就是:node1->_next = node2;和node2->_prev = node1;时weak_ptr的_next和_prev不会增加node1和node2的引用计数。

#include <iostream>
using namespace std;struct ListNode 
{int _data;weak_ptr<ListNode> _prev;weak_ptr<ListNode> _next;~ListNode() { cout << "~ListNode()" << endl; }
};
int main()
{shared_ptr<ListNode> node1(new ListNode);shared_ptr<ListNode> node2(new ListNode);cout << node1.use_count() << endl;cout << node2.use_count() << endl;node1->_next = node2;node2->_prev = node1;cout << node1.use_count() << endl;cout << node2.use_count() << endl;return 0;
}

结果:
在这里插入图片描述
weak_ptr解决了shared_ptr的循环用于的问题

四、C++11和boost中智能指针的关系

  1. C++ 98 中产生了第一个智能指针auto_ptr。
  2. C++ boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr。
  3. C++ TR1,引入了shared_ptr等。不过注意的是TR1并不是标准版。
  4. C++ 11,引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。

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

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

相关文章

python爬虫学习第二天----类型转换

&#x1f388;&#x1f388;作者主页&#xff1a; 喔的嘛呀&#x1f388;&#x1f388; &#x1f388;&#x1f388;所属专栏&#xff1a;python爬虫学习&#x1f388;&#x1f388; ✨✨谢谢大家捧场&#xff0c;祝屏幕前的小伙伴们每天都有好运相伴左右&#xff0c;一定要天天…

电脑如何录视频?进阶教程来了!

随着科技的飞速发展&#xff0c;视频录制已成为我们日常生活和工作中不可或缺的一部分。无论是进行在线教育、制作教学视频&#xff0c;还是记录游戏过程、直播分享&#xff0c;录屏都扮演着至关重要的角色。可是您知道电脑如何录视频吗&#xff1f;本文将介绍两种电脑录视频的…

稀碎从零算法笔记Day23-LeetCode:二叉树的最大深度

题型&#xff1a;链表、二叉树的遍历 链接&#xff1a;104. 二叉树的最大深度 - 力扣&#xff08;LeetCode&#xff09; 来源&#xff1a;LeetCode 题目描述 给定一个二叉树 root &#xff0c;返回其最大深度。 二叉树的 最大深度 是指从根节点到最远叶子节点的最长路径上…

vue2从基础到高级学习笔记

在实际的工作中,我常使用vue的用法去实现效果,但是你要是问我为什么这样写,它的原理是啥就答不上来了。对vue的认知一直停留在表面,写这篇文章主要是为了理清并弄透彻vue的原理。 学习目标 1 学会一些基本用法的原理 2 弄懂vue核心设计原理 3 掌握vue高级api的用法 一 vue…

环境安装篇 之 安装kubevela

云原生学习路线导航页&#xff08;持续更新中&#xff09; 本文是 环境安装 系列文章&#xff0c;介绍 oam规范标准实施项目 kubevela 的安装详细步骤kubevela 官方安装文档&#xff1a;https://kubevela.io/zh/docs/installation/kubernetes/ 1.CentOS 安装kubevela 1.1.前提…

电脑数据安全新篇章:备份文件,守护您的珍贵数据

备份文件&#xff0c;无疑是电脑使用中不可或缺的重要一环。在数字化时代&#xff0c;我们的工作、学习和生活都离不开电脑&#xff0c;而电脑中的数据更是我们宝贵的财富。一旦数据丢失或损坏&#xff0c;可能会带来无法估量的损失。因此&#xff0c;备份文件的重要性不言而喻…

Vue.js+SpringBoot开发高校宿舍调配管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能需求2.1 学生端2.2 宿管2.3 老师端 三、系统展示四、核心代码4.1 查询单条个人习惯4.2 查询我的室友4.3 查询宿舍4.4 查询指定性别全部宿舍4.5 初次分配宿舍 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBootMySQL的…

力扣hot100:4. 寻找两个正序数组的中位数(归并排序/二分/双指针)

目录 一、暴力排序 二、归并排序 三、双指针 四、二分查找 如果本题不说明时间复杂度应该为O(log(mn)&#xff0c;那么本题应该是一个简单题&#xff0c;解决的方法有很多。这里先列举出普通方法&#xff0c;再来讨论二分。 一、暴力排序 不管啦&#xff0c;直接纯暴力&…

黑白照片怎么变彩色?3个实用上色方法分享

黑白照片怎么变彩色&#xff1f;这是一个让许多人感到好奇和期待的问题。随着科技的发展&#xff0c;现在已经有多种软件可以将黑白照片转变为彩色&#xff0c;让历史的回忆重新焕发出生机。这些软件利用先进的算法和图像处理技术&#xff0c;能够精准地还原出照片中的色彩&…

【前端】卡片渐变色阴影效果 旋转动画

【前端】卡片渐变色阴影效果 旋转动画 <!DOCTYPE html> <html lang"en"> <head> <meta charset"UTF-8"> <meta name"viewport" content"widthdevice-width, initial-scale1.0"> <title>Breathing…

【G3D笔记】AI生成式3D算法相关环境安装爬坑笔记

【G3D笔记】AI生成式3D算法相关环境安装爬坑笔记) 1、 RayMarching1.1 error C1189: #error: You need C++17 to compile PyTorch1.2 raymarching安装环境版本测试1.3 host_config.h(231): fatal error C1083: 无法打开包括文件: “crtdefs.h”2、Tiny-Cuda-nn2.1 HTTP/2 stre…

mysql体系结构及主要文件

目录 1.mysql体系结构 2.数据库与数据库实例 3.物理存储结构​编辑 4.mysql主要文件 4.1数据库配置文件 4.2错误日志 4.3表结构定义文件 4.4慢查询日志 4.4.1慢查询相关参数 4.4.2慢查询参数默认值 4.4.3my.cnf中设置慢查询参数 4.4.4slow_query_log参数 4.4.…

opengl日记10-opengl使用多个纹理示例

文章目录 环境代码CMakeLists.txt文件内容不变。fragmentShaderSource.fsvertexShaderSource.vsmain.cpp 总结 环境 系统&#xff1a;ubuntu20.04opengl版本&#xff1a;4.6glfw版本&#xff1a;3.3glad版本&#xff1a;4.6cmake版本&#xff1a;3.16.3gcc版本&#xff1a;10.…

设计模式之工厂方法模式解析

工厂方法模式 1&#xff09;问题 简单工厂模式 当需要引入新产品时&#xff0c;由于静态工厂方法通过所传入参数的不同来创建不同的产品&#xff0c;需要修改工厂类的源代码。 2&#xff09;概述 针对不同的产品提供不同的工厂&#xff0c;系统提供一个与产品等级结构对应…

我的保研材料全部损坏了!这个压缩包文件格式未知或数据已经被损坏不可预料的压缩文件末端

求助各位友友&#xff0c;我的保研材料全部没了&#xff01; 之前为了清理D盘&#xff0c;把之前保研期间准备的几个G的材料全部压缩放在了U盘&#xff0c;但是现在却损坏打不开了&#xff0c;之前为了省事也没有添加过“恢复记录”&#xff01;&#xff01;&#xff01; 先声…

阿赵UE学习笔记——20、角色蓝图和动画蓝图

阿赵UE学习笔记目录 大家好&#xff0c;我是阿赵。   继续学习虚幻引擎的使用。这次来看看角色控制动画相关的东西&#xff0c;主要用到了动画蓝图和角色蓝图。 一、动画蓝图 之前分析过&#xff0c;蓝图对于虚幻引擎来说&#xff0c;是存在于各个系统里面的&#xff0c;相当…

智慧公厕:卫生、便捷、安全的新时代厕所变革

在城市快速发展的背景下&#xff0c;公共厕所的建设和管理变得越来越重要。智慧公厕作为厕所变革的一项全新举措&#xff0c;通过建立公共厕所全面感知监测系统&#xff0c;以物联网、互联网、大数据、云计算、自动化控制技术为支撑&#xff0c;实现对公共厕所的智能化管理和运…

FPGA学习_时序约束以及VIVADO时序报告

文章目录 前言时序约束的目的一、时序约束种类1、约束主时钟2、约束衍生时钟3、约束虚拟时钟4、input delay5、output delay6、约束异步时钟组7、约束互斥时钟8、假路径约束9、多周期约束 二、VIVADO时序报告三、从时序的角度看为什么寄存器赋值慢一拍 前言 一边学习一边补充当…

容器中的大模型(三)| 利用大语言模型:容器化高效地部署 PDF 解析器实践...

作者&#xff1a;宋文欣&#xff0c;智领云科技联合创始人兼CTO 01 简介 大语言模型&#xff08;LLMs&#xff09;正逐渐成为人工智能领域的一颗璀璨明星&#xff0c;它们的强大之处在于能够理解和生成自然语言&#xff0c;为各种应用提供了无限可能。为了让这些模型更好地服务…

【Hadoop】Hadoop 编译源码

目录 为什么要源码编译Hadoop 编译源码1前期工作准备2jar 包安装2.1安装 Maven2.2安装 ant2.3安装 glibc-headers 和 g2.4安装 make 和 cmake2.5安装 protobuf2.6安装 openssl 库2.7安装 ncurses-devel 库 3编译源码3.1解压源码到 /opt/ 目录3.2 进入到 hadoop 源码主目录 /opt…