【C++】深入解析C++智能指针:从auto_ptr到unique_ptr与shared_ptr

news2024/11/15 4:15:27

文章目录

  • 前言:
  • 1. 智能指针的使用及原理
  • 2. C++ 98 标准库中的 auto_ptr:
  • 3. C++ 11 中的智能指针
    • 循环引用:
    • shared_ptr 定制删除器
  • 4. 内存泄漏
  • 总结:

前言:

随着C++语言的发展,智能指针作为现代C++编程中管理动态分配内存的一种重要工具,越来越受到开发者的青睐。智能指针不仅简化了内存管理,还有助于避免内存泄漏等常见问题。本文将深入探讨智能指针的使用及其原理,从C++98标准库中的auto_ptr开始,逐步过渡到C++11中更为强大和灵活的智能指针类型,如unique_ptrshared_ptr。此外,文章还将讨论循环引用问题、内存泄漏的原因及其危害,并提供相应的解决方案。通过本文的学习,读者将能够更好地理解和运用智能指针,编写出更安全、更高效的C++代码。

1. 智能指针的使用及原理

RAII(Resource Acquisition Is Initialization)是一种利用对象生命周期来控制程序资源(如内
存、文件句柄、网络连接、互斥量等等)的简单技术。
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在
对象析构的时候释放资源
。借此,我们实际上把管理一份资源的责任托管给了一个对象。这种做
法有两大好处:

  • 不需要显式地释放资源。
  • 采用这种方式,对象所需的资源在其生命期内始终保持有效。
// SmartPtr.h
// 使用RAII思想设计的smartPtr类

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

	~SmartPtr()
	{
		if (_ptr) {
			std::cout << "delete: " << _ptr << std::endl;
			delete _ptr;
		}
	}

private:
	T* _ptr;
};
    

int div()
{
	int a, b;
	cin >> a >> b;
	if (b == 0)
 		throw invalid_argument("除0错误");
 		
 	return a / b;
}
void Func()
{
	ShardPtr<int> sp1(new int);
    ShardPtr<int> sp2(new int);
	cout << div() << endl;
}

int main()
{
	try {
		Func();
	}
    catch(const exception& e)
    {
        cout<<e.what()<<endl;
   	}
   	
 	return 0;
}
//test.cpp
#include <iostream>
#include "SmartPtr.h"
using namespace std;

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

	return a / b;
}

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

	cout << div() << endl; 
}

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

	return 0;
}

在这里插入图片描述

  • 需要像指针一样的去使用:
// 像指针一样使用
T& operator*()
{
	return *_ptr;
}

T* operator->()
{
	return _ptr;
}
SmartPtr<int> sp1(new int(1));
SmartPtr<int> sp2(new int(0));
*sp1 += 10;

SmartPtr<pair<string, int>> sp3(new pair<string, int>);
sp3->first = "apple";
sp3->second = 1; // 等价于 sp3.opertor->()->second = 1;

cout << sp3->first << " " << sp3->second << endl;
  • 智能指针的拷贝问题
// 智能指针的拷贝问题
int main()
{
	SmartPtr<int> sp1(new int(1));
	SmartPtr<int> sp2(sp1);

	return 0;
}

在这里插入图片描述

vector / list.… 需要深拷贝,它们都是利用资源存储数据,资源是自己的。拷贝时,每个对象各自一份资源,各管各的,所以深拷贝。

智能指针 / 迭代器… 期望的是浅拷贝
资源不是自己的,代为持有,方便访问修改数据。他们拷贝的时候期望的指向同一资源,所以浅拷贝。而且智能指针还要负责释放资源。

itertor it = begin();

2. C++ 98 标准库中的 auto_ptr:

auto_ptr 管理权转移,被拷贝的对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空
注意:在使用auto_ptr 过后不能访问对象,否则就出现空指针了。很多公司禁止使用它,因为他很坑!

 // 智能指针的拷贝问题
// 1. auto_ptr 管理权转移,被拷贝的对象把资源管理权转移给拷贝对象,导致被拷贝对象悬空
// 注意:在使用auto_ptr 过后不能访问对象,否则就出现空指针了。很多公司禁止使用它,因为他很坑!
int main()
{
	std::auto_ptr<int> sp1(new int(1));
	std::auto_ptr<int> sp2(sp1);

	*sp2 += 10;

	// 悬空
	*sp1 += 10;

	return 0;
}

auto_ptr 的实现:

namespace hd
{
	template<class T>
	class auto_ptr {
	public:
		// RAII
		auto_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		// ap2(ap1)
		auto_ptr(auto_ptr<T>& ap)
		{
			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}
	
		~auto_ptr()
		{
			if (_ptr) {
				std::cout << "delete: " << _ptr << std::endl;
				delete _ptr;                                                             
			}
		}
	
		// 像指针一样使用
		T& operator*()
		{
			return *_ptr;
		}
	
		T* operator->()
		{
			return _ptr;
		}
	
	private:
		T* _ptr;
	};
}

3. C++ 11 中的智能指针

boost 智能指针
scoped_ptr / scoped_array
shared_ptr / shared_array

C++ 11
unique_ptrscoped_ptr类似的
shared_ptrshared_ptr类似的

unique_ptr
禁止拷贝,简单粗暴,适合于不需要拷贝的场景
在这里插入图片描述
赋值也禁掉了:
在这里插入图片描述
unique_ptr:实现

namespace hd
{
	template<class T>
	class unique_ptr {
	public:
		// RAII
		unique_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{}

		// ap2(ap1)
		unique_ptr(const unique_ptr<T>& ap) = delete;  // 禁掉拷贝构造
		// 赋值也要禁掉,赋值会生成默认成员函数,浅拷贝,也会出现问题
		unique_ptr<T>& operator=(const unique_ptr<T>& ap) = delete;


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

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

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

	private:
		T* _ptr;
	};
}

如果必须要拷贝用shared_ptr:
shared_ptr 允许自由拷贝,使用引用计数解决多次释放的问题

引用计数: 记录有几个对象参与管理这个资源
在这里插入图片描述
shared_ptr 实现:
使用静态成员变量实现。

namespace hd
{
	template<class T>
	class shared_ptr {
	public:
		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
		{
			_count = 1;
		}

		// sp(sp1)
		shared_ptr(const shared_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			++_count;
		}

		~shared_ptr()
		{
			if (--_count == 0)
			{
				std::cout << "delete:" << _ptr << std::endl;
				delete _ptr;
			}
			
		}

		int use_count()
		{
			return _count;
		}

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

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

	private:
		T* _ptr;
		
		static int _count;
	};

	template<class T>
	int shared_ptr<T>::_count = 0;
}

在这里插入图片描述
中释放了一个资源!
如果使用静态成员属于这个类,属于这个类的所有对象
需求:每个资源配一个引用计数,而不是全部都是一个引用计数!

所以,一个资源配一个引用计数无论多少个对象管理这个资源,只有这一个计数对象!
怎么找到这个引用呢?每个对象存一个指向计数的指针!

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

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			_pcount = sp._pcount;

			// 拷贝时++计数
			++(*_pcount);
		}


		void release()
		{
			// 说明最后一个管理对象析构了,可以释放资源了
			if (--(*_pcount) == 0)
			{
				std::cout << "delete:" << _ptr << std::endl;
				delete _ptr;
				delete _pcount;
			}
		}


		// 赋值 sp1 = sp3;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			if (_ptr != sp._ptr) // 避免自己给自己赋值
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				// 拷贝时++计数
				++(*_pcount);
			}

			return *this;
		}

		~shared_ptr()
		{
			release();
		}

		int use_count()
		{
			return *_pcount;
		}

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

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

	private:
		T* _ptr;
		int* _pcount;
	};

}

在这里插入图片描述

shared_ptr 的缺陷:

// shared_ptr 的缺陷
struct ListNode
{
	int _val;
	std::shared_ptr<ListNode> _next;
	std::shared_ptr<ListNode> _prev;

	ListNode(int val = 0)
		:_val(val)
		,_next(nullptr)
		,_prev(nullptr)
	{}

};

int main()
{
	std::shared_ptr<ListNode> n1(new ListNode(10));
	std::shared_ptr<ListNode> n2(new ListNode(20));

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

	//delete n1;
	//delete n2;

	return 0;
}

循环引用:

  1. 左边的节点,是由右边的节点_prev管着的,_prev析构,引用计数减到 0, 左边的节点就是释放
  2. 右边节点中_prev 什么时候析构呢?右边的节点被delete时,_prev 析构。
  3. 右边节点什么时候delete呢?右边的节点被左边的节点的_next管着的,_next析构,右边的节点就释放了。
  4. _next 什么时候析构呢?_next 是左边节点的成员,左边节点 delete, _next 就析构了
  5. 左边节点什么时候释放呢?回调 1 点 又循环上去了

右边节点释放 -> _prev析构 -> 左边节点的释放 -> _next析构 -> 右边节点释放

所以这是 shared_ptr 特定场景下的缺陷, 只要有两个shared_ptr 互相管理就会出现这样的情况,所以即使用了智能指针,同样可能导致内存的泄漏。

struct ListNode
{
	int _val;
	std::weak_ptr<ListNode> _next;
	std::weak_ptr<ListNode> _prev;

	ListNode(int val = 0)
		:_val(val)
	{}

};

int main()
{
	std::shared_ptr<ListNode> n1(new ListNode(10));
	std::shared_ptr<ListNode> n2(new ListNode(20));

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

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

	cout << n1.use_count() << endl;
	cout << n2.use_count() << endl;

	//delete n1;
	//delete n2;

	return 0;
}

在这里插入图片描述
weak_ptr 可以通过不增加引用计数的方式,避免这个问题。(存在单独自己的 引用计数)
weak_ptr 不支持RAII, 不参与资源管理,不支持指针初始化,但是还是能起到指向你的作用
weak_ptr 的实现:

namespace hd
{
	template<class T>
	class weak_ptr
	{
	public:
		weak_ptr()
			:_ptr(nullptr)
		{}

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

		weak_ptr<T>& operator=(const shared_ptr<T>& sp)
		{  
			_ptr = sp.get(); // 用 get方法调原生指针
		}

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

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

	private:
		T* _ptr;
	};
}

shared_ptr 定制删除器

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

// 定制删除器
int main()
{
	std::shared_ptr<ListNode> p1(new ListNode(10));
	std::shared_ptr<ListNode[]> p2(new ListNode[10]); // 可以用数组的

	std::shared_ptr<ListNode> p2(new ListNode[10], DeleteArry<ListNode>()); // 用仿函数的对象去释放!
	std::shared_ptr<FILE> p3(fopen("test.cpp", "r"), [](FILE* ptr) {fclose(ptr);  }); // 用lamada表达式也是可以的

	return 0;
}

在这里插入图片描述

定制删除器实现:

namespace hd
{
	template<class T>
	class shared_ptr
	{
	public:

		// function<void(T*)> _del = [](T* ptr) {delete ptr; };
		template<class D>
		shared_ptr(T* ptr, D del)
			:_ptr(ptr)
			, _pcount(new int(1))
			, _del(del)
		{}

		// RAII
		shared_ptr(T* ptr = nullptr)
			:_ptr(ptr)
			, _pcount(new int(1))
		{}

		// sp2(sp1)
		shared_ptr(const shared_ptr<T>& sp)
		{
			_ptr = sp._ptr;
			_pcount = sp._pcount;

			// 拷贝时++计数
			++(*_pcount);
		}

		// sp1 = sp4
		// sp4 = sp4;
		// sp1 = sp2;
		shared_ptr<T>& operator=(const shared_ptr<T>& sp)
		{
			//if (this != &sp)
			if (_ptr != sp._ptr)
			{
				release();

				_ptr = sp._ptr;
				_pcount = sp._pcount;

				// 拷贝时++计数
				++(*_pcount);
			}

			return *this;
		}

		void release()
		{
			// 说明最后一个管理对象析构了,可以释放资源了
			if (--(*_pcount) == 0)
			{
				std::cout << "delete:" << _ptr << std::endl;
				//delete _ptr;
				_del(_ptr);

				delete _pcount;
			}
		}

		~shared_ptr()
		{
			// 析构时,--计数,计数减到0,
			release();
		}

		int use_count()
		{
			return *_pcount;
		}

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

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

		T* get() const
		{
			return _ptr;
		}
	private:
		T* _ptr;
		int* _pcount;

		std::function<void(T*)> _del = [](T* ptr) {delete ptr; };
	};

}

4. 内存泄漏

什么是内存泄漏:内存泄漏指因为疏忽或错误造成程序未能释放已经不再使用的内存的情况。内
存泄漏并不是指内存在物理上的消失,而是应用程序分配某段内存后,因为设计错误,失去了对
该段内存的控制,因而造成了内存的浪费。
内存泄漏的危害:长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现
内存泄漏会导致响应越来越慢,最终卡死。

void MemoryLeaks()
{
   // 1.内存申请了忘记释放
  int* p1 = (int*)malloc(sizeof(int));
  int* p2 = new int;
  
  // 2.异常安全问题
  int* p3 = new int[10];
  
  Func(); // 这里Func函数抛异常导致 delete[] p3未执行,p3没被释放.
  
  delete[] p3;
}

总结:

本文详细介绍了智能指针的概念、使用和原理,从C++98的auto_ptr到C++11的unique_ptrshared_ptr,展示了智能指针在现代C++编程中的应用和发展。我们了解到RAII(资源获取即初始化)的设计模式,它通过将资源管理封装在对象的生命周期中,简化了资源的获取和释放过程。文章还讨论了智能指针的拷贝问题,特别是auto_ptr的缺陷和shared_ptr的循环引用问题,以及如何使用weak_ptr和定制删除器来解决这些问题。

此外,文章还探讨了内存泄漏的概念、原因和危害,以及如何在实际编程中避免这些问题。通过具体的例子和代码,我们学习了如何使用智能指针来管理资源,确保资源在使用完毕后能够被正确释放,从而避免内存泄漏和其他潜在的资源管理问题。

总的来说,智能指针是C++中一个强大的特性,它不仅提高了代码的安全性和效率,还使得资源管理变得更加简单和直观。通过本文的学习,读者应该能够更加自信地在C++项目中使用智能指针,编写出更加健壮和可靠的软件。

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

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

相关文章

infoq读书笔记-云原生时代,如何建设稳定性可观测体系?

而可观测性则是把Log、Trace、Metric拧成了一股绳&#xff0c;让三大支柱互相之间建立亲密的“血缘关系”&#xff0c;通过这种关系我们可以结构化的从整体到局部再到具体细节的观测业务&#xff1a; 图片来自网络如果把业务系统比作一座海上的冰山&#xff0c;监控仅能看到的…

02_前端三大件HTML

文章目录 HTML用于网页结构搭建1. 标签2. 客户端服务器交互流程3. 专业词汇4. html语法细节5. 安装VSCODE安装插件6. Live Server插件使用7. 标题&段落&换行&列表8. 超链接标签使用9. 图片10. 表格的写法11. 表单标签*(重点)12. 下拉框13. 页面布局标签14. 块元素和…

机器学习大模型驱动:未来的趋势与应用

文章目录 &#x1f4d1;前言一、什么是机器学习大模型&#xff1f;1.1 大模型的特点1.2 大模型的技术基础 二、大模型的技术实现2.1 Transformer 架构2.2 预训练和微调2.3 模型并行和数据并行 三、大模型的应用场景3.1 自然语言处理&#xff08;NLP&#xff09;3.2 计算机视觉&…

02324 自学考试 离散数学屈婉玲教材 目录

02324 自学考试 离散数学屈婉玲教材 目录 02324 自学考试 离散数学屈婉玲教材 02324离散数学全程班历年真题资料

21.2zabbix低级自动发现-mysql多实例

配置mysql多实例 注释&#xff1a;自动发现&#xff1a;创建监控主机&#xff1b;低级自动发现&#xff1a;创建监控项 mysql单实例是直接yum安装&#xff0c;开启mysql多实例 准备配置文件 #mysql3307实例 cp /etc/my.cnf /etc/my3307.cnf vim /etc/my3307.cnf [mysqld] dat…

Maven多环境打包配置

一、启动时指定环境配置文件 在启动springboot应用的jar包时&#xff0c;我们可以指定配置文件&#xff0c;通常把配置文件上传到linux服务器对应jar包的同级目录&#xff0c;或者统一的配置文件存放目录 java -jar your-app.jar --spring.config.location/opt/softs/applicat…

4.Redis之Redis的通用命令

0.Redis 实战操作 通过 redis-cli 客户端和 redis 服务器交互 涉及到很多的 redis 的命令 【redis 的命令非常非常多!!! 1.掌握常用命令(多操作多练习) 2.学会使用 redis 的文档-> 阅读文档, 是程序猿的基操!! redis 的命令非常非常多!!! 1.掌握常用命令(多操作多练习…

Golang文件操作

文章目录 文件操作基本介绍普通的文件操作方式&#xff08;os包&#xff09;带缓冲的文件操作方式&#xff08;bufio包&#xff09;文件拷贝操作&#xff08;io包&#xff09; 命令行参数基本介绍解析命令行参数&#xff08;flag包&#xff09; JSON基本介绍JSON序列化JSON反序…

【手把手带你搓组件库】从零开始实现Element Plus

从零开始实现Element Plus 前言亮点项目搭建1、创建项目初始化monorepo创建 .gitignore目录结构安装基础依赖配置文件创建各个分包入口utilscomponentscoreplaytheme 2、创建VitePress文档3、部署到Github Actions生成 GH_TOKENGitHub Page 演示 4、总结 前言 在本文中&#xf…

vim操作手册

vim分为插入模式、命令模式、底行模式。 插入模式&#xff1a;编辑模式 命令模式&#xff1a;允许使用者通过命令&#xff0c;来进行文本的编辑控制 底行模式&#xff1a;用来进行让vim进行包括但不限于shell进行交互 w&#xff1a;保存 wq&am…

北邮22级信通院DSP:用C++程序实现给定参数下四种滤波器的Butterworth模拟滤波器设计:给定上下截频和衰减系数求H(p)和H(s)

北邮22信通一枚~ 跟随课程进度更新北邮信通院DSP的笔记、代码和文章&#xff0c;欢迎关注~ 获取更多文章&#xff0c;请访问专栏&#xff1a; 北邮22级信通院DSP_青山入墨雨如画的博客-CSDN博客 目录 一、 核心算法 1.1判断滤波器类型 1.2 带通滤波器BP 1.3带阻滤波器B…

十二、shell编程之awk

12.1 什么是awk 虽然sed编辑器是非常方便自动修改文本文件的工具&#xff0c;但其也有自身的限制。通常你需要一个用来处理文件中的数据的更高级工具&#xff0c;它能提供一个类编程环境来修改和重新组织文件中的数据。这正是awk能够做到的。 awk程序是Unix中的原始awk程序的…

P4097 【模板】李超线段树 / [HEOI2013] Segment 题解

题意 有一个平面直角坐标系&#xff0c;总共 n n n 个操作&#xff0c;每个操作有两种&#xff1a; 给定正整数 x 0 , y 0 , x 1 , y 1 x_0,y_0,x_1,y_1 x0​,y0​,x1​,y1​ 表示一条线段的两个端点。你需要在平面上加入这一条线段&#xff0c;第 i i i 条被插入的线段的标…

【面试干货】完全平方数

【面试干货】完全平方数 1、实现思想2、代码实现 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 一个整数&#xff0c;它加上 100 后是一个完全平方数&#xff0c;再加上 168 又是一个完全平方数&#xff0c;请问该数是多少&#xff1f; 1、…

设计模式 17 组合模式 Composite Pattern

设计模式 17 组合模式 Composite Pattern 1.定义 组合模式&#xff08;Composite Pattern&#xff09;&#xff0c;又叫部分整体模式&#xff0c;是用于把一组相似的对象当作一个单一的对象。组合模式依据树形结构来组合对象&#xff0c;用来表示部分以及整体层次。这种类型的设…

wps使用(解决毕业论文)

目录自动生成 页码自动生成 一部分使用I II III IV 格式&#xff0c;一部分使用1&#xff0c;2&#xff0c;3&#xff0c;4 格式 先设置全部文章为I II III IV 格式&#xff0c;然后再需要的地方再设置1&#xff0c;2&#xff0c;3&#xff0c;4 格式 一键设置中文、英文、数…

特斯拉FSD的「端到端」到底能不能成?

引言 近年来&#xff0c;特斯拉的全自动驾驶&#xff08;Full Self-Driving&#xff0c;FSD&#xff09;技术备受关注&#xff0c;尤其是其「端到端」的AI软件框架更是引发了广泛讨论。端到端技术到底是一条正确的路径吗&#xff1f;它能否真正实现完全自动驾驶&#xff1f;本…

Java面试八股之什么是锁消除和锁粗化

什么是锁消除和锁粗化 锁消除&#xff08;Lock Elimination&#xff09;&#xff1a; 锁消除是Java虚拟机&#xff08;JVM&#xff09;进行的一种高级优化策略&#xff0c;旨在消除那些没有必要存在的同步操作&#xff0c;以减少不必要的性能开销。这一优化发生在即时编译器&a…

Docker拉取镜像报错:x509: certificate has expired or is not yet v..

太久没有使用docker进行镜像拉取&#xff0c;今天使用docker-compose拉取mongo发现报错&#xff08;如下图&#xff09;&#xff1a; 报错信息翻译&#xff1a;证书已过期或尚未有效。 解决办法&#xff1a; 1.一般都是证书问题或者系统时间问题导致&#xff0c;可以先执行 da…

Nginx-狂神说

Nginx概述 公司产品出现瓶颈&#xff1f; 我们公司项目刚刚上线的时候&#xff0c;并发量小&#xff0c;用户使用的少&#xff0c;所以在低并发的情况下&#xff0c;一个jar包启动应用就够了&#xff0c;然后内部tomcat返回内容给用户。 但是慢慢的&#xff0c;使用我们平台…