当前位置: 首页 > news >正文

【C++】auto_ptr为何被唾弃?以及其他智能指针的学习

搭配异常可以让异常的代码更简洁

文章目录

  • 智能指针
  •     内存泄漏的危害
  •     1.auto_ptr(非常不建议使用)
  •     2.unique_ptr
  •     3.shared_ptr
  •     4.weak_ptr
  • 总结


智能指针

C++中为什么会需要智能指针呢?下面我们看一下样例:

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 = new int;cout << div() << endl;delete p1;delete p2;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

 在上面的代码中,一旦出现异常那就会造成内存泄漏,什么是内存泄漏呢:

内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。
内存泄漏分类:
堆内存泄漏 (Heap leak)
堆内存指的是程序执行中依据须要分配通过 malloc / calloc / realloc / new 等从堆中分配的一
块内存,用完后必须通过调用相应的 free 或者 delete 删掉。假设程序的设计错误导致这部分
内存没有被释放,那么以后这部分空间将无法再被使用,就会产生 Heap Leak
系统资源泄漏
指程序使用系统分配的资源,比方套接字、文件描述符、管道等没有使用对应的函数释放
掉,导致系统资源的浪费,严重可导致系统效能减少,系统执行不稳定。

如果func函数里的p1new的时候抛异常该怎么办呢?对于第一个new如果抛异常会直接跳到main函数中的catch被捕获,那么p2new失败了会怎么办呢?div函数抛异常我们可以捕获一下:

 那么如果是p2失败了我们还要再catch一下:

void Func()
{// 1、如果p1这里new 抛异常会如何?// 2、如果p2这里new 抛异常会如何?// 3、如果div调用这里又会抛异常会如何?int* p1 = new int;int* p2 = nullptr;try{p2 = new int;try{cout << div() << endl;}catch (...){delete p1;delete p2;throw;}}catch (...){delete p1;delete p2;}
}

 那么这样的代码看起来会不会有些冗余呢?为了处理这样的问题,智能指针就能起到很好的作用:

template <class T>
class SmartPtr
{
public:SmartPtr(T* ptr):_ptr(ptr){}~SmartPtr(){delete _ptr;cout << _ptr << endl;}
private:T* _ptr;
};
int div()
{int a, b;cin >> a >> b;if (b == 0)throw invalid_argument("除0错误");return a / b;
}
void Func()
{int* p1 = new int;SmartPtr<int> sp1(p1);int* p2 = new int;SmartPtr<int> sp2(p2);cout << div() << endl;delete p1;delete p2;
}
int main()
{try{Func();}catch (exception& e){cout << e.what() << endl;}return 0;
}

 可以看到有了智能指针即使抛异常了我们没有释放的空间也会被自动释放,因为抛异常后自定义类型出了作用域我们智能指针的析构函数会将这个空间释放。当然,我们的智能指针也可以直接创建资源,比如下面这样:

 但是我们这样写不就不可以对指针解引用访问指针的资源了吗?其实我们只需要再给智能指针多加加个功能让它变得像指针一样就解决了这个问题:

template <class T>
class SmartPtr
{
public://保存资源SmartPtr(T* ptr):_ptr(ptr){}//释放资源~SmartPtr(){delete _ptr;cout << _ptr << endl;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}
private:T* _ptr;
};

 这个时候我们来使用一下这个指针:

 可以看到现在我们的这个智能指针也可以正常使用了,再次说明一下:智能指针的构造函数是为了保存资源,析构函数是为了释放资源,其他功能是为了和指针一样。我们上面将资源管理的责任托管给对象的做法就叫做RAII(资源获得即初始化),这就是避免内存泄漏的一种方法。

避免内存泄漏:
1. 工程前期良好的设计规范,养成良好的编码规范,申请的内存空间记着匹配的去释放。 ps
这个理想状态。但是如果碰上异常时,就算注意释放了,还是可能会出问题。需要下一条智
能指针来管理才有保证。
2. 采用 RAII 思想或者智能指针来管理资源。
3. 有些公司内部规范使用内部实现的私有内存管理库。这套库自带内存泄漏检测的功能选项。
4. 出问题了使用内存泄漏工具检测。 ps :不过很多工具都不够靠谱,或者收费昂贵。
总结一下 :
内存泄漏非常常见,解决方案分为两种: 1 、事前预防型。如智能指针等。 2 、事后查错型。如泄
漏检测工具。

那么C++库里面有没有智能指针呢?答案是有的,并且有好几种。

auto_ptr:

下面我们看一下C++中被骂了很多年的一款智能指针auto_ptr.

 上图中出错的原因是重复析构了两次sp1,这是因为我们用的编译器自动生成的拷贝构造,是个浅拷贝。auto_ptr的最大问题在于就像我们的SmartPtr一样支持拷贝但是又会让另一个指针悬空,什么意思呢,我们先来调试看一下然后把代码写出来:

 上图是拷贝之前sp1的资源

 上图是拷贝之后sp2的资源,我们可以看到auto_ptr的拷贝就是将资源管理权转移,原本sp1指向的内容被sp2指向了,但是问题就在于auto_ptr竟然让原先的sp1指针悬空了也就是说什么也没指向,这就导致不知道的人对原先sp1这个指针解引用等操作,这样就对空指针进行解引用了,这就是auto_ptr被吐槽的根源所在。下面我们看看auto_ptr是如何实现的:

namespace sxy
{template <class T>class auto_ptr{public://保存资源auto_ptr(T* ptr):_ptr(ptr){}//拷贝构造auto_ptr(auto_ptr<T>& ap):_ptr(ap._ptr){ap._ptr = nullptr;}//释放资源~auto_ptr(){delete _ptr;cout << _ptr << endl;}//像指针一样T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T& operator[](size_t pos){return _ptr[pos];}private:T* _ptr;};
}

 上面代码中大家重点看auto_ptr的构造函数就可以了:

 一旦我们对之前的空指针sp1进行解引用操作程序立马就挂掉了。注意:auto_ptr这款指针指针,很多公司都明确规定不能使用它,如果有面试官让你写一款智能指针,一定不要写auto_ptr!!!

下面我们讲解三种算是经常被使用的智能指针:unique_ptr,   shared_ptr,    weak_ptr.我们可以先看看unique_ptr是如何实现的:

unique_ptr:

 其实unique_ptr的实现很简单,就是直接禁掉了拷贝构造函数和赋值重载。

template <class T>class unique_ptr{public://保存资源unique_ptr(T* ptr):_ptr(ptr){}//拷贝构造unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>&) = delete;//释放资源~unique_ptr(){delete _ptr;cout << _ptr << endl;}//像指针一样//.........private:T* _ptr;};

 后面像指针一样的功能都是相同的我们就直接删掉了,可以看到unique_ptr的实现还是非常简单粗暴的。

当然也不能都不能拷贝吧,所以又出现了一个可以进行拷贝的指针指针shared_ptr,这个智能指针可算是在这之中最优秀的了,下面我们来讲讲:

shared_ptr:

int main()
{shared_ptr<int> sp1(new int(0));shared_ptr<int> sp2(sp1);(*sp1)++;(*sp2)++;cout << *sp1 << endl;cout << *sp2 << endl;return 0;
}

 我们可以看到shared_ptr不仅可以拷贝还没有之前那么多的问题,那么shared_ptr是如何实现的呢?实际上shared_ptr的是借助引用计数实现的,我们可以调试看一下:

 经过拷贝后引用计数由1变成了2,如下图:

 那么如何实现引用计数比较好呢?是在类中加一个私有成员变量count吗?首先直接加私有变量肯定是不可以的,因为我们的引用计数是要所有的类对象都能看到并且只有一份,如果是私有变量count那么多个对象每个对象都有一个count就不叫引用计数了。那么能否用static静态成员变量呢?静态成员变量不就是所有对象都有的吗?注意:静态的是不可以的,静态变量是属于整个类的,前三个指针指向的都是同一块资源计数为3,然后第四个指针指向不同的资源,这个不同的指针的计数器应该是1才对,但是如果将静态计数器改为1那么前三个指针的计数右不对了,所以不能使用静态变量。这里我们可以使用静态的map来做计数器,让每个不同的资源与计数器做一个KV映射,拷贝哪个资源就映射到map让V值++即可,这里提供一个最好的方式:多开一个指针,这个指针里保存的就是一个计数器,相同拷贝的资源里的计数器指针直接指向这个计数器即可。

了解了这个后我们就来实现一下,不理解的也没关系看着下面的代码就理解了:

template <class 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);}//释放资源~shared_ptr(){if (--(*_pcount) == 0){delete _ptr;delete _pcount;}}//像指针一样//.......private:T* _ptr;int* _pcount;};

 首先类中私有成员多了一个_pcount的引用计数,注意:这是在堆上开的空间。我们在构造函数初始化的时候,每当有新的对象被创建我们就给这个引用计数初始化为1,释放资源的时候我们不能直接释放,因为有可能其他拷贝的对象和我们指向同一块资源,所以这个时候我们只需要将引用计数--即可,注意:我们用的前置--只要进入判断语句就会先解引用拿到计数器的值然后--之后才会判断,即使判断条件不满足还是会--计数器,只有当计数器为0说明没有对象在指向这个资源了,那么这个时候就可以将资源释放了,释放的时候记得将引用计数也释放了防止内存泄漏。我们的拷贝构造就非常简单了,直接让ptr和pcount指向被拷贝的那个对象的资源,然后让计数器++就行了。

当然即使支持了拷贝构造那么赋值重载也是能支持的,因为已经不惧怕拷贝了嘛。对于赋值重载我们一定要铭记:防止相同资源进行赋值,防止直接释放资源导致其他对象不能使用其资源,下面我们给出代码:

//赋值重载shared_ptr<T>& operator=(shared_ptr<T>& sp){if (_ptr != sp._ptr){if (--(*_pcount) == 0){delete _ptr;delete _pcount;}_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}

 首先我们判断了相同资源赋值的情况,因为我们本身是被sp赋值的,所以我们本身的计数一定会少一个,一旦计数少了那么就要判断是否需要释放资源,所以我们还是减自身的计数器,如果减到0了我们就将自身的资源释放掉,如果没有到0就不释放,然后获取sp的资源和引用计数,因为sp赋值给我们我们本身少了一个sp多了一个,所以获取sp的计数器资源后我们还要加加一下计数器。下面我们验证一下是否正确:

int main()
{sxy::shared_ptr<int> sp1(new int(0));sxy::shared_ptr<int> sp2(sp1);(*sp1)++;(*sp2)++;cout << *sp1 << endl;cout << *sp2 << endl;sxy::shared_ptr<int> sp3 = sp1;sxy::shared_ptr<int> sp4(new int(10));sxy::shared_ptr<int> sp5 = sp4;return 0;
}

 可以看到程序是没有问题的,不管是赋值还是拷贝都可以完成任务。下面我们提出一个新问题:如果我们目前的场景是多线程并发的,那么引用计数还能正确的计数吗我们来看看:

#include <thread>int main()
{sxy::shared_ptr<int> sp(new int(1));int n = 10000;thread t1([&, n](){for (int i = 0; i < n; i++){sxy::shared_ptr<int> cp1(sp);}});thread t2([&, n](){for (int i = 0; i < n; i++){sxy::shared_ptr<int> cp2(sp);}});t1.join();t2.join();cout << sp.use_count() << endl;return 0;
}

首先我们创造了一个场景,这个场景是两个线程多次对sp这个智能指针进行拷贝,最后我们输出这个智能指针的引用计数,注意:shared_ptr中通常会有use_count这个接口返回当前资源的引用计数,实现如下:

        int use_count() const{return *_pcount;}

注意:如果use_count返回1是正确的,因为我们是在返回前打印的,所以这个时候还有sp这个指针指向这个资源,所以是1.下面我们运行起来:

 如果我们多运行几次就会发现程序有时候是正常的有时候是不正常的,那么这种情况一定是有问题的,那么该如何解决这个问题呢?其实很简单加锁就可以了。

因为我们的shared指针可以支持拷贝和赋值,所以我们定义锁的时候还是像引用计数一样不能让每个对象都有一个锁,并且这个锁还要支持赋值等操作,要知道库里的锁是不支持赋值的直接禁掉了,所以我们只需要定义一个锁的指针,赋值的时候把锁资源让另一个对象指向即可。注意:我们的锁一定是要保护每个资源对应的引用计数器的,所以相当于每个对象有三个资源:数据资源,计数器资源,锁资源。

当然加锁之前我们还可以优化一下:

template <class 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){delete _ptr;delete _pcount;}}//赋值重载shared_ptr<T>& operator=(shared_ptr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);}return *this;}//释放资源~shared_ptr(){Release();}int use_count() const{return *_pcount;}//像指针一样//.........private:T* _ptr;int* _pcount;};

我们直接用一个Release()函数代替释放资源的函数,这样代码看起来会更简单,下面我们开始加锁:

template <class T>class shared_ptr{public://保存资源shared_ptr(T* ptr):_ptr(ptr),_pcount(new int(1)),_pmtx(new mutex){}//拷贝构造shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr),_pcount(sp._pcount),_pmtx(sp._pmtx){_pmtx->lock();++(*_pcount);_pmtx->unlock();}void Release(){_pmtx->lock();if (--(*_pcount) == 0){delete _ptr;delete _pcount;}_pmtx->unlock();}//赋值重载shared_ptr<T>& operator=(shared_ptr<T>& sp){if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pcount = sp._pcount;_pmtx = sp._pmtx;_pmtx->lock();++(*_pcount);_pmtx->unlock();}return *this;}//释放资源~shared_ptr(){Release();}int use_count() const{return *_pcount;}//像指针一样// ...............private:T* _ptr;int* _pcount;mutex* _pmtx;};

我们构造初始化的时候先给锁的指针开一个锁,拷贝构造如果谁和我们指向同一块资源那么就让他的锁的指针指向我们开好的锁,然后遇到计数器++或者--的地方我们就加锁保护起来,这样在计数器++或--的过程中即使是多线程也依旧是串行的而不是并行的,下面我们运行起来看看能否解决刚刚的问题:

 经过多次的运行后我们发现是没问题的,但是如果有细心的同学应该会发现我们的指针new了锁但是没有释放啊,所以下面我们赶紧加上:

        void Release(){bool flag = false;_pmtx->lock();if (--(*_pcount) == 0){delete _ptr;delete _pcount;flag = true;}_pmtx->unlock();if (flag){delete _pmtx;}}

我们再保护计数器的时候用了锁,所以不能在if语句中释放ptr和pcount资源的时候将锁释放,而是需要一个标志只要计数器为0需要释放资源了那么就将标志标记,最后解锁后将锁释放就好了。

那么我们前面已经说过,锁是保护计数器的,那么指针指向的资源该如何被保护呢?下面我们写个例子:

struct Date
{int _year = 0;int _month = 0;int _day = 0;
};
int main()
{sxy::shared_ptr<Date> sp(new Date);int n = 100000;thread t1([&, n](){for (int i = 0; i < n; i++){sxy::shared_ptr<Date> cp1(sp);cp1->_day++;cp1->_month++;cp1->_year++;}});thread t2([&, n](){for (int i = 0; i < n; i++){sxy::shared_ptr<Date> cp2(sp);cp2->_day++;cp2->_month++;cp2->_year++;}});t1.join();t2.join();cout << sp.use_count() << endl;cout << sp->_year << " : " << sp->_month << " : " << sp->_day << endl;return 0;
}

 我们可以看到正常的结果应该是200000才对,对于这种情况我们只需要对资源进行加锁即可:

int main()
{sxy::shared_ptr<Date> sp(new Date);int n = 100000;mutex mtx;thread t1([&, n](){for (int i = 0; i < n; i++){sxy::shared_ptr<Date> cp1(sp);mtx.lock();cp1->_day++;cp1->_month++;cp1->_year++;mtx.unlock();}});thread t2([&, n](){for (int i = 0; i < n; i++){sxy::shared_ptr<Date> cp2(sp);mtx.lock();cp2->_day++;cp2->_month++;cp2->_year++;mtx.unlock();}});t1.join();t2.join();cout << sp.use_count() << endl;cout << sp->_year << " : " << sp->_month << " : " << sp->_day << endl;return 0;
}

 加锁后我们运行多次答案依旧是正确的,所以一定要注意:shared_ptr的锁只保护引用计数,不保护指针所指向的资源。

总结:shared_ptr本身是线程安全的(拷贝和析构时,引用计数++ --是线程安全的)

shared_ptr管理资源的访问不是线程安全的,需要用的地方自行保护。

下面我们讲一下shared_ptr的循环引用问题:

struct ListNode
{int val;/*ListNode* _next;ListNode* _prev;*/sxy::shared_ptr<ListNode> _next;sxy::shared_ptr<ListNode> _prev;~ListNode(){cout << "distory" << endl;}
};int main()
{sxy::shared_ptr<ListNode> n1(new ListNode);sxy::shared_ptr<ListNode> n2(new ListNode);//n1->_next = n2;//n2->_prev = n1;return 0;
}

现在有两个链表,我们用指针指针让他们在析构的时候可以自己释放节点的空间,当我们不让n1和n2前后连接时,运行可以正常释放:

 注意:我们为了能在ListNode中将next和prev设为智能指在构造函数中加了缺省参数,否则编译不过去:

 然后我们让n1和n2两个节点前后链接再看看是否可以可以成功释放:

 可以看到释放不了,这就是循环引用,循环引用会导致内存泄漏。下面我们讲讲原理:

 刚开始这两个节点引用计数为1是因为n1和n2都是智能指针,初始化引用计数为1,一点n1的next链接到n2,这个时候相当于n1的next管理了n2(因为我们链表节点中prev和next也是智能指针),这样的话n2就由n1的next和n2本身这个智能指针一起管理,所以引用计数变成2,n1也同理变成2.然后出了n1和n2作用域要析构的时候他们引用计数--变成了1,如下图:

这个时候n1这个资源由n2的prev管理,n2这个资源由n1的next管理,析构的时候没有办法析构了呀,n1要析构那么next管理的资源的引用计数必须减为0但是n2的引用计数不为0,n2要析构那么prev管理的资源的引用计数必须减为0但是n1的引用计数不为0,谁都退出不了就导致了死循环,所以最后就无法成功释放,造成了内存泄漏。为了解决这个问题,weak_ptr就应运而生了。我们刚刚最主要的问题在于next和prev这两个指针参与资源的管理了导致引用计数变了,如果我们让这两个指针不参与资源的管理不就解决了吗,实际上weak_ptr就是一个不参与资源管理的指针,并且weak_ptr是配合shared_ptr使用的。

整体分析:

循环引用分析:
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 成员,所以这就叫循环引用,谁也不会释放。

weak_ptr:

下面是没有用weak_ptr的n1和n2的退出前的引用计数:

 下面是用weak_ptr的n1和n2的退出前的引用计数:

struct ListNode
{int val;//weak_ptr可以指向资源,访问资源,不参与资源管理,不增加引用计数weak_ptr<ListNode> _next;weak_ptr<ListNode> _prev;~ListNode(){cout << "distory" << endl;}
};

 下面我们就实现一下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是私有成员weak_ptr无法直接访问,所以shared_ptr有一个get()接口是返回_ptr的_ptr = sp.get();return *this;}//像指针一样.................private:T* _ptr;};

 首先weak_ptr不接受RAII操作,也就是说单独使用weak_ptr起不到释放的作用,是需要配合shared_Ptr解决循环引用问题的。我们前面说过,weak_ptr不管理资源,引用计数也不会++,所以这个指针只会指向shared_ptr指向的资源。

下面我们就用自己的weak_ptr解决一下循环引用问题:

 通过运行结果可以看到没有任何问题。


总结

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 中的实现的。
下一篇文章中我们会讲到和智能指针相关的定制删除器,定制删除器也有很多不一样的玩法。

相关文章:

【C++】auto_ptr为何被唾弃?以及其他智能指针的学习

搭配异常可以让异常的代码更简洁 文章目录 智能指针 内存泄漏的危害 1.auto_ptr(非常不建议使用) 2.unique_ptr 3.shared_ptr 4.weak_ptr总结 智能指针 C中为什么会需要智能指针呢&#xff1f;下面我们看一下样例&#xff1a; int div() {int a, b;cin >&g…...

数据结构练习题1:基本概念

练习题1&#xff1a;基本概念 1 抽象数据类型概念分析2. 逻辑结构与存储结构概念分析3.综合选择题4.综合判断题5.时间复杂度相关习题6 时间复杂度计算方法&#xff08;一、二、三层循环&#xff09; 1 抽象数据类型概念分析 1.可以用&#xff08;抽象数据类型&#xff09;定义…...

如何消除Msxml2.XMLHTTP组件的缓存

之前使用这个组件&#xff0c;是每隔十分钟取数据&#xff0c;没有遇到这个缓存问题&#xff0c; 这次使用它是频繁访问接口&#xff0c;就出现了一直不变的问题。觉得是缓存没有清除的问题。 网上搜了一些方案。最好的方案就是给url地址末尾给一个随机参数。用于让组件觉得是…...

深入理解Java虚拟机jvm-运行时数据区域(基于OpenJDK12)

运行时数据区域 运行时数据区域程序计数器Java虚拟机栈本地方法栈Java堆方法区运行时常量池直接内存 运行时数据区域 Java虚拟机在执行Java程序的过程中会把它所管理的内存划分为若干个不同的数据区域。这些区域有各自的用途&#xff0c;以及创建和销毁的时间&#xff0c;有的…...

(OpenCV) 基础demo

文章目录 前言Demo图片录制播放人脸识别 END 前言 OpenCV - Open Computer Vision Library OpenCV的名声想必不用多说了。 本文介绍4个基础使用demo。分别为&#xff0c;显示图片&#xff0c;录制视频&#xff0c;播放视频和一个基于开源算法库的人脸识别小demo。 只要环境…...

using 的使用

作者: 苏丙榅 链接: https://subingwen.cn/cpp/using/ 在 C 中 using 用于声明命名空间&#xff0c;使用命名空间也可以防止命名冲突。在程序中声明了命名空间之后&#xff0c;就可以直接使用命名空间中的定义的类了。在 C11 中赋予了 using 新的功能&#xff0c;让C变得更年轻…...

Websocket、Socket、HTTP之间的关系

Websocket、Socket、HTTP之间的关系 ★ Websocket是什么&#xff1f;★ Websocket的原理★ websocket具有以下特点&#xff1a;★ webSocket可以用来做什么?★ websocket与socket区别&#xff1a;★ WebSocket与HTTP区别 ★ Websocket是什么&#xff1f; ● Websocket是HTML5下…...

hustoj LiveCD版系统在局域网虚拟机安装和配置

root权限 打开terminal命令行输入sudo su输入初始密码freeproblemsetmysql数据库的密码的位置&#xff0c;如何登陆数据库 数据库账号密码存放在两个配置文件中&#xff1a; /home/judge/etc/judge.conf/home/judge/src/web/include/db_info.inc.php 新版本中&#xff0c;快…...

读书-代码整洁之道10-14

类 类的三大特性&#xff1a;封装、继承、多态&#xff1b;类应该短小&#xff1b;单一权责原则认为&#xff0c;类或模块应有且只有一条加以修改的理由&#xff1b;当类丧失了内聚性&#xff0c;就拆分它&#xff1b;隔离修改 系统 构造和使用是非常不一样的过程。每个应用…...

UDP 广播/组播

广播UDP与单播UDP的区别就是IP地址不同&#xff0c;广播使用广播地址xxx.xxx.xxx.255&#xff0c;将消息发送到在同一广播网络上的每个主机&#xff0c;广播/组播只能用udp进行实现 函数:int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_topt…...

高效创作助手:ChatGPT最新版实现批量撰写聚合文章的全新水平

随着人工智能技术的不断发展&#xff0c;ChatGPT最新版作为一款智能创作助手&#xff0c;实现了批量撰写聚合文章的全新水平。它能够在短时间内生成高质量的文章&#xff0c;极大地提高了创作效率。本文将从随机8-20个方面对ChatGPT最新版进行详细的阐述&#xff0c;让我们一起…...

Python中的包是什么,如何创建和使用包?

在Python中&#xff0c;包是一种将相关模块分组在一起的方式。它可以让我们更好地组织和重用代码。 一个Python包实际上是一个文件夹&#xff0c;其中包含该包的Python模块和其他资源文件&#xff08;例如配置文件、数据文件等&#xff09;。包的根目录通常包含一个名为__init…...

Spring Cloud Alibaba Seata(二)

目录 一、Seata 1、Seata-AT模式 1.1、具体案例 1.2、通过Seata的AT模式解决分布式事务 2、Seata-XA模式 3、Seata-TCC模式 4、Seata-SAGA模式 一、Seata 1、Seata-AT模式 概念&#xff1a;AT模式是一种无侵入的分布式事务解决方案&#xff0c;在 AT 模式下&#xff0c…...

如何在 MySQL 中使用 COALESCE 函数

1. 简介 在 MySQL 中&#xff0c;COALESCE 函数可以用来返回参数列表中的第一个非空值。如果所有参数都为空&#xff0c;则返回 NULL。本文将介绍 COALESCE 函数的语法和用法&#xff0c;并通过示例演示其效果。 2. 语法 COALESCE 函数的语法如下所示&#xff1a; COALESCE(…...

Python爬虫之Scrapy框架系列(22)——初识分布式爬虫scrapy_redis

目录: 分布式爬虫(Scrapy\_redis):1.简单介绍:2.Scrapy_redis的安装:分布式爬虫(Scrapy_redis): 官方文档:https://scrapy-redis.readthedocs.io/en/stable/1.简单介绍: scrapy_redis是一个基于Redis的Scrapy组件,用于scrapy项目的分布式部署和开发。 特点: 分布…...

ChatGPT的前世今生

原文首发于博客文章ChatGPT发展概览 ChatGPT 是OpenAI开发的人工智能聊天机器人程序&#xff0c;于2022年11月推出。该程序使用基于 GPT-3.5、GPT-4 架构的大语言模型并以强化学习训练。ChatGPT目前仍以文字方式交互&#xff0c;而除了可以用人类自然对话方式来交互&#xff0c…...

WireShark常用协议抓包与原理分析

1.ARP协议(地址解析协议) nmap 发现网关nmap -sn 192.168.133.2wireshark 抓请求包和响应包 arp请求包内容 arp响应包内容 总结:请求包包含包类型(request),源IP地址,源MAC地址,目标IP地址,目标MAC地址(未知,此处为全0);响应包包含包类型(reply),源IP地址,源…...

Mysql数据库操作总结

文章目录 1. DDL(Data Definition Language - 数据定义语言)1.1 数据库1.2 数据表(创建查询删除)1.3 数据表(修改) 2. 数据类型2.1 数值2.2 字符2.3 日期 3. 字段约束3.1 约束3.2 主键约束修改3.3 主键自增 联合主键 4. DML(Data Manipulation Language - 数据操作语言)4.1 添…...

在 ZBrush、Substance 3D Painter 和 UE5 中创作警探角色(P2)

大家好&#xff0c;下篇分享咱们继续来说警探角色的重新拓扑、UV、材质贴图和渲染处理。 重新拓扑/UV 这是对我来说最不有趣的部分——重新拓扑。它显然是实时角色中非常重要的一部分&#xff0c;不容忽视&#xff0c;因为它会影响大量的 UV、绑定和后期渲染&#xff0c;这里…...

如何在大规模服务中迁移缓存

当您启动初始服务时&#xff0c;通常会过度设计以考虑大量流量。但是&#xff0c;当您的服务达到爆炸式增长阶段&#xff0c;或者如果您的服务请求和处理大量流量时&#xff0c;您将需要重新考虑您的架构以适应它。糟糕的系统设计导致难以扩展或无法满足处理大量流量的需求&…...

【GPT LLM】跟着论文学习gpt

GPT1开山之作&#xff1a;Improving language understanding by generative pre-training 本文提出了gpt1&#xff0c;即使用无标签的数据对模型先进行训练&#xff0c;让模型学习能够适应各个任务的通用表示&#xff1b;后使用小部分 task-aware的数据对模型进行微调&#xff…...

【玩转Docker小鲸鱼叭】Docker容器常用命令大全

在 Docker 核心概念理解 一文中&#xff0c;我们知道 Docker容器 其实就是一个轻量级的沙盒&#xff0c;应用运行在不同的容器中从而实现隔离效果。容器的创建和运行是以镜像为基础的&#xff0c;容器可以被创建、销毁、启动和停止等。本文将介绍下容器的这些常用操作命令。 1、…...

专项练习11

目录 一、选择题 1、执行下列选项的程序&#xff0c;输出结果不是Window对象的是&#xff08;&#xff09; 2、以下哪些代码执行后 i 的值为10&#xff1a; 二、编程题 1、判断 val1 和 val2 是否完全等同 2、统计字符串中每个字符的出现频率&#xff0c;返回一个 Object&…...

ASP.NET+SQL通用作业批改系统设计(源代码+论文)

随着网络高速地融入当今现代人的生活,学校对网络技术的应用也在不断地提高。学校的教学任务十分复杂,工作也很繁琐,在教学任务中,作业的批改也是一个很重要的环节。为了提高老师工作效率,减轻教师的工作强度,提高作业批改的灵活性,《通用作业批改系统》的诞生可以说是事在…...

基于深度学习的高精度打电话检测识别系统(PyTorch+Pyside6+YOLOv5模型)

摘要&#xff1a;基于深度学习的高精度打电话检测识别系统可用于日常生活中或野外来检测与定位打电话目标&#xff0c;利用深度学习算法可实现图片、视频、摄像头等方式的打电话目标检测识别&#xff0c;另外支持结果可视化与图片或视频检测结果的导出。本系统采用YOLOv5目标检…...

Vue搭建智能文本检索视频界面

前言 随着人工智能技术的发展&#xff0c;智能文本检索已经成为了一种非常流行的技术。在视频领域中&#xff0c;智能文本检索技术可以帮助用户快速找到自己需要的视频片段&#xff0c;提高用户的观看体验。本文将介绍如何使用Vue框架搭建一个智能文本检索视频界面&#xff0c…...

软考A计划-系统集成项目管理工程师-一般补充知识-中

点击跳转专栏>Unity3D特效百例点击跳转专栏>案例项目实战源码点击跳转专栏>游戏脚本-辅助自动化点击跳转专栏>Android控件全解手册点击跳转专栏>Scratch编程案例点击跳转>软考全系列 &#x1f449;关于作者 专注于Android/Unity和各种游戏开发技巧&#xff…...

springboot-内置Tomcat

一、springboot的特性之一 基于springboot的特性 自动装配Configuretion 注解 二、springboot内置Tomcat步骤 直接看SpringApplication方法的代码块 总纲&#xff1a; 1、在SpringApplication.run 初始化了一个上下文ConfigurableApplicationContext configurableApplica…...

Flink流批一体计算(2):Flink关键特性

目录 Flink关键特性 流式处理 丰富的状态管理 丰富的时间语义支持 Data pipeline 容错机制 Flink SQL CEP in SQL Flink 应用程序可以消费来自消息队列或分布式日志这类流式数据源&#xff08;例如 Apache Kafka 或 Kinesis&#xff09;的实时数据&#xff0c;也可以从各…...

2023软件工程中各种图在现代企业级开发中的使用频率

概览 系统流程图 ✔ 数据流图 不常用 ER图 ✔ 状态转换图 ✔ Warnier图 不常用 IPO图 不常用 Petri网 不常用 层次方框图 不常用 层次图 a.k.a. H图 ✔ 1,层次图描绘软件的层次结构.层层次方框图描绘的是数据结构。 2,层次图的方框表示模块或子模块。层次方框图的方框表示数据结…...

跟我一起做网站/百度招聘2022年最新招聘

1、根目录 如果Tomcat服务器的安装目录是D:\Java_situ\apache-tomcat-8.5.28&#xff0c;那么Tomcat的web服务目录的根目录是&#xff1a;D:\Java_situ\apache-tomcat-8.5.28\webapps\ROOT 例如根目录下放置了example1_1.jsp文件&#xff0c;源码如下 <%page contentType &…...

如何个网站做二维码/百度关键词优化系统

什么是TBS 依托 X5 内核强大的能力&#xff0c;致力于提供优化移动端浏览体验的整套解决方案&#xff08;官网介绍&#xff09;。我们可利用其做文件浏览功能&#xff0c;支持多种文件格式&#xff0c;完全可以满足需求。更多介绍请看官网&#xff1a;http://x5.tencent.com/ 接…...

b2c购物网站/陕西网站seo

常见挖矿病毒处理方法 1、常见病毒 病毒名称&#xff1a;qW3xT&#xff1a; 现象&#xff1a;占用超高CPU&#xff0c;进程查杀之后自启动。 中毒案例&#xff1a;&#xff08;……&#xff09; 2、病毒名称&#xff1a;Ddgs.3011 现象&#xff1a;占用超高CPU&#xff0c;进程…...

做美女图片网站需要备案吗/电子商务网站建设与管理

本项目是一套基于安卓的图书馆管理系统&#xff0c;包括jsp服务端源码&#xff0c;安卓客户端源码和mysql数据库。代码比较简单&#xff0c;供学习anroid与j2ee交互。例如Sqlite的使用、安卓客户端与jsp的web服务端的交互不错的全套完整源码。已包括服务端&#xff0c;搭个Tomc…...

免费做自己的网站有钱赚吗/成都网络推广优化

使用w命令和uptime命令来查看Linux系统的负载在前面的教程中我们有提到&#xff0c;使用top、vmstat等命令可以查看Linux系统CPU的使用率&#xff0c;即系统负载。 除了这些命令外&#xff0c;还可使用w命令和uptime命令来查看Linux系统的负载&#xff0c;一起来了解下吧。1. w…...

初学者做动态网站项目例子/网盘搜索

区别&#xff1a;1,hashmap是无序的&#xff0c;treemap是有序的&#xff0c;整个key是按照自然顺序来的。2,hashmap可以put一个null当key &#xff0c;treemap却不支持。3&#xff0c;底层结构不一样&#xff0c;一个是数组➕红黑树&#xff0c;一个直接就是红黑树。 但是has…...