C++智能指針詳解:告別內存泄漏,擁抱安全高效

??小新課堂開課了,歡迎歡迎~??

🎈🎈養成好習慣,先贊后看哦~🎈🎈

所屬專欄: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 文件時&…