C++智能指针万字详细讲解(包含智能指针的模拟实现)

        在笔试,面试中智能指针经常出现,如果你对智能指针的作用,原理,用法不了解,那么可以看看这篇博客讲解,此外本博客还简单模拟实现了各种指针,在本篇的最后还应对面试题对智能指针的知识点进行了拓展。希望能加深你对智能指针的理解。那么开始学习吧!

一.智能指针作用

        C++的智能指针主要作用是为了防止内存泄漏。在代码中我们new出来的对象都需要delete,但是当我们我们忘记或者代码出现异常导致没有delete对象,就会产生内存泄漏。长期运行的程序出现内存泄漏,影响很大,如操作系统、后台服务等等,出现内存泄漏会导致响应越来越慢,最终卡死。

        下面我们看看一个常见的因为异常导致内存泄漏的例子:

#include<iostream>
using namespace std;static int sa = 1;
int div_func(int a, int b)
{if (b == 0){throw invalid_argument("除0错误");}return a / b;
}
int Func1()
{int* a1 = new int{1};int* a2 = new int{sa};int n=div_func(*a1,*a2);sa--;delete a1;delete a2;return n;
}int main()
{try{while (1){int n = Func1();cout << n;}}catch (exception& e){cout << e.what() << endl;}return 0;
}

运行结果:

        main函数调用func1函数,func中new出了a1,和a2,然后调用div_func,当b=0,此时就会抛出异常,异常被mian函数捕获直接跳转,此时new出来的a1和a2就不会被delete,导致内存泄漏。这种代码的内存泄漏,还是比较难防备,此时就需要使用智能指针。

二.智能指针原理 

        我们首先介绍一下什么是RAII。

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

        这种做法有两大好处:

  • 1.不需要显式地释放资源。
  • 2.对象所需的资源在其生命期内始终保持有效。

        智能指针也就是利用RAII的原理实现的,把管理一份资源的责任托管给了一个对象,通过构造函数获取资源,通过析构函数释放资源,看代码:

template<class T>
class SmartPtr {
public:SmartPtr(T* ptr = nullptr):_ptr(ptr){}~SmartPtr(){if (_ptr)delete _ptr;}
private:T* _ptr;
};

        这样我们就能通过类的生命周期来对资源进行管理和释放。对于最开始的代码我们只需要把 int* a1 = new int{1};int* a2 = new int{sa}代码写成SmartPtr<int > sp1=new{1};SmartPtr<int >sp2=new{sa},这样即使因为抛异常跳转到mian()函数也会因为生命周期的结束自动释放资源。上面的代码就是智能指针的基本原理。

三.智能指针介绍和使用 

C++常见的智能指针有三种,这里我们只做基本介绍和使用,详细特点我们后面实现再介绍。

1.std::unique_ptr

特点:独占资源所有权,不可复制(不能进行拷贝构造和赋值运算符重载)但支持移动语义,生命周期结束时自动释放资源,保证只有一个对象只有一个unique_ptr指针,避免重复析构。

class A
{
public:int a;A(int n){a = n;}~A(){std::cout << "调用析构" << std::endl;}};
int main()
{std::unique_ptr<A> ptr = std::make_unique<A>(1);//c++高版本写法。std::unique_ptr<A> ptr( new A(1));//第二种写法//std::unique_ptr<A> ptr1=ptr;//禁止了拷贝构造会报错std::unique_ptr<A> ptr2 = std::move(ptr);  // 所有权转移
}

2.std::shared_ptr

特点:共享资源所有权,通过引用计数管理生命周期,线程安全的引用计数更新。允许复制。

每复制一个shared_ptr,计数+1,析构一个计数-1,计数为零才调用析构。

class A
{
public:int a;A(int n){a = n;}~A(){std::cout << "调用析构" << std::endl;}};
int main()
{std::shared_ptr<A> ptr = std::make_shared<A>(1);std::shared_ptr<A> ptr1 = ptr;}

3.std::weak_ptr

  • 特点:弱引用,不影响 shared_ptr 的引用计数,需通过 lock() 提升为 shared_ptr 访问资源(后面详细讲解)。

四.简单模拟实现std::auto_ptr

        auto_ptr主要是在早期版本的C++使用,现在基本不会使用,特点是转移管理权,即当指针复制时,让新指针指向旧指针,再将旧指针指向空,我们主要做个了解。

namespace bit
{template<class T>class auto_ptr{public:auto_ptr(T* ptr = nullptr):_ptr(ptr){}//移交管理权auto_ptr(auto_ptr<T>& ap){_ptr = ap.get();ap._ptr = nullptr;}//释放原来的,接收管理权auto_ptr<T>& operator=(auto_ptr<T>& ap){if (this != &ap){// 释放当前对象中资源if (_ptr)delete _ptr;}_ptr=ap.get();ap.ptr=null;return *this;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}~auto_ptr(){delete _ptr;}T* get(){return _ptr;}private:T* _ptr;};}class Date
{
public:Date(int year,int month,int day){_year = year;_month = month;_day = day;cout << "调用构造函数" << endl;}~Date(){cout << "调用析构" << endl;}int _year;int _month;int _day;
};
int main()
{bit::auto_ptr<int> ap1 = new int{ 1 };bit::auto_ptr<Date> ap2 = new Date{ 1,1,1 };cout << *ap1 << endl;cout << ap2->_year << endl;bit::auto_ptr<Date> ap3 = ap2;}

运行结果:

最后一行时的监视窗口:

        auto_ptr作为智能指针,当调用拷贝构造或赋值运算符重载,不允许多个智能指针指向同一个对象,而是将一个智能指针的资源管理权移交给另外一个智能指针,这种做法是不太好的,这意味着,赋值后的原auto_ptr对象将不再拥有指针的所有权,其内部指针会被置为NULL。这种行为可能导致一些潜在的错误,因为程序员可能期望原对象仍然拥有指针的所有权。很多公司明确要求不能使用auto_ptr。

五.简单模拟实现std::unique_ptr

        unique_ptr的实现原理:简单粗暴的防拷贝,下面简化模拟实现了一份UniquePtr来了解它的原理。

	template<class T>class unique_ptr{public:unique_ptr(T* ptr)	:_ptr(ptr){}unique_ptr (unique_ptr& up) = delete;unique_ptr<T>& operator=(unique_ptr& up) = delete;T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get(){return _ptr;}private:T* _ptr;};

        通过删除拷贝构造函数和赋值运算符重载来确保指向该资源的只有该智能指针。也就是说是一个资源只能有一个智能指针。

六.简单模拟实现std::shared_ptr

       上面的俩种指针之所以只能做到一个资源只能有一个智能指针,是因为没有解决多个智能指针指向一份资源从而导致重复析构的问题而shared_ptr可以解决这个问题

        shared_ptr的原理:是通过引用计数的方式来实现多个shared_ptr对象之间共享资源所有智能指针都指向同一个内存和引用计数。

        当有智能指针指向内存资源时,同时让共享的引用计数++,当智能指针析构时,只是让引用计数--,只有当引用计数为0时再调用指向资源的析构函数。此外为了多线程访问,对计数需要加锁保护。

        具体看代码:

namespace bit
{template<class T>class shared_ptr{public:shared_ptr(T* ptr = nullptr):_ptr(ptr), _pRefCount(new int(1)), _pmtx(new mutex){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx){AddRef();}void Release(){_pmtx->lock();bool flag = false;if (--(*_pRefCount) == 0 && _ptr){cout << "delete:" << _ptr << endl;delete _ptr;delete _pRefCount;flag = true;}_pmtx->unlock();if (flag == true){delete _pmtx;}}void AddRef(){_pmtx->lock();++(*_pRefCount);_pmtx->unlock();}shared_ptr<T>& operator=(const shared_ptr<T>& sp){//if (this != &sp)if (_ptr != sp._ptr){Release();_ptr = sp._ptr;_pRefCount = sp._pRefCount;_pmtx = sp._pmtx;AddRef();}return *this;}int use_count(){return *_pRefCount;}~shared_ptr(){Release();}// 像指针一样使用T& operator*(){return *_ptr;}T* operator->(){return _ptr;}T* get() const{return _ptr;}private:T* _ptr;int* _pRefCount;mutex* _pmtx;};

         必须注意的是多线程的话,因为所有指针共享一个引用计数,对引用计数必须加锁访问,这里我们只做简单模拟。

七.weak_ptr的模拟实现

        weak_ptr有2个作用:

  • 打破循环引用:通过将类成员声明为 weak_ptr,避免 shared_ptr 的循环引用导致内存泄漏
  • 安全访问资源:通过 lock() 方法原子性地获取 shared_ptr,若对象已释放则返回空指针,避免悬垂指针

        下面我们引入第一个作用: 打破循环引用

        在shard_ptr中看似很安全,但是可能会出现循环引用的问题,下面让我们看看

class B;
class A {
public:std::shared_ptr<B> b_ptr;  // 强引用int a;~A() { std::cout << "A destroyed\n"; }
};
class B {
public:std::shared_ptr<A> a_ptr;  // 强引用int b;~B() { std::cout << "B destroyed\n"; }
};int main(){auto ptr_A = std::make_shared<A>();//c++新版本创建智能指针的新方法auto ptr_B = std::make_shared<B>();ptr_A->b_ptr = ptr_B;   //ptr_A的引用计数++;ptr_B->a_ptr = ptr_A;  //ptr_B的引用计数++  循环引用,引用计数均为2Bstd::cout << "运行完毕" << std::endl;
}  // mian结束,智能指针ptr_A,ptr_B引用计数只能减为1,对象未销毁

运行结果:

         可以看到我们并未成功调用对象A和B的析构函数,造成了内存泄漏。我们来分析一下原因。

        首先ptr_A指向A对象(假设为a),ptr_A的计数为1,ptr_B指向B对象(假设为b),ptr_B的计数也为1,然后 ptr_A->b_ptr = ptr_B; ptr_B->a_ptr = ptr_A; 此时ptr_A和ptr_B的计数增加为2.

        我们来画图理解。

        这里我们用控制块A和控制B代表指向A 和B的计数 ,当程序运行结束ptr_A,ptr_B调用析构时,计数都减少1,如下:

         此时指向A和B对象的计数都为1,无法自动调用析构,造成内存泄漏(new 出来的对象也是不会自动调用析构的)。

        我们要知道shared_ptr智能指针计数为0时才能调用指向对象的析构函数。

        为了解决上面的问题,我们创键了weak_ptr.

        weak_ptr一般和shard_ptr搭配使用,weak_ptr可以接受shard_prt类型的指针,但是不会影响计数。也就是说weak_ptr的构造和析构都不会增加和减少计数,同时weak_ptr也不会计数为0也不会调用指向对象的析构函数,只是充当指向作用

        我们使用weak_ptr对上面的代码进行修改。

class B;
class A {
public:std::weak_ptr<B> b_ptr;  // 强引用int a;~A() { std::cout << "A destroyed\n"; }
};
class B {
public:std::shared_ptr<A> a_ptr;  // 强引用int b;~B() { std::cout << "B destroyed\n"; }
};int main()
{auto ptr_A = std::make_shared<A>();auto ptr_B = std::make_shared<B>();ptr_A->b_ptr = ptr_B;ptr_B->a_ptr = ptr_A;std::cout << "运行完毕";
}

运行结果如下:

         我们将对象A的智能指针替换为weak_ptr,其他不变。再来分析析构过程

         首先ptr_A指向A对象,ptr_B指向B对象,计数都为1。ptr_A->b_ptr = ptr_B,此时A中的是weak_ptr<B>指针,不会增加ptr_B的计数,而 ptr_B->a_ptr = ptr_A,会增加ptr_A的计数(对哪个指针进行拷贝就是增加哪个指针的计数)。ptr_A计数为2,ptr_B计数为1。此时情况如图:

        当main结束时ptr_A调用自己的析构函数计数减少为1, 同时ptr_B自动调用自己的析构函数,计数减少为0。

        由于ptr_B计数为0需要调用b的析构函数调用b的析构函数释放资源后要调用成员变量a_ptr的析构函数(析构函数的顺序是先调用自己的,再调用成员变量的),此时控制块A计数为1(a_ptr是拷贝ptr_A的,俩者计数相同),减一后为0,计数为零需要调用a的析构函数,a的成员变量b_ptr计数为0,A可以直接析构,到此对象全部成功析构。图示如下:

        

        因此析构函数的调用顺序是先B后A,但是B对象是后于A对象被销毁的。 

        上面这么多就是为了论证weak_ptr的一个作用:​打破循环引用,避免内存泄漏。

        对于weak_ptr的第二个作用就好理解多了。 

  • 安全访问资源:通过 lock() 方法原子性地获取 shared_ptr,若对象已释放则返回空指针,避免悬垂指针。
std::weak_ptr<int> wp;
if (auto sp = wp.lock()) {  // 检查对象是否存在// 安全使用 sp
}

        wp.lock()是看计数是否为0,为0返回空,不为零返回一个shared_ptr(计数也会++)。

        以上这些讲讲的都是weak_ptr的作用和原理,下面我们给出weak_ptr的模拟实现。

template <typename T>
class WeakPtr {
public:// 默认构造函数(空指针)WeakPtr() : ptr_(nullptr), ref_count_(nullptr) {}// 从 SharedPtr 构造template <typename U>WeakPtr(const SharedPtr<U>& shared) : ptr_(shared.ptr), ref_count_(shared.ref_count) {}// 拷贝构造函数WeakPtr(const WeakPtr& other) : ptr_(other.ptr_), ref_count_(other.ref_count) {}shared_ptr<T> lock() const {if (*ptr<=0) return shared_ptr<T>();return shared_ptr<T>(ptr, ref_count_);}// 析构函数~WeakPtr() {}T& operator*(){return *ptr;}T* operator->(){return ptr;}T* get() const{return ptr;}private:T* ptr;                        // 指向对象的指针int* ref_count_;           // 指向控制块的指针// 允许 SharedPtr 访问私有成员template <typename U>friend class SharedPtr;
};

            要注意的这里只是简单实现,并不详细。

八.面试题拓展

        上面的讲解基本就能解决绝大多数的面试题了,但是面试的知识点也越来越细了,因此我们再根据常见的面试题进行拓展。

weak_ptr真的不计数?是否有计数方式?在哪分配的空间?

        

对于1,2小问,这里我们需要介绍一下share_ptr和weak_ptr中控制块的概念。

        控制块是智能指针实现引用计数机制的核心数据结构,包含以下信息

  1. 强引用计数(use_count)​:记录当前有多少个shared_ptr持有对象。
  2. 弱引用计数(weak_count)​:记录当前有多少个weak_ptr观察对象。
  3. 对象指针:指向实际管理的对象(可能为空,若对象已被销毁)。
  4. 自定义删除器(可选)。

     也就是说shared_ptr和weak_ptr中有2个强弱俩个计数,其中强引用计数作用就是当计数为0时调用指向对象的析构函数,但控制块仍存在,弱引用作用是当​弱引用归零时控制块本身被释放。

那么控制块的作用是什么:

1. 支持weak_ptr的安全操作

  • 感知对象状态:即使对象已被销毁(强引用归零),weak_ptr仍需通过控制块判断对象是否有效(如lock()要通过控制块判断);
  • 避免悬空控制块:若控制块随对象一起释放,weak_ptr将无法判断对象是否存在,导致未定义行为

2. 避免控制块内存泄漏

  • 生命周期分离:控制块的存活由弱引用计数决定。即使对象已销毁,只要存在weak_ptr观察,控制块就必须保留以记录弱引用信息
  • 最终释放机制:当所有weak_ptr销毁(弱引用归零),控制块才会被释放,避免内存残留

这里我们在总结一下智能指针的释放流程。

释放流程

  1. 对象销毁:当最后一个shared_ptr析构时,强引用计数归零,对象被释放。
  2. 控制块保留:若仍有weak_ptr观察(弱引用计数>0),控制块继续存在。
  3. 控制块释放:当所有weak_ptr析构(弱引用归零),控制块被销毁

 对于最后1个小问,我们还需要了解控制块的分配方式

  • new分配:直接使用new时,对象和控制块分两次分配,控制块独立存在
  • make_shared优化:通过make_shared创建shared_ptr时,对象和控制块分配在同一块连续内存中,减少内存碎片和分配次数。因此第二种方法更好一点。

因此上面面试题的答案是:

weak_ptr真的不计数?是否有计数方式,在哪分配的空间。

计数,控制块中有强弱引用计数,如果是使用make_shared初始化的函数则它所在的控制块空间是在所引用的shared_ptr中同一块的空间,若是new则控制器所分配的内存与shared_ptr本身所在的空间不在同一块内存。

  好了,智能指针就讲解到这了,感觉有帮助的话,请点点赞吧,这真的很重要。

                                

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

学习threejs,使用多面体(IcosahedronGeometry、TetrahedronGeometry、OctahedronGeometry等)

&#x1f468;‍⚕️ 主页&#xff1a; gis分享者 &#x1f468;‍⚕️ 感谢各位大佬 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍⚕️ 收录于专栏&#xff1a;threejs gis工程师 文章目录 一、&#x1f340;前言1.1 ☘️THREE.PolyhedronGeometry …

DeepSeek详解:探索下一代语言模型

文章目录 前言一、什么是DeepSeek二、DeepSeek核心技术2.1 Transformer架构2.1.1 自注意力机制 (Self-Attention Mechanism)(a) 核心思想(b) 计算过程(c) 代码实现 2.1.2 多头注意力 (Multi-Head Attention)(a) 核心思想(b) 工作原理(c) 数学描述(d) 代码实现 2.1.3 位置编码 (…

【目标检测】【深度学习】【Pytorch版本】YOLOV1模型算法详解

【目标检测】【深度学习】【Pytorch版本】YOLOV1模型算法详解 文章目录 【目标检测】【深度学习】【Pytorch版本】YOLOV1模型算法详解前言YOLOV1的模型结构YOLOV1模型的基本执行流程YOLOV1模型的网络参数YOLOV1模型的训练方式 YOLOV1的核心思想前向传播阶段网格单元(grid cell)…

网络运维学习笔记(DeepSeek优化版) 022 HCIP-Datacom路由概念、BFD协议详解与OSPF第一课

文章目录 路由概念、BFD协议详解与OSPF第一课一、路由协议优先级与选路原则1.1 路由协议优先级对照表1.2 路由选路核心原则 二、BFD&#xff08;Bidirectional Forwarding Detection&#xff0c;双向转发检测&#xff09;的配置与应用2.1 双向心跳探测&#xff08;双端配置&…

单应性矩阵(homography)

利用单应性矩阵计算内外参矩阵 利用单应性矩阵解决问题 问题描述&#xff1a;

Scavenge算法的优缺点问题

Scavenge 的缺点是只能使用堆内存中的一半&#xff0c;这是由划分空间和复制机制所决定的。但 Scavenge 由于只复制存活的对象&#xff0c;并且对于生命周期短的场景&#xff0c;存活对象只占少部分&#xff0c;所以它在时间效率上有优异的表现。 由于 Scavenge 是典型的牺牲空…

丝杆支撑座间隙调整不当会带来哪些影响?

丝杆支撑座是一种用于支撑滚珠丝杆的零件&#xff0c;通常用于机床、数控机床、自动化生产线等高精度机械设备中。支撑座间隙调整不当会对机械设备的运行产生多方面的影响&#xff0c;接下来一起了解一下&#xff1a; 1、降低加工精度&#xff1a;在机械加工设备中&#xff0c;…

Unity:EasyRoad3D插件学习 二期

前言&#xff1a; 书接上回。 一、场景视图状态&#xff1a; 创建好道路以后&#xff0c;切换到第一个选项&#xff0c;场景视图状态&#xff0c;查看道路信息&#xff0c;Main Settings修改道路名称、类型&#xff0c;宽度&#xff0c;是否闭环。 RoadWidth改为15&#xff…

内网渗透-DLL和C语言加载木马

免杀进阶技术 1、DLL的定义与使用 DLL:Dynamic Link library,动态链接库&#xff0c;是一个无法自己运行&#xff0c;需要额外的命令或程序来对其接口进行调用&#xff08;类方法、函数&#xff09;。 (1)在DevCpp中创建一个DLL项目 (2)在dllmain.c中定义源代码函数接口 #i…

一洽让常见问题的快速咨询,触手可及

在客户服务场景中&#xff0c;重复性常见问题的处理效率直接影响用户体验与客服成本。针对重复性常见问题&#xff0c;如何以直观的方式呈现给用户&#xff0c;使其能够快速、精准地提出咨询&#xff0c;已成为提升客户满意度的关键因素。 一、传统客服模式的效率枷锁 用户咨…

WEB攻防-Java安全SPEL表达式SSTI模版注入XXEJDBCMyBatis注入

目录 靶场搭建 JavaSec ​编辑​编辑 Hello-Java-Sec(可看到代码对比) SQL注入-JDBC(Java语言连接数据库) 1、采用Statement方法拼接SQL语句 2.PrepareStatement会对SQL语句进行预编译&#xff0c;但如果直接采取拼接的方式构造SQL&#xff0c;此时进行预编译也无用。 3、…

树莓集团南京园区启航:数字经济新地标!

深耕数字产业&#xff0c;构筑生态闭环 树莓集团在数字产业领域拥有超过十年的深厚积累&#xff0c;专注于构建“数字产业”的融合生态链。其核心优势在于有效整合政府、产业、企业及高校资源&#xff0c;形成一个协同创新、价值共生的产业生态闭环系统。 赋能转型&#xff0c…

Redis之bimap/hyperloglog/GEO

bimap/hyperloglog/GEO的真实需求 这些需求的痛点&#xff1a;亿级数据的收集清洗统计展现。一句话&#xff1a;存的进取得快多维度 真正有价值的是统计。 统计的类型 亿级系统中常见的四种统计 聚合统计 统计多个集合元素的聚合结果&#xff0c;就是交差并等集合统计。 排…

nara wpe去混响学习笔记

文章目录 1.WPE方法去混响的基本流程1.1.基本流程 2.离线迭代方法3.在线求法3.1.回顾卡尔曼方法3.2.在线去混响递推滤波器G方法 nara wpe git地址 博客中demo代码下载 参考论文 NARA - WPE: A Python Package for Weighted Prediction Error Dereverberation in Numpy and Ten…

JavaScript函数、箭头函数、匿名函数

1.示例代码(包括用法和注意事项) <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, initial-scale1.0"><title>JS-函数</title…

练习:求平方根

需求&#xff1a;键盘录入一个大于等于2的整数x&#xff0c;计算并返回x的平方根。结果只保留整数部分&#xff0c;小数部分将被舍去。 代码一&#xff1a; //求平方根 //方法一&#xff1a; package Online; import java.util.Scanner; public class SquareRoot {public sta…

win10 安装后的 系统盘的 分区

win10 安装后的 系统盘的 分区 MBR 分区 GPT 分区

反向 SSH 隧道技术实现内网穿透

反向 SSH 隧道技术实现内网穿透 场景描述 有一台内网的 Linux PC 机&#xff0c;想在其他地方&#xff08;如家中&#xff09;使用浏览器&#xff0c;在浏览器中能够使用内网 Linux PC 机的命令行。 实现思路 内网 Linux PC 机在内网可以使用 SSH 进行连接&#xff0c;但内…

[MRCTF2020]套娃

一。 按F12看源代码 发现代码 读代码发现 1.我们传的参数中不能存在_和%5f&#xff0c;可以通过使用空格来代替_&#xff0c;还是能够上传成功。 2.正则表达式"/^23333/ " &#xff0c;开头结尾都被 " " 和 " /"&#xff0c;开头结尾都被&qu…

基于Windows11的WSL2通过Ollama平台安装部署DeepSeek-R1模型

DeepSeek-R1模型各参数版本硬件要求 一、在Windows上安装Linux子系统WSL2 检查电脑是否支持虚拟化&#xff0c;按住<font style"color:rgb(199, 37, 78);background-color:rgb(249, 242, 244);">WindowsR</font>输入<font style"color:rgb(199,…