✨✨小新课堂开课了,欢迎欢迎~✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:C++:由浅入深篇

小新的主页:编程版小新-CSDN博客

引言:为什么引入智能指针?

1.C++手动释放内存的痛点:

  • 内存泄漏:忘记delete或异常导致未释放。
  • 野指针:访问已经释放的资源。
  • 重复释放:同一内存被释放多次。
  • 资源泄漏:不仅限于内存。
  • 代码复杂性与维护困难。

2.RAII(Resource Acquisition Is Initialization)原则:获取资源即初始化。

  • 核心思想:将资源的生命周期绑定到对象的生命周期。
  • 对象构造时获取资源,对象析构时自动释放资源。

3.智能指针作为RAII的实践者:

  • 智能指针是类模板,封装了原始指针,顾名思义就是比原始指针更智能。
  • 通过重载运算符(->,*)模拟原始指针的行为。
  • 核心价值:在析构函数中自动释放管理的资源,确保资源安全释放。
  • 引如现代C++标准(auto_ptr的教训与C++11的革新)。

一.智能指针的场景引入

在下面的程序中我们可以看到,new了以后,我们也delete了。但是new本身也有可能抛异常,如果是第一个那还好,array1未被成功分配,就无需释放资源,异常被捕获,无内存泄漏。但是如果第二个new失败,array1成功分配内存,array2抛异常,如果不做特殊处理,异常被main函数的catch捕获,array1的内存就泄漏了。在没有学智能指针之前,我们是按如下方式解决的,但是这让我们处理起来很麻烦。

double Divide(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Divide by zero condition!";} else{return (double)a / (double)b;}
} 
void Func()
{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;
} 
int main()
{try{Func();}catch(const char* errmsg){cout << errmsg << endl;} catch(const exception & e){cout << e.what() << endl;} catch(...){cout << "未知异常" << endl;} return 0;
}

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

RAII是一种管理资源的类的设计思想,本质是一种利用对象生命周期来代管(做到共同管理)获取到的动态资源,避免资源泄漏,这里的资源可以是内存、文件指针、网络连接、互斥锁等等。

RAII在获取资源时把资源委托给一个对象,接着控制对资源的访问,资源在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。

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

下面我们就来看一下是怎么用智能智能解决上面new的问题的。

template<class T>
class SmartPtr
{public :// RAIISmartPtr(T* ptr): _ptr(ptr){}~SmartPtr(){cout << "delete[] " << _ptr << endl;delete[] _ptr;} // 重载运算符,模拟指针的行为,方便访问资源T & operator*(){return *_ptr;} T* operator->(){return _ptr;} T& operator[](size_t i){return _ptr[i];}
private:T* _ptr;
};
double Divide(int a, int b)
{// 当b == 0时抛出异常if (b == 0){throw "Divide by zero condition!";} else{return (double)a / (double)b;}
} void Func()
{// 这里使用RAII的智能指针类管理new出来的数组以后,程序简单多了//将资源的生命周期绑定到对象的生命周期//对象构造时获取资源,对象析构时自动释放资源SmartPtr<int> sp1 = new int[10];SmartPtr<int> sp2 = new int[10];for (size_t i = 0; i < 10; i++){sp1[i] = sp2[i] = i;} 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和像指针一样访问的行为。

原理上而言主要是解决智能指针拷贝时的思路不同。

auto_ptr

auto_ptr - C++ Reference是C++98时设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源的管理权转移给拷贝对象,这是一个非常糟糕的设计,因为他会导致被拷贝对象悬空,访问报错的问题。

struct Date
{int _year;int _month;int _day;Date(int year = 1, int month = 1, int day = 1):_year(year), _month(month), _day(day){}~Date(){cout << "~Date()" << endl;}
};int main()
{auto_ptr<Date> ap1(new Date);// 拷贝时,管理权限转移,被拷贝对象ap1悬空auto_ptr<Date> ap2(ap1);// 空指针访问,ap1对象已经悬空//ap1->_year++;return 0;
}

**视频演示**

auto_ptr屏幕录制

**原理**

拷贝时,资源管理权转移,ap2代管资源,被拷贝对象ap1悬空。

**模拟实现**

namespace xin
{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 = nullptr;}return *this;}~auto_ptr(){if (_ptr){cout << "delete:" << _ptr << endl;delete _ptr;}}// 像指针⼀样使⽤T & operator*(){return *_ptr;} T* operator->(){return _ptr;}private:T* _ptr;};
}

unique_ptr

unique_ptr - C++ Reference是C++11设计出来的智能指针,他的名字翻译出来是唯一的指针,他的特点的不支持拷贝,只支持移动。如果不需要拷贝的场景就非常建议使用他。

int main()
{unique_ptr<Date> up1(new Date);// 不支持拷贝//unique_ptr<Date> up2(up1);// 支持移动,但是移动后up1也悬空,所以使用移动要谨慎//因为移动构造有被掠夺资源的风险,这里默认是你知道//你自己move的,就说明你知道有风险,所有才说他们本质是设计思路的不同unique_ptr<Date> up3(move(up1));return 0;
}

**视屏演示**

unique_ptr

**原理**

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;}}//不支持拷贝unique_ptr(const unique_ptr<T>& up) = delete;unique_ptr<T>& operator=(const unique_ptr<T>& up) = delete;//支持移动unique_ptr(unique_ptr<T>&& up):_ptr(up._ptr){up._ptr = nullptr;}unique_ptr<T>& operator=( unique_ptr<T>&& up){delete _ptr;_ptr = up._ptr;up._ptr = nullptr;}T& operator*(){return *_ptr;}T& operator->(){return _ptr;}private:T* _ptr;};

shared_ptr

shared_ptr - C++ Reference是C++11设计出来的智能指针,他的名字翻译出来是共享指针,他的特点是支持拷贝,也支持移动。如果需要拷贝的场景就需要使用他了。底层是用引用计数的方式实现的。

int main()
{shared_ptr<Date> sp1(new Date);// 支持拷贝shared_ptr<Date> sp2(sp1);shared_ptr<Date> sp3(sp2);cout << sp1.use_count() << endl;sp1->_year++;cout << sp1->_year << endl;cout << sp2->_year << endl;cout << sp3->_year << endl;// 支持移动,但是移动后sp1也悬空,所以使用移动要谨慎shared_ptr<Date> sp4(move(sp1));cout << sp4.use_count() << endl;return 0;
}

**视屏演示**

shared_ptr

**运行结果**

**原理**

他的特点是支持拷贝,也支持移动,底层是用引用计数的方式实现的。

引用计数就是统计有几个智能智能共同管理这块资源的,一个资源对应一个引用计数,不是sp1有一个自己的引用计数,sp2有一个自己的引用计数这种。看了图片大家就大概知道怎么理解引用计数了。这个跟操作系统里的文件系统里的硬链接,软链接计算引用计数那个挺像的。

智能指针析构时默认是用delete释放资源,这也就意味着如果不是new出来的资源,交给智能指针管理,析构时就会崩溃。但是因为new []经常使用,为了简洁一点,unique_ptr和shared_ptr都特化了一份[]的版本。

int main()
{//这样实现程序会崩溃/*unique_ptr<Date> up1(new Date[10]);shared_ptr<Date> sp1(new Date[10]);*/// 解决⽅案1// 因为new[]经常使⽤,所以unique_ptr和shared_ptr// 实现了⼀个特化版本,这个特化版本析构时用的delete[]unique_ptr<Date[]> up1(new Date[5]);shared_ptr<Date[]> sp1(new Date[5]);return 0;
}

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

template<class T>
void DeleteArrayFunc(T* ptr)
{delete[] ptr;
}template<class T>
class DeleteArray
{public :void operator()(T* ptr){delete[] ptr;}
};
class Fclose
{public :void operator()(FILE* ptr){cout << "fclose:" << ptr << endl;fclose(ptr);}
};int main()
{// 解决方案2// 仿函数对象做删除器// unique_ptr和shared_ptr支持删除器的方式有所不同// unique_ptr是在类模板参数支持的,shared_ptr是构造函数参数支持的// unique_ptr<Date, DeleteArray<Date>> up2(new Date[5], DeleteArray<Date>());// 这里没有使用相同的方式还是挺坑的// 使用仿函数unique_ptr可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调用// 但是下面的函数指针和lambda的类型不可以unique_ptr<Date, DeleteArray<Date>> up2(new Date[5]);//可以不在构造函数传递shared_ptr<Date> sp2(new Date[5], DeleteArray<Date>());//在构造函数传递// 函数指针做删除器unique_ptr<Date, void(*)(Date*)> up3(new Date[5], DeleteArrayFunc<Date>);shared_ptr<Date> sp3(new Date[5], DeleteArrayFunc<Date>);// lambda表达式做删除器auto delArrOBJ = [](Date* ptr) {delete[] ptr; };//我们无法知道lambda的类型unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);////但是这里要显示传类型,就用了decltype,其作用是查询表达式的类型shared_ptr<Date> sp4(new Date[5], delArrOBJ);// 实现其他资源管理的删除器shared_ptr<FILE> sp5(fopen("Test.cpp", "r"), Fclose());shared_ptr<FILE> sp6(fopen("Test.cpp", "r"), [](FILE* ptr) {cout << "fclose:" << ptr << endl;fclose(ptr);});return 0;
}

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

shared_ptr 和 unique_ptr 都支持了operator bool的类型转换,如果智能指针对象是一个空对象没有管理资源,则返回false,否则返回true,意味着我们可以直接把智能指针对象给if判断是否为空。

int main()
{shared_ptr<Date> sp1(new Date(2024, 9, 11));shared_ptr<Date> sp2 = make_shared<Date>(2024, 9, 11);auto sp3 = make_shared<Date>(2024, 9, 11);shared_ptr<Date> sp4;//支持无参构造// if (sp1.operator bool())if (sp1)cout << "sp1 is not nullptr" << endl;if (!sp4)cout << "sp4 is nullptr" << endl;// 报错 因为它们的构造函数都不支持隐士类型转化//shared_ptr<Date> sp5 = new Date(2024, 9, 11);//unique_ptr<Date> sp6 = new Date(2024, 9, 11);return 0;
}

**模拟实现**

下面的代码中使用了atomic<int>而不是普通的int是为了实现线程安全的引用计数,后面会更详细介绍。注意这里是不能用static的,static成员是所有同一类型实例共享的,而不是每个资源独立的。

template<class T>
class shared_ptr
{
public:explicit shared_ptr(T* ptr = nullptr )//标准库里支持无参构造:_ptr(ptr),_pcount(new atomic<int>(1))//_pcount(new int(1)){}template<class D>shared_ptr(T* ptr ,D del):_ptr(ptr),_pcount(new int(1)),_del(del){}shared_ptr(const shared_ptr<T>& sp):_ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del){++(*_pcount);}void release(){if (--(*_pcount)==0){//最后一个管理的对象,释放资源_del(_ptr);delete _pcount;_ptr = nullptr;_pcount = nullptr;}}shared_ptr<T>& operator=(const shared_ptr<T>& sp){if (_ptr != sp._ptr){release();_ptr = sp._ptr;_pcount = sp._pcount;++(*_pcount);_del = sp._del;}return *this;}~shared_ptr(){release();}T* get() const{return _ptr;}int use_count() const{return *_pcount;}T& operator*(){return *_ptr;}T* operator->(){return _ptr;}
private:T* _ptr;atomic<int>* _pcount; //原子操作//int* _pcount;function<void(T*)> _del = [](T* ptr) {delete ptr; };//包装器来包装删除器,默认使用lambda
};

四.循环引用和weak_ptr

shared_ptr导致的循环引用问题

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

struct ListNode
{int _data;std::shared_ptr<ListNode> _next;std::shared_ptr<ListNode> _prev;~ListNode(){cout << "~ListNode()" << endl;}
};
int main()
{// 循环引⽤ -- 内存泄露shared_ptr<ListNode> n1(new ListNode);shared_ptr<ListNode> n2(new ListNode);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;return 0;
}

没有析构,内存泄漏。

如上图所述场景,n1和n2析构后,管理两个节点的引用计数减到1

1. 右边的节点什么时候释放呢,左边节点中的_next管着呢,_next析构后,右边的节点就释放了。

2. _next什么时候析构呢,_next是左边节点的的成员,左边节点释放,_next就析构了。

3. 左边节点什么时候释放呢,左边节点由右边节点中的_prev管着呢,_prev析构后,左边的节点就释放了。

4. _prev什么时候析构呢,_prev是右边节点的成员,右边节点释放,_prev就析构了。

• 至此逻辑上成功形成回旋镖似的循环引用,谁都不会释放就形成了循环引用,导致内存泄漏。

weak_ptr版本:

struct ListNode
{int _data;// 这⾥改成weak_ptr,当n1->_next = n2;绑定shared_ptr时// 不增加n2的引用计数,不参与资源释放的管理,就不会形成循环引用了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.use_count() << endl;cout << n2.use_count() << endl;n1->_next = n2;n2->_prev = n1;cout << n1.use_count() << endl;cout << n2.use_count() << endl;return 0;
}

weak_ptr

weak_ptr - C++ Reference是C++11设计出来的智能指针,他的名字翻译出来是弱指针,他完全不同于上面的智能指针,他不支持RAII,也就意味着不能用它直接管理资源。

weak_ptr构造时不支持绑定到资源,只支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引用计数,那么就可以解决上述的循环引用问题。

int main()
{shared_ptr<string> sp1(new string("111111"));shared_ptr<string> sp2(sp1);weak_ptr<string> wp = sp1;cout << wp.expired() << endl;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;return 0;
}

**原理**

**模拟实现**

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

我们这里实现的shared_ptr和weak_ptr都是以最简洁的方式实现的, 只能满足基本的功能,这里的weak_ptr lock等功能是无法实现的,想要实现就要/把shared_ptr和weak_ptr一起改了,把引用计数拿出来放到一个单独类型,shared_ptr 和weak_ptr都要存储指向这个类的对象才能实现,有兴趣可以去翻翻源代码。

五.shared_ptr的线程安全问题

还记得我们在上面shared_ptr的模拟实现部分使用的atomic。原子操作(atomic operation)指的是在多线程环境下不会被中断的操作。这里的atomic<int>是C++11引入的原子类型,用于保证对引用计数的增减操作是原子性的,从而使得shared_ptr的引用计数在多线程环境下是线程安全的,当然这个也可以用加锁来实现。这个和操作系统处理访问临界资源的原理高度相似。

简单来说,就是shared_ptr的引用计数本身是线程安全的,但是shared_ptr管理的对象本身并不是线程安全的。因为多个线程同时修改同一个shared_ptr管理的对象时,需要额外的同步措施。

创作不易,还请各位大佬支持~

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

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

相关文章

算法训练营day57 图论⑦ prim算法精讲、kruskal算法精讲

两种最小生成树算法讲解 prim算法精讲 卡码网53. 寻宝 本题题目内容为最短连接&#xff0c;是最小生成树的模板题&#xff0c;那么我们来讲一讲最小生成树。最小生成树可以使用prim算法也可以使用kruskal算法计算出来。本篇我们先讲解prim算法。 最小生成树是所有节点的最小连…

148-基于Python的2024物流年度销售收入数据可视化分析系统

基于Python Django的物流数据可视化分析系统开发实录 项目背景 随着物流行业数据量的激增&#xff0c;企业对数据分析和可视化的需求日益增长。传统的Excel分析方式难以满足多维度、实时、交互式的数据洞察需求。为此&#xff0c;我们开发了一个基于Python Django的物流年度销售…

Python中的关键字参数:灵活与可读性的完美结合(Effective Python 第23条)

在Python编程中&#xff0c;函数参数的传递方式灵活多样&#xff0c;而其中一种特别强大的方式就是关键字参数。关键字参数不仅能够提升代码的可读性&#xff0c;还为函数的设计和调用提供了极大的便利。本文将深入探讨关键字参数的用法、优势以及实际应用中的注意事项。 一、关…

005.Redis 主从复制架构

主从复制概念与原理 核心概念 主节点&#xff08;Master&#xff09;&#xff1a;唯一接受写操作的节点&#xff0c;数据修改后异步复制到从节点。 从节点&#xff08;Replica&#xff09;&#xff1a;复制主节点数据的节点&#xff0c;默认只读&#xff08;可配置为可写但不…

Android Studio 模拟器 “******“ has terminated 问题

问题&#xff1a;Android Studio 模拟器 "**" has terminated 问题设备信息&#xff1a;CPU:I5 7500U RAM:64GB System:Windows 10 64位解决&#xff1a; 网上所有办法都尝试后仍然不可行可尝试如下办法&#xff1a;1、此电脑→管理→设备管理→显示适配器→右击→…

uniapp 懒加载图片

实现的功能 1.一次性获取图片。 2.按用户视野范围内看到的图片滚动下来进行懒加载,提高浏览器性能。 3.不要一次性加载全部的图片 1.给父组件绑定一个滚动监听 1.页面路径:/pages/Home/index.vue 不在一个页面的话用 EventBus去触发。@scroll="handleScroll2" Ev…

Android - 资源类型 MINE Type

一、概念MINE&#xff08;Multipurpose Internet Mail Extensions&#xff09;最初是为了标识电子邮件附件的类型&#xff0c;在 HTML 中使用 content-type 属性表示&#xff0c;描述了文件类型的互联网标准。格式&#xff1a;媒体类型/子类型&#xff0c;可使用通配符*。如 au…

php8.+ 新函数总结

PHP系统函数是PHP核心提供的内置函数&#xff0c;用于执行常见任务&#xff0c;如字符串操作、数组处理、数学运算等。它们通过预定义代码块封装了特定功能&#xff0c;开发者可直接调用而无需重复编写代码。 而 PHP 8.0以后又新增了一些实用函数&#xff0c;今天总结部分常见的…

Qt事件处理机制详解

一、事件处理基本流程在Qt中&#xff0c;所有从QObject派生的类都能处理事件。事件处理的核心流程如下&#xff1a;事件入口函数&#xff1a;bool QObject::event(QEvent *e)参数e包含事件信息&#xff0c;通过e->type()获取事件类型返回值true表示事件已被处理&#xff0c;…

Zynq中级开发七项必修课-第三课:S_AXI_GP0 主动访问 PS 地址空间

Zynq中级开发七项必修课-第三课&#xff1a;S_AXI_GP0 主动访问 PS 地址空间 目标1.0 编写 AXI-Lite Master&#xff1a;按键计数 → 写入 PS 内存1.1 PL 触发中断 → PS 响应并串口打印按键计数值BD图axi_lite_master.v // // AXI4-Lite Simple Master (single-shot, non-pip…

CVPR | 2025 | MAP:通过掩码自回归预训练释放混合 Mamba - Transformer 视觉骨干网络的潜力

文章目录CVPR | 2025 | MAP&#xff1a;通过掩码自回归预训练释放混合 Mamba - Transformer 视觉骨干网络的潜力创新点初步研究初步结论方法确定一个混合网络方法掩码机制掩码比例MAP的transformer解码器重建目标实验ImageNet-1k 上的 2D 分类CVPR | 2025 | MAP&#xff1a;通过…

Spring Boot + Spring AI 最小可运行 Demo

一. 项目依赖&#xff08;pom.xml&#xff09;<project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.0.0https://maven.apache.org/xsd/mav…

AI重塑校园教育:中小学AI智慧课堂定制方案+AI作业批改减负,告别一刀切学生进步快

家长们&#xff0c;你有没有听过孩子抱怨上学的烦恼&#xff1f;课堂上老师讲的内容&#xff0c;有的同学觉得太简单 “吃不饱”&#xff0c;有的却跟不上 “听不懂”&#xff1b;放学后作业堆成山&#xff0c;老师要熬夜批改到半夜&#xff0c;错题反馈要等第二天才能拿到&…

旧物循环,交易新生——旧物回收二手交易小程序,引领绿色消费新风尚

在资源日益紧张、环境污染问题日益突出的今天&#xff0c;绿色消费已经成为时代发展的必然趋势。旧物回收二手交易小程序&#xff0c;作为绿色消费的重要载体&#xff0c;正以其独特的优势和魅力&#xff0c;引领着一场关于旧物循环、交易新生的绿色革命。一、旧物循环&#xf…

刷机维修进阶教程-----如何清除云账号 修复wifi 指南针 相机 指纹等刷机故障

在刷机、系统升级或降级过程中,是否遇到过以下问题:WiFi无法开启、相机无响应、指南针或陀螺仪失灵 指纹等故障?另外,云账号是否仍会保留,即使通过9008模式刷机也无法彻底清除?那么这篇博文都可以找到答案。 通过博文了解💝💝💝 1💝💝💝----云账号信息分区如…

AI翻唱实战:用[灵龙AI API]玩转AI翻唱 – 第6篇

历史文章 [灵龙AI API] 申请访问令牌 - 第1篇 [灵龙AI API] AI生成视频API&#xff1a;文生视频 – 第2篇 图生视频实战&#xff1a;用[灵龙AI API]玩转AI生成视频 – 第2篇&#xff0c;从静图到大片 单图特效实战&#xff1a;用[灵龙AI API]玩转AI生成视频 – 第3篇&#…

大模型0基础开发入门与实践:第11章 进阶:LangChain与外部工具调用

第11章 进阶&#xff1a;LangChain与外部工具调用 1. 引言 在上一章&#xff0c;我们成功地创造了我们的第一个“生命”——一个可以对话的机器人。我们为它的诞生而兴奋&#xff0c;但很快我们就会发现它的局限性。它就像一个被囚禁在玻璃房中的天才大脑&#xff0c;拥有渊博…

SQL 日期处理:深入解析与高效实践

SQL 日期处理&#xff1a;深入解析与高效实践 引言 在数据库管理中&#xff0c;日期和时间数据的处理是不可或缺的一部分。SQL&#xff08;结构化查询语言&#xff09;提供了丰富的日期和时间函数&#xff0c;使得对日期的处理变得既灵活又高效。本文将深入探讨SQL日期处理的相…

源代码部署 LAMP 架构

源代码部署 LAMP 架构 &#xff08;Linux Apache MySQL PHP&#xff09; 一、LAMP 架构概述 LAMP 是一套经典的开源 Web 服务架构&#xff0c;通过源代码安装可实现高度定制化&#xff0c;适用于对软件版本、功能模块有特定需求的场景。本指南基于 CentOS 7 系统&#xf…

GO环境变量中GO111MODULE到底是干啥的?

查看GO111MODULE变量GO111MODULE的作用GO111MODULE的案例演示 一&#xff0c;查看GO111MODULE变量 ]# go env GO111MODULE 或者 ]# go env | grep GO111MODULE二&#xff0c;GO111MODULE的作用 auto : 自动判断机制 当项目位于 $GOPATH/src 目录外且包含 go.mod 文件时&…