C++语法|智能指针的实现及智能指针的浅拷贝问题、auto_ptr、scoped_ptr、unique_ptr、shared_ptr和weak_ptr详细解读

文章目录

  • 1.自己实现智能指针
    • 智能指针引起的浅拷贝问题
    • 尝试定义自己的拷贝构造函数解决浅拷贝
  • 2.不带引用计数的智能指针
    • auto_ptr
    • scoped_ptr
    • unique_ptr(推荐)
  • 3.带引用计数的智能指针
    • 模拟实现引用计数
    • shared_ptr和weak_ptr
      • 循环引用(交叉引用)
      • 循环引用导致了什么结果
    • 如何解决
      • 定义对象的时候用强智能指针,引用对象的地方使用弱智能指针
      • 弱智能指针提升为强智能指针,让其拥有裸指针类似的行为
  • 5.多线程访问共享对象问题
    • 给对象添加引用计数使用强弱智能指针监控共享对象
  • 6.自定义删除器
    • 自定义数组删除器
    • 自定义文件资源删除器
    • 使用lambda+function

1.自己实现智能指针

我们在C++变成中使用指针最麻烦的问题就是关于资源的及时释放,尽管我们已经很小心得检查了内存泄漏问题,但还是难免有一些突发情况导致我们定义的指针成为野指针。所以我们对智能指针最简单的要求:

  • 保证做到资源的自动释放
  • 利用栈上的对象出作用域自动析构的特征,来做到资源的自动释放(所以智能指针一定不能放到堆上)
template<typename T>
class SmartPointer {
private:T *mptr;
public:SmartPointer(T *p = nullptr): mptr(p) { }~SmartPointer(): { delete mptr; }T& operator*() { return *mptr;}T* operator->() { return mptr;}
}

由此我们实现了构造、析构函数和重载解引用和重载成员访问运算符,接下来我们来测试其功能。

int main() {SmartPointer<int> ptr1(new int(10));*ptr1 = 20;class Test {public:void test() { cout << "test()" << endl; }};SmartPointer<Test> ptr2(new Test());//(ptr2.operator->())->test();ptr2->test(); //(*ptr).test();return 0;
}

智能指针引起的浅拷贝问题

如果我们沿用上面实现的智能指针,

template<typename T>
class SmartPointer {
private:T *mptr;
public:SmartPointer(T *p = nullptr): mptr(p) { }~SmartPointer(): { delete mptr; }T& operator*() { return *mptr;}T* operator->() { return mptr;}
}
int main() {SmartPointer<int> p1(new int);SmartPointer<int> p2(p1);return 0;
}

我们通过智能指针p1管理一块整型资源,然后拷贝构造p2,发现程序运行崩溃,这是为什么呢?

因为我们这里在做拷贝构造的时候是做的浅拷贝,在程序结束后,把同一块资源释放了两次,造成了内存泄漏。

尝试定义自己的拷贝构造函数解决浅拷贝

template<typename T>
class SmartPointer {
private:T *mptr;
public:SmartPointer(T *p = nullptr): mptr(p) { }SmartPointer(const SmartPointer<T> &src) {mptr = new T(*src.mptr);}~SmartPointer() { delete mptr; }T& operator*() { return *mptr; } //注意这里返回值是一个引用T* operator->() { return mptr; }
};

现在我们在进行拷贝构造的时候,又new了一个空间,所以在析构的时候,各自析构自己的空间。此时代码就不会崩溃了,但是这样又引发了一个新问题:那就是不符合用户区域。

当我们使用p2拷贝构造p1的时候,希望p1和p2管理同一块资源。
也就是说,当用户操作p1和p2的时候,希望操作的是同一个指针,但其实并不是这样的,因为我们进行了深拷贝,所以p1和p2所管理的资源完全就是两块不同的资源。

所以不满足要求!

如何解决呢?

1. 不带引用计数的智能指针

2. 带引用计数的智能指针

2.不带引用计数的智能指针

auto_ptr:在C++17之后不再支持
scoped_ptr unique_ptr:C++11新标准

auto_ptr

auto_ptr<int> ptr1(new int); //C++17标准中删除
auto_ptr<int> ptr2(ptr1);
*ptr2 = 20;
cout << *ptr1 << endl;

现在我们想看看ptr1的值是否被ptr2覆盖,但是我们发现程序崩溃。

这是因为auto_ptr的拷贝构造函数,它先调用了一个release方法,然后返回release的调用结果,其中release的源码如下:

_LIBCPP_INLINE_VISIBILITY _Tp* release() _NOEXCEPT
{_Tp* __t = __ptr_;__ptr_ = nullptr;return __t;
}

他先把原指针拷贝给一个临时变量,然后把原指针置为nullptr,最后返回临时变量。这就表示我们的ptr1被置为空指针了,后来的cout << *ptr1 << endl;只在操作一个空指针了!这是不被允许的。

总结:auto_ptr的解决浅拷贝逻辑就是让后来的指针来管理资源,放弃之前的指针!所以我们不推荐auto_ptr,特别是在容器中不推荐使用,比如说vector<auto_ptr<int>> vec1; vec2(vec1)。我们容器的使用过程中往往会使用容器的拷贝和赋值操作,会导致容器中每个元素的拷贝和赋值,造成容器所有元素失效。

scoped_ptr

scoped_ptr解决浅拷贝问题更加直接,他直接删除了拷贝和赋值操作:

scoped_ptr(const scoped_ptr<T>&) = delete;
scoped_ptr<T>& operator=(const scoped_ptr<T>&) = delete;

unique_ptr(推荐)

这种智能指针也是只让一个指针来管理资源。首先unique_ptr也是做了一个这样的操作:

unique_ptr(const scoped_ptr<T>&) = delete;
unique_ptr<T>& operator=(const scoped_ptr<T>&) = delete;

但是,我们可以使用:

unique_ptr<int> p1(new int);
unique_ptr<int> p2(std::move(p1));

其中std::move可以得到当前变量的右值类型,也就是右值强转操作,那是因为unique_ptr提供了移动构造和移动赋值构造:

unique_ptr(const scoped_ptr<T> &&src)
unique_ptr<T>& operator=(const scoped_ptr<T> &&src)

所以我们的p1的资源全部移动给了p2,所以我们还是不能去访问p1。但是unique_ptr的好吃就是,我们在拷贝构造的过程当中,用户的用意是非常明显的,用户既然都调用move了,所以就是明确要把p1的资源移动给p2。而不像我们在使用auto_ptr在用户没有感知的情况下去操作一个空指针,也不像scoped_ptr那么死板。

3.带引用计数的智能指针

带引用计数的好处就是多个智能指针可以管理同一个资源;

什么叫引用计数呢?给每一个对象资源匹配一个引用计数,当一个智能指针管理这个资源的时候,引用计数+1;当一个智能指针不再使用资源的时候,引用计数-1;并且只要引用计数不为0,就不允许析构;当引用计数为0时,说明该指针已经是管理资源的最后一个指针了,所以它在析构的时候必须释放资源。

这样就完美解决了浅拷贝导致的多次析构同一资源的问题。接下来我们会首先根据之前写的简单的智能指针代码为它添加引用计数的功能。

template<typename T>
class SmartPointer {
private:T *mptr;
public:SmartPointer(T *p = nullptr): mptr(p) { }~SmartPointer(): { delete mptr; }T& operator*() { return *mptr;}T* operator->() { return mptr;}
}
int main() {SmartPointer<int> p1(new int);SmartPointer<int> p2(p1);return 0;
}

模拟实现引用计数

  • 首先我们完成一个对资源进行引用计数的类,这个类非常简单,主要就是能够记录有多少个智能指针指向了这个资源。
    其中成员方法addRef表示引用计数+1操作
    delRef表示引用计数-1操作并且返回当前指向资源的指针个数。
template <typename T>
class RefCnt {
public:RefCnt(T *ptr = nullptr): mptr(ptr), mcount(1) {if (mptr != nullptr)mcount = 1;}void addRef() { mcount++; } //添加资源的引用计数int delRef() { return --mcount;}  
private:T *mptr;int mcount;
};
  • 然后是重写我们的构造函数和析构函数
template<typename T>
class SmartPointer { //shared_ptr
private:T *mptr;    // 指向资源的指针RefCnt<T> *mpRefCnt; //指向该资源引用计数对象的指针
public:SmartPointer(T *p = nullptr): mptr(p) {mpRefCnt = new RefCnt<T>(mptr);}~SmartPointer() { if (0 == mpRefCnt->delRef()){delete mptr; mptr = nullptr;}}

构造函数需要初始化一个RefCnt,析构函数判断只有当引用计数为0的时候,才对资源进行释放。

  • 重构拷贝构造函数
    SmartPointer(const SmartPointer<T> &src):mptr(src.mptr), mpRefCnt(src.mpRefCnt) {if (mptr != nullptr)mpRefCnt->addRef();       }

拷贝构造需要首先把当前资源给过去,然后把当前的引用计数对象也给拷贝变量,并且我们需要把计数+1,这里调用的是引用计数类的addRef方法

  • 重构重载赋值运算符
    SmartPointer<T>& operator=(const SmartPointer<T> &src) {if (this == &src)return *this;if(0==mpRefCnt->delRef()){ //给原来使用的资源减少一个引用计数delete mptr;}mptr = src.mptr;mpRefCnt = src.mpRefCnt;mpRefCnt->addRef();return *this;}

在这里我们基本实现了一个shared_ptr的核心代码,不过我们这里还存在一个问题,再多线程操作中,该类涉及到了对共享资源count的频繁操作,所以标准库中的智能指针模板都是原子操作的,也就是说他们都是线程安全的。

shared_ptr和weak_ptr

shared_ptr为强智能指针,可以改变资源的引用计数。weak_ptr为弱智能指针,不会改变资源的引用计数。弱智能指针用来观察强智能指针,强智能指针来观察资源(内存)

为什么我们需要强弱智能指针呢?

主要就是因为我们的强智能指针有循环引用的问题。

循环引用(交叉引用)

class B;
class A {
public:A() { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }weak_ptr<B> _ptrb;
};class B {
public:B() { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }_ptra->testA();weak_ptr<A> _ptra;
};
int main () {shared_ptr<A> pa(new A());shared_ptr<B> pb(new B());cout <<  pa.use_count() << endl;cout <<  pb.use_count() << endl;return 0;
}

输出时:

A()
B()
1
1
~A()
~B()

符合资源分配的预期。那如果我们在main函数中做这样一个事情。

int main () {shared_ptr<A> pa(new A());shared_ptr<B> pb(new B());pa->_ptrb = pb;pb->_ptra = pa;cout <<  pa.use_count() << endl;cout <<  pb.use_count() << endl;return 0;
}

循环引用导致了什么结果

如果执行该程序,我们会发现我们的A、B类竟然没有析构,并且引用计数都是2.

引用计数造成了new出来的资源无法释放!! 这是严重的资源泄漏问题。

我们首先一段一段讲解代码。

首先我们new了两个堆内存 A类 和 B类,A类中有一个指向B类的智能指针_ptrb;B类中有一个指向A的智能指针_ptra。

栈上初始化了两个智能指针 shared_ptr<A> pa(new A()); shared_ptr<B> pa(new B());分别指向堆内存上的A类和B类:

此时我们的智能指针计数分别是 1 1

现在,我们堆上的_ptrb也指向了B类,所以堆上放B类的内存资源计数为2;
同理,_ptra指向了A类,A类的资源计数也为2;

所以我们打印资源计数的时候也是两个2,所以出作用域的时候,pb先析构,然后析构pa,但是他们并不能释放资源,因为2-1 = 1,所以堆上的内存还不能析构。

这就是典型的循环引用问题。

如何解决

定义对象的时候用强智能指针,引用对象的地方使用弱智能指针

class B;
class A {
public:A() { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }void testA() { cout << "非常好的方法!" << endl; }weak_ptr<B> _ptrb;
};class B {
public:B() { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }weak_ptr<A> _ptra;
};

此时就能完成正常的打印。A、B对象都析构了,这是为什么呢?
看下图:

堆上的资源都是弱智能指针,由于弱智能指针不会改变资源的引用计数,那也就是说我们_ptrb和_ptra都分别指向了A类和B类,但是由于他们是弱智能指针,他只起一个观察的作用,也就是说观察这个资源还活着没。所以说我们两个资源的引用计数都是 1 和 1,那么等智能指针作用域结束,资源也就能正常释放了。

弱智能指针提升为强智能指针,让其拥有裸指针类似的行为

假如说我们的A类有一个“非常好用的方法”,我们希望能够在B类中利用那个指向A类的成员属性来调用那个“非常好用的方法”。
如下:

class B;
class A {
public:A() { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }void testA() { cout << "非常好的方法!" << endl; }weak_ptr<B> _ptrb;
};
class B {
public:B() { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }void func () {_ptra->testA();}weak_ptr<A> _ptra;
};

但是很可惜,_ptra->testA()错误,不能这样调用,因为我们的weak_ptr只能观察资源,他不能使用资源,也就是说弱智能指针根本就没有提供operator*和operator->。我们不能把它当一个裸指针来操作。

那应该如何使用呢?

class B {
public:B() { cout << "B()" << endl; }~B() { cout << "~B()" << endl; }void func () {shared_ptr<A> ps = _ptra.lock(); //弱智能指针的提升方法,提升为强智能指针if (ps != nullptr) {ps->testA();}}weak_ptr<A> _ptra;
};

调用弱智能指针的lock()方法,把它提升为一个强智能指针。再一个,我们需要注意,在多线程编程中,由于weak_ptr只作为一个观察着,所以我们在使用提升方法的过程中,又可能提升失败,因为资源有可能已经释放了,所以我们必须检查调用lock()方法后生成的指针不为空,才说明提升强智能指针成功。这样我们才能在B类中调用A类的那个非常好用的方法。
并且,我们在主函数中使用它。

int main () {shared_ptr<A> pa(new A());shared_ptr<B> pb(new B());pa->_ptrb = pb;pb->_ptra = pa;cout <<  pa.use_count() << endl;cout <<  pa.use_count() << endl;pb->func();return 0;
}

5.多线程访问共享对象问题

加入有一个类A

class A {
public:A() { cout << "A()" << endl; }~A() { cout << "~A()" << endl; }void testA() { cout << "非常好的方法!" << endl; }
};

我们在main函数里new了一个对象A,然后启动一个线程,传入线程函数,随后是释放资源A。最后等待线程结束。在子线程中我们调用A类那个非常好用的方法。

void handler01(A *q) {q->testA();
}
int main () {A *p = new A();thread t1(handler01, p);std::this_thread::sleep_for(std::chrono::seconds(2));delete p;t1.join();return 0;
}

这个过程是没有任何问题的。


我们在handler01中模拟这样一个问题:我们让子线程睡两秒,不让主线程睡了,也就是说,我们想先delete掉A类这块资源,然后再让子线程来访问这块资源,按道理来说这是不被允许的,然而,子线程还是完成了调用,不符合我们的预期。

void handler01 (weak_ptr<A> pw) {std::this_thread::sleep_for(std::chrono::seconds(2));sp->testA();
}
int main () {A *p = new A();thread t1(handler01, p);delete p;t1.join();return 0;
}

这非常不合理!因为析构也就意味着我们已经把外部资源释放了,原来资源已经啥都没有了,但是子线程仍然在进行访问。


所以我们希望q在访问A对象的时候,需要侦测一下A对象是否存活,如果存活,我们可以访问,如果已经被析构了,我们就不应该调用该方法。
这就是我们的多线程访问共享对象的安全问题。

给对象添加引用计数使用强弱智能指针监控共享对象

在主函数的初始化中,我们定义一个强智能指针,并且给线程仍一个弱智能指针,然后去掉delete,因为有智能指针帮我们做资源释放,并且我们加一个作用域来模拟资源被析构时,观察子线程还能够访问A对象:

int main () {{shared_ptr<A> p(new A());thread t1(handler01, weak_ptr<A>(p));t1.detach();std::this_thread::sleep_for(std::chrono::seconds(1));}std:this_thread::sleep_for(std::chrono::seconds(5));return 0;
}

引用的时候用一个弱智能指针,并且在访问A对象的时候,侦测A是否存活

//子线程
void handler01 (weak_ptr<A> pw) {std::this_thread::sleep_for(std::chrono::seconds(2));//q访问A对象的时候,需要侦测一下A对象是否存活shared_ptr<A> sp = pw.lock();if (sp != nullptr)sp->testA();else cout << "A对象已经析构,不能再访问!" << endl;
}

最后我们能观察到理想中的结果:

A()
~A()
A对象已经析构,不能再访问!

然后将handler01睡觉的时间改成1,main函数子作用域的睡觉时间改成2,也就是说子线程在调用A对象的时候A还活着。
打印结果如下:

A()
非常好的方法!
~A()

符合预期!

6.自定义删除器

我们都知道智能指针能够保证资源的绝对释放,在之前,释放资源都是用的delete ptr.
那比如说,如果我们用智能指针来管理数组的资源,那么得在中间加一个中括号,又比如说用智能指针来管理一块文件资源或者是其他资源,那么释放这些资源也不是用的delete。

那么就需要思考一个问题了,如何给智能指针来自定义一个删除器来指导智能指针正确得删除资源呢?

库中的unique_ptr和shared_ptr都提供了自定义的删除器,如果看一下他们的源码的话会发现他们的析构函数调用了一个函数对象,通过对函数对象的调用来deletor(ptr)

~unique_ptr() { 函数对象的调用 	deletor(ptr) }template<typename T>
class default_delete {
public:void operator() (T *ptr) {delete ptr;}
}

自定义数组删除器

如果我们想自定义删除对象的方式,我们直接给他提供一个这样的模板即可,比如说

int main () {unique_ptr<int> ptr1(new int[100]); //delete []ptrreturn 0;
}

我们自己写一个删除资源的方法

template<typename T>
class MyDeletor {
public:void operator() (T *ptr)const{cout << "call MyDeletor.operator()" << endl;delete []ptr;}
};//调用
int main () {unique_ptr<int, MyDeletor<int>> ptr1(new int[100]); //delete []ptrreturn 0;
}

我们可以很顺利的看到终端打印出 call MyDeletor.operator()。说明用到了我们自己定义的删除器。

自定义文件资源删除器

template<typename T>
class MyFileDeletor {
public:void operator() (T *ptr)const{cout << "call MyDeletor.operator()" << endl;fclose(ptr);}
};//调用
int main () {unique_ptr<FILE, MyFileDeletor<FILE>> ptr2(fopen("data.txt", "w"));return 0;
}

我们可以很顺利的看到终端打印出 call MyFlieDeletor.operator()。说明用到了我们自己定义的删除器。


但是这样自定义删除器不是特别好,因为我们往往需要定义一个模板类型,然后只使用在智能指针定义的语句当中,其他地方都再也用不到了,这个东西就像我们的临时量一样,它的使用只出现在某一个语句当中,那么有没有什么方法可以让我们直接在语句当中去指定我们自定义的删除器,而不用啰哩啰嗦的去自定义上面的两个模板类出来呢?

没错!!答案就是使用lambda表达式!
然而,定义智能指针的时候需要指定删除器的类型,但是我们只有lambda表达式的对象,那么lambda表达式对象的类型如何确定呢?
没错!!function函数对象!他可以留下我们lambda表达式的类型

使用lambda+function

我们在第一个传入模版类型的时候传入function,初始化列表中的第二个参数中写上lambda表达式

int main () {unique_ptr<int, funciton<void (int*)>> ptr1(new int[100], [](int *p)->void{ cout << "call lambda release new int[100]" << endl;delete[]p;});unique_ptr<FILE, funciton<void (FILE*)>> ptr2(fopen("data.txt", "w"), [](FILE *p)->void{ cout << "call lambda release new fopen" << endl;fclose(p);});}

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

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

相关文章

DDD架构理论详解

文章目录 一、概念入门1. 概念简介2. DDD的核心理念3. 范式4. 模型5. 框架6. 方法论7. 软件设计的主要活动 二、DDD核心理论1. Domain领域层都包含什么&#xff1f;2. 聚合、实体和值对象3. 仓储&#xff0c;封装持久化数据4. 适配&#xff08;端口&#xff09;&#xff0c;调用…

AI应用案例:新闻文本分类

随着科学技术的不断发展&#xff0c;互联网技术得以快速的发展和普及&#xff0c;并已在各行各业得到了广泛的应用&#xff0c;从中致使了网络上的信息呈现出爆炸式的增长状态&#xff0c;达到了“足不出户&#xff0c;万事皆知”的境况&#xff0c;充分体现了互联网新闻给生活…

Java实现自定义注解,实现不需要token 验证就可以访问接口

目录 1 问题2 实现 1 问题 一个springboot 项目&#xff0c;需要token 验证&#xff0c;前端传过来token ,我们一般在项目全局写一个过滤器&#xff0c;去验证前端传过来的token ,如果有哪些接口不需要token验证&#xff0c;那么就排除这些接口&#xff0c;这个也需要配置。 …

前端 | TED打卡号分类查询

文章目录 &#x1f4da;实现效果&#x1f4da;模块实现解析&#x1f407;html&#x1f407;css&#x1f407;javascript &#x1f4da;实现效果 提供完整TED打卡号对应TED标题的查询列表 根据分类按需查询 &#x1f4da;模块实现解析 &#x1f407;html 搭框架<div cl…

Android Studio连接MySQL8.0

【序言】 移动平台这个课程要做一个app的课设&#xff0c;我打算后期增加功能改成毕设&#xff0c;就想要使用MySQL来作为数据库&#xff0c;相对于SQLlite来说&#xff0c;我更熟悉MySQL一点。 【遇到的问题】 一直无法连接上数据库&#xff0c;开始的时候查了很多资料&#…

海外云手机解决海外社交媒体运营难题

随着全球数字化浪潮的推进&#xff0c;海外社交媒体已成为外贸企业拓展市场、提升品牌影响力的重要阵地。Tiktok、Facebook、领英、twitter等平台以其庞大的用户基础和高度互动性&#xff0c;为企业提供了前所未有的营销机会。本文将介绍如何通过海外云手机&#xff0c;高效、快…

eNSP中小型园区网络拓扑搭建(下)

→b站直通车&#xff0c;感谢大佬← →eNSP中小型园区网络拓扑搭建&#xff08;上&#xff09;← 不带配置命令的拓扑图已上传~ 配置ospf SW5 # ospf 1 router-id 5.5.5.5area 0.0.0.0network 192.168.51.5 0.0.0.0network 192.168.52.5 0.0.0.0area 0.0.0.10network 192.1…

elk + filebeat 8.4.3 收集nginx日志(docker部署)

ELK filebeat docker部署 一、 elasticsearch部署1、运行elasticsearch临时配置容器2、拷贝文件目录到本地3、检查elasticsearch.yml4、删除之前elastic&#xff0c;运行正式容器5、docker logs记录启动日志 二、部署kibana1、运行kibana临时配置容器2、docker拷贝配置文件到本…

数据链路层——计算机网络学习笔记三

使用点对点信道的数据链路层 前言&#xff1a; 1.数据链路层的重要性&#xff1a;网络中的主机、路由器都必须实现数据连输层&#xff1b; 2.数据链路层中使用的信道&#xff1a; 点对点信道&#xff1a;这种信道是一对一的通信方式&#xff1b; 广播信道&#xff1a;使用一对多…

硬盘架构原理及其算法RAID工作原理写惩罚

一、硬盘的架构以及寻址原理 硬盘工作原理&#xff1a; 硬盘寻址原理&#xff1a;逻辑顺序磁道、盘片、扇区&#xff08;顺序CHS&#xff09; 二、机械硬盘算法 读取算法 寻道算法 个人与企业适合的算法和寻道 个人使用的机械硬盘适合的寻道算法和读取算法是&#xff1a…

WPS表格:使用vlookup函数解决乱序数据对应问题

我们常常会遇到两个表格的内容相同&#xff0c;但是顺序不一致的情况。并且这种顺序无关于简单的排序&#xff0c;而是一种业务性很强的复杂排序规则。下面我举个例子&#xff0c;使用VLOOKUP复制数据。 假设太阳系行星举办了一次卖萌比赛&#xff0c;由太阳妈妈决定谁是最萌的…

ElasticSearch 8.X 源码导入idea并配置环境启动调试(mac环境)

主要是用于自己记录配置流程 环境 IntelliJ IDEA 2024.1.1 (Community Edition) jdk17&#xff08;可以安装jenv管理&#xff09; macos 14.4.1 gradle 8.5 资源准备 先在官网下载elasticsearch源码&#xff08;GitHub - elastic/elasticsearch: Free and Open, Distrib…

GeoServer安装以及部署

GeoServer介绍 GeoServer是一个开源的服务器软件&#xff0c;用于共享和编辑地理空间数据。它支持多种地理空间数据格式&#xff0c;并且可以发布为多种服务格式&#xff0c;如Web Feature Service (WFS)、Web Map Service (WMS)、Web Coverage Service (WCS)&#xff0c;以及…

SeetaFace6人脸特征提取与对比C++代码实现Demo

SeetaFace6包含人脸识别的基本能力&#xff1a;人脸检测、关键点定位、人脸识别&#xff0c;同时增加了活体检测、质量评估、年龄性别估计&#xff0c;并且顺应实际应用需求&#xff0c;开放口罩检测以及口罩佩戴场景下的人脸识别模型。 官网地址&#xff1a;https://github.co…

pyqt颜色变换动画效果

pyqt颜色变换动画效果 QPropertyAnimation介绍颜色变换效果代码 QPropertyAnimation介绍 QPropertyAnimation 是 PyQt中的一个类&#xff0c;它用于对 Qt 对象的属性进行动画处理。通过使用 QPropertyAnimation&#xff0c;你可以平滑地改变一个对象的属性值&#xff0c;例如窗…

Python-VBA函数之旅-str函数

目录 一、str函数的常见应用场景 二、str函数使用注意事项 三、如何用好str函数&#xff1f; 1、str函数&#xff1a; 1-1、Python&#xff1a; 1-2、VBA&#xff1a; 2、推荐阅读&#xff1a; 个人主页&#xff1a; https://myelsa1024.blog.csdn.net/ 一、str函数的常…

iphone进入恢复模式怎么退出?分享2种退出办法!

iPhone手机莫名其妙的进入到了恢复模式&#xff0c;或者是某些原因需要手机进入恢复模式&#xff0c;但是之后我们不知道如何退出恢复模式怎么办&#xff1f; 通常iPhone进入恢复模式的常见原因主要是软件问题、系统升级失败、误操作问题等导致。那iphone进入恢复模式怎么退出&…

异常检测的学习和实战

1.应用&#xff1a; 1.在工业上的应用 当检测设备是否处于异常工作状态时&#xff0c;可以由上图分析得到&#xff1a;那些零散的点对应的数据是异常数据。因为设备大多数时候都是处于正常工作状态的&#xff0c;所以数据点应该比较密集地集中在一个范围内&#xff0c;而那些明…

【数据结构练习题】Map与Set——1.只出过一次的数字2.复制带随机指针的链表3.宝石与石头4.坏键盘打字

♥♥♥♥♥个人主页♥♥♥♥♥ ♥♥♥♥♥数据结构练习题总结专栏♥♥♥♥♥ ♥♥♥♥♥【数据结构练习题】堆——top-k问题♥♥♥♥♥ 文章目录 1.只出过一次的数字1.1问题描述1.2思路分析1.3绘图分析1.4代码实现2.复制带随机指针的链表2.1问题描述2.2思路分析2.3绘图分析2.4代…

远程点击没反应

目录 todesk远程登录后点击没反应 解决方法&#xff1a; 方法1 快捷键&#xff1a; 方法2 界面点击Ctrl Alt Delete todesk&#xff0c;向日葵远程登录后点击没反应 todesk远程登录后点击没反应 解决方法&#xff1a; 方法1 快捷键&#xff1a; Ctrl Alt Delete 方法…