C++11智能指針 unique_ptr、shared_ptr、weak_ptr、迴圈引用、定製刪除器

来源:https://www.cnblogs.com/DSCL-ing/p/18253156
-Advertisement-
Play Games

目錄智能指針場景引入 - 為什麼需要智能指針?記憶體泄漏什麼是記憶體泄漏記憶體泄漏的危害記憶體泄漏分類如何避免記憶體泄漏智能指針的使用及原理RAII簡易常式智能指針的原理智能指針的拷貝問題智能指針的發展歷史std::auto_ptr模擬實現auto_ptr常式:這種方案存在的問題:Boost庫中的智能指針un ...


目錄

智能指針

場景引入 - 為什麼需要智能指針?

C++有些場景下,處理異常安全問題時特別繁瑣

void example{
	int *p1 = new int;  //可能會拋異常1
	int *p2 = new int;  //可能會拋異常2
	Func();             //可能會拋異常3
	
	delete p1;
	delete p2;
}

int main(){
	try{
		example();
	}
	catch(std::exception& e){
		std::cout<<e.what()<<std::endl;
	}
}

如果需要將上面例子使用異常處理,則可能會面臨如下情況

  1. 只有p1拋異常:

    p1拋異常程式沒有安全問題,因為new沒有申請成功,之後程式就跳轉了

  2. p1不拋異常,但p2拋異常:

    image-20240610213932550

    如果直接複製粘貼到try中,p2就出作用域了.因此需要改代碼:

    image-20240610214612327

    這樣就可以了,但是這麼做會有點彆扭和麻煩.

  3. 如果p1和p2都不拋異常,Func拋異常

    void example() {
        int* p1 = new int;   
        int* p2 = nullptr;
        
        try {
            p2 = new int;
        }
        catch (...) {
            delete p1;
            throw;
        }
    
        try {
            Func();
        }
        catch(...){
            delete p1;
            delete p2;
            throw;
        }
        delete p1;
        delete p2;
    }
    

    使用try-catch處理後,代碼越來越膨脹.如果再多幾個可能會拋異常的函數.那代碼是相當長了.

先說結論:如果使用C++的智能指針,處理這些問題會變得很簡單.

記憶體泄漏

什麼是記憶體泄漏

記憶體泄漏指因為疏忽或錯誤造成程式未能釋放已經不再使用的記憶體的情況.記憶體泄漏並不是指記憶體在物理上的消失,而是應用程式分配某段記憶體後,因為設計錯誤,失去了對該段記憶體的控制,因而造成了記憶體的浪費.

記憶體泄漏的危害

長期運行的程式出現記憶體泄漏,影響很大,如操作系統、後臺服務等等,出現記憶體泄漏會導致響應越來越慢,最終卡死.(不怕一下子泄露完,就怕一點一點泄露,難以發現)

記憶體泄漏分類

C/C++程式中一般我們關心兩種方面的記憶體泄漏

  • 堆記憶體泄漏(Heap leak)
    堆記憶體指的是程式執行中依據須要分配通過malloc / calloc / realloc / new等從堆中分配的一塊記憶體,用完後必須通過調用相應的free或者delete刪掉。假設程式的設計錯誤導致這部分記憶體沒有被釋放,那麼以後這部分空間將無法再被使用,就會產生Heap Leak。
  • 系統資源泄漏
    指程式使用系統分配的資源,比方套接字、文件描述符、管道等沒有使用對應的函數釋放掉,導致系統資源的浪費,嚴重可導致系統效能減少,系統執行不穩定。

如何避免記憶體泄漏

  1. 工程前期良好的設計規範,養成良好的編碼規範,申請的記憶體空間記著匹配的去釋放。ps:這個理想狀態。但是如果碰上異常時,就算註意釋放了,還是可能會出問題。需要智能指針來管理才有保證。

  2. 採用RAII思想或者智能指針來管理資源。

  3. 有些公司內部規範使用內部實現的私有記憶體管理庫。這套庫自帶記憶體泄漏檢測的功能選項。

  4. 出問題了使用記憶體泄漏工具檢測。ps:不過很多工具都不夠靠譜,或者收費昂貴。

總結一下:
記憶體泄漏非常常見,解決方案分為兩種:1、事前預防型。如智能指針等。2、事後查錯型。如泄漏檢測工具。

智能指針的使用及原理

RAII

RAII(Resource Acquisition Is Initialization)是一種利用對象生命周期來控製程序資源(如記憶體、文件句柄、網路連接、互斥量等等)的簡單技術。
在對象構造時獲取資源,接著控制對資源的訪問使之在對象的生命周期內始終保持有效,最後在對象析構的時候釋放資源。藉此,我們實際上把管理一份資源的責任托管給了一個對象。這種做法有兩大好處:

  • 不需要顯式地釋放資源。
  • 採用這種方式,對象所需的資源在其生命期內始終保持有效。
  • 智能指針是RAII思想的一種產物,還有守護鎖lock_gard等...

簡易常式

template<class T>
class SmartPtr {
public:
    SmartPtr(T*ptr):_ptr(ptr) 
    {}
    ~SmartPtr() {
        delete _ptr;
        std::cout<<"delete ptr"<<"\n";
    }
private:
    T*_ptr;
};

int div()
{
    int a, b;
    std::cin >> a >> b;
    if (b == 0)
        throw std::invalid_argument("除0錯誤");
    return a / b;
}

void example() {
    SmartPtr<int> sp1(new int);
    SmartPtr<int> sp2(new int);
    try {
        div();
    }
    catch (...) {
        throw;
    }
}

執行結果:

image-20240610223732315

根據執行結果可以發現,在div()拋出異常後,SmartPtr的兩個對象都delete.

原因就是sp1和sp2都是類型為SmartPtr的局部對象,出了作用域會調用它的析構函數.

之後不需要再寫一堆try-catch,代碼更加簡潔

智能指針的原理

  • RAII
  • 像指針一樣使用
  • 拷貝問題

上述的SmartPtr還不能將其稱為智能指針,因為它還不具有指針的行為。指針可以解引用,也可
以通過->去訪問所指空間中的內容,因此:AutoPtr模板類中還得需要將* 、->重載下,才可讓其
像指針一樣去使用。

常式:

template<class T>
class SmartPtr {
public:
    SmartPtr(T*ptr):_ptr(ptr) 
    {}
    ~SmartPtr() {
        delete _ptr;
        std::cout<<"delete ptr"<<"\n";
    }
    T& operator*() {
        return *_ptr;
    }
    T* operator->() {
        return _ptr;
    }
private:
    T*_ptr;
};

智能指針的拷貝問題

智能指針最難的在於拷貝問題.

下麵例子中,嘗試使用拷貝構造初始化sp2:

image-20240616110527125

運行後出現了程式奔潰.

我們知道,預設生成的拷貝構造是淺拷貝.我們目前的SmartPtr並沒有寫拷貝構造,並且sp1是管理著一個動態申請的對象的.拷貝構造之後,sp1和sp2同時指向了同一個對象.最終會導致釋放時釋放兩次.

那給SmartPtr加上深拷貝可以嗎? 不可以,因為我們要的就是淺拷貝.因為智能指針是要模擬普通指針的行為.普通指針賦值也是淺拷貝,賦值後它們都指向同一個資源,由用戶進行delete.因此不能是深拷貝.

迭代器也是模擬指針行為,也是淺拷貝,為什麼迭代器不擔心拷貝問題?

因為迭代器只是用於訪問資源,修改資源,並不需要管理資源釋放,資源釋放由容器進行處理.

而智能指針需要管理資源釋放,不能單純的淺拷貝

如何解決.最終解決方案是使用引用計數.再此之前還有一段智能指針發展過程.

智能指針的發展歷史

C++98時,C++有了第一款智能指針,它叫做auto_ptr,自動指針.

它的出現也遇到瞭如我們上文中存在的拷貝問題.auto_ptr使用了管理權轉移的方案進行解決.

std::auto_ptr

頭文件

模擬實現auto_ptr常式:

namespace test {
    template<class T>
    class auto_ptr {
    public:
        auto_ptr(T* ptr) :_ptr(ptr)
        {}

        auto_ptr(auto_ptr& ap) {
            _ptr = ap._ptr;
            ap._ptr = nullptr;
        }

        ~auto_ptr() {
            delete _ptr;
            std::cout << "delete ptr" << "\n";
        }
        T& operator*() {
            return *_ptr;
        }
        T* operator->() {
            return _ptr;
        }
    private:
        T* _ptr;
    };
}

使用這種方案下,如果熟悉特性,使用效果還好.

這種方案存在的問題:

image-20240616141642877

管理權轉移後,ap1就成了垂懸指針,導致後續代碼不好維護,容易出錯.很多公司明確規定不能使用auto_ptr.

垂懸指針:指向曾經存在的對象,但該對象已經不再存在了,此類指針稱為懸垂指針。結果未定義,往往導致程式錯誤,而且難以檢測。

Boost庫中的智能指針

Boost庫

為C++語言標準庫提供擴展C++程式庫的總稱

Boost庫是為C++語言標準庫提供擴展的一些C++程式庫的總稱,由Boost社區組織開發、維護。Boost庫可以與C++標準庫完美共同工作,並且為其提供擴展功能。

Boost庫是為C++語言標準庫提供擴展的一些C++程式庫的總稱。

Boost庫由Boost社區組織開發、維護。其目的是為C++程式員提供免費、同行審查的、可移植的程式庫。Boost庫可以與C++標準庫共同工作,並且為其提供擴展功能。Boost庫使用Boost License來授權使用,根據該協議,商業或非商業的使用都是允許並鼓勵的。

Boost社區建立的初衷之一就是為C++的標準化工作提供可供參考的實現,Boost社區的發起人Dawes本人就是C++標準委員會的成員之一。在Boost庫的開發中,Boost社區也在這個方向上取得了豐碩的成果。在送審的C++標準庫TR1中,有十個Boost庫成為標準庫的候選方案。在更新的TR2中,有更多的Boost庫被加入到其中。從某種意義上來講,Boost庫成為具有實踐意義的準標準庫。

大部分boost庫功能的使用只需包括相應頭文件即可,少數(如正則表達式庫,文件系統庫等)需要鏈接庫。裡面有許多具有工業強度的庫,如graph庫。

很多Boost中的庫功能堪稱對語言功能的擴展,其構造用盡精巧的手法,不要貿然的花費時間研讀。Boost另外一面,比如Graph這樣的庫則是具有工業強度,結構良好,非常值得研讀的精品代碼,並且也可以放心的在產品代碼中多多利用。

boost中有兩套智能指針比較知名

  • scoped_ptr
  • shard_ptr/weak_ptr

它們分別是C++11標準庫中的unique_ptr與shared_ptr/weak_ptr的前身.

unique_ptr

原理:防拷貝

使用場景:在很多情況下,不允許對象拷貝,使用unique_ptr就能很好解決這類問題.比如i/ostring防拷貝,線程類緩衝區問題,mutex唯一性等,拷貝後衝突,拷貝代價大等這問題通過禁止拷貝能很好的解決.

模擬實現簡易unique_ptr常式

原理:封掉拷貝構造和賦值重載

namespace test {
    template<class T>
    class unique_ptr {
    public:
        unique_ptr(T* ptr) :_ptr(ptr)
        {}
        ~unique_ptr() {
            delete _ptr;
            std::cout << "delete ptr" << "\n";
        }
        T& operator*() {
            return *_ptr;
        }
        T* operator->() {
            return _ptr;
        }

        //C++11封拷貝 
        unique_ptr(const unique_ptr<T>& up) = delete;
        unique_ptr<T>& operator = (const unique_ptr<T>&up) = delete;
    private:
        //C++98封拷貝
        //unique_ptr(const unique_ptr<T>& up); //C++98
        //unique_ptr<T>& operator=(const unique_ptr<T>&up);

        T* _ptr;
    };
}

image-20240616152254421

shared_ptr

頭文件 shared_ptr

unique_ptr不能解決所有問題,還有一些場景是需要共用的.因此有了shared_ptr

核心原理:引用計數

技術實現分析:

  • 引用計數實現

    每個資源應該配對一個引用計數.

    實現方法:動態申請一個int

  • 能否使用static作為引用計數類型?

    不能.static雖然有共用性質,但是static是所有類共用一個,shared_ptr不僅僅只用於管理一個對象資源,還可能需要管理多個對象資源.如果使用static,管理多個資源的情況下,這些資源都會指向同一個計數了,因此不可以使用static.

  • 線程安全

    1. 線程安全問題,智能指針只負責自身線程安全,並不負責資源的線程安全,資源的線程安全由資源自己負責.而引用計數是屬於智能指針維護的,因此,智能指針的線程安全問題為引用計數的線程安全問題.即保護引用計數
    2. 鎖的定義方式和_pRefCount一樣.一份資源(此處資源為_pRefCount)對應一個.
  • operator=實現較為複雜,具有任意性(可以多次賦值,拷貝只能一次)

    1. 如果使用賦值進行初始化,則和拷貝賦值一樣.
    2. 如果是二次賦值,需要考慮有:
    • 新管理資源走拷貝構造一套.
    • 原先管理資源的引用計數減少.為0時需要析構(走一次析構)
    • 處理"自己"給"自己"賦值
      1. 智能指針對象相同,指向的資源相同
      2. 智能指針對象不同,指向的資源相同
        傳統的方法(*this!=對象),只能處理第一種;而(*this._ptr != sp._ptr)能夠相容兩種方法;
        註意:雖然處理邏輯和析構函數很像,但是C++中成員函數不能自己調用析構函數,因為肚子里的蛋不能殺的了雞.
        如果需要提高復用性,可以將邏輯獨立成一個函數,然後析構和賦值重載都分別進行調用

模擬實現簡易shared_ptr常式:

namespace test {
    template<class T>
    class shared_ptr {
    public:
        shared_ptr() : _ptr(nullptr), _pRefCount(new int(0)),_pmtx(new std::mutex)
        {}

        shared_ptr(T* ptr) :_ptr(ptr), _pRefCount(new int(1)),_pmtx(new std::mutex)
        {}

        shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pRefCount(sp._pRefCount) ,_pmtx(sp._pmtx){
            AddRef();
        }


        ~shared_ptr() {
            Release();
        }

        shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
            if (_ptr != sp._ptr)
            {
                if (*_pRefCount == 0) //天生就為0的情況
                {}
                else {
                    Release();
                }

                _ptr = sp._ptr;
                _pRefCount = sp._pRefCount;
                _pmtx = sp._pmtx;
                AddRef();
            }
            return *this;
        }

        void Release() {
            _pmtx->lock();
            if (--(*_pRefCount) <= 0) {
                _pmtx->unlock();
                delete _ptr;
                delete _pRefCount;
                delete _pmtx;
                std::cout << "delete " << _ptr << "\n";
            }
            else {
                _pmtx->unlock();
            }
        }

        void AddRef() {
            _pmtx->lock();
            ++(*_pRefCount);
            _pmtx->unlock();
        }
        
        T& operator*() {
            return *_ptr;
        }
        T* operator->() {
            return _ptr;
        }

        T* get() {
            return _ptr;
        }

        int use_count() {
            return *_pRefCount;
        }


    private:
        T* _ptr;
        int* _pRefCount;
        std::mutex* _pmtx;
    };
}

多線程測試常式

void SharePtrFunc(test::shared_ptr<Date>& sp, size_t n)
{
    for (size_t i = 0; i < n; ++i)
    {
        test::shared_ptr<Date> copy(sp);
        copy->_year++;
        copy->_month++;
        copy->_day++;
    }
}

int main() {
    test::shared_ptr<Date> sp (new Date);

    const size_t n = 1000000;
    std::thread t1(SharePtrFunc, std::ref(sp), n);
    std::thread t2(SharePtrFunc, std::ref(sp), n);
    std::thread t3(SharePtrFunc, std::ref(sp), n);

    t1.join();
    t2.join();
    t3.join();

    std::cout<<sp.use_count()<<"\n";
    std::cout<<sp->_year<<"\n";
    std::cout<<sp->_month<<"\n";
    std::cout<<sp->_day<<"\n";
    return 0;
}
//測試標準庫: 包含頭文件<memory>,將所有命名空間修改成std

測試結果:

image-20240617130605577

shared_ptr線程安全.管理的資源不是線程安全.

shared_ptr迴圈引用問題

通過shared_ptr構建的鏈表節點.

struct ListNode
{
    int _data;
    std::shared_ptr<ListNode> _prev;
    std::shared_ptr<ListNode> _next;

    ~ListNode() { std::cout << "~ListNode()" << std::endl; }
};

int main() {
    std::shared_ptr<ListNode> sp1 (new ListNode);
    std::shared_ptr<ListNode> sp2 (new ListNode);

    return 0;
}

當兩個節點獨立時,析構正常

image-20240617170509829

單鏈表時,析構正常

image-20240617170651857

當成環狀時,不能正常析構.

image-20240617170846618

原因圖解分析:

image-20240617170859962

weak_ptr

頭文件 weak_ptr

weak_ptr也叫弱指針.

weak_ptr的特點

  • 它不是一個常規的智能指針,並不符合RAII
  • 支持像指針一樣使用
  • 專門設計出來,輔助解決shared_ptr迴圈引用問題

weak_ptr的核心原理是不增加引用計數.

上述迴圈引用問題就是因為增加了內部引用增加了引用計數.我們需要一個不增加引用計數,又可以指向資源,像指針一樣的東西,因此有了weak_ptr.

模擬實現簡易weak_ptr

庫中的shaerd_ptr和weak_ptr實現機制非常複雜.在這裡僅模擬實現,復現出簡單場景功能

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

        weak_ptr(const shared_ptr<T>& sp)
            :_ptr(sp.get())
        {}

        weak_ptr<T>& operator=(const shared_ptr<T>& sp) 
        {
            _ptr = sp.get();
            return *this;
        }

        T& operator*()
        {
            return *_ptr;
        }

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

    private:
        T* _ptr;
    };
}

解決迴圈引用問題

struct ListNode{
    int _data;
    test::weak_ptr<ListNode> _prev;
    test::weak_ptr<ListNode> _next;

    ~ListNode() { std::cout << "~ListNode()" << std::endl; }
};

int main() {
    test::shared_ptr<ListNode> sp1(new ListNode);
    test::shared_ptr<ListNode> sp2(new ListNode);
    sp1->_next = sp2;
    sp2->_prev = sp1;
    return 0;
}

image-20240617182319998

定製刪除器

上述實現的析構方法,僅適用於析構delete.如果new了一個數組,則析構時程式會奔潰,因為析構數組需要delete[].因此需要定製刪除器

image-20240617190315534

定製刪除器是一個可調用對象(函數指針,仿函數/函數對象,lambda表達式)

仿函數版本實現

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

image-20240617190503669

lambda版本實現

int main() {
    std::shared_ptr<Date> sp2(new Date[10],[](Date*ptr){delete[] ptr;});
    std::shared_ptr<FILE> sp3(fopen("test.cpp", "r"), [](FILE* ptr) {
    	std::cout<<"fclose()"<<"\n"; fclose(ptr); });
    	
    return 0;
}

定製刪除器版本shared_ptr模擬實現

namespace test {
    template<class T>
    class shared_ptr {
    public:
        shared_ptr() : _ptr(nullptr), _pRefCount(new int(0)), _pmtx(new std::mutex)
        {}

        shared_ptr(T* ptr) :_ptr(ptr), _pRefCount(new int(1)), _pmtx(new std::mutex)
        {}

        shared_ptr(const shared_ptr<T>& sp) :_ptr(sp._ptr), _pRefCount(sp._pRefCount), _pmtx(sp._pmtx) {
            AddRef();
        }

        template<class T, class D>  //第二步
        shared_ptr(T* ptr, D del) : shared_ptr(ptr) {
            _del = del;
        }


        ~shared_ptr() {
            Release();
        }

        shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
            if (_ptr != sp._ptr)
            {
                if (*_pRefCount == 0) //天生就為0
                {
                }
                else {
                    Release();
                }

                _ptr = sp._ptr;
                _pRefCount = sp._pRefCount;
                _pmtx = sp._pmtx;
                AddRef();
            }
            return *this;
        }

        void Release() {
            _pmtx->lock();
            if (--(*_pRefCount) <= 0) {
                _pmtx->unlock();
                //delete _ptr;
                _del(_ptr); //第三步 
                delete _pRefCount;
                delete _pmtx;
                std::cout << "delete " << _ptr << "\n";
            }
            else {
                _pmtx->unlock();
            }
        }

        void AddRef() {
            _pmtx->lock();
            ++(*_pRefCount);
            _pmtx->unlock();
        }

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

        T* get() const {
            return _ptr;
        }

        int use_count() const {
            return *_pRefCount;
        }

    private:
        T* _ptr;
        int* _pRefCount;
        std::mutex* _pmtx;
        std::function<void(T* ptr)> _del = [](T*ptr){delete ptr;}; //第一步:包裝器+預設
    };
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 這篇文章介紹了Tailwind CSS框架的特點與優勢,包括其作為實用性的CSS框架如何通過預設的樣式類實現快速佈局和設計,以及如何在不犧牲響應式和自適應性的同時減少開發時間。此外,還提及了框架的可定製性,允許開發者輕鬆創建符合項目需求的樣式規則,從而提高前端開發效率。 ...
  • Chrome 在 121 版本開始,原生支持了兩個滾動條樣式相關的樣式 scrollbar-color 和 scrollbar-width。 要知道,在此前,雖然有 ::-webkit-scrollbar 規範可以控制滾動條,可是,::-webkit-scrollbar 是非標準特性,在 MDN 文 ...
  • Spring Cloud是一個相對比較成熟的微服務框架。雖然,Spring Cloud於2016年才推出1.0的release版本, 時間最短, 但是相比Dubbo等RPC框架, Spring Cloud提供的全套的分散式系統解決方案。 Spring Cloud是一系列框架的有序集合。它利用Spri ...
  • Windows應用軟體開發,會有很多常用的模塊,比如資料庫、配置文件、日誌、後臺通信、進程通信、埋點、瀏覽器等等。下麵是目前我們公司windows梳理的部分組件,梳理出來方便大家瞭解組件概念以及依賴關係: 每個應用里,現在或者以後都可能會存在這些模塊。以我團隊開發的全家桶為例,十多個應用對後臺訪問, ...
  • 通過本文我們深入瞭解了RabbitMQ的集群模式及其優缺點。無論是普通集群還是鏡像集群,都有其適用的場景和局限性。普通集群利用Erlang語言的集群能力,但消息可靠性和高可用性方面存在一定挑戰;而鏡像集群通過主動消息同步提高了消息的可靠性和高可用性,但可能會占用大量網路帶寬。因此,在選擇集群方案時,... ...
  • 寫在前面 在現目前項目開發中,一般都是前後端分離項目。前端小姐姐負責開發前端,苦逼的我們負責後端開發 事實是一個人全乾,在這過程中編寫介面文檔就顯得尤為重要了。然而作為一個程式員,最怕的莫過於自己寫文檔和別人不寫文檔 大家都不想寫文檔,那這活就交給今天的主角Swagger來實現了 一、專業名詞介紹 ...
  • 1 Zero-shot learning 零樣本學習。 1.1 任務定義 利用訓練集數據訓練模型,使得模型能夠對測試集的對象進行分類,但是訓練集類別和測試集類別之間沒有交集;期間需要藉助類別的描述,來建立訓練集和測試集之間的聯繫,從而使得模型有效。 Zero-shot learning 就是希望我們 ...
  • 本文基於 OpenJDK17 進行討論,垃圾回收器為 ZGC。 提示: 為了方便大家索引,特將在上篇文章 《以 ZGC 為例,談一談 JVM 是如何實現 Reference 語義的》 中討論的眾多主題獨立出來。 FinalReference 對於我們來說是一種比較陌生的 Reference 類型,因 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...