【C++】--遇到抛异常没有及时释放的空间该怎么办??---智能指针来帮你解决(以及定制删除器)

news2025/7/19 12:10:30

在这里插入图片描述

💖作者:小树苗渴望变成参天大树🎈
🎉作者宣言:认真写好每一篇博客💤
🎊作者gitee:gitee✨
💞作者专栏:C语言,数据结构初阶,Linux,C++ 动态规划算法🎄
如 果 你 喜 欢 作 者 的 文 章 ,就 给 作 者 点 点 关 注 吧!

文章目录

  • 前言
  • 一、为什么设计出智能指针??
  • 二、智能指针的使用及原理
    • 2.1 最简单的智能指针
  • 三、智能指针的发展史
    • 3.1 auto_ptr
    • 3.2 unique_ptr
    • 3.3 shared_ptr
    • 3.4 weak_ptr(循环引用)
    • 3.5定制删除器
  • 四、总结


前言

今天我们来讲解一个好理解,有好用的知识点–智能指针。智能指针有很多种,但是每种都有相同之处,一会按顺序介绍的哦都是每一种的补充,他的作用是解决没有办法再回头来释放的空间,导致会内存泄漏,所以需要指针指针来解决这个问题,话不多说,我们开始进入正文讲解


一、为什么设计出智能指针??

再以前是没有智能指针的东西,那为什么现在设计出来了看代码:

void f()
{
	pair<string, string>* p1 = new pair<string, string>;//可能会出现错误抛异常
	fun();
	delete p1;
}
int main()
{
	f();
	return 0;
}

以前我们申请空间就释放空间,中间有啥都无所谓,肯定没有问题的,但是异常出来之后,就会导致fun出现异常,这时候就会影响执行流,可能会释放不到p1,这样就导致内存泄漏,

**那我们的java有没有智能指针呢??**答案是没有的,他有垃圾回收器,可以自动回收内存,那C++为什么不搞呢??原因就是c++程序是直接跑在os上的,而Java先再虚拟机上跑,而虚拟机再os上跑,多了一层,所以C++想要直接去搞垃圾回收器是一件很耗成本的事,有便宜就有付出,所以再一些追求性能的程序,大部分都选择C++比如游戏,遥控器需要及时响应的。并且C++程序和os是直接互通的


有了异常来说我们来看下面的代码:

(1)

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}
void f()
{
	pair<string, string>* p1 = new pair<string, string>;//可能会出现错误抛异常

	try
	{
		div();//正常执行逻辑
	}
	catch (...)//接收任意类型的异常
	{
		cout << "delete:" << p1 << endl;
		delete p1;//先释放
		throw;//抛出去
	}
}
int main()
{
	try
	{
		f();
	}
	catch (const exception& e)//捕获抛出来的异常
	{
		cout << e.what() << endl;
	}
	return 0;
}

大家仔细看我们的f函数里面有一块动态申请的空间,如果div里面抛异常的,我们再抛出去之前就给释放就可以解决问题。也是可以解决问题的。
在这里插入图片描述
这就是由于异常那个出现导致执行流的变化。

(2)将上面的f()函数改成下面这样看看,其余不变。

void f()
{
	pair<string, string>* p1 = new pair<string, string>;//可能会出现错误抛异常
	pair<string, string>* p2 = new pair<string, string>;
	try
	{
		div();//正常执行逻辑
	}
	catch (...)//接收任意类型的异常
	{
		cout << "delete:" << p1 << endl;
		delete p1;//先释放
		throw;//抛出去
	}

	cout << "delete:" << p1 << endl;
	delete p1;
	cout << "delete:" << p2 << endl;
	delete p2;
}

我给大家来分析一下,再申请的时候也可能出现申请失败,概率很小但有可能,此时p1申请失败,那下面的代码就不会执行,退出函数,也不存申请内存释放问题,万一p1申请成功,执行申请p2的时候出现了异常,退出了函数,那么刚申请的p1就没有办法释放,有人说给p2也加一个异常处理:

void f()
{
pair<string, string>* p1 = new pair<string, string>;//可能会出现错误抛异常
pair<string, string>* p2 = new pair<string, string>;//可能会出现错误抛异常
try//是p2的异常捕获
{
	;//正常执行逻辑
}
catch (...)//接收任意类型的异常
{
	cout << "delete:" << p1 << endl;
	delete p1;//先释放
	throw;//抛出去
}
try//是div的异常捕获
{
	div();//正常执行逻辑
}
catch (...)//接收任意类型的异常
{
	cout << "delete:" << p1 << endl;
	delete p1;//先释放
	throw;//抛出去
}
cout << "delete:" << p1 << endl;
delete p1;
cout << "delete:" << p2 << endl;
delete p2;
}

上面代码可以解决我说的场景,但是出现下面的场景呢?
>

内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死
所以针对可能造成内存泄漏的空间,我们不可能每一个都要捕获一下异常,所以针对这个问题我们要想办法解决,这才引入了智能指针

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

相信通过上面的例子,大家应该明白C++11要搞出智能指针了吧,接下来我将带大家来学习智能指针。

2.1 最简单的智能指针

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
法有两大好处:
不需要显式地释放资源。
采用这种方式,对象所需的资源在其生命期内始终保持有效

我们抛出异常就意味结束了此函数,想要解决释放,将指针写成一个类,自动调用析构函数,就可以了,来看代码:

class smartptr
	{
	public:
		smartptr(pair<string, string>* sp)
			:_ptr(sp)
		{}
		~smartptr()
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
		}
	private:
		pair<string, string>* _ptr;
	};

这是一个最简单的智能指针,函数不管啥时候结束都会自动调用析构函数进行释放的,我们来看结果
在这里插入图片描述

为了能适应任何类型,我们将智能指针设计成模板的形式

template<class T>
	class smartptr
	{
	public:
		smartptr(T* sp)
			:_ptr(sp)
		{}
		~smartptr()
		{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
		}
	private:
		T* _ptr;
	};
xdh::smartptr<pair<string, string>> p1 = new pair<string,string>;
xdh::smartptr<pair<string, string>> p2 = new pair<string, string>;

难道智能指针这样就可以了??
我们要达到像普通指针一样完成下面的操作:

在这里插入图片描述
所以我们要实现*和->,这个再迭代器那一块已经孰能生巧了,再类里面去实现那一下就好了

T& operator*()
{
	return *_ptr;
}
T* operator->()
{
	return _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中的实现的。

我们会介绍四款智能指针,auto_ptr,unique_ptr,shared_ptr,weak_ptr,这四个我都会带大家去使用以及模拟实现一下,让大家更好的理解智能指针。

我们上面一开始自己实现的智能指针还有一些问题解决不了,那就是下面的场景:

int main()
{
	xdh::smartptr<string> sp1(new string("xxxxx"));
	xdh::smartptr<string> sp2(new string("yyyyy"));
	sp1 =sp2;
	return 0;
}

在这里插入图片描述
所以我们才要讲解上面四个智能指针。

3.1 auto_ptr

我们的第一款智能指针是C++98搞出来叫auto_ptr,但是这个太坑了,也会大部分公司禁止使用的,为什么会这么说他呢,他有悬空
我们来使用一下看看:

void f()
{
	auto_ptr<string> p1(new string("111"));
	auto_ptr<string> p2(p1);
	
	auto_ptr<string> p3(new string("111"));
	auto_ptr<string> p4(new string("111"));
	p4 = p3;
}

在这里插入图片描述
我们的auto_ptr再实现拷贝和赋值的时候就会使得被拷贝的对象发生悬空,如果再对被拷贝对象操作,就会出现问题,不使用就没事。但是这种再开发中就不被允许,万一再前面使用了被拷贝对象,后面悬空了,想看看值就会出现问题,他的实现原理很见到,就是把资源给别人,把自己置空就可以了。

auto_ptr的实现原理:管理权转移的思想,下面简化模拟实现了一份xdh::auto_ptr来了解它的原

template<class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}
		auto_ptr(auto_ptr<T>& sp)
			:_ptr(sp._ptr)
		{
			// 管理权转移
			sp._ptr = nullptr;
		}
		auto_ptr<T>& operator=(auto_ptr<T>& ap)
		{
			// 检测是否为自己给自己赋值
			if (this != &ap)
			{
				// 释放当前对象中资源
				if (_ptr)
					delete _ptr;
				// 转移ap中资源到当前对象中
				_ptr = ap._ptr;
				ap._ptr = NULL;
			}
			return *this;
		}
		~auto_ptr()
		{
			if (_ptr)
			{
				cout << "delete:" << _ptr << endl;
				delete _ptr;
			}
		}
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

拷贝构造里面就很简单,把指针交给新对象,自己置空,几乎不会自己给自己拷贝构造的,再赋值里面我们就要注意,自己给自己赋值,就不应该置空,所以加了一个判断,这有点像移动构造,把我的资源给你,但是移动构造是将将亡值的资源给你,而这里是左值,我可能还要使用,给你我自己就会出现问题,所以这就是auto_ptr设计坑的所在之处

3.2 unique_ptr

最上面说过赋值后可能会造成析构两次的问题,那么unique_ptr就是直接暴力的禁止赋值。
在这里插入图片描述

	template<class T>
	class unique_ptr
	{
	public:
		
		unique_ptr(T* ptr)
			:_ptr(ptr)
		{}

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

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

		T* operator->()
		{
			return _ptr;
		}
		//C++11防拷贝:delete
		unique_ptr(unique_ptr<T>& ap) = delete;
		unique_ptr<T>& operator=(unique_ptr<T>& ap) = delete;
	private:
		T* _ptr;
		//C++98:只声明不实现+私有化
		unique_ptr(unique_ptr<T>& ap);
		unique_ptr<T>& operator=(unique_ptr<T>& ap);
	};

接下来讲解的智能指针才是重点

3.3 shared_ptr

为什么会出现析构两次,就会因为同一块空间有两个对象共享,结束后每个对象都会自动调用自购函数进行释放,所以shared_ptr采取的思想就是当一块空间只有一个指向的时候才进行释放,所以需要使用一个计数来达到想要的效果

  1. shared_ptr在其内部,给每个资源都维护了着一份计数,用来记录该份资源被几个对象共
    享。
  2. 在对象被销毁时(也就是析构函数调用),就说明自己不使用该资源了,对象的引用计数减
    一。
  3. 如果引用计数是0,就说明自己是最后一个使用该资源的对象,必须释放该资源;
  4. 如果不是0,就说明除了自己还有其他对象在使用该份资源,不能释放该资源,否则其他对
    象就成野指针了。

库里面的使用:

shared_ptr<string>p1(new string("111"));
	shared_ptr<string>p2(new string("111"));

	shared_ptr<string>p3(p1);

	shared_ptr<string>p4(new string("111"));
	p4 = p2;

	shared_ptr<string>p5(p4);

在这里插入图片描述

我们使用一个静态成员作为计数行不行??答案是不行的,只有地址相同的对象才共享一块地址,静态变量是属于类,也是属于所有类对象的,所以对于这两块空间都是公用一个变量的。我们要达到每块空间有自己的计数变量,所以采用引用计数

	template<class T>
	class shared_ptr
	{
	public:
		// RAII
		// 像指针一样
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(new int(1))//自己创建一个对象的计数初始为1
		{}
		shared_ptr(const shared_ptr<T>& sp)//拷贝构造,指向同一块空间,计数++
			:_ptr(sp._ptr)
			,_pcount(sp._pcount)
		{
			++(*_pcount);
		}
		private:
		T* _ptr;
		int* _pcount;
	}

我们来看看析构函数怎么搞的:

		~shared_ptr()
		{
			if (--(*_pcount) == 0)//就很自己了就可以被释放
			{
				delete _ptr;
				cout << "delete:" << _ptr << endl;
				delete _pcount;
			}
		}

我们来看看赋值函数怎么搞的:

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

拷贝对象可能就自己指向那块空间了,被拷贝后,那块空间就没人指向了,所以需要判断将其释放
在这里插入图片描述
自己给自己赋值

p2=p2;这是直接自己给自己赋值
p2=p4;这是间接自己给自己赋值
都叫自己给自己赋值

我们用上面自己写的来测试一下,成功实现和库里面相同的效果。
在这里插入图片描述

shared_ptr最难的就在赋值那里面,要注意判断,通过第二个判断间接把自己的引用也减少一个,这才是关键

3.4 weak_ptr(循环引用)

我们来看一个场景,是shared_ptr解决不了的

class A
{
public:
	A(int a = 0)
		:_a(a)
	{
		cout << "A(int a = 0)" << endl;
	}
	~A()
	{
		cout << this;
		cout << " ~A()" << endl;
	}
private:
	int _a;
};


class Node
{
public:
	A _val;
	shared_ptr<Node> _next;
	shared_ptr<Node> _prev;
};
void f()
{

	shared_ptr<Node> p1(new Node);
	shared_ptr<Node> p2(new Node);
	p1->_next = p2;
	p2->_prev = p1;
}

在这里插入图片描述

我们运行程序会发现没有释放,原因就是下面这幅图:
在这里插入图片描述
造成这样的主要原因就是计数变量的增加导致结点不能释放,我们的weak_ptr就是来解决这个问题的,他的出现不增加计数,我们来看看使用:
在这里插入图片描述
这样就可以解决问题了。那这么一开有点类型不匹配的感觉,说明weak_ptr里面肯定有使用shared_ptr的构造函数,把shared_ptr的资源拿过来操作,但是不做释放也不增加计数,就是借此过度一下,让其赋值成功,间接控制没有增加计数。
我们来模拟实现一下:

	template<class T>
	class weak_ptr
	{
	public:
		// RAII
		// 像指针一样
		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*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
	};

在这里插入图片描述
也同样的解决我们的问题,我们再将结束打印出来看看,再shared_ptr里面写一个use_count函数


		int use_count()
		{
			return *_pcount;
		}
		
	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;

	sp1->_next = sp2;
	sp2->_prev = sp1;

	cout << sp1.use_count() << endl;
	cout << sp2.use_count() << endl;

weak_ptr就是专门用来解决shared_ptr这个场景的,没有其他作用,几乎大部分场景shared_ptr就足够了

3.5定制删除器

定制删除其只针对shared_ptr去定制了,因为他的使用范围最广,我们来回顾一下shared_ptr的析构:

	~shared_ptr()
		{
			if (--(*_pcount) == 0)//就很自己了就可以被释放
			{
				delete _ptr;
				cout << "delete:" << _ptr << endl;
				delete _pcount;
			}
		}

我们看到这种释放就写死了,只能这样去申请空间了
xdh::shared_ptr<A> p1(new A);
如果我想这样去申请,就会出现问题,因为需要delete[]去释放。
xdh::shared_ptr<A> p1(new A[10]);
有了上面的问题我们才引入了定制删除器。

在这里插入图片描述
其实就是传一个仿函数进去了

template<class T>
struct DeleteArray
{
	void operator()(T* ptr)
	{
		delete[] ptr;
	}
};

int main()
{
	shared_ptr<A> sp1(new A[10], DeleteArray<A>());
	return 0;
}

在这里插入图片描述
没使用之前是有九个都没有释放使用之后都释放了,我们接下来自己来写一个定制删除器

	//template<class T,class D>
	template<class T>
	class shared_ptr
	{
	public:
		// RAII 
		// 像指针一样
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))//自己创建一个对象的计数初始为1
		{}
		template<class D>
		shared_ptr(T* ptr, D del)//这里面传进来析构函数不能使用,这不是类的模板参数,当然也可以直接再类外面弄一个模板参数
			: _ptr(ptr)
			, _pcount(new int(1))//自己创建一个对象的计数初始为1
			,_del(del)
		{}
		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)
			{
				if (--(*_pcount) == 0)
				{
					delete _ptr;
					delete _pcount;
				}
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				++(*_pcount);
			}
			return *this;
		}

		~shared_ptr()
		{
			if (--(*_pcount) == 0)
			{
				cout << "delete:" << _ptr << endl;
				//delete _ptr;
				_del(_ptr);

				delete _pcount;
			}
		}
		T* get()const
		{
			return _ptr;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;
		function<void(T*)> _del;//本来就要传仿函数进来,而且也不止类型,刚好知道返回值和参数,可以使用包装器
	};
xdh::shared_ptr<A> sp2(new A[5], [](A* ptr) { delete[] ptr; });//使用lambda表达式也行

在这里插入图片描述

到这里我们的定制删除器就讲解结束,大家下来好好的去看看。

四、总结

今天讲解内容非常多,但是非常使用,他是以后你在公司可以避免事故的一个重要东西,因为内存泄漏是一个非常不好的问题,而智能指针可以很好的避免这样的问题出现,所以还是希望大家多去了解了解,再智能指针这环节还有一些知识点没讲到,博主后面都会补充到的,这篇我们就讲到这里,我们下篇再见

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

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

相关文章

04、RocketMQ -- 核心基础使用

目录 核心基础使用1、入门案例生产者消费者 2、消息发送方式方式1&#xff1a;同步消息方式2&#xff1a;异步消息方式3&#xff1a;一次性消息管控台使用过程中可能出现的问题 3、消息消费方式集群模式&#xff08;默认&#xff09;广播模式 4、顺序消息分析图&#xff1a;代码…

[uni-app] canvas绘制圆环进度条

文章目录 需求参考链接基本问题的处理1:画布旋转的问题2:注意arc()的起始位置是3点钟方向3: 如果绘制1.9*Matn.PI的圆环, 要保证其实位置在0点方向?4:小线段怎么画, 角度怎么处理? 源码 需求 要绘制一个如此的进度条 参考链接 uni-app使用canvas绘制时间刻度以及不显示问…

线段树【java实现】

一、解决问题 区间最值和区间求和问题 力扣相关题目&#xff1a; ​​​​​​303. 区域和检索 - 数组不可变 729. 我的日程安排表 I 二、线段树定义 平衡二叉树&#xff0c;数组中的元素都存储在叶子结点中&#xff0c;如图是一个求区间最大值的线段树。 已知数组arr[11…

电源特性测试之电源模块负载调整率测试方法及测试条件

负载调整率是衡量电源好坏的重要指标&#xff0c;它反映的是当负载电流变化时&#xff0c;稳压电源输出电压相应的变化情况。好的电源负载变化时引起的输出变化较小&#xff0c;通常是在3%-5%。负载调整率是电源模块测试的一个重要步骤&#xff0c;今天纳米软件将为大家介绍负载…

Yakit工具篇:综合目录扫描与爆破的使用

简介&#xff08;来自官方文档&#xff09; 目录扫描是一种常用的Web应用程序安全测试技术&#xff0c;用于发现Web应用程序中存在的可能存在的漏洞和弱点。其原理是通过对Web应用程序中的目录和文件进行遍历&#xff0c;来发现可能存在的安全漏洞和风险。 具体来说&#xff…

大语言模型在推荐系统的实践应用

本文从应用视角出发&#xff0c;尝试把大语言模型中的一些长处放在推荐系统中。 01 背景和问题 传统的推荐模型网络参数效果较小(不包括embedding参数)&#xff0c;训练和推理的时间、空间开销较小&#xff0c;也能充分利用用户-物品的协同信号。但是它的缺陷是只能利用数据…

《进化优化》第5章 进化规划

文章目录 5.1 连续进化规划5.2 有限状态机优化5.3 离散进化规划5.4 囚徒困境5.5 人工蚂蚁问题 5.1 连续进化规划 目的&#xff1a;最小化f(x)&#xff0c; 这里的x是一个n维向量&#xff0c;假定对所有的x, f(x)>0。 进化规划从随机生成的一个个体种群{xi}开始, 按如下方式…

Umi3实战教程

一、框架介绍 umi是蚂蚁金服的前端开发框架&#xff0c;它内置了路由、web/移动端UI库、数据流、权限控制、常用hooks库、构建、部署、测试、等等一些工具&#xff0c;几乎涵盖了正常前端开发要用到的所有工具。 二、环境准备 pnpm 相比npm、yarn&#xff0c;pnpm更小更快扁平…

为大模型而生!顶流大佬发起成立学术会议 COLM,或成为未来 NLP 最强顶会?!

夕小瑶科技说 原创 作者 | 智商掉了一地、ZenMoore 前段时间&#xff0c;ACL 2024 的主席公开抨击称“ arXiv是科研的毒瘤”&#xff0c;这引发了大范围的争论。 一时间&#xff0c;大家对 *CL 的抵触情绪愈发高涨&#xff0c;绝大多数学界都在这场辩论中站在了支持 arXivTwit…

PreparedStatement

使用参数化查询&#xff1a;使用预编译的语句和参数化查询来执行SQL语句&#xff0c;而不是将用户输入直接嵌入到SQL语句中。这将帮助防止恶意输入注入SQL语句。

Zoho WorkDrive荣获专业研究机构评定的“Leader”称号

近年&#xff0c;在云计算、大数据、移动互联网、社交所引领的数字化转型变革中&#xff0c;企业对于数字资产的保护和利用愈加重视。相较于结构化数据&#xff0c;企业对于非结构化数据&#xff08;文档、图片、音视频等&#xff09;管理的需求更强、难度更大。 同时&#xf…

NodeJS 菜鸟教程目录

NodeJS 七天入门教程 谁适合阅读本教程? 前端开发者和希望构建后端的开发者:如果你是一名前端开发者,或者是一名希望构建后端的开发者,那么本教程将为你提供一个很好的学习Node.js的机会。通过学习本教程,你可以更好地了解后端开发的技术和Node.js在后端开发中的应用。初学…

日常学习记录随笔-redis实战

redis的持久化&#xff08;rdb,aof,混合持久化&#xff09; redis的主从架构以及redis的哨兵架构 redis的clusterredis 是要做持久化的&#xff0c;一般用redis会把数据放到缓存中为了提升系统的性能 如果redis没有持久化&#xff0c;重启的化数据就会丢失&#xff0c;所有的请…

【LeetCode热题100】--31.下一个排列

31.下一个排列 思路&#xff1a; 方法&#xff1a;两遍扫描 注意到下一个排列总是比当前排列要大&#xff0c;除非该排列已经是最大的排列。我们希望找到一种方法&#xff0c;能够找到一个大于当前序列的新序列&#xff0c;且变大的幅度尽可能小。具体地&#xff1a; 我们需要…

5年经验之谈 —— App测试、Web测试和接口测试一般测试流程!

app测试流程&#xff1a; 1、需求分析&#xff0c;了解具体需求 2、测试准备&#xff1a;原型图、效果图、需求文件、测试用例、用例评审、各种测试数据准备 3、测试环节&#xff1a;接受版本&#xff0c;开始执行 1&#xff09;冒烟测试&#xff1a;对版本质量的控制以及此…

【LeetCode: 260. 只出现一次的数字 III | 位运算 | 哈希表】

&#x1f680; 算法题 &#x1f680; &#x1f332; 算法刷题专栏 | 面试必备算法 | 面试高频算法 &#x1f340; &#x1f332; 越难的东西,越要努力坚持&#xff0c;因为它具有很高的价值&#xff0c;算法就是这样✨ &#x1f332; 作者简介&#xff1a;硕风和炜&#xff0c;…

ModStartCMS v7.4.0 公共图片库支持,安全功能升级

ModStart 是一个基于 Laravel 模块化极速开发框架。模块市场拥有丰富的功能应用&#xff0c;支持后台一键快速安装&#xff0c;让开发者能快的实现业务功能开发。 系统完全开源&#xff0c;基于 Apache 2.0 开源协议&#xff0c;免费且不限制商业使用。 功能特性 丰富的模块市…

C# 开发工具包 – 现已正式发布

作者&#xff1a;Wendy Breiding 排版&#xff1a;Alan Wang 今天&#xff0c;我们很高兴地宣布 C# 开发工具包正式发布&#xff0c;C# 开发工具包是一个 Visual Studio Code 扩展&#xff0c;为 Linux、macOS 和 Windows 带来了改进的编辑器优先 C# 开发体验。 谢谢社区的努…

文件传输软件的挑战与发展趋势

无论是在教育、医疗、金融、媒体、政府等行业&#xff0c;还是在个人生活和工作中&#xff0c;文件传输软件都有着广泛的应用价值和意义。然而&#xff0c;随着信息技术的发展和数据量的增长&#xff0c;文件传输软件也面临着一些挑战和问题&#xff0c;同时也有着一些发展趋势…

eNSP在hybrid接口上配置vlan

一、什么是vlan VLAN&#xff08;Virtual Local Area Network&#xff0c;虚拟局域网&#xff09;是一种通信技术&#xff0c;它可以将一个物理的局域网在逻辑上划分成多个广播域。每个VLAN都是一个广播域&#xff0c;VLAN内的主机可以直接通信&#xff0c;而VLAN之间则不能直…