C++11: 智能指针(unique_ptr,shared_ptr和weak_ptr的使用及简单实现)

news2024/9/24 9:20:27

目录

1. 为何需要智能指针?

1.1 抛异常场景

1.2 什么是内存泄漏

2. 智能指针的原理

2.1 RAII技术

2.2 补充实现

3. auto_ptr

4. unique_ptr

4.1 使用及原理

4.2 定制删除器

5. shared_ptr

5.1 shared_ptr简介及使用

5.2 shared_ptr简单实现

5.2.1 基本框架

5.2.2 拷贝构造 赋值重载

5.2.3 删除器

5.2.4 其他接口函数及测试

6. weak_ptr

6.1 循环引用

6.2 weak_ptr使用

6.3 原理及简单实现

总结


1. 为何需要智能指针?

1.1 抛异常场景

在下面的场景中,Fun函数在堆上开辟了两个对象。如果Add函数中的两个参数相加为0,就会抛异常。抛出的异常被catch关键字捕获之后,会执行catch内的代码,不会再执行Func函数后面的内容。而catch内部代码可能会忘记释放之前的对象,就会造成内存泄漏。

//假设有特殊要求相加结果不能为0,为0抛异常
int Add(int x, int y)
{
	if (x + y == 0)
		throw "相加为0";
	
	return x + y;
}

void Func()
{
	int* p1 = new int;
	int* p2 = new int;

	try
	{
		int x, y;
		cin >> x >> y;
		Add(x, y);
	}
	catch(...)
	{
		delete p1;
        throw;
	}

	delete p1;
	delete p2;
}

int main()
{
	try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

	return 0;
}

1.2 什么是内存泄漏

内存泄漏是指在程序运行过程中,由于疏忽或错误而未能释放不再使用的内存,导致这部分内存得不到回收。内存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对该段内存的控制,从而造成了内存的浪费。

内存泄漏的影响可能包括:

  • 内存使用量增加:随着内存泄漏的累积,程序占用的内存会越来越多。
  • 程序性能下降:频繁的内存分配和释放可能导致内存碎片化,进而影响程序性能。
  • 系统资源耗尽:长时间运行的程序可能会耗尽系统内存,特别是对于操作系统游戏服务器数据库系统客户端应用程序等关键任务程序,会导致响应越来越满,最终卡死。

2. 智能指针的原理

2.1 RAII技术

RAII(Resource Acquisition Is Initialization)是一种编程技术,主要用于C++等语言中,它将资源的获取与对象的生命周期绑定在一起,以确保资源在使用完毕后能够被正确释放,从而防止资源泄漏。

RAII的基本思想是:将资源的获取封装在一个对象的构造函数中。将资源的释放封装在同一个对象的析构函数中。利用对象的自动生命周期管理(即对象创建时自动调用构造函数,对象销毁时自动调用析构函数)来管理资源。这种做法有两大好处:

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

如下面代码所示,将资源创建跟SmartPtr类对象的生命周期绑定在一起,不管什么时候抛异常,执行跳到哪一步,都会随着对象的销毁并释放资源。

struct A
{
	~A()
	{
		cout << "~A()" << endl;
	}
};

template<class T>
class SmartPtr 
{
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}
private:
	T* _ptr;
};

//假设有特殊要求相加结果不能为0,为0抛异常
int Add(int x, int y)
{
	if (x + y == 0)
		throw "相加为0";
	
	return x + y;
}

void Func()
{
    SmartPtr<A> sp1(new A);
    SmartPtr<A> sp2(new A);

	try
	{
		int x, y;
		cin >> x >> y;
		Add(x, y);
	}
	catch(...)
	{
        throw;
	}
}

int main()
{
    try
	{
		Func();
	}
	catch (exception& e)
	{
		cout << e.what() << endl;
	}

    return 0;
}

运行结果如下:

2.2 补充实现

上面实现的SmartPtr类还不算智能指针。因为他只能进行资源的获取和释放操作,却不能类似于指针,进行解引用操作和使用->访问所指向的空间内容。我们需要重载一下*和->符号,达到类似指针的操作。

如下面的代码所示,先创建一个日期类,成员变量有表示年月日的整型。使用SmartPtr类创建两个对象,对象类型分别是日期类和整型。可以使用该类进行类似于指针的操作,对日期类的成员变量进行修改,并调用Print函数打印年月日。对于内置类型也可以进行操作。

struct Date
{
	int _year;
	int _month;
	int _day;

	Date(int year = 1900, int month = 1, int day = 1)
		:_year(year)
		, _month(month)
		, _day(day)
	{}

	void Print()
	{
		cout << "日期:" << _year << "/" << _month << "/" << _day << endl;
	}

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

template<class T>
class SmartPtr
{
public:
	SmartPtr(T* ptr = nullptr)
		: _ptr(ptr)
	{}

	~SmartPtr()
	{
		if (_ptr)
			delete _ptr;
	}

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

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

private:
	T* _ptr;
};

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

	sp1->_year = 2024;
	sp1->_month = 9;
	sp1->_day++;
	sp1->Print();

	*sp2 = 3;
	cout << "sp2指向的内容->" << *sp2 << endl;

	return 0;
}

运行结果如下:

3. auto_ptr

auto_ptr 是 C++98 标准库中的一个智能指针,用于管理动态分配的对象,以避免内存泄漏。在 C++11 标准发布后,auto_ptr 被废弃,并在 C++17 标准中被彻底移除。它的功能由 unique_ptr 取代,因为 unique_ptr 提供了更安全、更灵活的内存管理功能。

auto_pt的定义位于头文件<memory>中。它是一个模版类,可以管理任何类型的动态分配的对象。使用方式和上面实现的SmartPtr类类似。

#include <memory>

void test_auto1()
{
	auto_ptr<Date> ap1(new Date);
	ap1->_year = 2024;
	ap1->Print();
}

运行结果如下:

看着上面的代码,发现auto_ptr好像也没什么问题,那为什么会被弃用呢?

void test_auto2()
{
	auto_ptr<Date> ap1(new Date);
	auto_ptr<Date> ap2(ap1);

	ap1->_day++;
}

运行这段代码,你会发现程序直接崩溃了,这是为什么?

 调试中,我们可以在监视窗口中可以变量。初始化ap1时,如下图所示,没有问题。

但是ap2调用拷贝ap2进行初始化时,会发现ap1变量内部的指针被置为空指针。也就是说,被其他auto_ptr对象拷贝之后,内部会被悬空。所以再使用ap1访问元素,会导致使用空指针访问。

这种将管理权转移的方法,会导致被拷贝对象悬空,进而造成访问空指针的现象。所以auto_ptr不建议使用(甚至可以说是禁止使用)。auto_ptr实现的原理就是进行拷贝构造函数时,将被拷贝对象的指针置为空指针

4. unique_ptr

4.1 使用及原理

如果你使用的指针不需要拷贝构造,就可以使用C++11推出的unique_ptr。unique_ptr也是一个模板类,用法跟auto_ptr类似。虽然不可以进行拷贝构造,但是可以使用移动构造,因为移动构造会窃取原来对象的资源。

void test_unique()
{
	unique_ptr<Date> up1(new Date);
    up1->_day = 10;

	//禁止拷贝构造
	unique_ptr<Date> up2(up1);
	//可以使用移动构造,此时up1不能使用了。
	unique_ptr<Date> up3(move(up1));
    up3->_day = 5;
}

unique_ptr的原理也比较简单,使用delete关键字禁用了拷贝构造函数和赋值重载函数

4.2 定制删除器

unique_ptr其实有两个模版参数,第一个模版参数是资源的类型,第二个模版参数是删除器,里面重载()符号的函数,内部是释放资源的代码。如果不传第二个模版参数,默认是使用default_delete类删除器。default_delete删除器释放资源的方式就是正常的delete操作。

unique_ptr默认删除器是使用delete释放资源,对于创建一块资源的对象,可以正常处理。如果对象使用多个资源初始化,如下面的第二行代码所示,运行的程序会报错。因为使用new[]创建的对象,要使用delete[]连续释放。

void test_unique2()
{
	unique_ptr<Date> up1(new Date);
	//会报错
	unique_ptr<Date> up2(new Date[5]);
}

我们可以写一个类,重载()符号。初始化unique_ptr对象时,第二个模版参数写上该类,就可以使用delete[ ]释放连续的资源。运行程序就不会报错。

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

void test_unique2()
{
	unique_ptr<Date> up1(new Date);
	
	unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);
}

如果需要使用delete[]释放资源,其实可以不用实现一个删除器类。unique_ptr给出了一个array specialization特化版本的删除器,是需要在第一个模版参数后面加个“[ ]”即可。

void test_unique2()
{
	unique_ptr<Date> up1(new Date);
	
	unique_ptr<Date[]> up2(new Date[7]);
}

由于可以自定义释放资源方式的类,我们可以使用unique_ptr来管理文件指针。只需要创建一个类,重载()符号函数中使用fclose即可。

class DeleteFclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose" << ptr << endl;
		fclose(ptr);
	}
};

void test_unique2()
{
	unique_ptr<Date> up1(new Date);
	unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);

	unique_ptr<FILE, DeleteFclose> up3(fopen("test.cpp", "r"));
}

运行结果如下:

5. shared_ptr

5.1 shared_ptr简介及使用

如果你想使用拷贝构造和赋值重载函数,那么就可以使用C++11推出的shared_ptr。shared_ptr使用了引用计数的方式支持多个对象共享一块资源

  • shared_ptr内部,对于共享同一块资源的对象,都维护着同一份计数。计数用来记录该资源被几个对象共享。
  • 在对象被销毁时,不在使用该资源,该对象的引用计数减一,但是不释放该资源。
  • 只有最后一个管理该资源的对象被删除,那么该资源就要被释放。

shared_ptr的用法跟unique_ptr类似,多了一些接口函数。get函数是获得该对象管理资源的指针,use_count函数可以查看共享资源的对象有几个,下面有两个对象共享该资源。

void test_shared1()
{
	shared_ptr<Date> sp1(new Date);
	sp1->_day = 5;

	shared_ptr<Date> sp2(sp1);
	sp2->_year = 2024;

	Date* ptr = sp1.get();
	ptr->_month = 9;

	sp1->Print();
	cout <<"引用计数:" << sp1.use_count() << endl;
}

运行结果如下:

如上图,可知shared_ptr只有一个模版参数,不能像unique_ptr在模版中传入删除器类,以此决定如何释放资源。不过可以在构造函数传个删除器类。对于使用new[]创建多个资源的对象,可以在传模版参数后面加一个“[ ]”,会使用delete[]释放资源。

如下面的代码,第一行代码使用的是模版特化,可以释放多个连续的资源。第二行代码,初始化sp2对象时,传入DeleteFclose的匿名对象,就可以使用shared_ptr管理文件指针。

class DeleteFclose
{
public:
	void operator()(FILE* ptr)
	{
		cout << "fclose" << ptr << endl;
		fclose(ptr);
	}
};

void test_shared2()
{
	shared_ptr<Date[]> sp1(new Date[5]);

	shared_ptr<FILE> sp2(fopen("test.cpp", "w"), DeleteFclose());
}

5.2 shared_ptr简单实现

5.2.1 基本框架

实现一个shared_ptr类,需要使用模版参数。成员变量一开始有模版类型对应的指针。那么计数该用什么变量存储?

  • 如果使用普通整型变量存储计数,会发现共享同一块资源的对象,每个计数变量都是独立的。当删除其中一个对象,其他对象的计数变量不会受影响,就无法确定有多少个对象管理该资源。
  • 如果使用静态整型变量,那么该计数变量属于该类的所有对象,管理两块不同的资源对象初始化时,都要对该变量加一。这样也无法判断某个资源有多少个对象管理。
  • 所以需要使用整型指针,对于同一块资源,可以清楚记录有多少个对象在进行共享。而不会干扰其他资源记录对象。

构造函数_pcount初始化为1,说明有一个对象正在管理该资源。析构函数只有解引用_pcount指针为0的情况下,即最后一个管理该资源的对象被销毁,才能释放_ptr指向的资源和_pcount指针,并且都置为空指针。并把这部分封装成release函数,因为赋值重载函数也需要使用到该函数。

namespace Rustle
{
	template<class T>
	class shared_ptr
	{
	public:
		shared_ptr(T* ptr = nullptr)
			: _ptr(ptr)
			,_pcount(new int(1))
		{}

		void release()
		{
			if (--(*_pcount) == 0)
			{
				delete _ptr;
				delete _pcount;
				_ptr = nullptr;
				_pcount = nullptr;
			}
		}

		~shared_ptr()
		{
			release();
		}

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

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

	private:
		T* _ptr;
		int* _pcount;

	};

5.2.2 拷贝构造 赋值重载

  • 拷贝构造函数只需要将被拷贝对象的资源和计数拷贝过来即可。再把计数加一。
  • 赋值重载函数需要先调用release函数,释放先前的资源和计数。将拷贝传参对象的资源和计数,再把计数加一。
		//s2(s1)
		shared_ptr(const shared_ptr<T>& sp)
			: _ptr(sp._ptr)
			, _pcount(sp._pcount)
		{
			++(*_pcount);
		}

        //s2 = s1
		shared_ptr<T> operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr)
			{
				release();

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

			return *this;
		}

5.2.3 删除器

shared_ptr的使用中,在构造函数中可以传入自定义的删除器,删除器中可以定义各种释放资源的方法。因此,我们也需要实现一个删除器,需要使用包装器。

包装器可以封装仿函数类,做到类似使用函数的形式。定义一个包装器类,模版传函数返回值和函数参数类型。我们给一个缺省值,Default_delete类是使用delete释放资源。

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

    class shared_ptr
    {
    public:
        //使用包装器
		void release()
		{
			if (--(*_pcount) == 0)
			{
				_del(_ptr);//类似使用函数的形式
				delete _pcount;
				_ptr = nullptr;
				_pcount = nullptr;
			}
		}

        template<class D>
		shared_ptr(T* ptr, D del)
			: _ptr(ptr)
			, _pcount(new int(1))
			,_del(del)
		{}

        //...
    private:
        //...
        //包装器
        function<void(T* ptr)> _del = Default_delete<T>();
    };

5.2.4 其他接口函数及测试

  • get函数是获得管理资源的指针变量。
  • use_count函数可以获取管理该资源的对象个数。
		T* get() const
		{
			return _ptr;
		}

		int use_count() const
		{
			return *_pcount;
		}

#include "shared_ptr.h"

void test_MySharedPtr()
{
	Rustle::shared_ptr<Date> sp1(new Date);
	sp1->_day = 5;
	sp1->_month = 9;
	sp1->_year = 2024;
	sp1->Print();

	Rustle::shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());
	Rustle::shared_ptr<FILE> sp3(fopen("test.cpp", "w"), DeleteFclose());
}
​

运行结果如下:

6. weak_ptr

weak_ptr是跟shared_ptr配套使用的,为了应对循环引用这个场景。

6.1 循环引用

下面的代码就是循环引用的示例。创建一个双向链表,链表指针的前后指针使用shared_ptr进行管理。这样才可以进行相互赋值。

struct ListNode
{
	int _data;
	std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;

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

int main()
{
	//下面是循环也能用-->导致内存泄漏
	std::shared_ptr<ListNode> n1(new ListNode);
	std::shared_ptr<ListNode> n2(new ListNode);

	cout << "n1结点计数:" << n1.use_count() << endl;
	cout << "n2结点计数:" << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << endl;
	cout << "n1结点计数:" << n1.use_count() << endl;
	cout << "n2结点计数:" << n2.use_count() << endl;

	return 0;
}

再创建两个shared_ptr对象管理ListNode类资源,当其中任意一个对象指向另外一个对象,并且不是互相指向时,是没有问题的。运行结果如下,资源被正常释放。

但是两个对象内部指针互相指向,如上面的代码所示。运行结果如下,两块资源都没有释放

观察下图,跟着下面的推导过程一步一步走,你会发现下面的推导过程形成了一个循环。也就是说,无法释放这两个结点所指向的资源。这就是循环引用。

6.2 weak_ptr使用

  • 上面是weak_ptr构造函数的原型。我们可以看到weak_ptr不支持给一块资源初始化。支持正常拷贝构造。最值得注意的是支持使用shared_ptr对象进行构造。

  • weak_ptr的赋值重载函数不仅提供了对同类型的赋值操作,还有对shared_ptr对象的赋值操作。

下面的代码是使用示例。

void test_weak()
{
	//不支持管理资源,不支持RAII计数
	std::weak_ptr<Date> wp1(new int);//error

	//默认构造,拷贝构造
	std::weak_ptr<Date> wp2;
	std::weak_ptr<Date> wp3(wp2);

	//赋值重载
	std::weak_ptr<Date> wp4;
	wp4 = wp2;

	//对shared_ptr的构造
	std::shared_ptr<ListNode> sp1(new ListNode);
	std::weak_ptr<ListNode> wp5(sp1);

	//对shared_ptr赋值重载
	std::weak_ptr<ListNode> wp6;
	wp6 = sp1;
}

6.3 原理及简单实现

struct ListNode
{
	int _data;
	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);

	cout << "n1结点计数:" << n1.use_count() << endl;
	cout << "n2结点计数:" << n2.use_count() << endl;

	n1->_next = n2;
	n2->_prev = n1;

	cout << endl;
	cout << "n1结点计数:" << n1.use_count() << endl;
	cout << "n2结点计数:" << n2.use_count() << endl;

	return 0;
}

前面有提到weak_ptr是为了解决循环引用这个场景而产生的。我们将ListNode类中的_next和_prev类型换成weak_ptr。并分别打印两次n1结点和n2结点的计数。

运行程序发现这两块资源得到释放,并且在n1结点和n2结点互相指向时。因为weak_ptr会获得管理这块资源的权利,但是计数不会发生改变。这样在释放n1和n2时,两个资源计数都会减到0。

实现简单的weak_ptr,最主要的是支持shared_ptr对象对weak_ptr对象的构造和赋值。

  • 默认构造函数只要将内部指针变量置为空指针即可。
  • shared_ptr对象对weak_ptr对象的构造,可使用shared_ptr的get函数获取内部管理资源的指针。赋值重载函数也是类似的做法。
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
		{}

		weak_ptr(const shared_ptr<T>& sp)
			:_ptr(sp.get())
		{}

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			_ptr = sp.get();

			return *this;
		}
	private:
		T* _ptr = nullptr;
	};

将上述循环引用例子中的智能指针类替换成自己实现的智能指针类,进行测试。运行结果如下,没有问题。

当然,weak_ptr还有一些其他接口函数,也可以尝试去实现。


总结

正确使用智能指针是避免内存泄漏的关键策略。在C++中,智能指针提供了一种自动管理内存资源的方式,从而大幅降低了因疏忽而导致的内存泄漏风险。auto_ptr和unique_ptr作为早期的智能指针实现,其原理较为简单,主要是通过独占所有权模型来防止内存泄漏。然而,在需要多个对象共同管理同一块资源的情况下,shared_ptr和weak_ptr的配套使用则显得尤为重要。

创作不易,希望这篇文章能给你带来启发和帮助,如果喜欢这篇文章,请留下你的三连,你的支持的我最大的动力!!!

ee192b61bd234c87be9d198fb540140e.png

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

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

相关文章

CentOS7 部署 Zabbix 监控平台———监控网络设备,Linux 主机、Windows 主机

Node 有自己的配置文件和数据库&#xff0c;其要做的是将配置信息和监控数据向 Master 同步。 当 Master 发生故障或损坏&#xff0c; Node 可以保证架构的完整性。 3&#xff09;Server-Prxoy-Client 架构 Proxy 是 Server、Client 之间沟通的桥梁&#xff0c;Proxy 本身没…

Cortex-M3架构学习:存储器系统

存储系统功能 CM3 的存储器系统与从传统 ARM 架构的相比&#xff0c;进行如下改革&#xff1a; 它的存储器映射是预定义的&#xff0c;并且还规定好了哪个位置使用哪条总线。 CM3 的存储器系统支持所谓的“位带”&#xff08;bit-band&#xff09;操作。通过它&#xff0c;实…

超详细!!!electron-vite-vue开发桌面应用之创建新窗口以及主进程和子进程的通信监听(十二)

云风网 云风笔记 云风知识库 一、新建打开窗口 1、在electron/main.ts中加入主进程打开窗口逻辑代码 import { ipcMain } from "electron"; ipcMain.handle("open-win", (_, arg) > {const childWindow new BrowserWindow({webPreferences: {preloa…

代码执行漏洞-Log4j2漏洞

1.执行以下命令启动靶场环境并在浏览器访问 cd log4j/CVE-2021-44228docker-compose up -ddocker ps 2.先在自己搭建的DNSLOG平台上获取⼀个域名来监控我们注⼊的效果 3.可以发现 /solr/admin/cores?action 这⾥有个参数可以传&#xff0c;可以按照上⾯的原理 先构造⼀个请求…

TomCat环境配置(实验报告)

实验 Tomcat 实验环境配置 一、实验目的 1、掌握Tomcat的安装和启动 2、掌握在IntelliJ IDEA中配置Tomcat的方法 二、实验环境 1、硬件&#xff1a;PC电脑一台&#xff0c;网络正常&#xff1b; 2、配置&#xff1a;Windows10系统&#xff0c;内存8G及以上&#xff0c;硬盘…

LAN变压器的DCR

在变压器技术中&#xff0c;DCR代表直流电阻&#xff08;DC Resistance&#xff09;。它是变压器线圈在直流条件下测得的电阻值&#xff0c;通常用来评估变压器的质量和效率。直流电阻是线圈材料和尺寸的一个函数&#xff0c;它与变压器线圈的发热量和功率损耗直接相关。在变压…

在平板电脑上使用Chrome观看视频的最佳技巧

随着移动设备的普及&#xff0c;越来越多的人选择在平板电脑上观看视频。谷歌浏览器&#xff08;Chrome&#xff09;作为一款功能强大的网页浏览器&#xff0c;提供了许多方便的功能来优化你的视频观看体验。以下是一些在平板电脑上使用Chrome观看视频时的最佳技巧。 &#xf…

个人旅游网(2)——功能详解——用户登录注册功能

文章目录 一、用户登录1.1、接口详解1.1.1、isLogged (判断用户是否登录)1.1.2、find-by-telephone&#xff08;输入手机号时校验手机号是否注册&#xff09;1.1.3、send-message&#xff08;发送验证码&#xff09;1.1.4、login-by-telephone&#xff08;登录按钮&#xff09;…

给力!Python配置文件,这一篇就够了!

在开发过程中&#xff0c;我们常常会用到一些固定参数或者是常量。对于这些较为固定且常用到的部分&#xff0c;往往会将其写到一个固定文件中&#xff0c;避免在不同的模块代码中重复出现从而保持核心代码整洁。 这里插播一条粉丝福利&#xff0c;如果你在学习Python或者有计划…

修改设置内以及手机桌面的软件icon和名称

// 设置 - 应用和元服务内修改软件icon和名称等相关信息 // 位置&#xff1a; 设置 - 应用和元服务 // AppScore/app.json5// 这里不是在桌面看, 是在设置 - 应用和元服务内看的 // 模拟器内: 版本号改了之后要卸载后重新安装才能看 {"app": {"bundleName"…

【GC】垃圾回收原理分析

本文基于小许先生的编程世界学习了gc机制。 面试官&#xff1a;了解gc机制吗&#xff1f;简述一下。 我&#xff1a;golang为清除不再被使用的对象&#xff0c;回收内存的机制。采用了三色标记法混合写屏障。首先开启屏障&#xff0c;一旦在gc期间发生指针引用变化&#xff0c;…

解决启动Nginx时80端口被占用的问题

文章目录 前言 解决方法1. 搜索服务打开![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/c7600d266d194bdb864f7660a5bc5890.png)2. 找到World Wide Web 发布服务这个服务&#xff0c;右键属性&#xff0c;将它的启动类型改为手动&#xff0c;然后停止掉该服务。3. 此…

虚拟化技术:新能源汽车空调控制系统的智能新突破

汽车生产中&#xff0c;空调系统已经成为标配&#xff0c;空调系统的性能是衡量一辆汽车是否舒适的重要指标之一。 01.汽车空调系统组成 (1) 制冷系统&#xff1a;制冷系统的功能是给汽车内部提供冷空气&#xff0c;主要由压缩机、冷凝器、膨胀阀以及蒸发器组成。首先由压缩机…

释放国产光耦合器的潜力

光耦合器&#xff0c;也称为光隔离器&#xff0c;已成为现代电子产品中不可或缺的组件。它们的主要功能是在电路的不同部分之间提供电气隔离&#xff0c;确保安全并防止干扰。随着技术的发展&#xff0c;光耦合器也在不断发展&#xff0c;国产光耦合器现在在性能和成本方面都具…

回归预测|基于粒子群优化核极限学习机PSO-KELM结合Adaboost集成的数据预测Matlab程序 多特征输入单输出

回归预测|基于粒子群优化核极限学习机PSO-KELM结合Adaboost集成的数据预测Matlab程序 多特征输入单输出 文章目录 一、基本原理1. 数据预处理2. PSO优化&#xff08;粒子群优化&#xff09;3. KELM训练&#xff08;核极限学习机&#xff09;4. AdaBoost集成5. 模型评估和优化6.…

004: VTK读入数据---vtkImageData详细说明

VTK医学图像处理---vtkImageData类 目录 VTK医学图像处理---vtkImageData类 简介&#xff1a; 1 Mricro软件的安装和使用 (1) Mricro安装 (2) Mricro转换DICOM为裸数据 2 从硬盘读取数据到vtkImageData 3 vtkImageData转RGB或RGBA格式 4 练习 总结 简介&#xff1a;…

Minikube Install Kubernetes v1.18.1

文章目录 简介安装工具配置代理运行集群检查集群加入rancher 简介 模拟客户环境&#xff0c;测试 kubernetes v1.18.x 是否可以被 rancher v2.9.1 纳管。 安装工具 docker 安装Install and Set Up kubectl on Linux 安装 minikube 配置代理 docker proxylinux proxy 运行…

python进阶篇-day07-进程与线程

day06进程与线程 一. 进程 每个软件都可以看作是一个进程(数据隔离) 软件内的多个任务可以看作是多个线程(数据共享) 单核CPU: 宏观并行, 微观并发 真正的并行必须有多核CPU 多任务介绍 概述 多任务指的是, 多个任务"同时"执行 目的 节约资源, 充分利用CPU资源, …

万龙觉醒辅助:VMOS云手机辅助前期宝物选择!万龙觉醒怎么挂机?

《万龙觉醒》作为一款策略战争类游戏&#xff0c;玩家们在日常进行游戏时&#xff0c;可能会遇到游戏时间不足或无法长时间在线的问题。而通过使用VMOS云手机&#xff0c;可以实现24小时游戏云端在线托管&#xff0c;无需手动操作&#xff0c;彻底解放双手&#xff0c;随时随地…

【人工智能学习笔记】1_人工智能基础

本系列是个人学习《阿里云人工智能工程师ACA认证免费课程&#xff08;2023版&#xff09;》的笔记&#xff0c;仅为个人学习记录&#xff0c;欢迎交流&#xff0c;感谢批评指正 人工智能概述 智能的三大能力&#xff1a;感知、记忆与思维、学习与适应能力人工智能的定义 明斯基…