【ONE·C++ || 智能指针 特殊类的设计】

总言

  主要介绍智能指针(auto_ptr、unique_ptr、shared_ptr、weak_ptr)和特殊类的设计(单例模式)。

文章目录

  • 总言
  • 1、为什么需要智能指针?(内存泄漏)
    • 1.1、什么是内存泄漏
    • 1.2、内存泄漏的分类和常见解决方式
  • 2、智能指针的使用及原理
    • 2.1、RAII:Resource Acquisition Is Initialization(资源获取即初始化)
      • 2.1.1、概念介绍
      • 2.1.2、使用演示
      • 2.1.3、问题说明
    • 2.2、std::auto_ptr
      • 2.2.1、基本说明与使用演示
      • 2.2.2、auto_ptr实现原理与缺陷
      • 2.2.3、模拟实现
    • 2.3、std::unique_ptr
      • 2.3.1、基本说明与使用演示
      • 2.3.1、模拟实现
    • 2.4、std::shared_ptr
      • 2.4.1、基本说明与使用演示
      • 2.4.2、模拟实现:引用计数
        • 2.4.2.1、version1.0(引用计数为静态的成员变量)
        • 2.4.2.2、version2.0(引用计数在堆区申请)
      • 2.4.3、问题说明(线程安全&&循环引用)
        • 2.4.3.1、version4.0(解决线程安全问题)
        • 2.4.3.2、循环引用
      • 2.4.4、std::weak_ptr
        • 2.4.4.1、简化版实现
      • 2.4.5、定制删除器
        • 2.4.5.1、问题引入
        • 2.4.5.2、删除器介绍
        • 2.4.5.3、使用演示:version3.0(附加删除器版本的shared_ptr)
  • 3、特殊类的设计
    • 3.1、设计一个不能被拷贝的类
    • 3.2、设计一个只能在堆上创建对象的类
      • 3.2.1、限制析构
      • 3.2.2、限制构造
    • 3.3、设计一个只能在栈上创建对象的类
      • 3.3.1、限制构造
    • 3.4、请设计一个类,不能被继承
    • 3.5、请设计一个类,只能创建一个对象(单例模式)
      • 3.5.1、基本介绍
      • 3.5.2、饿汉模式
      • 3.5.3、懒汉模式
        • 3.5.3.1、基本说明(version1.0)
        • 3.5.3.2、线程安全说明(version2.0)
        • 2.5.3.3、其它写法说明
      • 3.5.4、单例释放问题

  
  
  
  

1、为什么需要智能指针?(内存泄漏)

  主要是存在内存泄漏、异常安全等问题。
  

1.1、什么是内存泄漏

  1)、内存泄漏,丢的是指针,还是地址?

  什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费
  
  
  内存泄漏的危害:我们知道进程结束,会回收释放各种PCB等资源,如此看来内存泄漏也没什么,进程结束了也会被释放。但实际上,长期运行的程序出现内存泄漏影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死

在这里插入图片描述
  
  
  
  

1.2、内存泄漏的分类和常见解决方式

  1)、C/C++程序中一般有两种方面的内存泄漏

  ①堆内存泄漏(Heap leak):
  说明:堆内存指的是程序执行中依据须要分配通过malloc / calloc / realloc / new等从堆中分配的一块内存,用完后必须通过调用相应的 free或者delete 删掉。假设程序的设计错误导致这部分内存没有被释放,那么以后这部分空间将无法再被使用,就会产生Heap Leak

  
  
   ②系统资源泄漏
  说明:指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。
  (比如僵尸进程,就属于系统级别的内存泄漏。)
  
  
  2)、内存泄漏非常常见,解决方案分为两种
  1、事前预防型:如智能指针等。
  2、事后查错型:如使用泄漏检测工具。
  
  实际上,我们应该养成良好的编码规范,申请的内存空间记着匹配的去释放。
  
  
  
  
  

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

2.1、RAII:Resource Acquisition Is Initialization(资源获取即初始化)

2.1.1、概念介绍

  1)、定义介绍
  RAII(Resource Acquisition Is Initialization) :是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。
  功能: 在对象构造时获取资源,接着控制对资源的访问,使之在对象的生命周期内始终保持有效,最后在对象析构时候释放资源。借此,我们把管理一份资源的责任托管给了一个对象。这种做法有两大好处:
    ①不需要显式地释放资源。
    ②采用这种方式,对象所需的资源在其生命期内始终保持有效。
  
  

2.1.2、使用演示

  1)、基本构架
  RAII实则是一种设计思想。这里我们使用它设计一个类,用于资源的自动释放(delete/free)。
  相关演示如下:关于这个类,可以是模板,也可以是具体的类型,根据需求来定。

template<class T>
class SmartPtr//智能指针
{
public:SmartPtr(T* ptr = nullptr)//构造函数:_ptr(ptr){}~SmartPtr()//析构函数{cout << "delete: " << _ptr << endl;delete _ptr;//在析构时将ptr指向位置资源释放}private:T* _ptr;
};

  
  基于此,针对1中问题引入,于是便有了如下解决:
在这里插入图片描述
  用于演示的代码:

double Division(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw invalid_argument("除零错误!");}return (double)a / (double)b;
}void Func()
{SmartPtr<int>sp1(new int);SmartPtr<int>sp2(new int);int a, b;//a = 3; b = 0;a =3; b = 2;printf("Divesion: %d / %d = %lf\n", a, b, Division(a, b));}int main()
{try {Func();}catch(const exception & e){cout << e.what() << endl;}return 0;
}

  
  此外,无论是否出现异常,对于动态申请出来的空间,都得到了释放。
在这里插入图片描述
  
  
  
  2)、完善1.0
  说明:上述的SmartPtr还不能将其称为智能指针,因为它还不具有指针的行为。指针可以解引用*也可以通过->去访问所指空间中的内容,因此,我们需要继续完善该对象:

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

  
  以下为相关实现:

template<class T>
class SmartPtr//智能指针
{
public:SmartPtr(T* ptr = nullptr)//构造函数:_ptr(ptr){}~SmartPtr()//析构函数{cout << "delete: " << _ptr << endl;delete _ptr;//在析构时将ptr指向位置资源释放}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;
};

  
  
  

2.1.3、问题说明

  虽然上述RAII为我们提供了解决内存泄漏的一种思想设计,但上述的SmartPtr实则存在问题:拷贝。
  
  
  1)、此处浅拷贝存在什么问题?
  SmartPtr中,成员变量为指针类型,其是内置类型。根据之前所学可知,对内置类型会进行浅拷贝,那么调用析构时必然会引起两个指针指向的同一块空间被析构两次。
在这里插入图片描述

  
  
  2)、是否能用深拷贝?
  回答:不能,因为这里使用指针的需求就是让指针指向同一块空间共同管理。
在这里插入图片描述

  
  
  上述问题可以如何解决?
  实际C++库中有提供几类智能指针,这里我们可以学习了解一下它们的相关实现方案。
  
  
  
  

2.2、std::auto_ptr

2.2.1、基本说明与使用演示

  C++98版本的库中就提供了auto_ptr的智能指针,相关参考网站:auto_ptr。

  1)、演示auto_ptr
  验证:我们以一个自己实现的类来演示auto_ptr有做到资源释放。以下为我们自己写的类,主要用于观察库中实现的智能指针出了作用域自动调用析构会释放资源(RAII)。

class A
{
public:~A(){cout << "~A() : "<< this << endl;}//private:int _a = 0;//初始化列表int _b = 0;
};

  以下为验证代码:


void Func()
{auto_ptr<A>ap1(new A);auto_ptr<A>ap2(new A);(*ap1)._a = 2;//若成员变量为公有,这里可以演示智能指针内部实现了 *和->(*ap2)._b = 4;printf("ap1: %d, %d \n", ap1->_a, ap1->_b);printf("ap2: %d, %d \n", ap2->_a, ap2->_b);cout << "---------------------------------" << endl;auto_ptr<A>ap3(ap2);//验证拷贝ap3->_a = 333;printf("ap3: %d, %d \n", ap3->_a, ap3->_b);ap3 = ap1;//验证赋值ap3->_a = 444;printf("ap3: %d, %d \n", ap3->_a, ap3->_b);}int main()
{try {Func();}catch (...){cout << "another" << endl;}return 0;
}

  演示结果如下:
在这里插入图片描述

  
  
  

2.2.2、auto_ptr实现原理与缺陷

  基于2.2.1中的演示,我们调试观察:
在这里插入图片描述

  说明:auto_ptr的拷贝/赋值运算符重载,会将资源的管理权转移,如此一来会导致被拷贝的对象悬空。此时若错用该对象,进行解引用等操作行为,会带来解引用空指针等问题。(虽然不会释放同一块多次,但这种拷贝方式并不妥当。)

在这里插入图片描述

  
  
  
  
  

2.2.3、模拟实现

  虽然auto_ptr存在缺陷,但我们仍旧需要学习了解它的实现方式:

	template<class T>class auto_ptr{public:auto_ptr(T* ptr = nullptr):_ptr(ptr){}~auto_ptr(){cout << "delete: " << _ptr << endl;//用于检测delete _ptr;}auto_ptr(auto_ptr<T>& ap){if(_ptr)//若当前指针存在指向,需先释放当前对象指向资源{cout << "delete: " << _ptr << endl;//用于检测delete _ptr;}_ptr = ap._ptr;//浅拷贝ap._ptr = nullptr;//将原先对象指向置空(注意这里是delete)}auto_ptr<T>& operator = (auto_ptr<T>& ap){if (this != &ap)//检测是否自己给自己赋值{if (_ptr){cout << "delete: " << _ptr << endl;//用于检测delete _ptr;}_ptr = ap._ptr;//浅拷贝ap._ptr = nullptr;//将原先对象指向置空(注意这里是delete)}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};

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

  
  
  
  
  

2.3、std::unique_ptr

2.3.1、基本说明与使用演示

  C++11中提供unique_ptr,其用法和之前相同,区别在于 unique_ptr防拷贝,因此不能赋值、拷贝
  相关参考网站:unique_ptr
  演示代码如下:

class A
{
public:~A(){cout << "~A() : " << this << endl;}//private:int _a = 0;//初始化列表int _b = 0;
};void Func()
{//验证析构unique_ptr<A> up1(new A);unique_ptr<A> up2(new A);//验证operator*()、operator->()up1->_a = 2;(*up1)._b = 3;printf("up1: %d, %d \n", up1->_a, up1->_b);printf("up2: %d, %d \n", up2->_a, up2->_b);//验证是否能拷贝构造/赋值unique_ptr<A> up3 = up1;//errorunique_ptr<A> up4(up2);//error}int main()
{try {Func();}catch (...){cout << "another" << endl;}return 0;
}

  
  演示结果如下:

在这里插入图片描述

  
  
  
  
  

2.3.1、模拟实现

  说明:C++11提供了关键字delete,我们直接对拷贝构造函数和赋值运算符重载函数使用该关键字即可。若是C++98,我们可以只声明不实现,为了防止类外实现,需要将它们设置为私有成员。

	template<class T>class unique_ptr{public:unique_ptr(T* ptr = nullptr):_ptr(ptr){}~unique_ptr(){cout << "delete: " << _ptr << endl;//用于检测delete _ptr;}unique_ptr(unique_ptr<T>& up) = delete;//注意这里语句加;unique_ptr<T>& operator = (unique_ptr<T>& up) = delete;T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};

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

  
  
  
  
  

2.4、std::shared_ptr

2.4.1、基本说明与使用演示

  虽然unique_ptr相较于auto_ptr更为靠谱,但禁用并不能满足拷贝/赋值的需求,因此有了这里能支持拷贝的shared_ptr
  相关文档:shared_ptr。

  演示代码:

class A
{
public:~A(){cout << "~A() : " << this << endl;}//private:int _a = 0;//初始化列表int _b = 0;
};void Func()
{//验证析构shared_ptr<A> sp1(new A);shared_ptr<A> sp2(new A);//验证operator*()、operator->()sp1->_a = 2;(*sp1)._b = 3;printf("sp1: %d, %d \n", sp1->_a, sp1->_b);printf("sp2: %d, %d \n", sp2->_a, sp2->_b);//验证是否能拷贝构造/赋值shared_ptr<A> sp3 = sp1;sp3->_a++;sp3->_b++;shared_ptr<A> sp4(sp2);sp4->_a--;sp4->_b--;printf("sp1: %d, %d \n", sp1->_a, sp1->_b);printf("sp2: %d, %d \n", sp2->_a, sp2->_b);
}int main()
{try {Func();}catch (...){cout << "another" << endl;}return 0;
}

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

  
  说明:shared_ptr是通过引用计数的方式来实现多个shared_ptr对象之间共享资源的

在这里插入图片描述

  1、在shared_ptr对象内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共享。
  2、出了作用域调用析构函数,对象销毁,此时引用计数会减一(--)。
  3、若引用计数减到0,说明当前shared_ptr是最后一个使用该资源的对象,此时必须释放资源;若引用计数自减后未达到0,说明除了当前shared_ptr外还有其他shared_ptr在使用该份资源,此时不能释放资源,否则其他对象就成了野指针。
  
  
  
  
  

2.4.2、模拟实现:引用计数

2.4.2.1、version1.0(引用计数为静态的成员变量)

  1)、如何设计引用计数?
  问题一:考虑到引用计数要被多个对象共享,是否可以将其设置为静态的成员变量?

	template<class T>class shared_ptr{public://……private:T* _ptr;//指针static int count;//定义一个静态成员变量:用于表示引用计数};

  如下:在析构、赋值、拷贝时,对引用计数count进行++--,从而控制多个指针管理同一份资源。

	template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr){if(_ptr)++_count;}void release(){if (_ptr && (--_count) == 0){cout << "delete: " << _ptr << endl;delete _ptr;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& sp){release();_ptr = sp._ptr;_count = sp._count;_count++;}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr == sp._ptr)return *this;release();_ptr = sp._ptr;_count = sp._count;_count++;return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}static int getCount(){return _count;}private:T* _ptr;static int _count;};template<class T>int shared_ptr<T>::_count = 0;//静态成员:类中声明,类外定义

  
  
在这里插入图片描述

  回答:上述使用静态成员变量,看似没有问题,实则存在缺陷。要知道静态成员是被整个类共享的,假如有多个不同的资源空间,分别为其申请多个指针进行资源管理,那么静态的count就乱套了。

在这里插入图片描述

  
  
  
  

2.4.2.2、version2.0(引用计数在堆区申请)

  基于上述,引用计数可以使用一个指针来表示,_pcount(new int(1)),构造时在堆上申请。

	template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr)//构造:_ptr(ptr),_pcount(new int(1)){}//……private:T* _ptr;int* _pcount;};}

  
  以下为其它实现细节:

	template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr)//构造:_ptr(ptr),_pcount(new int(1)){}void release(){//当count减到0时才释放资源,其余情况减计数器if (--(*_pcount) == 0  && _ptr)//短路设计:不用分别判断两种情况,count都会自减一次{cout << "Delete:" << _ptr << endl;delete _ptr;delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount){(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>& sp){//存在原本就指向同一份资源的情况。//这里不使用this!= &sp的原因:除了sp1=sp1,还可以是sp1=sp2,虽然对象不同,但指向的资源相同.if (_ptr != sp._ptr){release();//赋值时,若当前指针原先有指向,要先进行处理。_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int getCount()//返回引用计数{return *_pcount;}T* get()const//返回指针{return _ptr;}private:T* _ptr;int* _pcount;};

  
  演示结果如下:

在这里插入图片描述

  相关验证代码:

class A
{
public:~A(){cout << "~A() : " << this << endl;}//private:int _a = 0;//初始化列表int _b = 0;
};void Func()
{//operator*()、operato->()mySAII::shared_ptr<A> sp1(new A);cout << "count: " << sp1.getCount() << endl;printf("sp1: %d, %d \n", sp1->_a, sp1->_b);验证是否能拷贝构造/赋值mySAII::shared_ptr<A> sp3 = sp1;cout << "count: " << sp1.shared_ptr<A>::getCount() << endl;sp3->_a++;sp3->_b++;printf("sp1: %d, %d \n", sp1->_a, sp1->_b);cout << "--------------------------" << endl;//另一个动态空间mySAII::shared_ptr<A> sp2(new A);cout << "count: " <<sp2.getCount() << endl;sp2->_a = 2; sp3->_b = 2;printf("sp2: %d, %d \n", sp2->_a, sp2->_b);mySAII::shared_ptr<A> sp4(sp2);cout << "count: " << sp4.getCount() << endl;printf("sp2: %d, %d \n", sp2->_a, sp2->_b);cout << "--------------------------" << endl;mySAII::shared_ptr<int> sp5(new int);cout << "count: " << sp5.getCount() << endl;
}

  
  
  
  
  

2.4.3、问题说明(线程安全&&循环引用)

2.4.3.1、version4.0(解决线程安全问题)

  version3.0见下述删除器版本(注意那里的实现方案和库中不同,主要在于了解并学习删除器问题。

  1)、上述shared_ptr面临的线程安全问题举例
  演示代码如下:

namespace mySAII_3
{template<class T>struct Delete{void operator()(T* ptr){cout << "缺省参数delete:" << ptr << endl;delete ptr;//默认情况下,new单个,若new多个,则传入对应的删除器}};template<class T, class D = Delete<T>>class shared_ptr{public:shared_ptr(T* ptr = nullptr)//构造:_ptr(ptr), _pcount(new int(1)){}void release(){//当count减到0时才释放资源,其余情况减计数器if (--(*_pcount) == 0 && _ptr)//短路设计:不用分别判断两种情况,count都会自减一次{D()(_ptr);//释放指针:使用一个匿名对象delete _pcount;//释放引用计数}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int getCount(){return *_pcount;}T* get()const{return _ptr;}private:T* _ptr;int* _pcount;};}int main()
{mySAII_3::shared_ptr<int> sp1 = new int(1);mySAII_3::shared_ptr<int> sp2(sp1);vector<thread> vt(2);//创建两个线程int n = 100;for (auto& t : vt){t = thread([&]() {for (size_t i = 0; i < n; ++i){mySAII_3::shared_ptr<int> sp(sp1);//在两个线程中:分别创建新的shared_ptr指针,指向sp1}});}for (auto& t : vt){t.join();//一一捕捉创建出来的线程。}//检验:看看count计数是否正确cout << "count: " << sp1.getCount() << endl;cout << *sp1 << endl;return 0;
}

  演示结果如下:
在这里插入图片描述

  说明: 智能指针对象中引用计数是多个智能指针对象共享的,两个线程中智能指针的引用计数同时++或–,这个操作不是原子的。最终会导致资源未释放或者程序崩溃的问题。所以需要对智能指针中引用计数++、–的操作进行加锁,以保障线程安全
  
  
  
  
  2)、version4.0(加锁版shared_ptr)
  相关实现如下:实际只用修改构造、析构对引用计数++、–的部分。(需要注意,这里我们新增了一个成员变量,并将其在构造时new出来,那么析构时不要忘记释放)。

namespace mySAII_3
{template<class T>struct Delete{void operator()(T* ptr){cout << "缺省参数delete:" << ptr << endl;delete ptr;//默认情况下,new单个,若new多个,则传入对应的删除器}};template<class T, class D = Delete<T>>class shared_ptr{public:shared_ptr(T* ptr = nullptr)//构造:_ptr(ptr), _pcount(new int(1)),_pmutex(new mutex){}void release(){bool isdelete = false;_pmutex->lock();//加锁//当count减到0时才释放资源,其余情况减计数器if (--(*_pcount) == 0 && _ptr)//短路设计:不用分别判断两种情况,count都会自减一次{D()(_ptr);//释放指针:使用一个匿名对象delete _pcount;//释放引用计数isdelete = true;}_pmutex->unlock();//解锁if (isdelete)//判断是否需要释放锁:为了保证引用计数的线程安全问题,同时也为了能够释放锁,引入flags做为判断delete _pmutex;}~shared_ptr(){release();}//拷贝构造:指向对象相同、引用计数相同、持有/竞争的锁相同shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount),_pmutex(sp._pmutex){_pmutex->lock();//加锁(*_pcount)++;//引用计数一次只能允许一个线程操作_pmutex->unlock();//解锁}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();//赋值:该智能指针可能原本就指向某一个对象,需要先做处理_ptr = sp._ptr;_pcount = sp._pcount;_pmutex = sp._pmutex;_pmutex->lock();//加锁(*_pcount)++;//引用计数一次只能允许一个线程操作_pmutex->unlock();//解锁}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int getCount(){return *_pcount;}T* get()const{return _ptr;}private:T* _ptr;//智能指针int* _pcount;//引用计数mutex* _pmutex;//锁};}

  
  
  
  3)、其它问题说明
  说明:加锁只是解决了智能指针内部赋值、拷贝时,引用计数存在的线程安全问题。但不能保障该智能指针指向的对象,在多线程中使用时存在的线程安全问题。
  例如:智能指针管理的对象存放在堆上,两个线程中同时去访问,仍旧存在线程安全,但这属于外部实现时,需要我们自己手动保障

在这里插入图片描述
  对于shared_ptr,即使是库中版本,它也只是解决自身引用计数的线程安全问题。
在这里插入图片描述
  
  
在这里插入图片描述
  
  
  
  
  

2.4.3.2、循环引用

  1)、场景引入
  说明:在shared_ptr中,当两个对象相互引用时,即使其他对象不再引用它们,它们的引用计数会一直保持非零状态,导致这些对象所占用的内存无法被释放,造成内存泄漏。

  以下为演示代码:

struct Node
{int _val;//当前节点变量std::shared_ptr<Node> _next;//指向前一个节点和后一个节点的指针std::shared_ptr<Node> _prev;~Node()//析构:这里不用做处理,因为_val为内置类型,而_next、_prev会去调用它们自己的析构函数{cout << "~Node" << endl;}
};void Func()
{std::shared_ptr<Node> n1(new Node);std::shared_ptr<Node> n2(new Node);cout << "n1:" << n1.use_count() << endl;cout << "n2:" << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << "n1:" << n1.use_count() << endl;cout << "n2:" << n2.use_count() << endl;
}

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

  
  问题分析:

在这里插入图片描述
  如上图, 虽然n1和n2析构使得引用计数减到1,但此时_next还指向右侧节点,_prev还指向左侧节点。
  也就是说,只有当左侧的_next析构了,使得引用计数减到0,右侧节点才会被delete(释放资源)。可是此时因为n1指针已近释放,能对左侧_next释放的,只有右侧还在指向它的_prev指针。但对于右侧的_prev指针来说,其面临的情况和_next相同。(由于二者彼此卡住,此时谁也不会释放)
  
  
  
  
  2)、解决方案
  为了解决这个问题,一种常用的方法是使用弱引用(weak reference),将在后续讲解。实际上循环引用的一个关键点在于,要识别到当前场景发生了循环引用

struct Node
{int _val;//当前节点变量std::weak_ptr<Node> _next;//指向前一个节点和后一个节点的指针std::weak_ptr<Node> _prev;//这里使用了weak_ptr~Node(){cout << "~Node" << endl;}
};void Func()
{std::shared_ptr<Node> n1(new Node);std::shared_ptr<Node> n2(new Node);cout << "n1:" << n1.use_count() << endl;cout << "n2:" << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << "n1:" << n1.use_count() << endl;cout << "n2:" << n2.use_count() << endl;
}

在这里插入图片描述

  
  
  
  

2.4.4、std::weak_ptr

  相关文档:weak_ptr。通过查阅可知,weak_ptr支持使用shared_ptr进行构造。

在这里插入图片描述
  
  
  

2.4.4.1、简化版实现

  核心原理:不增加引用计数,不参与资源释放管理(无需delete指针)。

	template<class T>class weak_ptr{public:weak_ptr()//无参构造:_ptr(nullptr){}weak_ptr(const weak_ptr<T>& wp)//拷贝构造:_ptr(wp._ptr){}weak_ptr(const shared_ptr<T>& sp)//shared_ptr进行构造:_ptr(sp.get()){}weak_ptr<T>& operator = (const weak_ptr<T>& wp){if (_ptr != wp._ptr){_ptr = wp.get();}return *this;}//赋值运算符重载中,不用考虑delete释放问题,因为weak_ptr不参与资源释放管理。//析构函数~weak_ptr()同理。不需要在内部 delete _ptr。weak_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp.get()){_ptr = sp.get();}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}private:T* _ptr;};

  
  使用演示如下:这里只是用于演示weak_ptr解决了循环引用。而实际实现中,其功能更为复杂。

在这里插入图片描述
  
  
  
  

2.4.5、定制删除器

2.4.5.1、问题引入

  1)、关于new和new[ ]类型不匹配(delete、delete[ ])
  说明:关于这里演示的自定义类型和内置类型new/delete不匹配时进程崩溃的区别,其中一个因素与平台有关。但不管是什么类型,建议new/delete匹配使用

在这里插入图片描述
  
  原因解释:实则是delete释放时访问位置不对。(与new的底层实现有关)
在这里插入图片描述
  
  
  
  
  在上述场景中,对于这类new[ ]一次多个的对象,和那些不是new出来的对象,如何通过智能指针进行管理?
  为此,我们引入了删除器。
  
  
  

2.4.5.2、删除器介绍

  shared_ptr中设计了一个删除器来解决这个问题。实际官方库中也提供了一个删除器::default_delete。

在这里插入图片描述
  
  在unique_ptr和shared_ptr的构造函数中,会提供带删除器参数的构造:(可自行查阅文档查看。)

在这里插入图片描述

  
  
  

2.4.5.3、使用演示:version3.0(附加删除器版本的shared_ptr)

  实际上我们可以自己写一个删除器,它可以是仿函数对象、也可以是lambda表达式。
  
  1)、代码演示一
  演示代码:

struct Node
{int _val;//当前节点变量std::weak_ptr<Node> _next;std::weak_ptr<Node> _prev;~Node(){cout << "~Node" << endl;}
};template<class T>
class DeleteArray
{
public:void operator()(T* ptr)//该删除器就是作用于一次申请多个资源的指针delete[]{cout << "delete[]" << ptr << endl;//用于检测观察delete[] ptr;}
};template<class T>
struct  FreeArray
{void operator()(T* ptr){cout << "free()" << ptr << endl;//用于检测观察free(ptr);}
};void Func()
{//std::shared_ptr<Node> n1(new Node[5]);std::shared_ptr<Node> n1(new Node[5],DeleteArray<Node>());//shared_ptr中,该删除器是作为构造函数的参数出现的,因此这里传递的是一个对象(我们使用了匿名对象)std::shared_ptr<Node> n2(new Node);//std::shared_ptr<int> n3(new int[5]);std::shared_ptr<int> n3(new int[5],DeleteArray<int>());//std::shared_ptr<int> n4((int*)malloc(sizeof(int) * 5));std::shared_ptr<int> n4((int*)malloc(sizeof(int) * 5), FreeArray<int>());}

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

  
  使用lambda表达式的情况如下:(注意这里是针对shared_ptr而已,对于unique_ptr,由于其删除器设置在模板参数中,因此只能传递类类型。)

void Func()
{//std::shared_ptr<Node> n1(new Node[5]);std::shared_ptr<Node> n1(new Node[5], [](Node* ptr) {delete[] ptr; });std::shared_ptr<Node> n2(new Node);//std::shared_ptr<int> n3(new int[5]);std::shared_ptr<int> n3(new int[5], [](int* ptr) {delete[] ptr; });//std::shared_ptr<int> n4((int*)malloc(sizeof(int) * 5));std::shared_ptr<int> n4((int*)malloc(sizeof(int) * 5), [](int* ptr) {free(ptr); });}

  
  
  
  
  2)、代码演示二
  这里我们也可以实现一个带删除器版本的shared_ptr,只不过此处借用了unique_ptr的模板参数方式。
  
  只改变了release()函数中delete指针的部分。

template<class T>struct Delete{void operator()(T* ptr){cout << "缺省参数delete:" << ptr << endl;delete ptr;//默认情况下,new单个,若new多个,则传入对应的删除器}};template<class T, class D = Delete<T>>class shared_ptr{public:shared_ptr(T* ptr = nullptr)//构造:_ptr(ptr), _pcount(new int(1)){}void release(){//当count减到0时才释放资源,其余情况减计数器if (--(*_pcount) == 0 && _ptr)//短路设计:不用分别判断两种情况,count都会自减一次{D()(_ptr);//使用一个匿名对象delete _pcount;}}

  整体情况

namespace mySAII_2
{template<class T>struct Delete{void operator()(T* ptr){cout << "缺省参数delete:" << ptr << endl;delete ptr;//默认情况下,new单个,若new多个,则传入对应的删除器}};template<class T, class D = Delete<T>>class shared_ptr{public:shared_ptr(T* ptr = nullptr)//构造:_ptr(ptr), _pcount(new int(1)){}void release(){//当count减到0时才释放资源,其余情况减计数器if (--(*_pcount) == 0 && _ptr)//短路设计:不用分别判断两种情况,count都会自减一次{D()(_ptr);//使用一个匿名对象delete _pcount;}}~shared_ptr(){release();}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount){(*_pcount)++;}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;(*_pcount)++;}return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}int getCount(){return *_pcount;}T* get()const{return _ptr;}private:T* _ptr;int* _pcount;};

  演示结果:
在这里插入图片描述
  注意这里传参,和库中的不同,我们实现时,将删除器作为模板参数使用,那么传参时不是传递函数对象,而是类类型。

void Func()
{//std::shared_ptr<Node> n1(new Node[5]);mySAII_2::shared_ptr<Node, DeleteArray<Node>> n1(new Node[5]);mySAII_2::shared_ptr<Node> n2(new Node);//std::shared_ptr<int> n3(new int[5]);mySAII_2::shared_ptr<int, DeleteArray<int>> n3(new int[5]);//std::shared_ptr<int> n4((int*)malloc(sizeof(int) * 5));mySAII_2::shared_ptr<int, FreeArray<int>> n4((int*)malloc(sizeof(int) * 5));}

  
  
  
  
  

3、特殊类的设计

3.1、设计一个不能被拷贝的类

  1)、问题分析
  两个涉及拷贝的场景:赋值运算符重载、拷贝构造。
  因此,想要让一个类禁止拷贝,只需让该类不能调用拷贝构造函数以及赋值运算符重载即可。结合之前所学,在C++98中,我们可以对这两个默认函数只声明不实现,并将其设置为私有;在C++11中我们可以使用delete关键字。
  
  
  2)、相关写法
  C++98: ①只声明不定义,该函数就不会被调用。②设置成私有,就禁止了在类外定义实现这两个函数的可能。

class Bancopy
{//……
private:Bancopy(const Bancopy& copy);Bancopy& operator=(const Bancopy& copy);//……
};

  
  
  C++11:C++11扩展delete的用法,delete除了释放new申请的资源外,如果在默认成员函数后跟上=delete,表示让编译器删除掉该默认成员函数。

class Bancopy
{Bancopy(const Bancopy& copy) = delete;Bancopy& operator=(const Bancopy& copy) = delete;//……
};

  
  
  
  

3.2、设计一个只能在堆上创建对象的类

  1)、基础认知:一个类可以在地址空间的哪些区域被创建?

int main()
{HeapOnly hp1;//在栈区:函数栈帧static HeapOnly hp2;//在静态区:静态变量HeapOnly* hp3 = new HeapOnly;//在堆区:new/malloc等return 0;
}

  
  
  

3.2.1、限制析构

  1)、思路分析:析构函数私有化

  说明: 由于对象创建出来,其声明周期结束后都需要调用析构函数。若将析构函数私有,在函数中创建的类对象(main函数也是函数栈帧、不论该对象是静态与否)会因为无法调用析构而不能创建。因此最终只剩下堆上申请空间的方式。

在这里插入图片描述
  
  问题: 为什么new堆区创建出来的对象可以?


int main()
{HeapOnly* hp3 = new HeapOnly;//在堆区:new/malloc等return 0;
}

  回答: 这里hp3并不是实际的对象本身,而是指向该对象的指针,那么其生命周期结束后,hp3指针本身不需要析构(析构函数作用于其指向的对象),但这样一来也会造成一个问题,我们无法释放该指针指向空间。

在这里插入图片描述

  
  问题: 针对上述问题,如何解决?
  回答: ①private私有,虽然不能在类外访问,但可以在类内访问。因此我们可以提供一个用于delete的函数。②delete底层为operator delete()。
  
  
  2)、相关实现
  ①提供函数的情况:
在这里插入图片描述
  
  
  
  ②使用operator delete()的情况演示:
在这里插入图片描述
  

class HeapOnly
{
public:void Destroy()//写法一{delete this;}static void Destroy(HeapOnly* ptr)//写法二:静态可以使用类名调用{delete ptr;}private:~HeapOnly(){cout << "~HeapOnly()" << endl;//用于观察}int _a = 111;//用于观察
};int main()
{HeapOnly* hp3 = new HeapOnly;hp3->Destroy();//写法一HeapOnly::Destroy(hp3);//写法二operator delete (hp3);//写法三:operator delete()return 0;
}

  
  
  

3.2.2、限制构造

  说明: 关于构造函数私有的写法,我们在之前 类和对象(四)中演示讲解过。

在这里插入图片描述
  
  上述类还存在一定缺陷: 可以被拷贝和赋值。因此我们可以将其禁用。
在这里插入图片描述

class HeapOnly
{
public:static HeapOnly* CreateObj(){return new HeapOnly;}HeapOnly(const HeapOnly& hp) = delete;//禁止拷贝HeapOnly& operator =(const HeapOnly& hp) = delete;//禁止赋值private:HeapOnly()//构造私有{cout << "HeapOnly()" << endl;//用于观察}int _a = 111;//用于观察
};int main()
{HeapOnly* hp1 = HeapOnly::CreateObj();cout << endl;return 0;
}

  
  
  
  

3.3、设计一个只能在栈上创建对象的类

3.3.1、限制构造

  说明: 这里的实现方法和上述相同,只是此处由于传值返回,不能使用delete禁止拷贝构造函数。
在这里插入图片描述
  
  相关实现:

class StackOnly
{
public:static StackOnly CreateObj(){return StackOnly();}void* operator new(size_t size) = delete;void operator delete(void* p) = delete;private:StackOnly()//构造函数私有化{cout << "StackOnly()" << endl;}int _b = 222;
};int main()
{StackOnly s1 = StackOnly::CreateObj();cout << endl;return 0;
}

在这里插入图片描述

  
  
  
  

3.4、请设计一个类,不能被继承

  C++98: 基类构造函数私有化。

class NonInherit
{
public:static NonInherit GetInstance(){return NonInherit();}
private:NonInherit(){}
};

在这里插入图片描述
  
  
  
  C++11: 使用final关键字。被final修饰类,表示该类不能被继承。

class NonInherit final
{
public:private:int _parent;
};class child:public NonInherit
{
private:int _child;
};int main()
{child ch;return 0;
}

在这里插入图片描述
  
  
  
  

3.5、请设计一个类,只能创建一个对象(单例模式)

3.5.1、基本介绍

  1)、设计模式
  设计模式(Design Pattern):是一套被反复使用、多数人知晓的、经过分类的、代码设计经验的总结。
  使用设计模式的目的: 为了代码可重用性、让代码更容易被他人理解、保证代码可靠性。 设计模式使代码编写真正工程化,是软件工程的基石脉络,如同大厦的结构一样。
  
  
  
  2)、单例模式
  单例模式: 一个类只能实例化一个对象,其是进程内的唯一对象,即单例模式。该模式可以保证系统中该类只有一个实例,被所有程序模块共享,访问时会提供一个全局访问点。
  
  场景举例: 比如在某个服务器程序中,该服务器的配置信息存放在一个文件中,这些配置数据由一个单例对象统一读取,然后服务进程中的其他对象再通过这个单例对象获取这些配置信息,这种方式简化了在复杂环境下的配置管理。
  
  单例模式的两种实现模式: 饿汉模式、懒汉模式。(实际可以有好几种模式说法,这里统一划分为两类)
  
  

3.5.2、饿汉模式

  饿汉模式: 一开始(在main函数前)就创建出对象。即,不管将来用不用到,程序启动时就创建一个唯一的实例对象。
  
  方法: ①构造函数私有化:防止在类外任意创建多个对象。②提供一个静态类成员变量,其类型可以是类本身,也可以是指向该类的指针。③提供一个静态成员函数,用于返回该静态成员变量。

  以下为框架搭建:

class Singleton
{
public:static Singleton* getInstance(){return psg;//方式一return &sg;//方式二}private:Singleton()//构造函数私有化{}static Singleton* psg;//提供一个静态成员·方式一static Singleton sg;//方式二
};
Singleton* Singleton::psg = new Singleton;//方式一
Singleton Singleton::sg;//方式二:在程序入口之前就完成单例对象的初始化int main()
{return 0;
}

  
  简单演示:单例,为了确保唯一性防止拷贝,需要将拷贝构造、赋值都禁止。

class Singleton
{
public:static Singleton* getInstance(){return _psg;}Singleton(const Singleton& sn) = delete;// 防拷贝Singleton& operator=(const Singleton& sn) = delete;// 防拷贝void Debug()//测试{cout << "Debug: " << _psg << endl;}private:Singleton()//构造函数私有化{cout << "Singleton() " << _psg << endl;}static Singleton* _psg;
};
Singleton* Singleton::_psg = new Singleton;//在程序入口之前就完成单例对象的初始化int main()
{Singleton* ps = Singleton::getInstance();ps->Debug();Singleton::getInstance()->Debug();return 0;
}

在这里插入图片描述
在这里插入图片描述

  
  优点说明: 简单、没有线程安全问题
  缺陷说明:
    ①一个程序中,多个单例,若有先后创建初始化顺序要求时,饿汉无法控制。(例如:程序两个单例类A 和 B,假设要求A先创建初始化,B再创建初始化。静态成员无法控制谁先被初始化创建,尤其是一个项目多个文件中。)
    ②饿汉模式的单例类,若初始化时任务多,会影响程序启动速度。
  
  PS:
  ①如果这个单例对象在多线程高并发环境下频繁使用,性能要求较高,那么使用饿汉模式来避免资源竞争,提高响应速度更好。
  ②如果单例对象构造十分耗时或者占用很多资源,如需要加载插件、 初始化网络连接、读取文件等等,而有可能该对象程序运行时不会用到,那么也要在程序一开始就进行初始化,就会导致程序启动时非常的缓慢。 所以这种情况使用懒汉模式(延迟加载)更好。
  
  
  
  

3.5.3、懒汉模式

3.5.3.1、基本说明(version1.0)

  懒汉模式: 第一次实际使用时,对象才创建。

  优点: ①不影响启动速度;②多个单例实例启动顺序自由控制(只用在调用时按照需求的顺序初始化即可)。

  缺点: 相对复杂,需要处理线程安全的问题。
  
  基本框架如下:(此处没有引入线程安全问题,后续可继续完善。)

class Singleton
{
public:static Singleton* getInstance(){if (_psg == nullptr)_psg = new Singleton;return _psg;}Singleton(const Singleton& sn) = delete;// 防拷贝Singleton& operator=(const Singleton& sn) = delete;// 防拷贝void Debug()//测试{cout << "Debug: " << _psg << endl;}private:Singleton()//构造函数私有化{cout << "Singleton() " << _psg << endl;}static Singleton* _psg;};
Singleton* Singleton::_psg = nullptr;int main()
{Singleton* ps = Singleton::getInstance();ps->Debug();Singleton::getInstance()->Debug();return 0;
}

在这里插入图片描述
  
  
  

3.5.3.2、线程安全说明(version2.0)

  问题说明: 懒汉模式中,多线程存在的问题主要体现在这里的首次实例化对象。单例对象第一次使用时,_psg = new Singleton;,对于多线程而言,若不加锁保护,有可能new多次,造成内存泄漏。

	static Singleton* getInstance(){if (_psg == nullptr)_psg = new Singleton;//多线程同时访问,首次时new对象,那么此处检验时在多个线程中同时满足条件,_psg被多次赋值,而先前new出来的空间无指针指向,造成内存泄漏问题。return _psg;}

  
  
  解决方案: ①最好RAII,因为new会抛异常。②加锁设置为双检查。

	static Singleton* getInstance(){//双检查模式设计if (_psg == nullptr)//2、这里再加一层条件判断,是因为只有首次才会存在现象安全问题, 后续使用时,反复加锁判断又解锁属于无意义行为,影响效率。{lock_guard<mutex> lck(_mutex);//首次:加锁//unique_lock<mutex> lck(_mutex);//其它写法if (_psg == nullptr)//1、这里是加锁是为了解决单例首次实例化时,多线程造成的线程安全问题_psg = new Singleton;}return _psg;}

  
  其它说明:
在这里插入图片描述
  
  整体情况:

class Singleton
{
public:static Singleton* getInstance(){//双检查模式设计if (_psg == nullptr)//2、这里再加一层条件判断,是因为只有首次才会存在现象安全问题, 后续使用时,反复加锁判断又解锁属于无意义行为,影响效率。{lock_guard<mutex> lck(_mutex);//首次:加锁//unique_lock<mutex> lck(_mutex);//其它写法if (_psg == nullptr)//1、这里是加锁是为了解决单例首次实例化时,多线程造成的线程安全问题_psg = new Singleton;}return _psg;}Singleton(const Singleton& sn) = delete;// 防拷贝Singleton& operator=(const Singleton& sn) = delete;// 防拷贝void Debug()//测试{cout << "Debug: " << _psg << endl;}//实现一个内嵌垃圾回收类 :内部类是外部类的友元class CGarbo {public:~CGarbo() {///……///处理其它事务///……cout << "~CGarbo() " << _psg << endl;//测试if (_psg)delete _psg;}};private:Singleton()//构造函数私有化{cout << "Singleton() " << _psg << endl;}static Singleton* _psg;//单例对象(声明)static mutex _mutex;//锁};
Singleton* Singleton::_psg = nullptr;
mutex Singleton::_mutex;// 全局的回收对象,main函数结束后,会调用它的析构函数。析构函数内对单例做了处理,会释放单例对象。
static Singleton::CGarbo gc;

  
  
  
  

2.5.3.3、其它写法说明

  说明:如下,是其它懒汉的写法,但这种写法存在的限制。
  在C++11之前,这种写法不能保证线程安全,因为C++11之前,对于局部静态对象,构造函数调用初始化时并不能保证线程安全的原子性。
  C++11的时候修复了这个问题,所以这种写法,只能在支持C++11以后的编译器上使用。

class Singleton
{
public:static Singleton* GetInstance(){// 局部的静态对象,在第一次调用时初始化static Singleton _s;return &_s;}private:// 构造函数私有Singleton(){};// C++11Singleton(Singleton const&) = delete;Singleton& operator=(Singleton const&) = delete;
};

  
  
  
  
  
  

3.5.4、单例释放问题

  如上述,对于new出来的空间对象,我们似乎没有进行释放。
    ① 一般情况下,单例对象不需要释放。通常整个程序运行期间都可能会用它,那么在进程正常结束后,单例模式也会被资源释放。
    ②在一些特殊场景需要下,比如单例对象析构时,要进行一些持久化(往文件、数据库写)操作。此时需要我们释放,可以实现一个内嵌垃圾回收类来解决。(这里以懒汉模式演示)

class Singleton
{
public:static Singleton* getInstance(){if (_psg == nullptr)_psg = new Singleton;return _psg;}Singleton(const Singleton& sn) = delete;// 防拷贝Singleton& operator=(const Singleton& sn) = delete;// 防拷贝void Debug()//测试{cout << "Debug: " << _psg << endl;}//实现一个内嵌垃圾回收类 :内部类是外部类的友元class CGarbo {public:~CGarbo() {///……///处理其它事务///……cout << "~CGarbo() " << _psg << endl;//测试if (_psg)delete _psg;}};private:Singleton()//构造函数私有化{cout << "Singleton() " << _psg << endl;}static Singleton* _psg;};
Singleton* Singleton::_psg = nullptr;// 全局的回收对象,main函数结束后,会调用它的析构函数。析构函数内对单例做了处理,会释放单例对象。
static Singleton::CGarbo gc;int main()
{Singleton* ps = Singleton::getInstance();ps->Debug();Singleton::getInstance()->Debug();return 0;
}

在这里插入图片描述

  
  
  
  
  
  
  
  
  

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

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

相关文章

设计模式-单例模式 (Singleton)

单例模式 &#xff08;Singleton&#xff09; 单例模式是一种创建型设计模式&#xff0c;它确保类只有一个实例&#xff0c;并提供了一种访问该实例的全局方法。这种模式有助于确保系统中的某些组件只有一个实例&#xff0c;并提供了一种方便的方法来访问该实例。 1、单例类只…

【AOA-VMD-LSTM分类故障诊断】基于阿基米德算法AOA优化变分模态分解VMD的长短期记忆网络LSTM分类算法(Matlab代码)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

7.20 SpringBoot项目实战【图书详情-学生端】:图书信息 + 评论列表 + 是否收藏

文章目录 前言一、接口规划二、编写服务层三、编写数据访问层四、编写控制器五、PostMan测试1. getBook 根据id获取图书2. getBookCommentList 根据id获取图书详情 - 评论列表3. getFavoriteId 获取学生收藏了某图书的收藏id 最后 前言 学生的【借阅申请】审核通过以后&#x…

Node学习笔记之包管理工具

一、概念介绍 1.1 包是什么 『包』英文单词是package &#xff0c;代表了一组特定功能的源码集合 1.2 包管理工具 管理『包』的应用软件&#xff0c;可以对「包」进行 下载安装 &#xff0c; 更新 &#xff0c; 删除 &#xff0c; 上传 等操作 借助包管理工具&#xff0c;可…

C++前缀和算法:生成数组原理、源码及测试用例

本文涉及的基础知识点 C算法&#xff1a;前缀和、前缀乘积、前缀异或的原理、源码及测试用例 包括课程视频 动态规划&#xff0c;日后完成。 题目 给定三个整数 n、m 和 k 。考虑使用下图描述的算法找出正整数数组中最大的元素。 请你构建一个具有以下属性的数组 arr &#…

Go并发编程之一

一、前言 新年学新语言Go系列文章已经完结&#xff0c;用了最简单的例子去了解Go基础语法&#xff0c;但Go最牛B的是它对并发的友好支持&#xff0c;每一门语言都有它自己独特的优势&#xff0c;如Java适合大型工程化项目&#xff0c;Python适合做数据分析及运维脚本&#xff0…

AD9371 官方例程HDL详解之JESD204B TX_CLK生成 (一)

AD9371 系列快速入口 AD9371ZCU102 移植到 ZCU106 &#xff1a; AD9371 官方例程构建及单音信号收发 ad9371_tx_jesd -->util_ad9371_xcvr接口映射&#xff1a; AD9371 官方例程之 tx_jesd 与 xcvr接口映射 参考资料&#xff1a; UltraScale Architecture GTH Transceive…

Android---OkHttp详解

OkHttp 是一套处理 HTTP 网络请求的依赖库&#xff0c;由 Square 公司设计研发并开源&#xff0c;目前可以在 Java 和 Kotlin 中使用。对于 Android App&#xff0c;OkHttp 现在几乎已经占据了所有的网络请求操作。RetroFit OkHttp 实现网络请求似乎成了一种标配。 因此&…

html 按钮点击倒计时,限制不可点击

html 按钮点击倒计时&#xff0c;限制不可点击 e94cbabd25cfc7f3f53a50a235734c22.jpg <!DOCTYPE html> <html><head><meta http-equiv"Content-Type" content"text/html; charsetutf-8" /><title></title></head&…

Upload-labs(1-20关保姆级教程)

靶场下载链接 https://github.com/c0ny1/upload-labs 话不多说&#xff0c;直接喂饭 lab-1 上传php木马&#xff0c;发现弹出提示框&#xff0c;查看源码可知是前端过滤 bp抓包&#xff0c;先上传一张正常的jpg图片 修改文件内容和后缀&#xff0c;大概就是想怎么改就怎么…

leetCode 30.串联所有单词的子串

给定一个字符串 s 和一个字符串数组 words。 words 中所有字符串 长度相同。s 中的 串联子串 是指一个包含 words 中所有字符串以任意顺序排列连接起来的子串。 例如&#xff0c;如果 words ["ab","cd","ef"]&#xff0c; 那么 "abcdef&…

互联网Java工程师面试题·Java 面试篇·第五弹

目录 79、适配器模式和装饰器模式有什么区别&#xff1f; 80、适配器模式和代理模式之前有什么不同&#xff1f; 81、什么是模板方法模式&#xff1f; 82、什么时候使用访问者模式&#xff1f; 83、什么时候使用组合模式&#xff1f; 84、继承和组合之间有什么不同&#…

【Python】图像和办公文档的处理

图像和办公文档处理 用程序来处理图像和办公文档经常出现在实际开发中&#xff0c;Python的标准库中虽然没有直接支持这些操作的模块&#xff0c;但我们可以通过Python生态圈中的第三方模块来完成这些操作。 操作图像 计算机图像相关知识 颜色。如果你有使用颜料画画的经历&…

【JavaEE】网络编程(网络编程基础、Socket套接字)

一、网络编程基础 1.1、什么是网络编程&#xff1f; 网络编程&#xff0c;指网络上的主机&#xff0c;通过不同的进程&#xff0c;以编程的方式实现网络通信&#xff08;或称为网络数据传输&#xff09; 注意&#xff1a;我们只要满足进程不同就行&#xff1b;所以即便是同一…

03-Android App logger策略

背景 经常会为log定位而烦恼。比如&#xff1a;同一个类&#xff0c;一样的log输出&#xff0c;无法定位到Log输出的行。 方案 1.java StackTraceElement 通过java StackTraceElement获取类名&#xff0c;以及log输出行 2. 具体实现 NonNullprivate static String getSour…

[AUTOSAR][诊断管理][ECU][$14] 清除诊断相关信息

文章目录 一、简介(1)应用场景(2)清除DTC原理(3) 请求格式二、示例代码(1) 14_cls_dtc_info.c三、 常见bug大揭秘一、简介 根据ISO14119-1标准中所述,诊断服务14主要用于Client向Server(ECU)请求清除诊断相关信息。 (1)应用场景 一般而言,14诊断服务,主要应用场景…

中间件安全-CVE复现WeblogicJenkinsGlassFish漏洞复现

目录 服务攻防-中间件安全&CVE复现&Weblogic&Jenkins&GlassFish漏洞复现中间件-Weblogic安全问题漏洞复现CVE_2017_3506漏洞复现 中间件-JBoos安全问题漏洞复现CVE-2017-12149漏洞复现CVE-2017-7504漏洞复现 中间件-Jenkins安全问题漏洞复现CVE-2017-1000353漏…

Linux-管道、环境变量、常用命令

文章目录 管道概念要点与文件重定向的区别 环境变量概念查看 常用命令查看系统状况权限文件查找 用户相关工具 管道 概念 管道的作用类似于文件重定向&#xff0c;可以将前一个命令的stout做为下一个命令的stdin 要点 管道命令进处理stdout&#xff0c;会忽略stderr管道右边…

京东数据分析:2023年9月京东洗烘套装品牌销量排行榜!

鲸参谋监测的京东平台9月份洗烘套装市场销售数据已出炉&#xff01; 根据鲸参谋平台的数据显示&#xff0c;今年9月份&#xff0c;京东平台洗烘套装的销量为7100&#xff0c;环比下降约37%&#xff0c;同比增长约87%&#xff1b;销售额为6000万&#xff0c;环比下降约48%&#…

Swingbench 压力测试(超详细)

目录 前提需要有配置好的oracle哦 1、环境准备 2、安装Swingbench 3、造数据 4、压测 前提需要有配置好的oracle哦 1、环境准备 启动监听 lsnrctl start 启动数据库 sqlplus / as sysdba startup 创建表 CREATE TABLESPACE soe DATAFILE /u01/app/oracle/oradata/or…