C++11 --- 智能指针详解

news2024/11/13 2:38:53

C++11 智能指针

  • 一、智能指针的使用场景分析
  • 二、RAII和智能指针的设计思路
  • 三、智能指针的本质及衍生的问题
  • 四、C++标准库的智能指针的使用
  • 五、智能指针的原理(模拟实现)
    • 1. auto_ptr的模拟实现
    • 2. unique_ptr的模拟实现
    • 3. shared_ptr的模拟实现(简单版)
  • 六、定制删除器
    • 1. 默认delete释放资源的不足
    • 2. 定制删除器的类型
    • 3. 比较完整的shared_ptr的实现
  • 七、shared_ptr循环引用问题和weak_ptr
    • 1. 问题的产生
    • 2. 分析原因
    • 3. 解决问题
    • 4. weak_ptr的原理
      • 4.1 weak_ptr的简单实现
      • 4.2 weak_ptr的一些成员函数

一、智能指针的使用场景分析

在我们需要动态申请内存时,难免最后会有忘记释放内存的时候,这就导致了内存泄漏。在使用到异常时,某个函数抛出异常后,很可能前面申请的空间也未释放,因此也导致内存泄漏。
例如:

场景1:普通情况下申请空间后忘记释放

void test() {
	int* p = new int(10);
	double* pp = new double(1.1);
}
int main() {
	test();

	return  0;
}

场景2:抛出异常后,申请的空间无法释放

下⾯程序中我们可以看到,new了以后,我们也delete了,但是因为抛异常导,后⾯的delete没有得到执⾏,所以就内存泄漏了,所以我们需要new以后捕获异常,捕获到异常后delete内存,再把异常抛出,但是因为new本⾝也可能抛异常,连续的两个new和下⾯的Divide都可能会抛异常,让我们处理起来很⿇烦。智能指针放到这样的场景⾥⾯就让问题简单多了

double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero condition!";
	}
	else
	{
		return (double)a / (double)b;
	}
}
void test()
{
	// 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array和array2没有得到释放。
	// 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。
	// 但是如果array2new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案
	// 是智能指针。
	int* array1 = new int[10];
	int* array2 = new int[10]; // 抛异常呢
	try
	{
		int len, time;
		cin >> len >> time;
		cout << Divide(len, time) << endl;
	}
	catch (...)
	{
		cout << "delete []" << array1 << endl;
		cout << "delete []" << array2 << endl;	
		delete[] array1;
		delete[] array2;
		throw; // 异常重新抛出,捕获到什么抛出什么
	}
	// ...
	cout << "delete []" << array1 << endl;
	delete[] array1;
	cout << "delete []" << array2 << endl;
	delete[] array2;
}

二、RAII和智能指针的设计思路

RAII是Resource Acquisition Is Initialization的缩写,他是⼀种管理资源的类的设计思想本质是
⼀种利⽤对象⽣命周期来管理获取到的动态资源,避免资源泄漏
。RAII在获取资源时把资源委托给⼀个对象,接着控制对资源的访问,资源在对象的⽣命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。

智能指针类除了满⾜RAII的设计思路,还要⽅便资源的访问,所以智能指针类还会想迭代器类⼀
样,重载 operator*/operator->/operator[] 等运算符,⽅便访问资源。

设计一个简单的智能指针解决上面抛异常导致资源无法释放的问题:

template<class T>
struct SmartPtr {
	SmartPtr(T* ptr) :_ptr(ptr) {

	}
	T& operator*() {
		return *_ptr;
	}
	T* operator->() {
		return _ptr;
	}
	T& operator[](size_t pos) {
		return *(_ptr + pos);
	}

	~SmartPtr() {
		cout << "delete[] ..." << endl;
		delete[] _ptr;
	}
	T* _ptr = nullptr;
};
double Divide(int a, int b)
{
	// 当b == 0时抛出异常
	if (b == 0)
	{
		throw "Divide by zero condition!";
	}
	else
	{
		return (double)a / (double)b;
	}
}
void Func()
{
	SmartPtr<int> sp1 = new int[10];
	SmartPtr<int> sp2 = new int[10];

	
	int len, time;
	cin >> len >> time;
	cout << Divide(len, time) << endl;

	//不需要再手动释放资源
}
int main()
{
	try
	{
		Func();
	}
	catch (const char* errmsg)
	{
		cout << errmsg << endl;
	}
	catch (const exception& e)
	{
		cout << e.what() << endl;
	}
	catch (...)
	{
		cout << "未知异常" << endl;
	}
	return 0;
}

运行结果:
在这里插入图片描述

三、智能指针的本质及衍生的问题

在这里插入图片描述

四、C++标准库的智能指针的使用

C++标准库中的智能指针都在< memory >这个头⽂件下⾯,我们包含< memory >就可以是使⽤了,智能指针有好⼏种,除了weak_ptr他们都符合RAII和像指针⼀样访问的⾏为,它们的区别在原理上⽽⾔主要是解决智能指针拷⻉时的思路不同

  1. auto_ptr是C++98时设计出来的智能指针,他的特点是拷⻉时把被拷⻉对象的资源的管理权转移给拷⻉对象,这是⼀个⾮常糟糕的设计,因为它会让被拷⻉对象悬空,访问报错的问题,C++11设计出新的智能指针后,强烈建议不要使⽤auto_ptr。
    在这里插入图片描述
    在这里插入图片描述

  2. unique_ptr是C++11设计出来的智能指针,他的名字翻译出来是唯⼀指针,他的特点的不⽀持拷
    ⻉,只⽀持移动(即,将被移动的指针进行资源交换,通常被移动的指针会被置空)。如果不需要拷⻉的场景就⾮常建议使⽤他。
    在这里插入图片描述
    在这里插入图片描述

  3. shared_ptr是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是⽀持拷⻉,也⽀持移动。如果需要拷⻉的场景就需要使⽤他了。底层是⽤引⽤计数的⽅式实现的。
    在这里插入图片描述

shared_ptr也支持移动:
在这里插入图片描述

  1. shared_ptr 除了⽀持⽤指向资源的指针构造,还⽀持 make_shared ⽤初始化资源对象的值
    直接构造。

shared_ptr的多种构造方式如下:

struct Person {
	int _age;
	string _name;
};
void test2() {
	shared_ptr<Person> sp1(new Person({ 19,"lwx" }));
	shared_ptr<Person> sp2 = make_shared<Person>(29,"hlp");
	auto sp3 = make_shared<Person>(39, "lpo");
	shared_ptr<Person> sp4;
	//不可以,报错!!!
	shared_ptr<Person> sp5 = new Person({ 29,"cda" }); 
}
  1. shared_ptr 和 unique_ptr 都⽀持了operator bool的类型转换,如果智能指针对象是⼀个
    空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。
    在这里插入图片描述

五、智能指针的原理(模拟实现)

1. 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;
};

2. unique_ptr的模拟实现

template<class T>
class unique_ptr
{
public:
	explicit unique_ptr(T* ptr)
		:_ptr(ptr)
	{}
	~unique_ptr()
	{
	       if (_ptr)
    	{
			cout << "delete:" << _ptr << endl;
			delete _ptr;
	    }
	}
		// 像指针⼀样使⽤
		T& operator*()
	{
		return *_ptr;
	}
	T* operator->()
	{
		return _ptr;
	}
	//不支持拷贝的做法:
	unique_ptr(const unique_ptr<T>& sp) = delete;
	unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;

	unique_ptr(unique_ptr<T>&& sp)  //移动构造
		:_ptr(sp._ptr)
	{
		sp._ptr = nullptr;
	}
	unique_ptr<T>& operator=(unique_ptr<T>&& sp) //移动赋值
	{
		delete _ptr;
		_ptr = sp._ptr;
		sp._ptr = nullptr;
	}
private:
	T* _ptr;
};

3. shared_ptr的模拟实现(简单版)

template<class T>
class shared_ptr {
public:
	//默认构造与有参构造
    shared_ptr(T* ptr = nullptr)
		:_ptr(ptr), _pcount(new int(1)) {

	}
	//拷贝构造
	shared_ptr(const shared_ptr<T>& sp) {
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		(*_pcount)++;
	}
	//拷贝赋值
	void operator= (const shared_ptr<T>& sp) {

		if (_ptr != sp._ptr) {  //忽略自己给自己赋值
			// 和本身就已经共同管理一块空间的对象之间的赋值

			if ((*_pcount)-- == 1) {  //本对象原本是一块资源的最后管理者
				                      //(即使不是,也做到了减去了一个管理者)
				delete _ptr;          //释放原来的资源
				delete _pcount;
			}
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			(*_pcount)++;
		}
	}
	~shared_ptr() {

		if ((*_pcount)-- == 1) {  //如果(*_pcount)==1,说明本对象
			//是某块资源的最后管理者,判断完后要--
			cout << "delete _ptr..." << endl;
			delete _ptr;
			delete _pcount;
		}
	}
	//使用:
	T* get() const	
	{
		return _ptr;
	}
	int use_count() const
	{
		return *_pcount;
	}
	T& operator*() {
		return *_ptr;
	}
	T* operator->() {
		return _ptr;
	}
	T& operator[](size_t pos) {
		return _ptr[pos];
	}
private:
	T* _ptr;
	int* _pcount;
};

注意:计数器不能用一个静态成员来充当!!!
在这里插入图片描述

六、定制删除器

1. 默认delete释放资源的不足

对于上面简单版的shared_ptr,一般场景下的使用是没问题的,但是如果资源是new[] 出来的就会发生错误,因为new[] 要与delete[] 搭配。
标准库的解决方案是特化出delete[]的版本。
使用:

std::shared_ptr<int[]> sp(new int[10]);

但是,假如这个指针要管理的资源是一个文件呢?文件最终不是被delete的,而是用fclose()关闭文件。如:

std::shared_ptr<FILE> sp(fopen("test.cpp","w"));

这个场景下,普通的shared_ptr是无法解决问题的,即文件打开了,确无法自动关闭。

因此,智能指针⽀持在构造时给⼀个删除器,所谓删除器本质就是⼀个可调⽤对象这个可调⽤对象中实现你想要的释放资源的⽅式,当构造智能指针时,给了定制的删除器,在智能指针析构时就会调⽤删除器去释放资源。

标准库里shared_ptr支持定制删除器:
在这里插入图片描述
注: 第一个模版参数U是资源的类型,第二个模版参数D是删除器的类型。
p是资源的指针,del是一个对象。

2. 定制删除器的类型

删除器的类型可以有很多种,可以是仿函数,可以是函数指针,也可以是lambda表达式

  1. 例如:仿函数版本的删除器:
struct Fclose {
	void operator()(FILE* ptr) {
		fclose(ptr);
		cout << "fclose..." << endl;
	}
};
void test3() {

	std::shared_ptr<FILE> sp(fopen("test.cpp", "w"),Fclose());

}

该例子的调用逻辑解析:
在这里插入图片描述

  1. 再例如:以lambda表达式为类型的定制删除器:
    在这里插入图片描述

3. 比较完整的shared_ptr的实现

template<class T>
class shared_ptr {
public:
	//默认构造与有参构造
    explicit shared_ptr(T* ptr = nullptr)
		:_ptr(ptr), _pcount(new int(1)) {

	}

	//注:
	//这里如果构造时参数只有一个,就走上面的构造,如果
	//有两个就走下面这个:
	template<class D>
	shared_ptr(T* ptr, const D& del)
		: _ptr(ptr),
		 _del(del),
		 _pcount(new int(1)) {

	}


	//拷贝构造
	shared_ptr(const shared_ptr<T>& sp) {
		_ptr = sp._ptr;
		_pcount = sp._pcount;
		(*_pcount)++;
	}
	//拷贝赋值
	void operator= (const shared_ptr<T>& sp) {

		if (_ptr != sp._ptr) {  //忽略自己给自己赋值
			// 和本身就已经共同管理一块空间的对象之间的赋值

			if ((*_pcount)-- == 1) {  //本对象原本是一块资源的最后管理者
				                      //(即使不是,也做到了减去了一个管理者)
				delete _ptr;          //释放原来的资源
				delete _pcount;
			}
			_ptr = sp._ptr;
			_pcount = sp._pcount;
			(*_pcount)++;
		}
	}

	~shared_ptr() {

		if ((*_pcount)-- == 1) {  //如果(*_pcount)==1,说明本对象
			//是某块资源的最后管理者,判断完后要--
			cout << "delete _ptr..." << endl;

			//delete _ptr;
			
			//用删除器:
			_del(_ptr);
			delete _pcount;
		}
	}

	
	//使用:
	T& operator*() {
		return *_ptr;
	}
	T* operator->() {
		return _ptr;
	}
	T& operator[](size_t pos) {
		return _ptr[pos];
	}

private:
	T* _ptr;
	int* _pcount;


	//包装器封装_del (因为这里无法拿到D来定义_del):
	function<void(T*)> _del = [](T* ptr) {delete ptr; };

	//这里默认给_del赋值为一个lambda表达式对象是为了迎合
	//当只有一个参数构造时(简单来说就是普通的new空间时,
	//                因为走的是上面的普通构造,没有构造出_del),
	//也一样可以析构时用删除器_del。
};

注:不支持下面这种构造方法的原因:
在这里插入图片描述

七、shared_ptr循环引用问题和weak_ptr

shared_ptr⼤多数情况下管理资源⾮常合适,⽀持RAII,也⽀持拷⻉。但是在循环引⽤的场景下会
导致资源没得到释放内存泄漏,所以我们要认识循环引⽤的场景和资源没释放的原因,并且学会使
⽤weak_ptr解决这种问题。

1. 问题的产生

例如:有下面这样一个场景:

在这里插入图片描述
连接成双向链表:
在这里插入图片描述
修改成:
在这里插入图片描述
结果发生内存泄漏:
在这里插入图片描述

2. 分析原因

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

3. 解决问题

把ListNode结构体中的_next和_prev的类型改成weak_ptr,weak_ptr指向shared_ptr所管理的资源时不会增加它的引⽤计数,因此_next和_prev不参与资源释放管理逻辑,就成功打破了循环引⽤,解决了这⾥的问题。
在这里插入图片描述

4. weak_ptr的原理

weak_ptr不⽀持RAII,也不⽀持访问资源,所以weak_ptr构造时不⽀持绑定到资源,只⽀持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引⽤计数,那么就可以解决上述的循环引⽤问题。
在这里插入图片描述

4.1 weak_ptr的简单实现

注:

  1. 这里是对标准库的weak_ptr做了很大的阉割了,库里的实现更为复杂。
  2. weak_ptr不需要析构函数,因为它不需要管理和释放资源。
  3. 标准库里的weak_ptr也是有计数器的,因为即使weak_ptr它不管理资源,但是它也应该知道这块资源有几个管理者。
template<class T>
class weak_ptr {
public:
	weak_ptr() 
	{}
	weak_ptr(const shared_ptr<T>& sp)
		:_ptr(sp.get())
	{}
	void operator=(const shared_ptr<T>& sp){
		_ptr(sp.get());
	}
	//不需要析构函数,因为它不需要管理和释放资源
private:
	T* _ptr=nullptr;
};

4.2 weak_ptr的一些成员函数

  1. weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的
    shared_ptr已经释放了资源,那么他去访问资源就是很危险的。
  2. weak_ptr有expired成员函数去检查指向的资源是否过期,use_count也可获取shared_ptr的引⽤计数。
  3. weak_ptr想访问资源时,可以调⽤lock返回⼀个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是⼀个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。
int main() {
		std::shared_ptr<string> sp1(new string("111111"));
		std::shared_ptr<string> sp2(sp1);
		std::weak_ptr<string> wp = sp1;

		//1.检查wp所指向的资源是否过期:过期返回1,未过期返回0:
		cout << wp.expired() << endl;  

		//2.查看wp指向的资源有几个shared_ptr对象在管理:
		cout << wp.use_count() << endl;


		// sp1和sp2都指向了其他资源,则weak_ptr就过期了
		sp1 = make_shared<string>("222222");
		cout << wp.expired() << endl;
		cout << wp.use_count() << endl;
		sp2 = make_shared<string>("333333");
		cout << wp.expired() << endl;
		cout << wp.use_count() << endl;


		wp = sp1;
		//std::shared_ptr<string> sp3 = wp.lock();
		//将sp1的资源锁住,并交给另一个shared_ptr对象(sp3):
		auto sp3 = wp.lock();
		cout << wp.expired() << endl;
		cout << wp.use_count() << endl;
		*sp3 += "###";  
		cout << *sp1 << endl;
		return 0;
}

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

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

相关文章

(实战)WebApi第13讲:怎么把不同表里的东西,包括同一个表里面不同的列设置成不同的实体,所有的给整合到一起?【前端+后端】、前端中点击标签后在界面中显示

一、实现全局跨域&#xff1a;新建一个Controller&#xff0c;其它的controller都继承它 1、新建BaseController 2、在后端配置&#xff0c;此处省略【详情见第12讲四、3、】 3、其它的控制器继承BaseController&#xff0c;这个时候就能够完成全局的跨域 【向后台传cookie和…

【C++】map和set的介绍及使用

前言&#xff1a; map和 set 是 C STL&#xff08;标准模板库&#xff09;中的两种非常重要的容器&#xff0c;它们基于一种叫做平衡二叉搜索树&#xff08;通常是红黑树&#xff09;的数据结构来实现。在 C 中&#xff0c;map 是一个键值对容器&#xff0c;set 只存储唯一的键…

Python的函数(补充浅拷贝和深拷贝)

一、定义 函数的定义&#xff1a;实现【特定功能】的代码块。 形参&#xff1a;函数定义时的参数&#xff0c;没有实际意义 实参&#xff1a;函数调用/使用时的参数&#xff0c;有实际意义 函数的作用&#xff1a; 简化代码提高代码重用性便于维护和修改提高代码的可扩展性…

el-input 正则表达式校验输入框不能输入汉字

<el-form :model"data1" :rules"rules" ref"ruleForm" label-width"210px" class"demo-ruleForm"><el-form-item label"锯路&#xff1a;" prop"sawKref"><el-input class"inptWid…

嵌入式linux系统中I2C控制实现AP3216C传感器方法

大家好,今天主要给大家分享一下,如何使用linux系统里面的I2C进行控制实现。 第一:Linux系统中I2C简介 Linux 内核开发者为了让驱动开发工程师在内核中方便的添加自己的 I2C 设备驱动程序,更容易的在 linux 下驱动自己的 I2C 接口硬件,进而引入了 I2C 总线框架。与 Linux 下…

OceanBase 应用实践:如何处理数据空洞,降低存储空间

问题描述 某保险行业客户的核心系统&#xff0c;从Oracle 迁移到OceanBase之后&#xff0c;发现数据存储空间出现膨胀问题&#xff0c;数据空间 datasize9857715.48M&#xff0c;实际存储占用空间17790702.00M。根据 required_mb - data_mb 值判断&#xff0c;数据空洞较为严重…

【flask开启进程,前端内容图片化并转pdf-会议签到补充】

flask开启进程,前端内容图片化并转pdf-会议签到补充 flask及flask-socketio开启threading页面内容转图片转pdf流程前端主js代码内容转图片-browser端browser端的同步编程flask的主要功能route,def 总结 用到了pdf,来回数据转发和合成,担心flask卡顿,响应差,于是刚好看到threadi…

QT栅格布局的妙用

当groupBox中只有一个控件时&#xff0c;我们想要它满格显示可以对groupBox使用栅格布局

MyBatis快速入门(上)

MyBatis快速入门&#xff08;上&#xff09; 一、MyBatis 简介1、概述2、JDBC、Hibernate、MyBatis 对比 二、MyBatis 框架搭建1、开发环境2、创建maven工程3、创建MyBatis的核心配置文件4、创建mapper接口5、创建MyBatis的映射文件6、通过junit测试功能7、加入log4j2日志功能 …

在Pybullet中加载Cinema4D创建的物体

首先明确我们的目标&#xff0c;是希望在cinema4D中创建自己想要的模型&#xff0c;并生成.obj文件&#xff0c;然后在pybullet中加载.obj文件作为静态物体&#xff0c;可以用于抓取物体&#xff0c;避障物体。&#xff08;本文提到的方法只能实现静态物体的建模&#xff0c;如…

第十三届交通运输研究(上海)论坛┆智能网联汽车技术现状与研究实践

0.简介 交通运输研究&#xff08;上海&#xff09;论坛&#xff08;简称为TRF&#xff09;是按照国际会议的组织原则&#xff0c;为综合交通运输领域学者们构建的良好合作交流平台。交通运输研究&#xff08;上海&#xff09;论坛已经成功举办了十二届&#xff0c;凝聚了全国百…

Pr:视频过渡快速参考(合集 · 2025版)

Adobe Premiere Pro 自带七组约四十多个视频过渡 Video Transitions效果&#xff0c;包含不同风格和用途&#xff0c;可在两个剪辑之间创造平滑、自然的转场&#xff0c;用来丰富时间、地点或情绪的变化。恰当地应用过渡可让观众更好地理解故事或人物。 提示&#xff1a; 点击下…

stm32 踩坑笔记

串口问题&#xff1a; 问题&#xff1a;会改变接收缓冲的下一个字节 串口的初始化如下&#xff0c;位长度选择了9位。因为要奇偶校验&#xff0c;要选择9位。但是接收有用数据只用到1个字节。 问题原因&#xff1a; 所以串口接收时会把下一个数据更改

昇思大模型平台打卡体验活动:项目4基于MindSpore实现Roberta模型Prompt Tuning

基于MindNLP的Roberta模型Prompt Tuning 本文档介绍了如何基于MindNLP进行Roberta模型的Prompt Tuning&#xff0c;主要用于GLUE基准数据集的微调。本文提供了完整的代码示例以及详细的步骤说明&#xff0c;便于理解和复现实验。 环境配置 在运行此代码前&#xff0c;请确保…

后悔没早点知道,Coze 插件 + Cursor 原来可以这样赚钱

最近智能体定制化赛道异常火爆。 打开闲鱼搜索"Coze 定制",密密麻麻的服务报价直接刷屏,即使表明看起来几十块的商家,一细聊,都是几百到上千不等的报价。 有趣的是,这些智能体定制化服务背后,最核心的不只是工作流设计,还有一个被很多人忽视的重要角色 —— …

基于STM32的节能型路灯控制系统设计

引言 本项目基于STM32微控制器设计了一个智能节能型路灯控制系统&#xff0c;通过集成多个传感器模块和控制设备&#xff0c;实现对路灯的自动调节。该系统能够根据周围环境光照强度、车辆和行人活动等情况&#xff0c;自动控制路灯的开关及亮度调节&#xff0c;从而有效减少能…

Qml 模型-视图-代理(贰)之 动态视图学习

目录 动态视图 动态视图用法 ⽅向&#xff08;Orientation&#xff09; 键盘导航和⾼亮 页眉与页脚 网格视图 动态视图 动态视图用法 Repeater 元素适合有限的静态数据&#xff0c; QtQuick 提供了 ListView 和 GridView, 这两个都是基于 Flickable(可滑动) 区域的元素…

新标准大学英语综合教程1课后习题答案PDF第三版

《新标准大学英语&#xff08;第三版&#xff09;综合教程1 》是“新标准大学英语&#xff08;第三版&#xff09;”系列教材之一。本书共包含6个单元&#xff0c;从难度和话题上贴近大一上学生的认知和语言水平&#xff0c;包括与学生个人生活领域和社会文化等相关内容&#x…

Python闭包|你应该知道的常见用例(下)

引言 在 Python 编程语言中&#xff0c;闭包通常指的是一个嵌套函数&#xff0c;即在一个函数内部定义的另一个函数。这个嵌套的函数能够访问并保留其外部函数作用域中的变量。这种结构就构成了一个闭包。 闭包在函数式编程语言中非常普遍。在 Python 中&#xff0c;闭包特别有…

Rocky、Almalinux、CentOS、Ubuntu和Debian系统初始化脚本v9版

Rocky、Almalinux、CentOS、Ubuntu和Debian系统初始化脚本 Shell脚本源码地址&#xff1a; Gitee&#xff1a;https://gitee.com/raymond9/shell Github&#xff1a;https://github.com/raymond999999/shell脚本可以去上面的Gitee或Github代码仓库拉取。 支持的功能和系统&am…