深入篇【C++】总结智能指针的使用与应用意义(auto_ptr/unique_ptr/shared_ptr/weak_ptr)底层原理剖析+模拟实现

news2024/10/6 20:40:20

深入篇【C++】总结智能指针的使用与应用意义&&(auto_ptr/unique_ptr/shared_ptr/weak_ptr)底层原理剖析+模拟实现

  • 智能指针的出现
  • 智能指针的使用
  • 应用意义/存在问题
  • 智能指针原理剖析+模拟实现
    • auto_ptr
    • unique_ptr
    • shared_ptr
    • weak_ptr

智能指针的出现

首先我们要理解智能指针是什么。为什么要有智能指针。什么场景会用到智能指针。
首先我们知道C++的异常有很大的缺陷,那就是执行流会乱跳,这样就可能会造成内存泄露问题,比如在new和delete之间出现异常,那么就会出现资源没有释放,内存泄露。


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

	return a / b;
}
void func()
{
	div();
}
int main()
{

	try
	{
		func();
	}
	catch(const exception& e)//抛基类异常,用父类来接受
	{
		cout << e.what() << endl;
	}
}

在这里插入图片描述
首先这是正常的使用异常处理,当div函数抛异常时,就会直接跳到catch捕获的地方,进行处理。

//异常的缺点:内存泄露,在new和delete之间抛异常
#include <vector>
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}
void func()
{
	vector<int>* p1 = new vector<int>;
	div();
	delete p1;
	cout << "delete:" << p1 << endl;
}
//正常情况下,如果不抛异常,就不会内存泄露,但抛异常后,就会泄露,解决方法
//是再套一层异常判断,捕获的异常不处理,继续抛出(在抛出之前将资源释放)
int main()
{

	try
	{
		func();
	}
	catch (const exception& e)//抛基类异常,用父类来接受
	{
		cout << e.what() << endl;
	}
}

在这里插入图片描述

那如果是这样的场景呢,在new和delete之间如果div()抛异常了,那么开辟的空间p1就无法释放。最终会造成内存泄露的。
而想要处理这样的问题,就需要对div函数套一层异常判断,如果出现异常,先释放资源了,再将异常抛出给外面的捕获处理。

//异常的缺点:内存泄露,在new和delete之间抛异常
#include <vector>
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}
void func()
{
	vector<int>* p1 = new vector<int>;
	//如果p1抛异常就 没有问题,没有资源的申请,所以也不需要释放
	vector<int>* p2 = new vector<int>;
	//如果p2抛异常,p1就内存泄露
	vector<int>* p3 = new vector<int>;
	//如果p3抛异常,p1和p2就内存泄露
	//…………
	//所以异常出现后,很容易造成内存泄露,有什么办法可以解决呢?
	try
	{
		div();
	}
	catch (...)
	{
		delete p1;
		cout << "delete" << p1 << endl;
		throw;//再抛出
	}


	delete p1;
	cout << "delete:" << p1 << endl;
}
//正常情况下,如果不抛异常,就不会内存泄露,但抛异常后,就会泄露,解决方法
//是再套一层异常判断,捕获的异常不处理,继续抛出(在抛出之前将资源释放)
int main()
{

	try
	{
		func();
	}
	catch (const exception& e)//抛基类异常,用父类来接受
	{
		cout << e.what() << endl;
	}
}

在这里插入图片描述
只不过这样做还是有缺陷,如果有很多个资源申请呢?或者有连续的资源申请呢?比如资源1申请,如果出现异常,那么就直接跳出去,如果资源2异常那么就需要对这个操作套一层异常处理,需要先将资源1的资源释放了,然后再重新抛异常给外面。如果资源3申请时出现异常呢?…………这样是不是每次申请资源时都需要套上异常处理呢?这样也太麻烦了吧!但你又不得不这样做,因为你要保证内存安全啊!

//异常的缺点:内存泄露,在new和delete之间抛异常
#include <vector>
int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
		throw invalid_argument("除0错误");

	return a / b;
}
void func()
{
	vector<int>* p1 = new vector<int>;
	//如果p1抛异常就 没有问题,没有资源的申请,所以也不需要释放
	vector<int>* p2 = new vector<int>;
	//如果p2抛异常,p1就内存泄露,需要给该步骤套上异常处理,释放p1然后再将异常抛出
	vector<int>* p3 = new vector<int>;
	//如果p3抛异常,p1和p2就内存泄露,需要给该步骤套上异常处理,释放p1,p2然后再将异常抛出
	//…………
	//所以异常出现后,很容易造成内存泄露,有什么办法可以解决呢?
	try
	{
		div();
	}
	//如果div()抛异常,,p1,p2,p3都得释放
	catch (...)
	{
		delete p1;
		delete p2;
		delete p3;
		cout << "delete" << p1 << endl;
		throw;//再抛出
	}


	delete p1;
	cout << "delete:" << p1 << endl;
}
//正常情况下,如果不抛异常,就不会内存泄露,但抛异常后,就会泄露,解决方法
//是再套一层异常判断,捕获的异常不处理,继续抛出(在抛出之前将资源释放)
int main()
{

	try
	{
		func();
	}
	catch (const exception& e)//抛基类异常,用父类来接受
	{
		cout << e.what() << endl;
	}
}

这时,智能指针就出现了!
智能指针有三大特性:

1.RAII
2.可以像指针一样
3.存在拷贝问题。

一.RAII,是一种利用对象生命周期来控制程序资源的技术。
我们在对象构造的时候,将开辟的资源交给对象,那么这样在对象生命周期内,该资源一直存在,然后在该对象的析构函数里,进行释放资源。这样对象析构,资源也就释放了。
也就是我们将资源交给一个对象进行管理,当对象的生命周期还在时,资源就存在,当对象销毁时,资源就被释放,这样,我们就将管理资源的责任托管给了对象。
好处:

1.释放了双手,不需要我们显示的释放资源。就不用怕资源最后没有释放。
2.采用这种方式,对象所需的资源在其生命周期内始终保持有效。

二.智能指针,从指针二字我们就应该能意识到它是具备指针的特性的,而指针有哪些特性呢?

1.可以解引用。通过解引用访问资源。
2.可以使用→运算符来访问资源里的内容。

三.所以说智能指针本质上也是一个指针,那指针之间也是可以赋值,拷贝的。并且是值拷贝。

智能指针的使用

智能指针的实现其实很简单,智能指针底层就是封装着该类型的指针。原理(RAII)就是将申请的资源托管给一个对象管理,所以在对象构造时,将资源给对象即可。当对象析构时,就将资源释放。
然后还要实现*运算符重载和→运算符重载。

namespace tao
{
	template<class T>

class smater_ptr
{
public:
	smater_ptr( T* ptr)//将资源交给对象管理
		:_ptr(ptr)
	{}

	~smater_ptr()//对象销毁时就将资源释放
	{
		cout << "delete:" << _ptr << endl;
		delete _ptr;
	}

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

private:
	T* _ptr;
};

那么我们就可以处理上面遗留的问题了:


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

	return a / b;
}
void func()
{
	//vector<int>* p1 = new vector<int>;
	如果p1抛异常就 没有问题,没有资源的申请,所以也不需要释放
	//vector<int>* p2 = new vector<int>;
	如果p2抛异常,p1就内存泄露
	//vector<int>* p3 = new vector<int>;
	如果p3抛异常,p1和p2就内存泄露
	//…………
	//所以异常出现后,很容易造成内存泄露,有什么办法可以解决呢?
	smater_ptr<vector<int>> p1(new vector<int>);
	//将资源给对象p1管理
	smater_ptr<vector<int>> p2(new vector<int>);
	smater_ptr<vector<int>> p3(new vector<int>);

	smater_ptr<string> ps(new string("小陶来咯"));
	div();
	cout << *ps << endl;
	cout << ps->size() << endl;
}

int main()
{

	try
	{
		func();
	}
	catch (const exception& e)//抛基类异常,用父类来接受
	{
		cout << e.what() << endl;
	}
}

在这里插入图片描述
这里再怎么抛异常都不会影响资源的释放,因为资源是被智能指针对象管理着,当对象销毁时,管理的资源肯定会释放的。
我们也不用担心连续的申请资源会出现异常的情况了。

应用意义/存在问题

一般正常使用智能指针,就可以避免大多数的内存泄露问题,但不排除乱用的。智能指针的应用能帮助我们很好的处理因为异常出现而导致的内存泄露问题。不过初期的智能指针还存在着问题,比如拷贝问题。C++98时期就已经存在智能指针auto_ptr.不过吐槽点很多,现在公司基本禁止使用auto_ptr.
随着C++的发展,又出现其他的智能指针比如:uniqe_ptr
,shared_ptr,weak_ptr(本质不是)。

那智能指针存在什么问题呢?我们来分析分析:


int main()
{
	smater_ptr<string> sp1(new string("xioatao"));
	smater_ptr<string> sp2(new string("xioyao"));

	//存在这样的场景:
	smater_ptr<string> sp2(sp1);
	//指针之间的拷贝就应该是浅拷贝,我们不用写编译器会自动生成。
	//但浅拷贝会出现什么问题呢?
	//1.同一块资源被释放两次 2.内存泄露
	return 0;
}

在这里插入图片描述
呐,这就是智能指针的拷贝问题,指针之间的拷贝肯定是值拷贝,因为是内置类型,不存在深拷贝。那浅拷贝我们不写,编译器生成的拷贝构造就是浅拷贝,所以我们就不用写了吗?
首先,我们要分析,确实是浅拷贝,但是如果指针浅拷贝了,就会出现这样的问题:①指向同一块的空间被释放两次②有一块空间没有释放,内存泄露。

那我们来看看C++库里是如何处理这些问题的。

智能指针原理剖析+模拟实现

auto_ptr

C++98库里提供的auto_ptr智能指针,处理这种问题的原理是:管理权转移。
什么叫管理权转移呢?就比如sp3(sp1),将sp1拷贝给sp3。也就是用sp1构造sp3。首先我们要明白,sp1是管理着一块资源的,sp3还没有实例化,没有管理资源。这里直接将sp1管理资源的权力转移给sp3。然后sp1就没有权力管理资源了也就是不需要管理资源了。不管理资源是如何做到的呢?直接将智能指针对象里面的指针置空即可。

namespace tao
{
	template <class T>
	class auto_ptr
	{
	public:
		auto_ptr(T* ptr)
			:_ptr(ptr)
		{}

		~auto_ptr()
		{
			delete _ptr;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		//sp3(sp1)
		auto_ptr(auto_ptr<T>& p)
			:_ptr(p._ptr)
		{
			//将资源转移后,被拷贝对象再置空
			p._ptr = nullptr;
			//如果不置空,就会释放两次
		}
	private:
		T* _ptr;
	};

这就是C++98提供的auto_ptr智能指针。这个版本被吐槽的很多,因为你将sp1对象管理资源的权转移后,sp1就悬空了。如果有人要再次访问sp1呢?就是那个不管理资源的智能指针。(里面的指针必须置空,不然就会释放两次。)


//我们写一个类,方便观察资源创建和释放
#include "smater_ptr.h"
class B
{
public:

	B(int b = 0)
		:_b(b)
	{
		cout << "b=0" << endl;
	}

	~B()
	{
		cout << this;
		cout << "~B()" << endl;
	}

private:

	int _b;
};
//自定义类型构造会调用它的构造函数,和析构函数。
int main()
{
	tao::auto_ptr<B> b1(new B(1));
	tao::auto_ptr<B> b2(new B(2));
	//这个是auto_ptr C++98时期就出现,但不好,很多公司严禁不给使这个
	tao::auto_ptr<B> b3(b1);
	//因为存在严重的不合理地方,当出现智能指针拷贝赋值的地方
	//auto_ptr处理的方式是:直接转移资源的管理权。
	//拷贝时,会把被拷贝对象的资源管理转移给拷贝对象。而被被拷贝对象就没有资源管理,直接置空
	//存在问题:管理权转移后,再次访问被拷贝对象
	//b1这个智能指针已经没有资源可以管理了,里面的指针直接置空了,不能再访问了
	//b1->_b++;
	b3->_b++;
}

unique_ptr

由于auto_ptr的设计太不合理,C++11中开始提供更靠谱的unique_ptr智能指针。
unique_ptr的原理其实很简单,四个字:简单粗暴。
直接不给拷贝,简单粗暴的防止拷贝。利用delete关键字,将函数定义为删除函数,不能使用。指针赋值的操作也被禁止。

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

		~unique_ptr()
		{
			delete _ptr;
		}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		unique_ptr(unique_ptr<T>& p) = delete;//直接删除掉,不能拷贝
	private:
		T* _ptr;
	};

在这里插入图片描述

shared_ptr

但是如果就是存在智能指针间的拷贝,那该怎么办呢?unique_ptr肯定不能使用了。
所以C++11又提供了一个可以允许拷贝的智能指针,那就是shared_ptr.
shared_ptr智能指针实现的原理是:<引用计数>

原理
①当有一个对象管理资源时,计数器就显示1.当有两个对象指向资源时,计数器就显示2.当有n个对象指向资源时,计数器就显示n。
②当指向资源的对象生命周期结束时,首先将该资源的上的计数器减减。然后判断计数器是否为0.如果不为0,说明还存在对象管理着资源。如果为0,就说明没有对象管理资源了,该资源就可以释放了。

那这个计数器应该如何设计呢?是设计成普通计数器就可以吗?还是设计成静态的呢?
如果设计成普通的计数器,就是在智能指针内部存一个计数器。这就表明每个对象都有一个计数器。这合理吗?在这里插入图片描述
这肯定不合理啊,指向相同资源时如何进行计数呢?这样不能根据计数器来判断一个资源上有多少对象管理了。一个资源就一个计数器就可以了。那设计成静态计数器呢?

在这里插入图片描述

设计成静态计数器,如果只有一块资源的话,也不是不可以,但我们不知道会有多少资源啊,当有新的资源开辟后,该资源上应该只有一个对象管理,但使用静态计数器后,所有对象共享该计数器,就造成了该资源上的计数不对。

我们想要的计数器应该是要伴随着资源的申请而生成。当有一个资源生成被智能指针管理后,就会生成一个计数器,来计算该资源受管理的个数。当有两个资源时,就会有两个计数器,各计算各的,互不影响。所以这里的计数器应该是动态计数器。
当有资源申请时并给对象管理时(也就是调用对象的构造函数)时,就会动态生成一个计数器。
在这里插入图片描述
计数器实现完后,我们就可以利用引用计数来实现拷贝。比如用p2(p1),用p1拷贝构造p2.那么p2就要指向p1指向的资源,并且资源上的计数器要加加。这两个智能指针都管理着资源。不过当p2销毁时,资源并不会释放,计数器首先会减减,然后判断计数器是否为0,只有计数为0了,才可以将资源释放。

template <class T>
class shared_ptr
{
public:
	shared_ptr(T* ptr=nullptr)
		:_ptr(ptr)
		,_pcount(new int(1))
	{}//当有资源申请并交给对象管理时,计数器才会生成。


     //只有对象管理一个资源时,才会生成计数器。不然不会生成。
	//当拷贝时(没有资源的生成管理)就让其他对象的指针向生成的计数器。
	~shared_ptr()
	{
	    //当有对象要销毁时,首先先减减计数器
		//只有最后一个对象管理时才可以释放资源
		if (--(*_pcount) == 0)
		{
			cout << "delete" << _ptr << endl;
			delete _ptr;
			delete _pcount;
		}
	}
	T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	shared_ptr(shared_ptr<T>& ps)
		:_ptr(ps._ptr)
		, _pcount(ps._pcount)
		//这个单纯指向没有再管理一个新的资源,所以不会生成计数器,只需要指向原来生成的计数器。
	{
		++(*_pcount);//然后原来的计数器加加即可,表面该资源上多了一个对象管理
	}
	
	T* get()const//获取_ptr 
	{
		return _ptr;
	}
private:
	T* _ptr;
	int* _pcount;
	//动态计数器
};

shared_ptr除了支持智能指针间的拷贝,还支持指针间的赋值。那么赋值重载是如何实现的呢?
首先赋值是两个都已经存在的对象进行赋值。而拷贝构造是已存在的拷贝给不存在的对象。
假设两个存在的对象,各自都管理着一块资源,当两个对象进行赋值,会发生什么呢?
在这里插入图片描述
不过这里要注意两个细节,细节一就是赋值对象的计数器减减后,需要进行判断,是否为0.如果计数器为0了,就说明该资源上没有对象管理,那么该资源就可以释放了。如果不是0,那就没事。
细节二,就是自己给自己赋值的场景会有bug存在。
在这里插入图片描述

//shared_ptr的赋值运算符重载
shared_ptr<T>& operator=(const shared_ptr<T>& ps)
{
      //自己给自己赋值的场景要避免
   	if (_ptr == ps._ptr)return *this;
	
	//首先要对赋值对象管理的资源的计数器减减,要注意被赋值对象管理的资源是否只有一个智能指针控制
	if (--(*_pcount) == 0)
	{
		delete _ptr;
		delete _pcount;
	}
	_ptr = ps._ptr;
	_pcount = ps._pcount;//正常转移指向即可
	//被赋值对象的计数器++即可
	(*ps._pcount)++;
	return *this;
}

验证一下:

int main()
{
	tao::shared_ptr<B> b1(new B(1));
	tao::shared_ptr<B> b2(b1);
	tao::shared_ptr<B> b3(b1);

	  
    tao::shared_ptr<B> b4(new B(2));

	tao::shared_ptr<B> b5(b4);
	b1 = b5;

}

在这里插入图片描述

weak_ptr

shared_ptr几乎已经完美了,既具有智能指针的特性,又允许拷贝和赋值。但还是具有缺点的,具有什么缺点呢?
该问题就是:循环计数
当出现循环计数时,shared_ptr是没有办法解决。什么叫循环计数问题呢?

当存在这种需求时:动态开辟的节点需要链接起来时。我们使用智能指针来管理资源会发生什么呢?


struct Node
{
	B _val;
	Node* _next;
	Node* _prev;
};

int main()
{

	tao::shared_ptr<Node> sp1(new Node);
	tao::shared_ptr<Node> sp2(new Node);

	//sp1->_next = sp2;
	//类型不匹配,sp1->next的类型是Node而sp2的类型是shared_ptr类型所以这样无法链接起来。
//	//所以Node节点里存的应该是shared_ptr类型的指针,这样才可以链接起来
}

我们发现无法链接起来,因为节点里next是Node*类型的,而sp2是shared_ptr类型的。所以为了能够链接起来,节点里存的应该是shared_ptr类型的next。

struct Node
{
	B _val;
	shared_ptr<Node> _next;
	shared_ptr<Node> _prev;
};

int main()
{
	tao::shared_ptr<Node> sp1(new Node);
	tao::shared_ptr<Node> sp2(new Node);
//	//所以Node节点里存的应该是shared_ptr类型的指针,这样才可以链接起来
	sp1->_next = sp2;
	sp2->_prev = sp1;

}

这样两个节点就链接起来了。可是链接起来后就出现问题了:
在这里插入图片描述
我们发现两个智能指针管理的资源都没有释放,这是为什么呢?
在这里插入图片描述
在这里插入图片描述
C++中是如何解决循环计数的呢?C++11提供了weak_ptr专门用来处理shared_ptr出现的循环计数问题。
那么weak_ptr解决循环计数的原理是什么呢?
首先我们需要明白引起循环计数的原因是什么,要理解什么场景下会发生循环计数。
1.主要原因就是因为智能指针定义在节点的内部。
2.然后就是因为计数器要等于1时才可以释放资源。

就是因为内部的智能指针参与了资源的管理,导致计数器增加,外面管理的资源的对象销毁后资源也无法销毁,需要里面的智能指针对象销毁才可以销毁,而里面的对象销毁又需要资源先销毁才可以销毁。所以最主要原因就是内部的智能指针管理资源。
所以weak_ptr实现的原理就是让节点里面的智能指针不管理资源,就单纯的链接节点,不参与资源的管理,但可以访问资源。
这样计数器就不会增加,当外面的对象销毁,资源就会正常销毁。
所以正常操作应该是这样:

struct Node
{
	B _val;
	weak_ptr<Node> _next;
    weak_ptr<Node> _prev;
    //weak_ptr不管理资源,不会增加计数器
};
int main()
{
	shared_ptr<Node> sp1(new Node);
	shared_ptr<Node> sp2(new Node);
	sp1->_next = sp2;
	sp2->_prev = sp1;
}

在这里插入图片描述
所以weak_ptr严格上来说不是智能指针,它不具备RAII特性。不管理资源。它主要是提供支持由shared_ptr类型转换成weak_ptr类型的拷贝构造和赋值。而不是管理资源。

	template <class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}
		T& operator*()
		{
			return *_ptr;
		}
		T* operator->()
		{
			return _ptr;
		}
		weak_ptr(const shared_ptr<T>& sp)//在类外无法访问到sp的保护成员_ptr,所以徐娅get函数来获取。
			:_ptr(sp.get())
		{}
		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();
			return *this;
		}
	private:
		T* _ptr;
	};
};

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

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

相关文章

【PCIE733】基于PCI Express总线架构的2路160MSPS AD采集、12路LVDS图像数据采集卡

PCIE733是一款基于PCI Express总线架构的&#xff0c;实现2路16-bit、160MSPS ADC采集功能、12路LVDS数据采集板卡。该板卡遵循PCI Express 2.0规范&#xff0c;全高半长尺寸&#xff0c;板卡采用Xilinx的28nm高性能FPGA处理器XC7K325T作为主控制器&#xff0c;板卡ADC器件采用…

【Jenkins】调用API构建并钉钉通知

文章目录 Jenkins API介绍提交作业带参数的作业API 令牌 Shell调用代码 Jenkins API介绍 Jenkins 提供了远程访问 API。目前它有三种格式&#xff1a; XML JSON Python 远程访问 API 形式为"…/api/" 例如&#xff0c; Jenkins 安装位于https://ci.jenkins.io&a…

day05-前后端项目上传到gitee、后端多方式登录接口、发送短信功能、发送短信封装、短信验证码接口、短信登录接口

1 前后端项目上传到gitee 2 后端多方式登录接口 2.1 序列化类 2.2 视图类 2.3 路由 3 发送短信功能 4 发送短信封装 4.0 目录结构 4.1 settings.py 4.2 sms.py 5 短信验证码接口 6 短信登录接口 6.1 视图类 6.2 序列化类 1 前后端项目上传到gitee # 我们看到好多开源项目…

协同育人|电巢携手武汉软件工程职业学院项目实训顺利开班!

为深化校企合作&#xff0c;产教融合助力新工科建设&#xff0c;提升学生工程实践能力&#xff0c;电巢工程能力实训班按照不同岗位类别&#xff0c;匹配对应的企业岗位任职能力要求对学生开展分级培养&#xff0c;以产业需求为导向&#xff0c;培养创新型、应用型人才。 10月…

新手小白学自动化测试必须要知道的知识!

1、为什么要进行自动化测试&#xff1f; ①黑盒测试回归效率低&#xff1b; ②手动测试的偶然性和不确定性&#xff1b; ③回归的覆盖率不足&#xff1b; ④交付的产品质量无法保证&#xff0c;全靠评估&#xff1b; ⑤系统越复杂&#xff0c;问题越多&#xff1b; ⑥上线…

14私有化属性的意义

目录 私有化属性有两个意义&#xff0c;一个是数据保护&#xff0c;一个是数据过滤 一、数据保护 二、数据过滤 私有化属性有两个意义&#xff0c;一个是数据保护&#xff0c;一个是数据过滤 一、数据保护 class Person:def __init__(self):self.__age 10pass p1 Person(…

windows安装nvm以及解决yarn问题

源代码 下载 下一步一下步安装即可 检查是否安装成功 nvm出现上面的代码即可安装成功 常用命令 查看目前安装的node版本 nvm list [available]说明没有安装任何版本&#xff0c;下面进行安装 nvm install 18.14使用该版本 node use 18.14.2打开一个新的cmd输入node -…

Excel 从网站获取表格

文章目录 导入网站数据导入股票实时行情 用 Excel 获取网站数据的缺点&#xff1a;只能获取表格类的数据&#xff0c;不能获取非结构化的数据。 导入网站数据 转到地址之后&#xff1a; 实测该功能经常导致 Excel 卡死。 导入股票实时行情

Observability:使用 Elastic Observability 摄取和分析 Prometheus 指标

作者&#xff1a;Jenny Morris 在监控和可观察性领域&#xff0c;Prometheus 凭借其强大的数据收集机制、灵活的查询功能以及与其他工具的集成以实现丰富的仪表板和可视化&#xff0c;已成长为云原生环境中监控的事实上的标准。 Prometheus 主要是为短期指标存储而构建的&…

软件工程与计算总结(十)软件体系结构设计与构建

目录 ​编辑 一.体系结构设计过程 1.分析关键需求和项目约束 2.选择体系结构风格 3.体系结构逻辑设计 4.体系结构实现 5.完善体系结构设计 6.定义构件接口 二.体系结构原型构建 1.包的创建 2.重要文件的创建 3.定义构件之间的接口 4.关键需求的实现 三.体系结构的…

算法通过村第十四关-堆|青铜笔记|堆结构

文章目录 前言堆的概念和特征堆的构成过程插入操作删除操作总结 前言 若执于空&#xff0c;空亦为障。 --彼得马西森《雪豹》 堆结构是一种非常重要的基础数据结构&#xff0c;也是算法的重要内容&#xff0c;很多题目甚至只能通过用堆来进行&#xff0c;所以我们必须明确什么类…

AnyLogic Pro 8.8.4 Crack

为什么选择 AnyLogic 仿真软件&#xff1f; 行业特定库 用于通用业务流程或工作流程的流程建模库。 流体库可模拟采矿或石油天然气等行业的散装货物和液体输送。 用于铁路运输、码头和堆场的铁路图书馆。 行人图书馆&#xff0c;用于记录机场、体育场馆、车站或购物中心的行人…

深度学习环境 | Linux下安装,卸载,查看pytorch版本

一 在Linux下安装pytorch 1 进入Linux环境以后 新建一个名为pytorch的虚拟环境&#xff0c;执行以下代码&#xff1a; conda create -n pytorch python3.82 激活新建的pytorch虚拟环境&#xff0c;执行以下代码&#xff1a; conda activate pytorch # conda版本较新使用这条…

目录启示:PHP 与命名空间的声明

文章目录 参考环境命名空间概念版本支持影响范围 全局命名空间概念魔术常量 \_\_NAMESPACE\_\_声明全局命名空间 声明命名空间为空间命名命名规则核心命名空间 子命名空间的声明在同一文件中定义多个命名空间无括号命名空间声明有括号命名空间声明禁止混合使用推荐使用有括号命…

【Python学习笔记】类型/运算/变量/注释

前言 人生苦短&#xff0c;追求生产力&#xff0c;做一只时代风口的猪&#xff0c;应该学python Python语言中&#xff0c;所有的数据都被称之为对象。 1. 对象类型 Python语言中&#xff0c;常用的数据类型有&#xff1a; 整数&#xff0c; 比如 3 小数&#xff08;也叫浮…

如何查找文献,如何阅读文献

一、高效查找需要阅读的文献 1、首先进入知网的高级检索页&#xff0c;点击“学术期刊”&#xff0c;你会看到“来源类别”选择&#xff0c;在这个里选择北核和C刊 2、在检索结果页选择一篇自己感兴趣的文章&#xff0c;点击篇名进入文章详情页&#xff0c;下拉可看到核心文献…

高速数字化仪和AWG在车辆总线(CAN/LIN/PSI5)测试中的应用(二)

前情回顾 上期德思特向大家介绍了德思特模块化数字化仪在车辆测试中的应用&#xff0c;8到16位的通道&#xff0c;高达5 GS/s的采样率允许选择与应用相匹配的快速或慢速采样&#xff0c;即使缺少组件&#xff0c;它们也可以进行测试。这期将为大家介绍如何使用信号源进行仿真&…

呼吁社区共同维护Sui品牌和商标

Sui社区成员可以通过举报Sui商标和品牌资产的不当使用来帮助保护网络的信誉。Sui商标政策解释了logo和名称的可接受和不可接受的使用方式。这些展示代表Sui面向公众&#xff0c;而善意行为者的正确使用有助于维护Sui的声誉。 Sui网络在公众中享有良好声誉&#xff0c;Sui社区都…

遥感影像的面向对象方法

面向对象分割算法是一种将图像中的像素分割成不同的对象的算法。它通过对像素进行聚类&#xff0c;将相似的像素分为同一个对象&#xff0c;从而实现图像分割的目的。常见的面向对象分割算法包括基于区域的分割算法、基于边缘的分割算法和基于能量的分割算法等。 其中&#xff…

【2023年11月第四版教材】专题1 - 计算题考点汇总 (合集篇)

专题1 - 计算题考点汇总 (合集篇&#xff09; 1 进度类1.1 PERT三点估算1.1.1 β分布1.1.2 三角分布 1.2 单代号网络图1.2.1 画图1.2.2 找关键路径1.2.3 计算总工期1.2.4 总时差1.2.5 自由时差1.2.6 工期压缩 1.3 双代号网络图1.4 双代号时标网络图1.4.1 画图1.4.2 找关键路径1…