【C++】智能指针

文章目录

  • 📖 前言
  • 1. 智能指针的引入
    • 1.1 内存泄露的危害:
    • 1.2 异常安全中的内存泄露:
    • 1.3 RAII思想:
    • 1.3 拦截异常解决不了的内存泄漏:
    • 1.4 智能指针解决:
  • 2. 智能指针的拷贝
    • 2.1 直接拷贝的问题:
    • 2.2 auto_ptr:
    • 2.3 unique_ptr:
    • 2.4 shared_ptr:
  • 3. 循环引用
    • 3.1 循环引用的过程:
    • 3.2 weak_ptr:

📖 前言

前倾回顾,在我们之前学习异常的时候,讲到过异常安全的问题,会有内存泄露的问题。

  • 内存泄露这个问题对程序员的要求很高,申请的空间就必须要手动释放,不像Java这种语言自带垃圾回收器(gc)
  • 就算是我们手动释放了空间,也有可能存在内存泄露的问题(异常安全),抛异常时会乱跳,有可能就会导致即使手动释放了,也没会内存泄露。
  • 上节在异常种我们可以通过拦截异常手动释放掉,但是防不胜防并不是所有的都能拦截到,于是C++就引入了智能指针。

异常安全回顾: 传送门


1. 智能指针的引入

1.1 内存泄露的危害:

什么是内存泄漏
  • 内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。
  • 内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,因而造成了内存的浪费。

内存泄漏的危害:

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

1.2 异常安全中的内存泄露:

在上一章节我们在异常安全中,我们提到了,抛异常时会存在内存泄露的问题,即使是手动放了空间,也有可能在抛异常之后还是会导致内存泄露,因为没有执行到释放空间那段代码。

我们当时采用的是拦截异常的方法,但是还是会有防不胜防的情况:


1.3 RAII思想:

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

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

这种做法有两大好处:

  • 不需要手动显式地释放资源
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效

注意:

  • RAII只是智能指针的一种思想,切不可说RAII就是智能指针

1.3 拦截异常解决不了的内存泄漏:

在上节中我们讲到了在抛异常时会出现内存泄漏的问题,即使是手动释放掉内存也会出现内存泄漏,原因时抛异常时直接跳到了捕获的地方,所以会泄露。

  • 对于上述问题我们也给出了对应的解决办法,那就是拦截异常的方式
  • 为了避免内存泄漏,我们先将异常拦截下来,先释放掉再将捕获的异常抛出

本以为有了上述方法再也不会出现内存泄露了,直到我遇到了下面的问题:

void func()
{
	int* p1 = new int[10]; //这里亦可能会抛异常
	int* p2 = new int[10]; //这里亦可能会抛异常 -- 这里抛异常,p1就没释放掉
	int* p3 = new int[10]; //这里亦可能会抛异常
	int* p4 = new int[10]; //这里亦可能会抛异常

	try
	{
		div();
	}
	catch (...)
	{
		delete[] p1;
		delete[] p2;
		delete[] p3;
		delete[] p4;

		throw;
	}

	delete[] p1;
	delete[] p2;
	delete[] p3;
	delete[] p4;
}

问题:

  • 假设我们每个new出来的空间非常大,我们也不确定到底是哪个new失败了

  • 然后下面拦截异常拦截到了,开始手动释放到空间

  • 结果我们也不知道到底是哪个new空间失败了,也不知道该怎么释放空间

  • 很有可能就将没有申请空间的指针给释放掉了

  • 这种情况怎么处理怎么难受。在这里插入图片描述

  • 除非我们在try外层现将每个指针置成空,然后在try内部开空间,开空间失败后,下面捕获到对每个指针挨个判空,非空就释放,或者说直接释放(C++对释放空指针没有规定),真的是这样子能把人烦死~~😱

所以此时C++就提供了智能指针~


1.4 智能指针解决:

引入智能指针:

template<class T>
class SmartPtr
{
public:
	//RAII思想
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}

	~SmartPtr()
	{
		cout << "delete" << _ptr << endl;
		//delete[] _ptr;
		delete _ptr;
		_ptr = nullptr;
	}

	//像指针一样
	T& operator*()
	{
		return *_ptr;
	}

	T* operator->()
	{
		return _ptr;
	}

	T* Get()
	{
		return _ptr;
	}
private:
	T* _ptr;
};
  • 将一个指针封装成一个类,new的资源直接交给智能指针的对象去管理,起到了托管的作用。
  • 借助对象的生命周期(析构函数)管理资源。

有了智能指针之后我们就有了以下的解决办法:

void func()
{
	//new的资源直接交给对象去管理
	SmartPtr<int> sp1(new int[10000]);
	
	//如果这里抛异常就直接栈帧销毁,出作用域,调用析构函数,后面代码不执行
	SmartPtr<int> sp2(new int);
	
	SmartPtr<int> sp3(new int);
	SmartPtr<int> sp4(new int);
	SmartPtr<pair<string, int>> sp5(new pair<string, int>("sort", 1));

	//借助对象的生命周期(析构函数)管理资源
	//无论如何都会正常释放资源,抛异常也好,中间抛异常也好,或者是正常结束

	*sp1 = 0;
	sp5->second++;
}
  • 利用对象生命周期的特性:出了作用域之后自动调用对象的析构函数,通过析构函数来释放空间。
  • 无论如何都会正常释放资源,抛异常也好,中间抛异常也好,或者是正常结束,出了作用域就调用对象析构函数。

2. 智能指针的拷贝

2.1 直接拷贝的问题:

智能指针衍生的问题~

  • 智能指针管理资源的方式我们不难理解,但是智能指针的拷贝却是个令人头疼的问题
  • 我们知道我们只是将指针封装了一层
  • 如果是简单的只拷贝的话,会出两个指针指向同一块资源
  • 在释放的时候会发生同一块空间释放多次的问题

我们上述实现的智能指针,当一个智能指针赋值给另一个智能指针的时候,是明显的一个浅拷贝(值拷贝),那么在析构的时候就会出现问题:

int main()
{
	SmartPtr<int> sp1(new int);

	//当前拷贝会有析构两次的问题
	SmartPtr<int> sp2(sp1);

	return 0;
}

在这里插入图片描述
问题出现了,当两个智能指针共同管理同一块空间,在析构的时候就会将同一块空间释放两次~

如何解决当下问题:

  • 有的小伙伴脱口而出的就是 — 深拷贝
  • 真的可以这样嘛??我们先来回一下深拷贝:
    • 深拷贝是将整个资源拷贝一份
    • 然后再用另一个指针来管理这个拷贝出来的的空间

补充:

为什么迭代器可以支持浅拷贝,而不会产生多次释放同一块空间的问题呢?
原因:首先不存在两个迭代器指向同一个空间的情况,其次结点的释放不归迭代器管,而是由容器内部管理释放,综上迭代器就是需要浅拷贝。

那我们的是想让sp2来管理一块新的资源嘛?很显然不是,我们是想让两个智能指针共同管理同一块资源。

那我们能否用静态的指针呢?

  • 假设每一个智能指针内的成员指针是静态的
  • 那么问题来了,虽然这实现了两个指针(实则同一个指针)管理同一个空间
  • 但是如果有其他的空间要管理,用的还是这个指针(因为是静态的),很显然并不妥当
  • 用静态的也解决不了,因为不同对象有不同的空间

2.2 auto_ptr:

我们先来看C++98给的解决办法:

在这里插入图片描述
核心思想:

  • 管理权转移,被拷贝的对象悬空。

模拟实现:

namespace YY
{
	template<class T>
	class auto_ptr
	{
	public:
		//RAII思想
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}

		//sp2(sp1) -- 拷贝构造(简直就是神人)
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			//将sp1置空,交给sp2管了,sp1不管了
			sp._ptr = nullptr;
		}

		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		T* get()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};
}

思路:

  • auto_ptr支持拷贝,但是方式很挫
  • 拷贝之后直接将原来的指针给置空了
  • 这要是不知情的人使用了原来指针,直接就造成非法访问
  • 每个学C++的人都要来瞻仰一下auto_ptr,然后都要来吐槽一下
  • 很多公司明确要求,不能使用auto_ptr

在这里插入图片描述


2.3 unique_ptr:

我们再来看C++11给的解决办法:
在这里插入图片描述
核心思想:

  • 不让拷贝 / 防拷贝 — 拷贝编译就报错。

模拟实现:

namespace YY
{
	//不能拷贝用unique_ptr
	template<class T>
	class unique_ptr
	{
	public:
		//RAII思想
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~unique_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
				_ptr = nullptr;
			}
		}

		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		T* get()
		{
			return _ptr;
		}

		//C++98的方式:
		//private:
		//sp2(sp1)
		//1、只声明,不实现(不声明会默认生成一个)
		//2、声明成私有
		//不过还是有问题,在类里面可以调用
		//unique_ptr(const unique_ptr<T>& sp);

		//C++11的方式:
		unique_ptr(const unique_ptr<T>& sp) = delete;
		unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	private:
		T* _ptr;
	};
}

因为存在两个指针指向同一个空间造成两次释放的问题,所以我们直接禁止其拷贝 / 赋值

C++98中提供的方式:

*

  • 只声明,不实现(不声明会默认生成一个)— 此时会报一个链接错误
  • 声明成私有(防止有人在类外头将这个函数实现了) — 此时会报编译错误,私有不可访问
  • 不过还是有问题,在类里面可以直接赋值(这个是真的防不住,不过一般类是不会被人修改的)

C++11中提供的方式:

在这里插入图片描述
不生成对应函数的默认版本。

unique直接不给拷贝,没有隐患,但是功能不全


2.4 shared_ptr:

C++11中开始提供更靠谱的并且支持拷贝的shared_ptr

在这里插入图片描述
核心思想:

  • 核心原理就是引用计数,记录几个对象管理这块资源,析构的时候 - -(减减)计数,最后一个析构的对象释放资源。

上述几个智能指针都回避掉了指针的值拷贝,但是真的能回避掉吗,总有的场景是需要用到拷贝的。

思路:

  • 用到引用计数的方式,拷贝就计数++,析构就计数- -
  • 最后一个析构的对象释放资源

这时,对计数这个变量(count)就有要求了,要求共同管理同一个对象的时候要做到对同一个count ++ 或 - -

此时我们首先想到的就是将count设置成静态的变量,但是这样是不行的~

在这里插入图片描述
_count竟然减到-2去了。

  • 用静态的话下面就会有问题,因为静态类型的变量,属于这个类的所有对象
  • sp3应该有自己的一个_count,然而这时的sp3中的_count被构造函数重新构造了
  • 静态的_count被改成了1,导致析构的时候,sp3的_count变成了0,sp2将_count变成了-1,sp1将_count变成了-2

导致sp2 和 sp1的共同管理的空间没有被释放掉。

应该释放两次,我们期望一个资源跟一个计数,并且我改变,你也要跟着变。

核心点:

  • 可以几个智能指针共同管理同一个资源,这几个智能指针的计数可以相互影响
  • 但是当另一块资源也需要被管理时,就需要另一个智能指针来管理
  • 这个智能指针里的计数和上述的智能指针里的计数互不影响

模拟实现:

namespace YY
{
	//指针版本(可采用)
	//要想拷贝就用shared_ptr
	template<class T>
	class shared_ptr
	{
	public:
		void Release()
		{
			if (--(*_pCount) == 0 && _ptr)
			{
				cout << "delete:" << _ptr << endl;
				//将资源释放掉
				delete _ptr;
				_ptr = nullptr;

				//将计数释放掉,因为也是new出来的,在堆上
				delete _pCount;
				_pCount = nullptr;
			}
		}

		//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)++;
		}

		//sp1 = sp3
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (this != &sp)
				//	if (_ptr != sp._ptr)
			{
				//--(*_pCount);
				//减减之后还要释放,不然内存泄露
				//sp1和sp2都交给sp3管理了
				Release();

				_ptr = sp._ptr;
				_pCount = sp._pCount;
				++(*_pCount);
			}

			return *this;
		}

		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

		T* get() const
		{
			return _ptr;
		}

	private:
		T* _ptr;
		//搞一个计数,直接定义一个变量不行,因为一个对象中_count的改变不会影响另一个对象的
		int* _pCount;
	};
}

拷贝构造:

  • 说明又增加了一个指向该空间管理的指针
  • 引用计数++

在这里插入图片描述
赋值重载:

  • 现将原来的指向的空间释放掉或者计数 - -
  • 再将指针赋值过来,将新指向这块空间的指针计数++
    在这里插入图片描述

可以正常的析构掉:
在这里插入图片描述
相同的资源实现了用两个指针来管理,同时实现了管理相同资源用同一个计数。

  • 我们通过传指针的方式,拷贝构造时直接将计数的指针赋值
  • 这样就实现了拷贝之后用同一个计数

这里还存在一些多线程的问题,我们以后再说~~


3. 循环引用

我们先来看一段代码:

struct ListNode
{
	ListNode* _next = nullptr;
	ListNode* _prev = nullptr;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	ListNode* p1 = new ListNode;
	ListNode* p2 = new ListNode;

	p1->_next = p2;
	p2->_prev = p1;

	//中间抛异常了还释放不了,考虑用智能指针

	delete p1;
	delete p2;

	return 0;
}

能够正常释放:
在这里插入图片描述

我们简单的实现了以下双向链表的部分:

  • 在申请结点和释放结点之间如果抛了异常
  • 就会存在内存泄漏的问题
  • 所以此时就有小伙伴想用智能指针来管理结点

问题接踵而至~

  • 那就是链接的时候匹配不上,类内部用原生指针
  • 外面链接的时候则是用智能指针链接
  • 那就只能将类内部的原生指针换成智能指针

这不换不要紧,一换就出了大问题~~😱😱😱

struct ListNode
{
	YY::shared_ptr<ListNode> _prev = nullptr;
	YY::shared_ptr<ListNode> _next = nullptr;

	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};

int main()
{
	YY::shared_ptr<ListNode> p1(new ListNode);
	YY::shared_ptr<ListNode> p2(new ListNode);

	p1->_next = p2;
	p2->_prev = p1;

	return 0;
}

运行结果啥都没有,说明内存泄露了,哪个结点都没释放!!

3.1 循环引用的过程:

为了更好的理解,看下图:

在这里插入图片描述
从图中我们可以清晰看出来:

  • p1和右边结点里的_prve共同管理着左边结点
  • p2和左边结点里的_next共同管理着右边结点
  • 当一个结点中有自定义类型的时候,其自定义类型的对象析构是在该结点析构的时候才析构的。
  • 一个对象析构时,该对象内置类型不处理,自定义类型调用其析构函数。

重点:

  • 右边结点析构,右边结点的_prve才能析构
  • 左边结点的_next析构,右边结点才能析构
  • 左边结点析构,左边结点的_next才能析构
  • 右边结点的_prve析构,左边结点才能析构
  • 右边结点析构,右边结点的_prve才能析构
  • ……

直接无限循环了,一整个尬住了。。。😅😅😅

只有其中一个智能指针不引用计数,才能打破这个循环~


3.2 weak_ptr:

在这里插入图片描述

  • 其他的智能指针的构造函数可以传一个指针
  • weak_ptr构造函数不支持接收指针,不管理资源
  • 但是它接收一个shared_ptr,可以通过shared_ptr来构造weak_ptr
  • 可以指向一块空间,但是不参与空间的管理

在这里插入图片描述

  • 可以说weak_ptrshared_ptr的小弟 — 不是传统的智能指针
  • 专门用来辅助解决shared_ptr循环引用的问题

模拟实现:

namespace YY
{
	//不参与指向资源的释放管理,意义就是解决循环引用的问题
	//不需要RAII
	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)
		{
			if (_ptr != sp.get())
			{
				_ptr = sp.get();
			}

			return *this;
		}

		//像指针一样
		T& operator*()
		{
			return *_ptr;
		}

		T* operator->()
		{
			return _ptr;
		}

	public:
		T* _ptr;
	};
}

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

STM32实战项目-触摸按键

前言&#xff1a; 通过触摸按键控制LED灯以及继电器&#xff0c;具体实现功能如下&#xff1a; 1、触摸按键1单击与长按&#xff0c;控制LED1&#xff1b; 2、触摸按键2单击与长按&#xff0c;控制LED2; 3、触摸按键3单击与长按&#xff0c;控制LED3; 4、触摸按键4单击与长…

详解Spring、SpringBoot、SpringCloud三者的联系与区别

一、Spring Spring 是一个轻量级的Java 开发框架&#xff0c;主要依存于SSM 框架&#xff0c;即Spring MVC Spring Mybatis&#xff0c;定位很明确&#xff0c;Spring MVC主要负责view 层的显示&#xff0c;Spring 利用IOC 和AOP 来处理业务&#xff0c;Mybatis则是数据的持…

跨域解决方案

跨域解决方案 1.跨域基本介绍 文档&#xff1a;https://developer.mozilla.org/zh-CN/docs/Web/HTTP/CORS 跨域问题是什么&#xff1f; 一句话&#xff1a;跨域指的是浏览器不能执行其他网站的脚本&#xff0c;它是由浏览器的同源策略造成的&#xff0c;是浏览器对 javascr…

数据结构 | 栈的中缀表达式求值

目录 什么是栈&#xff1f; 栈的基本操作 入栈操作 出栈操作 取栈顶元素 中缀表达式求值 实现思路 具体代码 什么是栈&#xff1f; 栈是一种线性数据结构&#xff0c;具有“先进后出”&#xff08;Last In First Out, LIFO&#xff09;的特点。它可以看作是一种受限的…

“国产版ChatGPT”文心一言发布会现场Demo硬核复现

文章目录前言实验结果一、文学创作问题1 :《三体》的作者是哪里人&#xff1f;问题2&#xff1a;可以总结下三体的核心内容吗&#xff1f;如果要续写的话&#xff0c;可以从哪些角度出发&#xff1f;问题3&#xff1a;如何从哲学角度来进行续写&#xff1f;问题4&#xff1a;电…

学习28个案例总结

学习前 对于之前遇到的问题没有及时总结&#xff0c;导致做什么事情都是新的一样。没有把之前学习到接触到的内容应用上。通过这次对28个案例的学习。把之前遇到的问题总结成自己的经验&#xff0c;在以后的开发过程中避免踩重复性的坑。多看帮助少走弯路。 学习中 对28个案例…

2023年安徽省中职网络安全跨站脚本攻击

B-4&#xff1a;跨站脚本攻击 任务环境说明&#xff1a; √ 服务器场景&#xff1a;Server2125&#xff08;关闭链接&#xff09; √ 服务器场景操作系统&#xff1a;未知 √ 用户名:未知 密码&#xff1a;未知 1.访问服务器网站目录1&#xff0c;根据页面信息完成条件&am…

Shader基础

参考文章:Unity着色器介绍 Shader基础 Properties 声明格式 [optional: attribute] name(“display text in Inspector”, type name) default value 属性类型 Color&#xff1a;颜色属性&#xff0c;表示 RGBA 颜色值。Range&#xff1a;范围属性&#xff0c;表示一个在…

基于微信小程序的校园二手交易平台小程序

文末联系获取源码 开发语言&#xff1a;Java 框架&#xff1a;ssm JDK版本&#xff1a;JDK1.8 服务器&#xff1a;tomcat7 数据库&#xff1a;mysql 5.7/8.0 数据库工具&#xff1a;Navicat11 开发软件&#xff1a;eclipse/myeclipse/idea Maven包&#xff1a;Maven3.3.9 浏览器…

22讲MySQL有哪些“饮鸩止渴”提高性能的方法

短连接风暴 是指数据库有很多链接之后只执行了几个语句就断开的客户端&#xff0c;然后我们知道数据库客户端和数据库每次连接不仅需要tcp的三次握手&#xff0c;而且还有mysql的鉴权操作都要占用很多服务器的资源。话虽如此但是如果连接的不多的话其实这点资源无所谓的。 但是…

Web自动化——前端基础知识(二)

1. Web前端开发三要素 web前端开发三要素 什么是HTMl&#xff1f; Html是超文本标记语言&#xff0c;是用来描述网页的一种标记语言HTML是一种标签规则的形式将内容呈现在浏览器中可以以任意编辑器创建&#xff0c;其文件扩展名为.html或.htm保存即可 什么是CSS&#xff1f;…

ElasticSearch-第五天

目录 es中脑裂问题 脑裂定义 脑裂过程分析 解决方案 数据建模 前言 nested object 父子关系数据建模 父子关系 设置 Mapping 索引父文档 索引子文档 Parent / Child 所支持的查询 使用 has_child 查询 使用 has_parent 查询 使用 parent_id 查询 访问子文档 …

学习 Python 之 Pygame 开发魂斗罗(一)

学习 Python 之 Pygame 开发魂斗罗&#xff08;一&#xff09;Pygame回忆Pygame1. 使用pygame创建窗口2. 设置窗口背景颜色3. 获取窗口中的事件4. 在窗口中展示图片(1). pygame中的直角坐标系(2). 展示图片(3). 给部分区域设置颜色5. 在窗口中显示文字6. 播放音乐7. 图片翻转与…

人脸活体检测系统(Python+YOLOv5深度学习模型+清新界面)

摘要&#xff1a;人脸活体检测系统利用视觉方法检测人脸活体对象&#xff0c;区分常见虚假人脸&#xff0c;以便后续人脸识别&#xff0c;提供系统界面记录活体与虚假人脸检测结果。本文详细介绍基于YOLOv5深度学习技术的人脸活体检测系统&#xff0c;在介绍算法原理的同时&…

【C++】用手搓的红黑树手搓set和map

目录 一、set/map的底层结构 1、set/map的源码 2、利用模板区分set/map 3、利用仿函数控制比较大小 二、set/map的迭代器&#xff08;红黑树的迭代器&#xff09; 1、红黑树的begin、end迭代器 2、红黑树迭代器的operator 3、红黑树迭代器的operator-- 三、set的const…

人工智能大模型之ChatGPT原理解析

前言 近几个月ChatGPT爆火出圈&#xff0c;一路狂飙&#xff1b;它功能十分强大&#xff0c;不仅能回答各种各样的问题&#xff0c;还可以信写作&#xff0c;给程序找bug…我经过一段时间的深度使用后&#xff0c;十分汗颜&#xff0c;"智障对话"体验相比&#xff0c…

Spring-Data-Redis 和 Redisson TLS/SSL 连接

先决条件已经部署好redis tls环境。如未部署好&#xff0c;可参考&#xff1a;Redis 6.0 Docker容器使用SSL/TLS已知redis tls环境使用的证书&#xff1a;其中&#xff1a;ca.crt :服务器证书ca.key:服务器私钥redis.crt:客户端证书redis.key:客户端私钥证书处理生成证书p12文件…

Linux环境C语言开发基础

C语言是一门面向过程的计算机编程语言&#xff0c;与C、C#、Java等面向对象编程语言有所不同。C语言的设计目标是提供一种能以简易的方式编译、处理低级存储器、仅产生少量的机器码以及不需要任何运行环境支持便能运行的编程语言。C语言诞生于美国的贝尔实验室&#xff0c;由丹…

信创办公–基于WPS的PPT最佳实践系列(表格和图标常用动画)

信创办公–基于WPS的PPT最佳实践系列&#xff08;表格和图标常用动画&#xff09; 目录应用背景操作步骤图表常用动画效果&#xff1a;擦除效果表格常用动画效果&#xff1a;轮子效果应用背景 文不如表&#xff0c;表不如图。在平时用ppt做总结时&#xff0c;我们会经常用到图…

手撕数据结构与算法——树(三指针描述一棵树)

&#x1f3c6;作者主页&#xff1a;king&南星 &#x1f384;专栏链接&#xff1a;数据结构 &#x1f3c5;文章目录&#x1f331;树一、&#x1f332;概念与定义二、&#x1f333;定义与预备三、&#x1f334;创建结点函数四、&#x1f340;查找五、&#x1f341;插入六、&a…