C++学习进阶:智能指针

news2025/1/18 20:06:28

目录

前言:

1.知识引入

1.1.异常安全问题

1.2.RALL和智能指针雏形

2.智能指针的发展

2.1.auto_ptr的引入

2.2.unique_ptr的引入 

2.3.救世主shared_ptr(重点)

2.4.weak_ptr的引入(重点)

2.5.测试函数

3.定制删除器

3.1.shared_ptr中删除的漏洞

3.2.定制删除器的实现


前言:

这是一篇又臭又长又精华的博客,需要每一个模块认真学习,仔细理解,这一部分也是C++面试常考的内容,那么废话不多说,just do it!

在这一篇章中我们需要重点学习:RAII思想、shared_ptr的实现和相关问题

1.知识引入

1.1.异常安全问题

在C++学习进阶:异常-CSDN博客我们引入这一段代码是为了提出异常的重新抛出这个作法,但是异常重新抛出一定就是解决异常安全问题的最优法吗?

#include<iostream>
#include<string.h>
using namespace std;

int Exception(int i)
{

	int arr[5] = { 0, 1, 2, 3, 4 };
	if (i >=5)
	{
		throw "数组越界";
	}
	else
	{
		return arr[i];
	}
}
void Print()
{
    int* array = new int[10];
    try
    {
	    int i = 0;
	    cin >> i;
	    cout << Exception(i) << endl;
        // 不出现异常也是需要释放空间
        delete[] array;
        cout<<"释放array资源"<<endl;
    }
    catch(...)
    {    
        delete[] array;
        cout<<"释放array资源"<<endl;
    }
}

int main()
{
	try
	{
		Print();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
}

我们看回这段代码,对于Print中,我们发现当不发生异常时我们也是需要继续的释放掉array这段空间,也就是在代码简洁上重新抛出显然是不太适合这个场景的。那么如何解决这个问题呢?先不着急,我们先引入一个新概念RAII。

1.2.RALL和智能指针雏形

RAII(Resource Acquisition Is Initialization)是C++编程中的一种技术,用于管理资源(如内存、文件句柄、网络连接等)的生命周期。RAII的核心思想是,将资源的获取(即初始化)与资源的释放(即析构)绑定到对象的生命周期上。通过这样做,可以确保资源在不再需要时得到正确的释放,从而避免资源泄漏和其他相关问题。

显然十分抽象,接下来我们结合上一段代码来体会一下RAII思想。

#include<iostream>

using namespace std;

// 智能指针的引入
template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		delete _ptr;
		cout << "~SmartPtr() 已经释放传入的指针资源" << endl;
	}
private:
	T* _ptr;
};

int Exception(int i)
{

	int arr[5] = { 0, 1, 2, 3, 4 };
	if (i >= 5)
	{
		throw "数组越界";
	}
	else
	{
		return arr[i];
	}
}
void Print()
{
	int* array = new int[10];
	SmartPtr<int> s(array);

	int i = 0;
	cin >> i;
	cout << Exception(i) << endl;

}

int main()
{
	try
	{
		Print();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
}
  1. 我们引入一个类,来实现传入指针的释放,这里我们传入了外部资源的指针,当我们在类内部释放这个指针,外部资源的指针同时也被释放(共用一块地址)。
  2. 而这个类SmartPtr就是智能指针,不过这是我们自己手搓的一个雏形,功能较少
  3. RAII思想:就是将具有资源的、且不方便管理资源的对象绑定到容易管理资源的另一个对象上,来实现资源的管理。而智能指针是这个思想的一种体现

另外我们通过黑窗口打印,也发现无论出现异常还是不出现异常,这个指针的资源最终都得到释放

 通过上面的例子我们大概知道智能指针本质上就是实现一个对指针进行操作的对象,而在C++中封装为了在原生指针的基础上的一个具有多样功能的指针类,因而可以构造出“智能”指针对象……

2.智能指针的发展

智能指针是C++中用于自动管理内存生命周期的类模板。它们被设计为像原生指针一样使用,但提供了额外的功能来确保在适当的时候自动删除所指向的对象。智能指针通过封装原生指针并重载相关的操作符(如*->)来实现这一点,使得它们可以像使用普通指针一样方便。

智能指针主要有四种,分别是:auto_ptr(C++98标准,已弃用)、unique_ptr(C++11标准)、shared_ptr(C++11标准)和weak_ptr(C++11标准) ,为什么需要这几种智能指针呢?接下来我们通过几个场景来讲一下智能指针的发展史。

2.1.auto_ptr的引入

template<class T>
class SmartPtr
{
	// RAII思想
public:
	SmartPtr(T* ptr)
		:_ptr(ptr)
	{}
	~SmartPtr()
	{
		delete _ptr;
		cout << "~SmartPtr() 资源已经释放" << endl;
	}

	// 模拟指针功能

	// 实现解引用
	T& operator*() { return *_ptr; }
	// 对多成员类型访问
	T* operator->() { return _ptr; }
private:
	T* _ptr;
};
int main()
{
	zhong::SmartPtr<int> sp(new int(1));
	cout << *sp << endl;
	*sp = 2;
	cout << *sp << endl;
	// zhong::SmartPtr<int> sp2(sp);
}

首先我们定义了一个智能指针类,然后我们可以对他进行操作,比如值的修改和打印,左上角为注释了// zhong::SmartPtr<int> sp2(sp);调用函数的结果。

因为我们是通过共用同一块地址空间来实现智能指针的,而当我们析构时,因为有两个对象 ,所以这两个对象都需要被析构,而用的是同一份地址,也就是这块地址被析构了两次,因此崩溃了。

这里就是智能指针雏形的缺陷所在,为了解决这个问题C++98对这个智能指针做了一定的优化。

// C++98的智能指针
	// 进行管理权的转移
	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 << "~auto_ptr() 资源已经释放" << endl;
		}

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

	private:
		T* _ptr;
	};

 我们通过它的拷贝构造函数可知,对于拷贝对象,直接把自身地址传给被拷贝对象,然后将自身地址置为空。这样子我们解决:同一份地址被析构两次的问题。通过析构一份为空的地址和析构一份具有意义的地址来实现的。


2.2.unique_ptr的引入 

但是这样子会不会有点捞!!!实际场景下我们可能同时对这两个对象都需要进行操作!而其中一个对象我们已经置空了,那么就会产生对空指针的使用错误,显然是不符合实际应用。这时unique_ptr就横空出世了!

	template<class T>
	class unique_ptr
	{
	public:
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}
		~unique_ptr()
		{
			delete _ptr;
			cout << "~unique_ptr() 资源已经释放" << endl;
		}

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

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

为了解决拷贝构造导致的共用同一块地址析构两次和auto_ptr将其中一个对象值为空的这两个问题,unique_ptr察觉到了问题的根本来源,就是拷贝构造的问题,所以秉着不失败就是最大的成功这个伟大真理,直接删除了智能指针的拷贝构造功能。 


2.3.救世主shared_ptr(重点)

这样确实是能够从根源上解决问题,但是这是合理的吗??? 所以这时主角shared_ptr闪亮登场!

	// 合理的智能指针
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr)
			:_ptr(ptr)
			, _p_r_count(new int(1))
		{}
		~shared_ptr()
		{
			release();
			cout << "~shared_ptr() 资源已经释放" << endl;
		}
		shared_ptr(const shared_ptr<T>& sp)
			:_ptr(sp._ptr)
			,_p_r_count(sp._p_r_count)
		{
			++(*_p_r_count);
		}
		shared_ptr<T>& operator=(const shared_ptr<T>& sp) 
		{
			// 自己给自己赋值
			if (sp._ptr != this->_ptr)
			{
				// = 本质上替换左边的智能指针
				// 需要考虑是否需要释放
				release();

				_ptr = sp._ptr;
				_p_r_count = sp._p_r_count;
				++(*_p_r_count);
				return *this;
			}
		}

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

	private:
		// 引用计数为0时对资源的释放
		void release()
		{
			// 当引用计数为0时才释放资源
			// 判断的同时已经对引用计数--了
			if (--(*_p_r_count) == 0)
			{
				delete _ptr;
				delete _p_r_count;
			}
		}

	private:
		T* _ptr;
		// 维护引用计数的指针
		int* _p_r_count;
	};

在讲shared_ptr是怎么实现之前,我们先来回顾智能指针雏形的问题 

退出当前调用栈时,两个对象对应的同一块空间无法避免析构两次!!!

 为了解决这个问题:shared_ptr维护了一个int*类型的指针,用来存放这个智能指针对象的地址被引用了几次,即这个地址维护了几个智能指针的问题。

具体实现:

  1. 通过析构某一个智能指针对象时,先判断当前智能指针的引用计数是否大于1,如果大于1说明这个地址仍被其他对象使用中,不能释放这个地址,但是可以释放当前智能指针对象,并将这个智能指针的引用数减一。
  2. 我们在进行拷贝构造,也就是增加该地址对应的智能指针对象时,需要增加引用计数。

这样子我们就完善了智能指针并且解决了问题。


我们已经知道用引用计数可以解决问题,但是为什么一定是int*类型呢?我们画个图来体会一下


那么假如只是在智能指针中维护int类型呢?这时由于对象不同,所以他们各自的int也是不同的,其他的智能指针对象就算拥有同一块_ptr,但是int的地址却是各自开辟的,无法实现三者独立操作。


那么可能有人会提出:如果维护static int这个成员变量时,在静态区中独立开辟,总是可以满足独立操作的需求了吧? 当然如果只是满足独立操作的需求,通过静态区是能够实现的,然而如果我们有另一块内存开辟的资源,就会导致上一块资源对应的static int和这一块对应的static int冲突。


2.4.weak_ptr的引入(重点)

shared_ptr的缺陷:

然而shared_ptr并不是万能的,shared_ptr会出现循环引用的问题,也就是两个智能指针内部维护智能指针对象时可能会出现互相进行指向的问题。

在这段代码中我们直接使用C++原生的shared_ptr来展示shared_ptr存在的缺陷

// shared_ptr的缺陷
struct ListNode
{
	int _val;
	std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	std::shared_ptr<ListNode> sp1 = new ListNode;
	std::shared_ptr<ListNode> sp2 = new ListNode;
	
	// 这里会出现循环引用
	sp1->_next = sp2;
	sp2->_prev = sp1;
}

运行这段代码时,我们发现我们显性的进行ListNode的析构,但是却没有析构的打印,而是没有任何输出结果的正常退出程序。这也就表示了这一段代码中的ListNode结构体并没有被释放资源,存在内存泄漏的问题!!!

为什么会出现这种问题,已经循环引用是什么意思呢? 如图所示:

当我们运行到函数结束前,我们会出现左图的现象,智能指针的指向图,而当函数退出时,乡音函数内部的sp1、sp2作为临时变量,编译器自动调用它的析构函数进行回收,然而此时我们发现这两个ListNode的资源仍然未被释放,这是为什么呢?

因为ListNode内部维护的智能指针的引用计数仍然不为0,这里因为上面的ListNode中的智能指针指向下一个ListNode,而下面的智能指针指向上一个ListNode,即:如果想要ListNode资源得到释放,就需要内部维护的互相指向的智能指针的引用计数为0,但是他们各自为1。

那么现在我们就要研究一下_next和_prev什么时候释放的问题?

  1. 对于_next而言,如果他需要被释放,也就是引用计数为0,那么就是需要下面的ListNode节点被释放,这时维护_next的ListNode(上面的ListNode)才会释放。
  2. 然而下面的ListNode节点被释放的条件,就是_prev的引用计数为0,而_prev引用计数为0的条件就是上面的ListNode节点被释放。

这时是不是发现:1的条件需要2为结果,2的条件需要1为结果。就产生了因果循环!

所以这时我们发现shared_ptr并不是万能的,他会出现循环引用的问题! 这时C++11就引入了weak_ptr这一个智能指针来解决这个问题。

// 循环引用的问题就是ListNode中维护了shared_ptr指针
struct ListNode
{
	int _val;
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;
	~ListNode()
	{
		cout << "~ListNode()" << endl;
	}
};
int main()
{
	std::shared_ptr<ListNode> n1 (new ListNode);
	std::shared_ptr<ListNode> n2 (new ListNode);
	
	// 这里会出现循环引用
	n1->_next = n2;
	n2->_prev = n1;
}

这时问题就完美解决了!!! 

weak_ptr的实现:

  1. weak_ptr是专门为了解决shared_ptr循环引用问题而产生的
  2. 不支持RAII
  3. 不参与shared_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;
		}
		weak_ptr<T>& operator=(const weak_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			return *this;
		}

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

		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		// 注意weak_ptr也是拥有引用计数的
		// 这里我们只是实现简单的逻辑,就不实现了
	};

 在这一部分中,我们没有实现weak_ptr的引用计数,只是根据std库大致实现了,解决循环引用的weak_ptr具有的功能


2.5.测试函数

附:为了不与库中的智能指针冲突,建议在自己的命名空间实现这几个智能指针

void test1()
{
	zhong::SmartPtr<int> sp(new int(1));
	cout << *sp << endl;
	*sp = 2;
	cout << *sp << endl;
	//zhong::SmartPtr<int> sp2(sp);
}
void test2()
{
	zhong::auto_ptr<int> sp(new int(1));
	cout << *sp << endl;
	*sp = 2;
	cout << *sp << endl;
	zhong::auto_ptr<int> sp2(sp);
	cout << *sp2 << endl;
	// auto_ptr对原对象进行访问 会出现访问空指针错误
	// cout << *sp << endl;
}
void test3()
{
	zhong::unique_ptr<int> sp(new int(1));
	cout << *sp << endl;
	*sp = 2;
	cout << *sp << endl;
	// unique_ptr直接就不允许拷贝构造
	// zhong::unique_ptr<int> sp2(sp);

}
void test4()
{
	zhong::shared_ptr<int> sp(new int(1));
	*sp = 2;
	zhong::shared_ptr<int> sp2(sp);
	cout << "sp对应值为:" << *sp << endl;
	cout << "sp2对应值为:" << *sp2 << endl;
}

3.定制删除器

这里我们主要介绍一下shared_ptr的定制删除器,因为其他的智能指针实现也是一致的。为什么需要定制删除器?我们先看回我们的release函数

3.1.shared_ptr中删除的漏洞

void release()
{
	// 当引用计数为0时才释放资源
	// 判断的同时已经对引用计数--了
	if (--(*_p_r_count) == 0)
	{
		delete _ptr;
		delete _p_r_count;
	}
}

我们发现我们释放智能指针指向资源的指针是通过delete _ptr,但是实际场景中我们传入的可能是数组对象,那么这时就需要delete[ ] _ptr,不然就仅仅释放了数组对象对应的首个对象的资源,数组后面的资源并没有的到释放。也就是说release有点写死了。

int main()
{
    // 我们传入数组对象给shared_ptr,然后析构时程序崩溃
	std::shared_ptr<string> n1(new string[10]);
}

C++库中是删除数组指针,通过传入删除器对象来实现的,而这个删除器一般就是函数指针、仿函数、lambda表达式这种可调用对象 。需要注意的是:类型需要匹配。

// 定制删除器
template<class T>
struct Delete
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};
int main()
{
    // 通过仿函数
	std::shared_ptr<string> n1(new string[10], Delete<string>());
	// 通过lambda表达式
    std::shared_ptr<string> n2(new string[10], [](string* ptr) {delete[] ptr; });
}

3.2.定制删除器的实现

接下来我们学习一下shared_ptr是如何实现定制删除器的。

  1. 我们增加数组指针和传入删除器的构造函数
  2. 接着需要重写一下release函数的逻辑,增加删除器删除的代码实现
  3. 用包装器统一传入的函数指针、仿函数、lambda表达式类型
// 合理的智能指针
template<class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr = nullptr)
		:_ptr(ptr)
		, _p_r_count(new int(1))
	{}
	template<class D>
	shared_ptr(T* ptr, D del)
		: _ptr(ptr)
		, _p_r_count(new int(1))
		, _del(del)
	{}
	~shared_ptr()
	{
		release();
		// cout << "~shared_ptr() 资源已经释放" << endl;
	}
	shared_ptr(const shared_ptr<T>& sp)
		:_ptr(sp._ptr)
		, _p_r_count(sp._p_r_count)
	{
		++(*_p_r_count);
	}
	shared_ptr<T>& operator=(const shared_ptr<T>& sp)
	{
		// 自己给自己赋值
		if (sp._ptr != this->_ptr)
		{
			// = 本质上替换左边的智能指针
			// 需要考虑是否需要释放
			release();

			_ptr = sp._ptr;
			_p_r_count = sp._p_r_count;
			++(*_p_r_count);
			return *this;
		}
	}

	T& operator*() { return *_ptr; }
	T* operator->() { return _ptr; }
	// 返回引用计数长度
	int use_count() const { return *_p_r_count; }
	// 提供指向资源的地址
	T* get() const { return _ptr; }

private:
	// 引用计数为0时对资源的释放
	void release()
	{
		// 当引用计数为0时才释放资源
		// 判断的同时已经对引用计数--了
		if (--(*_p_r_count) == 0)
		{
			// 通过删除器删除指向对象的指针
			_del(_ptr);
			delete _p_r_count;
		}
	}

private:
	T* _ptr;
	// 维护引用计数的指针
	int* _p_r_count;
	// 增加包装器来接受定制删除器
	// 并且默认初始化,当没有传入删除器,也就是删除普通指针
	function<void(T*)> _del = [](T* ptr) {delete ptr; };
};


 对这段代码我们只要求能够理解即可,需要注意的点:

  1. 包装器我们需要进行初始化,当没有传入删除器时,我们通过lambda表达式将删除器设置为删除普通指针,当传入删除器时,我们在release中通过可调用的删除器对象_delete删除。
  2. 传入含有资源指针类型和删除器的构造函数,也需要满足shared_ptr正常的功能和拥有的成员变量

那么到了这里我们智能指针的篇章就告一段落了。

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

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

相关文章

sqli-labs靶场学习(一)

一.知识点 1.数据库 数据库是一个用于存储和管理数据的仓库。数据按照特定的格式存储&#xff0c;可以对数据库中的数据进行增加、修改、删除和查询操作。数据库的本质是一个文件系统&#xff0c;按照一定的逻辑结构组织数据&#xff0c;以方便高效地访问和维护。 2.数据库管…

MDC及EFK安装与使用

MDC 1.简介 MDC 介绍​ MDC&#xff08;Mapped Diagnostic Context&#xff0c;映射调试上下文&#xff09;是 log4j 和 logback 提供的一种方便在多线程条件下记录日志的功能。MDC 可以看成是一个与当前线程绑定的Map&#xff0c;可以往其中添加键值对。MDC 中包含的内容可以…

Aurora 协议学习理解与应用——Aurora 64B66B协议学习

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 Aurora 协议学习理解与应用——Aurora 64B66B协议学习 概述数据发送和接收帧传输过程链路层帧描绘64B/66B 编码多lane传输 帧接收过程Control Block Stripping 控制块剥离多l…

Scala 04 —— 函数式编程底层逻辑

函数式编程 底层逻辑 该文章来自2023/1/14的清华大学交叉信息学院助理教授——袁洋演讲。 文章目录 函数式编程 底层逻辑函数式编程假如...副作用是必须的&#xff1f;函数的定义函数是数据的函数&#xff0c;不是数字的函数如何把业务逻辑做成纯函数式&#xff1f;函数式编程…

Oracle数据库的简单使用

Oracle简单使用 一、数据库的介绍二、Oracle介绍账号管理Oracle的安装Oracle服务的作用OracleRemExecService服务创建数据库 常用命令 三、SQL语言SQL分类实用的数据表添加注释数据操纵语言&#xff08;DML&#xff09;查询语句&#xff08;SELECT&#xff09;wherelikedistinc…

UWB人员定位系统适用的场景有哪些?​​​​​​​10厘米工业级实时轨迹高精度定位

UWB人员定位系统适用的场景有哪些&#xff1f;10厘米工业级实时轨迹高精度定位 一、应用场景 1、商场与零售领域&#xff1a;商场可以使用UWB人员定位系统来跟踪顾客的行踪&#xff0c;以收集顾客行为数据&#xff0c;为营销策略提供有力支持。帮助商场优化商品布局和陈列&…

【鸿蒙开发】生命周期

1. UIAbility组件生命周期 UIAbility的生命周期包括Create、Foreground、Background、Destroy四个状态。 UIAbility生命周期状态 1.1 Create状态 Create状态为在应用加载过程中&#xff0c;UIAbility实例创建完成时触发&#xff0c;系统会调用onCreate()回调。可以在该回调中…

了解 Python 底层的解释器 CPython 和 Python 的对象模型

&#x1f349; CSDN 叶庭云&#xff1a;https://yetingyun.blog.csdn.net/ 一、CPython CPython 是 Python 编程语言的官方和最广泛使用的实现。它是用 C 语言编写的&#xff0c;因此得名 “CPython”。作为 Python 生态系统的核心&#xff0c;了解 CPython 的工作原理、主要特…

数据结构- 顺序表-单链表-双链表 --【求个关注!】

文章目录 一 顺序表代码&#xff1a; 二 链表单链表双向链表 一 顺序表 顺序表是线性表的一种 所谓线性表指一串数据的组织存储在逻辑上是线性的&#xff0c;而在物理上不一定是线性的 顺序表的底层实现是数组&#xff0c;其由一群数据类型相同的元素组成&#xff0c;其在逻辑…

bug(警告):[vue-router] Duplicate named routes definition: …

查看警告&#xff1a;[vue-router] Duplicate named routes definition——翻译[vue-router]重复命名路由定义 小编劝诫&#xff1a;当我们在开发过程中警告也一定不要忽略&#xff0c;虽然你在本地跑代码时这些警告影响项目的正常运行&#xff0c;但是会让你产生误区&#xff…

【MIT6.824】lab2C-persistence, lab2D-log compaction 实现笔记

引言 lab2C的实验要求如下 Complete the functions persist() and readPersist() in raft.go by adding code to save and restore persistent state. You will need to encode (or “serialize”) the state as an array of bytes in order to pass it to the Persister. Us…

《游戏系统设计十二》灵活且简单的条件检查系统

目录 1、序言 2、需求 3、实现 3.1 思路 3.2 代码实现 4、总结 1、序言 每个游戏都有一些检查性的任务&#xff0c;在做一些判断的时候&#xff0c;判断等级是不是满足需求。 比如如下场景&#xff1a;在进入副本的时候需要检查玩家等级是否满足&#xff0c;满足之后才…

配置linux的oracle 21c启停服务

一、配置启停 1、使用root用户登陆 su - root 2、修改oratab文件 修改oratab文件&#xff0c;将红框里面的N改为“Y”&#xff0c;使启停脚本能够生效 vi /etc/oratab 3、验证 配置好后就能够使用 dbshut 停止服务 和 dbstart 启动服务 了 2.1启动服务 su - oracle dbstart…

现代商业中首席人工智能官(CAIO)的角色与影响

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

Spark01

Spark01 一. Spark概述二. Spark环境部署 - Local三. Spark环境部署 - Standalone1. Standalone集群概述2. Standalone环境部署3. 测试环境 四. Spark环境部署 - Standalone-HA1. 安装部署Zookeeper1. 下载2. zookeeper安装3. 配置StandAlone-HA集群 五. Spark On YARN -- 重点…

深入挖掘C语言 ---- 文件操作

目录 1. 文件的打开和关闭1.1 流和标准流1.1.1流1.1.2标准流 1.2 文件指针1.3 文件的打开和关闭 2. 顺序读写3. 随机读写3.1 fseek3.2 ftell3.3 rewind 4. 读取结束判定 正文开始 1. 文件的打开和关闭 1.1 流和标准流 1.1.1流 我们程序的数据需要输出到各种外部设备, 也需要…

小白也能看懂的BEV感知技术(二)

1. 引言 在自动驾驶的领域中&#xff0c;BEV&#xff08;Birds Eye View&#xff0c;鸟瞰图&#xff09;感知技术扮演着至关重要的角色。它允许自动驾驶车辆从上帝视角“看到”周围的环境&#xff0c;就像一只鸟从空中俯瞰地面一样。这项技术对于理解车辆周围的复杂场景至关重…

【Linux系统】地址空间 Linux内核进程调度队列

1.进程的地址空间 1.1 直接写代码&#xff0c;看现象 1 #include<stdio.h>2 #include<unistd.h>3 4 int g_val 100;5 6 int main()7 {8 int cnt 0;9 pid_t id fork();10 if(id 0)11 {12 while(1)13 {14 printf(&…

javaagent使用

Java Agent是什么&#xff1f; Java Agent是Java平台提供的一个强大工具&#xff0c;它可以在运行时修改或增强Java应用程序的行为。是在JDK1.5以后引入的&#xff0c;它能够在不影响正常编译的情况下修改字节码&#xff0c;相当于是在main方法执行之前的拦截器&#xff0c;也叫…

Python | Leetcode Python题解之第32题最长有效括号

题目&#xff1a; 题解&#xff1a; class Solution:def longestValidParentheses(self, s: str) -> int:stack[]maxL0nlen(s)tmp[0]*n #标记数组cur0for i in range(n):if s[i](:stack.append(i)else:if stack:jstack.pop()if s[j](:tmp[i],tmp[j]1,1 #匹配成…