C++11 智能指針 shared_ptr

来源:https://www.cnblogs.com/champrin/archive/2023/01/17/17056814.html
-Advertisement-
Play Games

C++11 智能指針 shared_ptr Written on 2023-01-16 個人學習智能指針記錄合集: C++11 智能指針 C++11 智能指針 shared_ptr C++11 智能指針 unique_ptr C++11 智能指針 weak_ptr std::shared_ptr 共 ...


C++11 智能指針 shared_ptr

Written on 2023-01-16

個人學習智能指針記錄合集:

std::shared_ptr 共用智能指針,也被稱為計數智能指針。

共用智能指針會記錄有多少個共用智能指針指向同一個對象,當這個數為 0 的時候,程式自動的預設釋放(析構)這個對象,記錄有多少個的這個方法叫做引用計數

共用智能指針可以有多個共用智能指針同時管理同一個對象。

舉個慄子

普通指針管理

#include <iostream>
#include <memory>
using namespace std;

class Person{
public: 
    Person(){ cout << "Constructor: person's age = " << m_age << endl; }
    Person(int age) : m_age(age){ cout << "Constructor: person's age = " << m_age << endl; }
    ~Person(){ cout << "Destructor: person's age = " << m_age << endl; }
    void getAge(){ cout << "Person's age = " << m_age << endl; }
private:
    int m_age = 0; 
}; 

int main()
{
    {
        Person *p = new Person(18);
    }

    cout << endl << "Before return" << endl;
    return 0;
}
/** 輸出:
Constructor: person's age = 18

Before return

**/

依輸出可見,p指向的對象並沒有被析構,因為並沒有析構函數中的列印,這造成了記憶體泄漏。

shared_ptr智能指針管理

// ...
int main()
{
    {
        shared_ptr<Person> sPtr1 {new Person(18)};
    }

    cout << endl << "Before return" << endl;
    return 0;
}
/** 輸出:
Constructor: person's age = 18
Destructor: person's age = 18

Before return

**/

依輸出可見,離開了程式塊的作用域後,析構函數中的列印體現出來了,sPtr1管理的對象自動的被析構了。

獲取引用計數

long use_count() const noexcept;

shared_ptrnullptr 時,返回為 0。

初始化

創建空管理的 shared_ptr

constexpr shared_ptr() noexcept;
constexpr shared_ptr(std::nullptr_t) noexcept;
  • std::nullptr_t:空指針nullptr

創建對非數組對象和數組對象管理的 shared_ptr

template< class Y >
explicit shared_ptr( Y* ptr );
  • Y:動態分配的對象的類型
// ...
int main()
{
    shared_ptr<int> sPtr1; // 創建空管理的shared_ptr
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    
    shared_ptr<int> sPtr2(nullptr); // 創建空管理的shared_ptr
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    
    shared_ptr<int> sPtr3(new int(100)); // 創建對非數組對象管理的shared_ptr
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    
    shared_ptr<int> sPtr4(new int[10]); // 創建對數組對象管理的shared_ptr
    cout << "sPtr4 use count = " << sPtr4.use_count();
    return 0;
}
/** 輸出:
sPtr1 use count = 0
sPtr2 use count = 0
sPtr3 use count = 1
sPtr4 use count = 1
**/

解釋:
sPtr1sPtr2都為nullptr,故引用計數都為 0;
sPtr3sPtr4所管理的對象,都只有一個 shared_ptr 管理,故引用計數都為 1。

創建指定刪除器的 shared_ptr

template< class Y, class Deleter >
shared_ptr( Y* ptr, Deleter d );

template< class Deleter >
shared_ptr( std::nullptr_t ptr, Deleter d );

template< class Y, class Deleter, class Alloc >
shared_ptr( Y* ptr, Deleter d, Alloc alloc );

template< class Deleter, class Alloc >
shared_ptr( std::nullptr_t ptr, Deleter d, Alloc alloc );
  • Deleter:刪除器,在引用計數為 0 時,shared_ptr 會自動的執行刪除器
  • Alloc:分配器(Allocator)
  1. 預設刪除器:預設對指向的記憶體地址進行釋放,即析構掉這塊地址的內容
    1. 非數組類型,以 delete ptr 為預設刪除器
    2. 數組類型,以 delete[] ptr 為預設刪除器
  2. 指定刪除器:能夠自行指定引用計數為 0 時,要做什麼,比如有些場景不需要對指向的記憶體進行釋放,比如關閉指向文件,關閉指向套接字
    1. 指定刪除器的函數結構:
      void deletePtr(T* p){ ... }
      
    2. 也可使用 Lambda表達式

例,對於文件的使用場景,不是直接delete文件,而是關閉文件:

// ...
void closeFile(FILE* fp) 
{
    if (fp == nullptr) return;
    fclose(fp);
    cout << "File closed" << endl;
}
int main() 
{
    FILE* fp = fopen("data.txt" ,"w");
    shared_ptr<FILE> sfp{fp,closeFile};
    if (sfp == nullptr)
        cout << "Failed opened" << endl;
    else 
        cout << "File opened" << endl;
    return 0;
}

創建通過現有共用智能指針的 shared_ptr

主要是通過複製構造函數和移動構造函數std::move()

shared_ptr( const shared_ptr& r ) noexcept;

template< class Y >
shared_ptr( const shared_ptr<Y>& r ) noexcept;

shared_ptr( shared_ptr&& r ) noexcept;

template< class Y >
shared_ptr( shared_ptr<Y>&& r ) noexcept;    

例,

// ...
int main()
{
    shared_ptr<int> sPtr1(new int(100));
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl << endl;
    // 創建通過現有現有共用智能指針的 shared_ptr
    // 通過拷貝構造函數創建
    shared_ptr<int> sPtr2(sPtr1);
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl << endl;
    // 通過賦值運算符創建
    shared_ptr<int> sPtr3 = sPtr1;
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl << endl;
    // 通過移動構造函數創建
    shared_ptr<int> sPtr4(move(sPtr1));
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
    std::shared_ptr<int> sPtr5 = move(sPtr2);
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count() << endl;
    cout << "sPtr5 use count = " << sPtr5.use_count();
    return 0;
}
/** 輸出:
sPtr1 use count = 1

sPtr1 use count = 2
sPtr2 use count = 2

sPtr1 use count = 3
sPtr2 use count = 3
sPtr3 use count = 3

sPtr1 use count = 0
sPtr2 use count = 3
sPtr3 use count = 3
sPtr4 use count = 3

sPtr1 use count = 0
sPtr2 use count = 0
sPtr3 use count = 3
sPtr4 use count = 3
sPtr5 use count = 3
**/

解釋:
sPtr2sPtr3都是通過對sPtr1執行了複製構造函數,因此sPtr1的引用計數一次增加 1;sPtr2sPtr3同理。
通過move(sPtr1),使得sPtr1釋放了被管理對象的所有權,此時sPtr1被設置為nullptr,因此sPtr1引用計數為 0;sPtr2同理。

通過 std::make_shared

template< class T, class... Args >
shared_ptr<T> make_shared( Args&&... args );
  • T:非數組,指針指向的數據類型
  • Args&&... argsT 的構造函數參數列表

這種初始化效率更高,在 C++17 之前的編譯器更安全

// ...
int main()
{
    shared_ptr<int> sPtr1 = make_shared<int>(100);
    shared_ptr<int> sPtr2(make_shared<int>(200));
    shared_ptr<int> sPtr3{make_shared<int>(300)};
    return 0;
}

通過 .reset( ptr )

使用.reset( ptr ),使得釋放對shared_ptr原管理對象的所有權,轉為對新對象管理的所有權。

void reset() noexcept;

template< class Y >
void reset( Y* ptr );

template< class Y, class Deleter >
void reset( Y* ptr, Deleter d );

template< class Y, class Deleter, class Alloc >
void reset( Y* ptr, Deleter d, Alloc alloc );

註意.reset( ptr )若傳入的所指向的對象已被占有,程式會異常。

// ...
int main()
{
    shared_ptr<int> sPtr1(new int(100));
    shared_ptr<int> sPtr2 = sPtr1;
    shared_ptr<int> sPtr3 = sPtr2;
    shared_ptr<int> sPtr4 = sPtr1;
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
    
    sPtr4.reset();
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count() << endl << endl;
    
    sPtr3.reset(new int(100));
    cout << "sPtr1 use count = " << sPtr1.use_count() << endl;
    cout << "sPtr2 use count = " << sPtr2.use_count() << endl;
    cout << "sPtr3 use count = " << sPtr3.use_count() << endl;
    cout << "sPtr4 use count = " << sPtr4.use_count();
    
    std::shared_ptr<int> sPtr5;
    sPtr5.reset(sPtr1.get()); // 異常
    return 0;
}
/** 輸出:
sPtr1 use count = 4
sPtr2 use count = 4
sPtr3 use count = 4
sPtr4 use count = 4

sPtr1 use count = 3
sPtr2 use count = 3
sPtr3 use count = 3
sPtr4 use count = 0

sPtr1 use count = 2
sPtr2 use count = 2
sPtr3 use count = 1
sPtr4 use count = 0
**/

解釋:

  1. 通過複製構造函數後,sPtr1-4的引用計數均為 4。
  2. sPtr4使用了reset()後,釋放被管理對象的所有權,被管理對象的shared_ptr個數減 1,sPtr4被設置為nullptr,同時,sPtr1-3的引用計數均變為 3。
  3. sPtr3使用了reset(new int(100))後,釋放原被管理對象的所有權,被管理對象的shared_ptr個數減 1,sPtr4被設置為新的管理對象int(100),同時,sPtr1-3的引用計數均變為 2。
  4. sPtr5reset傳入的所指向的對象已被占有,程式異常,沒有正常結束。

.reset() 釋放被管理對象的所有權

用於取消shared_ptr對管理對象的所有權;
當這個對象被shared_ptr管理的數量為 0,會執行刪除器。

使用.reset(),會使得shared_ptr設置為nullptr

獲取原始儲存的指針

T* get() const noexcept;

解引用存儲的指針 operator* and operator->

可以像普通指針一樣,使用shared_ptr對所管理的對象進行訪問。

// ...
int main()
{
    shared_ptr<int> sPtr1 {new int(100)};
    cout << *sPtr1 << endl << endl;
    
    int *i = sPtr1.get();
    cout << *i << endl;
    cout << *sPtr1 << endl << endl;
    
    *i = 200;
    cout << *i << endl;
    cout << *sPtr1 << endl << endl;
    
    *sPtr1 = 300;
    cout << *i << endl;
    cout << *sPtr1;
    return 0;
}
/** 輸出:
100

100
100

200
200

300
300
**/

直觀展示自動管理記憶體

// ...
int main()
{
    shared_ptr<Person> sPtr1 {make_shared<Person>()};
    shared_ptr<Person> sPtr2 {make_shared<Person>(18)};
    shared_ptr<Person> sPtr3 {make_shared<Person>(22)};
    
    shared_ptr<Person> sPtr4 = sPtr1;
    sPtr1.reset();
    sPtr2.reset();
    
    sPtr3.reset(new Person(19));
    
    cout << endl << "Before return" << endl;
    return 0;
}
/** 輸出:
Constructor: person's age = 0
Constructor: person's age = 18
Constructor: person's age = 22
Destructor: person's age = 18
Constructor: person's age = 19
Destructor: person's age = 22

Before return
Destructor: person's age = 0
Destructor: person's age = 19

**/

解釋:

  1. 定義了三個shared_ptrsPtr1sPtr2sPtr3,它們管理的對象分別為age = 0age = 18age = 22,列印了三行Person對象的構造函數中的輸出
  2. 通過賦值運算符,使得sPtr4同時管理sPtr1管理的對象
  3. 釋放sPtr1被管理對象的所有權,此時因為sPtr4還在管理原sPtr1管理的對象age = 0,因此age = 0對象並沒有被析構
  4. 釋放sPtr2被管理對象的所有權,此時因為age = 18對象沒有任何shared_ptr進行管理,age = 18對象被析構,列印了age = 18對象的析構函數中的輸出
  5. 釋放sPtr3被管理對象的所有權,sPtr3轉為管理age = 19的對象;是先構造age = 19對象,後再釋放sPtr3被管理對象的所有權,列印了age = 19對象的構造函數中的輸出;此時因為sPtr3原管理的age = 22對象沒有任何shared_ptr進行管理,age = 22對象被析構,列印了age = 22對象的析構函數中的輸出
  6. 在程式返回前Before return,管理age = 0age = 19對象的智能指針sPtr4sPtr3被析構,age = 0age = 19對象沒有智能指針管理,age = 0age = 19對象被析構,列印了它們對象的析構函數中的輸出。

別名

template< class Y >
shared_ptr( const shared_ptr<Y>& r, element_type* ptr ) noexcept;

別名用於訪問類的成員變數。

我們需要訪問的是某個實例的成員,因此並不希望在使用這個成員的時候,對應的實例被銷毀了。

使用別名的shared_ptr,增加了對這個實例的控制權,但仍然訪問的是成員。

// ...
struct Person{ int age = 18; };
struct Student{ Person person; };

int main(){
    shared_ptr<Student> studentPtr{make_shared<Student>()};
    cout << "studentPtr use count = " << studentPtr.use_count() << endl;
    shared_ptr<Person> personPtr{studentPtr, &(studentPtr->person)};
    cout << "studentPtr use count = " << studentPtr.use_count() << endl;
    cout << personPtr->age << endl;
    return 0;
}
/** 輸出:
studentPtr use count = 1
studentPtr use count = 2
18

**/

shared_ptr 與函數

按值傳遞

int main(){
	auto func = [](shared_ptr<int> sPtr){
        cout << "value = " << *sPtr << endl;
        cout << "enter func: use count = " << sPtr.use_count() << endl;
    };
    
    auto sPtr = make_shared<int>(100);
    cout << "init: use count = " << sPtr.use_count() << endl;
    func(sPtr);
    cout << "exit func: use count = " << sPtr.use_count() << endl;
    return 0;
}
/** 輸出:
init: use count = 1
value = 100
enter func: use count = 2
exit func: use count = 1

**/

按值傳遞,函數會複製一份參數,因此傳入的sPtr會被覆制一份,造成其對象的引用計數增加 1;
執行完這個函數,複製的那一份sPtr被銷毀,使得其對象的引用計數減少 1;

按引用傳遞

int main(){
	auto func = [](shared_ptr<int> &sPtr){
        cout << "value = " << *sPtr << endl;
        cout << "enter func: use count = " << sPtr.use_count() << endl;
    };
    
    auto sPtr = make_shared<int>(100);
    cout << "init: use count = " << sPtr.use_count() << endl;
    func(sPtr);
    cout << "exit func: use count = " << sPtr.use_count() << endl;
    return 0;
}
/** 輸出:
init: use count = 1
value = 100
enter func: use count = 1
exit func: use count = 1

**/

按引用傳遞,函數不會複製一份參數;
因此若函數內部無其它導致增加引用計數的操作,函數執行過程中引用計數都不會改變。

按引用傳遞,但為const

int main(){
    auto func = [](const shared_ptr<int> &sPtr){
        cout << "value = " << *sPtr << endl;
        cout << "enter func: use count = " << sPtr.use_count() << endl;
        sPtr.reset(); // error
        sPtr.reset(new Person()); // error
        sPtr.release(); // error
    };
    
    auto sPtr = make_shared<int>(100);
    cout << "init: use count = " << sPtr.use_count() << endl;
    func(sPtr);
    cout << "exit func: use count = " << sPtr.use_count() << endl;
    return 0;
}

使用const的引用傳遞,不能改變shared_ptr所管理的對象是哪一個,使用.reset()等都會造成編譯錯誤。

返回值為shared_ptr

int main(){
	auto createSPtr = [](int i) -> shared_ptr<Person>{
        shared_ptr<Person> sPtr = make_shared<Person>(i);
        cout << "age = " << i << " use count = " << sPtr.use_count() << endl;
        return sPtr;
    };
    shared_ptr<Person> sPtr = createSPtr(100);
    sPtr->getAge(); cout << "use count = " << sPtr.use_count() << endl;

    // 用作鏈式函數
    createSPtr(200)->getAge();
    
    cout << endl << "Before main return" << endl;
    return 0;
}
/** 輸出:
Constructor: person's age = 100
age = 100 use count = 1
Person's age = 100
use count = 1
Constructor: person's age = 200
age = 200 use count = 1
Person's age = 200
Destructor: person's age = 200

Before main return
Destructor: person's age = 100

**/

可見當用作鏈式函數時,使用完畢後,unique_ptr會被銷毀,同時被管理的對象也被析構。

管理動態數組需要指定刪除器

shared_ptr 的預設刪除器不支持釋放數組對象,需要指定刪除器。

例,一維數組指定刪除器

    // ...
    shared_ptr<int> ptr(new int[10], [](int* p) {delete[] p; });

同時,也可以使用 std::default_delete<T>() 函數作為刪除器,這個函數內部的刪除功能是通過delete釋放,T 為釋放什麼類型的記憶體的類型。

例,一維數組指定刪除器

    // ...
    shared_ptr<int> ptr(new int[10], default_delete<int[]>());

可以自己封裝模板函數來使 shared_ptr 支持釋放數組對象。

// ...
template <typename T>
shared_ptr<T> arrayShared_ptr(size_t size)
{
    // 返回匿名對象
    return shared_ptr<T>(new T[size], default_delete<T[]>());
}

int main()
{
    shared_ptr<int> sPtr1 = arrayShared_ptr<int>(100);
    shared_ptr<int> sPtr2 = arrayShared_ptr<char>(200);
    return 0;
}

危險行為

仍然可使用delete釋放智能指針管理的對象的地址

可以使用delete釋放shared_ptr管理的對象的地址,但是其它共用指針仍然可能會訪問這塊地址,若訪問了則會出現程式異常,因此應避免使用手動的delete

    // ...
    shared_ptr<int> sPtr1 {new int(100)};
    shared_ptr<int> sPtr2 = sPtr1;
    delete sPtr1.get();
    cout << *sPtr2 << endl; // error,運行時異常

同時存在原始指針和智能指針

如果一個地址記憶體,同時有原始指針和shared_ptr指向它,即使當所有shared_ptr都被銷毀,原始指針還依然存在的情況下,這個地址記憶體仍然會被釋放,若再用原始指針去訪問這個記憶體地址就是訪問了一塊未知地址的內容。

    // ...
	int *i1 = new int{100};
    shared_ptr<int> sPtr1{i1};
    int *i2 = sPtr1.get();
    cout << *i1 << endl;
    cout << *i2 << endl << endl;
    sPtr1.reset();
    cout << *i1 << endl; // 危險行為 
    cout << *i2 << endl; // 危險行為 
	return 0;
/** 輸出:
100
100

1664688 // 這是隨機的,訪問了一塊未知地址的內容
1664688 // 這是隨機的,訪問了一塊未知地址的內容

**/

同時,無論是使用shared_ptr,還是其它的智能指針,都應該避免與原始指針混用。

避免同時存在原始指針和智能指針的解決方案:

    // ...
    int *i = new int{100};
    shared_ptr<int> sPtr1{i};
    i = nullptr;
    delete i;
    cout << *sPtr1 << endl; // ok

使用一個原始指針初始化多個shared_ptr

不能使用一個原始指針初始化多個shared_ptr

    // ...
    int *i = new int{100};
    shared_ptr<int> sPtr1{i};
    shared_ptr<int> sPtr2{i}; // error 編譯通過 運行錯誤

最後

shared_ptr 由於使用引用計數,因此會造成額外的記憶體和性能開銷,因此在性能要求極為苛刻的情況下不適用。

unique_ptr是 0 開銷的智能指針,也能夠自動管理記憶體,但不會造成性能損失。


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

-Advertisement-
Play Games
更多相關文章
  • 抽獎程式 ''' 抽獎程式 使用時可以修改嘉賓名單,然後單機‘開始’和‘停止’按鈕 來控制界面上名單的滾動實現抽獎功能,涉及的模塊主要 有多線程 ''' import itertools import random import threading import time import tkinte ...
  • 摘要:本文主要講解圖像局部直方圖均衡化和自動色彩均衡化處理。這些演算法可以廣泛應用於圖像增強、圖像去噪、圖像去霧等領域。 本文分享自華為雲社區《[Python從零到壹] 五十四.圖像增強及運算篇之局部直方圖均衡化和自動色彩均衡化處理》,作者: eastmount。 一.局部直方圖均衡化 前文通過調用O ...
  • 簡介 限流顧名思義是對流量大小進行限制,防止請求數量超過系統的負載能力,導致系統崩潰,起到保護作用。 現實生活中限流也隨處可見,節假日出門旅行的人數會劇增,對於旅游景點來說往往會不堪重負,如果不進行人數控制,對整個景點的壓力會非常大,游客的體驗也會非常差,還容易出現安全事故等危險。 同樣的在一線城市 ...
  • 轉載:https://blog.csdn.net/tslx1020/article/details/128250777 1、spawn - 冷啟動 frida-trace -U -f com.apple.ExampleCode -m “+[NSURL URLWithString:]" 2、attac ...
  • 伺服器信息 在阿裡雲買了個搶占式的伺服器,地區為華南廣州,系統為Ubuntu 20.04,8核16GB。 安裝Docker 命令如下: $ apt-get update -y $ apt-get upgrade -y $ apt-get install -y docker.io 安裝成功後,檢查一下 ...
  • 2023-01-14 一、Spring底層IOC實現 1、IOC:將對象的控制器反轉給Spring 2、BeanFactory與ApplicationContext (1)BeanFactory:IOC容器的基本實現,是Spring內部的使用介面,是面向Spring本身的,不是提供給開發人員使用的。 ...
  • 牛牛剛剛出生,嗷嗷待哺,一開始他只能學說簡單的數字,你跟他說一個整數,他立刻就能學會。輸入一個整數,輸出這個整數。 ...
  • 本篇文章,我們就一起聊一聊如何來更好的使用緩存,探尋下如何降低緩存交互過程的性能損耗、如何壓縮緩存的存儲空間占用、如何保證多個操作命令原子性等問題的解決策略,讓緩存在項目中可以發揮出更佳的效果。 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...