C++ 三种智能指针及其设计实现unique_ptr、 share_ptr 指针

news2024/11/23 0:41:33

0、差不多春节啦。。。。。 好久没有写博客,写一写吧。。。。。。 祝大家嗨皮,提前恭喜发财

1、三种智能指针的使用方法

C++ 有3种指针:share_ptr, unique_ptr, weak_ptr

1.1)unique_ptr 指针 

std::unique_ptr 是一种独占的智能指针,它禁止其他智能指针与其共享同一个对象,从而保证代码的安全。它无法复制到其他 unique_ptr,无法通过值传递到函数,也无法用于需要副本的任何标准模板库 (STL)算法。【拷贝构造函数、赋值重载运算符是private or deleted的

std::unique_ptr<int> pointer = std::make_unique<int>(10); // make_unique 从 C++14 引入
std::unique_ptr<int> pointer2 = pointer; // 非法

 创建unique_ptr 指针的3种方式:

 
// 1.使用reset方法
    unique_ptr<SmartPtrClass> smart1;
    smart1.reset(new SmartPtrClass("smart1"));
 
// 2. 使用make_unique 方法,C++14支持。括号里的是参数
    unique_ptr<SmartPtrClass> smart3 = make_unique<SmartPtrClass>("smart3");
 
// 3. () 方法
    unique_ptr<SmartPtrClass> smart2(new SmartPtrClass("smart2"));

unique_ptr 不能赋值或者调用复制构造函数,但是可以调用移动构造函数,即对资源管理权限可以实现转移。这意味着,内存资源所有权可以转移到另一个 unique_ptr,并且原始 unique_ptr 不再拥有此资源。【下列ptrA 指针将变为空指针

在这里插入图片描述

 

class SmartPtrClass{
private:
    string className;
public:
    SmartPtrClass(string m_name):className(m_name){
        cout << className << " constructor" << endl;
    }
    ~SmartPtrClass(){
        cout << className << " destructor" << endl;
    }
 
    void show(){
        cout << "this is object: " << className << " show" << endl; 
    }
};
 
 
int main(){
 
    unique_ptr<SmartPtrClass> smart2(new SmartPtrClass("smart2"));
    (*smart2).show();
 
    // 方法3创建对象,括号的是参数
    unique_ptr<SmartPtrClass> smart3 = make_unique<SmartPtrClass>("smart3");
    smart3->show();
 
    // smart2释放所有权给 tmp,但是 smart2不会调用析构函数,只能通过 delete tmp1
    SmartPtrClass* tmp1 = smart2.release();
 
    // 可以通过move 函数将 smart2 转移给 tmp,此时可以释放析构函数
    unique_ptr<SmartPtrClass> tmp2(std::move(smart3));
    // 无法再调用 smart2,会运行错误;smart2 已经是空指针了,会打印下列的log
    if (!smart2){
        cout << "smart2 pointer is null =======" << endl;
    }
    if (!smart3){
        cout << "smart3 pointer is null ========" << endl;
    }
 
    cout << "===========testSmartPtr===========" << endl;
 
}
 
// 输出结果:
smart2 constructor
this is object: smart2 show
smart3 constructor
this is object: smart3 show
smart2 pointer is null =======
smart3 pointer is null ========
===========testSmartPtr===========
smart3 destructor

由上可知:

通过 release 方法释放所有权、通过移动语义std::move转移所有权的区别:

1. release 方法不能调用析构函数【需要通过delete】;move可以调用析构函数

相同点:

源指针变为空指针

auto_ptr 指针被废弃的原因

auto_ptr< string> ps (new string ("I reigned lonely as a cloud.”);
auto_ptr<string> vocation; 
// 主要是因为下列赋值语句可以成功,拷贝构造函数不是private 的
vocaticn = ps;

上述赋值语句将完成什么工作呢?如果 ps 和 vocation 是常规指针,则两个指针将指向同一个 string 对象。这是不能接受的,因为程序将试图删除同一个对象两次,一次是 ps 过期时,另一次是 vocation 过期时。

要避免这种问题,方法有多种:

  1. 定义陚值运算符,使之执行深复制。这样两个指针将指向不同的对象,其中的一个对象是另一个对象的副本,缺点是浪费空间,所以智能指针都未采用此方案。
  2. 建立所有权(ownership)概念。对于特定的对象,只能有一个智能指针可拥有,这样只有拥有对象的智能指针的析构函数会删除该对象。然后让赋值操作转让所有权。这就是用于 auto_ptr 和 unique_ptr 的策略,但 unique_ptr 的策略更严格。【转让了所有权,则源指针是为空指针的】
  3. 创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数。例如,赋值时,计数将加 1,而指针过期时,计数将减 1,。当减为 0 时才调用 delete。这是 shared_ptr 采用的策略。

unique_ptr 比 auto_ptr 更加安全,因为 auto_ptr 有拷贝语义,拷贝后原对象变得无效,再次访问原对象时会导致程序崩溃;unique_ptr 则禁止了拷贝语义,但提供了移动语义,即可以使用 std::move() 进行控制权限的转移,如下代码所示: 

unique_ptr<string> upt(new string("lvlv"));
unique_ptr<string> upt1(upt);	//编译出错,已禁止拷贝
unique_ptr<string> upt1=upt;	//编译出错,已禁止拷贝
unique_ptr<string> upt1=std::move(upt);  //控制权限转移, upt1 为空指针
 
auto_ptr<string> apt(new string("lvlv"));
auto_ptr<string> apt1(apt);	//编译通过
auto_ptr<string> apt1=apt;	//编译通过

1.2)shared_ptr 指针 

shared_ptr 是一个标准的共享所有权的智能指针,允许多个指针指向同一个对象,定义在 memory 文件中,命名空间为 std。shared_ptr最初实现于Boost库中,后由 C++11 引入到 C++ STL。shared_ptr 利用引用计数的方式实现了对所管理的对象的所有权的分享,即允许多个 shared_ptr 共同管理同一个对象。

shared_ptr 是为了解决 auto_ptr 在对象所有权上的局限性(auto_ptr 是独占的),在使用引用计数的机制上提供了可以共享所有权的智能指针,当然这需要额外的开销:

(1)shared_ptr 对象除了包括一个所拥有对象的指针外,还必须包括一个引用计数代理对象的指针;
(2)时间上的开销主要在初始化和拷贝操作上, * 和 -> 操作符重载的开销跟 auto_ptr 是一样;
(3)开销并不是我们不使用 shared_ptr 的理由,,永远不要进行不成熟的优化,直到性能分析器告诉你这一点。

3种创建 shared_ptr 指针的方法:

// 1. () 括号形式
    shared_ptr<SmartPtrClass> shSmart1(new SmartPtrClass("shSmart1"));
 
// 2. make_shared形式
    shared_ptr<SmartPtrClass> shSmart2 = make_shared<SmartPtrClass>("shSmart2");
 
// 3. reset 方法
    shared_ptr<SmartPtrClass> shSmart3;
    shSmart3.reset(new SmartPtrClass("shSmart3"));
    shSmart3->show();

通过 get() 方法来获取原始指针,通过 reset() 来减少一个引用计数, 并通过use_count()来查看一个对象的引用计数

// 可以直接使用赋值运算
    shared_ptr<SmartPtrClass> shTmp1 = shSmart1;
    auto shTmp2 = shSmart1;
    cout << "shSmart1 ref cout: " << shSmart1.use_count() << endl;
    shTmp1.reset();
    cout << "shSmart1 ref cout afther reset: " << shSmart1.use_count() << endl;
    auto orig = shTmp2.get();
 
    cout << "===========testSmartPtr===========" << endl;
 
//输出log:
shSmart1 ref cout: 3
shSmart1 ref cout afther reset: 2
===========testSmartPtr===========

1.3)weak_ptr  指针

std::weak_ptr是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用)。弱引用不会引起引用计数增加。weak_ptr 为解决循环引用的问题,如下代码:

struct A;
struct B;
 
struct A {
    std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};
struct B {
    std::shared_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};
int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;
}

运行结果是 A, B 都不会被销毁,这是因为 a,b 内部的 pointer 同时又引用了 a,b,这使得 a,b 的引用计数均变为了 2,而离开作用域时,a,b 智能指针被析构,却只能造成这块区域的引用计数减一,这样就导致了 a,b 对象指向的内存区域引用计数不为零,而外部已经没有办法找到这块区域了,也就造成了内存泄露,

 

解决这个问题的办法就是使用弱引用指针 std::weak_ptrstd::weak_ptr是一种弱引用(相比较而言 std::shared_ptr 就是一种强引用)。弱引用不会引起引用计数增加,当换用弱引用时候,最终的释放流程如图 5.2 所示:

struct A;
struct B;
 
struct A {
    std::shared_ptr<B> pointer;
    ~A() {
        std::cout << "A 被销毁" << std::endl;
    }
};
struct B {
// 使用 weak 指针
    std::weak_ptr<A> pointer;
    ~B() {
        std::cout << "B 被销毁" << std::endl;
    }
};
int main() {
    auto a = std::make_shared<A>();
    auto b = std::make_shared<B>();
    a->pointer = b;
    b->pointer = a;

// weak_ptr管理的资源不能被直接访问,需要通过lock函数从其中获取shared_ptr才可以被访问。             
    std::shared_ptr<A> tempA = b->pointer.lock();
}

1.4)智能指针使用的场景

(1)如果程序要使用多个指向同一个对象的指针,应选择shared_ptr。这样的情况包括:

  1. 有一个指针数组,并使用一些辅助指针来标示特定的元素,如最大的元素和最小的元素;
  2. 两个对象包含都指向第三个对象的指针;
  3. STL容器包含指针。很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,但不能用于unique_ptr(编译器发出warning)和auto_ptr(行为不确定)。如果你的编译器没有提供shared_ptr,可使用Boost库提供的shared_ptr。

(2)如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr。

如果函数使用new分配内存,并返还指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择。这样,所有权转让给接受返回值的unique_ptr,而该智能指针将负责调用delete。可将unique_ptr存储到STL容器在那个,只要不调用将一个unique_ptr复制或赋给另一个算法(如sort())。
 

2. 实现 unique_ptr 智能指针

智能指针的实现原理:

利用在栈上创建的类,其会自动调用析构函数。创建一个包装类,包装对应的指针,重载运算符:* 与 -> 让其对象像是在指针上操作。该类在栈上创建,当对象不再使用时,自动析构,在析构函数中delete 对应包装的指针

实现需要注意的点:

1. 构造函数的隐式变换

2. 编写模板类

3. 禁止拷贝构造函数和 赋值构造函数

4.  移动构造、移动赋值重载运算符 

5. 判断对象是否存在 ,重载 operator bool (),explicit关键字不能少

6. 如果未标记noexcept,标准库将产生一个额外的操作,增加了开销。

7. 使用 std::swap 提高运行速度

// 实现一个 unique_ptr 智能指针, T 是传入的对象
template <typename T>
class UniquePtr{
 
private:
    T* ptr = nullptr;
 
public:
    // 拷贝构造函数和 赋值构造函数是不能使用的
    UniquePtr(const UniquePtr& copy) noexcept = delete;
    // 赋值构造函数 不能使用
    UniquePtr& operator = (const UniquePtr& copy) noexcept = delete;
 
    // 交换 2 个指针
    void swap(UniquePtr<T>& src){
        std::swap(ptr, src.ptr);
    }
 
    // 移动所有权是可以使用的,移动构造函数和移动的赋值重载运算符是可以使用的
    UniquePtr(UniquePtr&& uptr) noexcept {
        cout << "UniquePtr move construtor" << endl;
        // 或者直接使用下列swap函数
        // uptr.swap(*this);
        ptr = uptr.ptr;
        delete uptr.ptr;
        uptr.ptr = nullptr;
    }    
 
    // 移动构造函数和移动的赋值重载运算符
    UniquePtr& operator = (const UniquePtr&& uptr) noexcept{
        cout << "UniquePtr move UniquePtr& operator = " << endl;
        // 或者直接使用下列swap函数
        // uptr.swap(*this);
        if (*this != uptr){
            if (ptr)
                delete ptr;
            ptr = uptr.ptr;
            delete uptr.ptr;
            uptr.ptr = nullptr;
        }
        return *this;
    }
 
    // 析构函数删除传入的指针
    ~UniquePtr(){
        cout << "UniquePtr destructor" << endl;
        delete ptr;
    }
 
    // 构造函数, 需要使用隐式
    explicit UniquePtr(T* _ptr){
        cout << "UniquePtr costructor" << endl;
        ptr = std::move(_ptr);
    }
 
    UniquePtr():ptr(nullptr){
        cout << "UniquePtr costructor. params nullptr" << endl;
    }
 
    // 用于判断对象是否存在,explicit关键字不能少
    explicit operator bool () noexcept{
        cout << "UniquePtr operator bool" << endl;
        return this->ptr;
    }
 
    // ========== release, get, reset 方法 ==========
    T* release() noexcept{
        cout << "UniquePtr release" << endl;
        // 需要先缓存前面的 指针
        T* tmp = ptr;
        delete ptr;
        ptr = nullptr;
        return tmp;
    }
 
    T* get() noexcept{
        return ptr;
    }
 
    void reset (T* srcPtr) noexcept{
        delete ptr;
        ptr = nullptr;
        std::swap(ptr, srcPtr);
    }
 
    // ========== 重载 * 和 -> ==========
    T* operator -> () noexcept{
        return this->ptr;
    }
 
    T& operator * () noexcept{
        return *this->ptr;
    }
 
};
 
class TestClass{
 
private:
    int value = 3;
 
public:
    TestClass(){
        cout << "TestClass costructor" << endl;
    }
 
    ~TestClass(){
        cout << "TestClass destructor" << endl;
    }
 
    void show(){
        cout << "TestClass show======" << endl;
    }
 
    void showValue(){
        cout << "TestClass showValue======" << value << endl;
    }
 
};
 
// 实现一个 shared_ptr 智能指针
 
 
void testSmartPointer(){
    cout << "=========testSmartPointer=========" << endl;
    UniquePtr<TestClass> tc(new TestClass);
    // UniquePtr<TestClass> tc2(tc);
    // UniquePtr<TestClass> tc2 = tc;
    tc->show();
    (*tc).show();
    UniquePtr<TestClass> tc1(std::move(tc));
    tc1->show();
    // tc 还是有所有权
    tc->show();
    // 如果需要判断,则必须重载 operator bool
    if (tc){
        cout << "tc is not NULL" << endl;
    }
    // value 资源不可用,程序崩溃
    // tc->showValue();
 
    UniquePtr<TestClass> tc3;
    tc3.reset(new TestClass);
    tc3->show();
 
    cout << "=========testSmartPointer=========" << endl;
}

3. 实现 share_ptr 智能指针

首先先明确的一点:为什么赋值运算符重载需要先减去 1

1. 对于对SharedPtr 的重新赋值,需要调用指针的析构函数

2.  指针的拷贝形式是:浅拷贝形式,两个count 值是相同的

    // 重载赋值 = 运算符,需要ref count 减去1
    SharedPtr<PointerType>& operator = (const SharedPtr<PointerType>& src) noexcept{
        cout << "SharedPtr  operator = " << endl;
        if (this == &src){
            return *this;
        }
        if (counter->reduceRef() == 0){
            delete ptr;
            delete counter;
            cout << "重新赋值,删除原始的指针========= " << endl;
        }
        ptr = src.ptr;
        // 浅拷贝形式,两个count 值是相同的
        counter = src.counter;
        // 增加一个计数,
        counter->addRef();
        return *this;
    }
 
 
    // why: 为什么重载 operator =, 使用if (counter->reduceRef() == 0)
    SharedPtr<TestClass> s5(new TestClass);
    cout << "before s5 ref cout: " << s5.use_count() << endl;
    SharedPtr<TestClass> s6(new TestClass);
    s5 = s6;
    // 浅拷贝形式,两个count 值是相同的
    cout << "afther s5 ref cout: " << s5.use_count() << endl;
    cout << "afther s6 ref cout: " << s6.use_count() << endl;
 
// ================= shared_ptr 类库 =================
    shared_ptr<TestClass> ptr1(new TestClass);
    shared_ptr<TestClass> ptr2(new TestClass);
    cout << "before shared_ptr ref cout: " << ptr1.use_count() << endl;
    // 会先调用下 TestClass 的析构函数
    ptr1 = ptr2;
    cout << "afther shared_ptr ref cout: " << ptr1.use_count() << endl;
    cout << "afther shared_ptr ref cout: " << ptr2.use_count() << endl;
 
// =======输出:
TestClass costructor
SharedPtr costructor
before s5 ref cout: 1
TestClass costructor
SharedPtr costructor
SharedPtr  operator = 
TestClass destructor
重新赋值,删除原始的指针========= 
afther s5 ref cout: 2
afther s6 ref cout: 2
 
------
TestClass costructor
TestClass costructor
before shared_ptr ref cout: 1
TestClass destructor
afther shared_ptr ref cout: 2
afther shared_ptr ref cout: 2
 
 

共享 shared 智能指针需要注意的几个点:

1. = 赋值重载运算符需要先减去 1 个计算

2. 在拷贝构造函数 和 赋值运算符去 增加 1 个计数引用

3. 在析构函数 和 赋值重载运算符去 减去 1 个计数引用

4. 如 unique 指针,在构造函数 增加隐式声明,noexcept。

class RefCounter{
 
private:
    long count;
 
public:
    RefCounter(long _count){
        count = _count;
    }
 
    long reduceRef(){
// c++标准库中的操作是一个原子操作,且要保证线程安全(如加锁操作)
        return --count;
    }
 
    void addRef(){
        ++count;
    }
 
    long getCount(){
        return count;
    }
 
};
 
// 实现一个 shared_ptr 智能指针
template <typename PointerType>
class SharedPtr{
 
private:
    PointerType* ptr;
    RefCounter* counter;
 
public:
    explicit SharedPtr(PointerType* _ptr = nullptr){
        cout << "SharedPtr costructor" << endl;
        ptr = _ptr;
        if (ptr){
            counter = new RefCounter(1);
        }else{
            counter = new RefCounter(0);
        }
    }
 
    ~SharedPtr(){
        if (counter->reduceRef() == 0){
            cout << "SharedPtr destructor" << endl;
            delete ptr;
            delete counter;
        }else{
            cout << "SharedPtr not destructor" << endl;
        }
    }
 
    // 重载赋值 = 运算符,需要ref count 减去1
    SharedPtr<PointerType>& operator = (const SharedPtr<PointerType>& src) noexcept{
        cout << "SharedPtr  operator = " << endl;
        if (this == &src){
            return *this;
        }
        if (counter->reduceRef() == 0){
            delete ptr;
            delete counter;
        }
        ptr = src.ptr;
        counter = src.counter;
        // 增加一个计数
        counter->addRef();
        return *this;
    }
 
    // 拷贝构造函数
    SharedPtr(const SharedPtr<PointerType>& copy){
        cout << "SharedPtr copy constructor " << endl;
        if (this != &copy){
            ptr = copy.ptr;
            counter = copy.counter;
            counter->addRef();
        }
    }
 
    // get函数
    PointerType* get() noexcept{
        return ptr;
    }
 
    // release 函数
    void release(){
        counter->reduceRef();
    }
 
    // use_count 函数
    long use_count() noexcept{
        return counter->getCount();
    }
 
    // 转换符 bool
    explicit operator bool () noexcept {
        return ptr;
    }
 
    // 重载 * 和 -> 预算符
    PointerType* operator -> () noexcept{
        return ptr;
    }
 
    PointerType operator * () noexcept{
        return *ptr;
    }
 
 
};

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Sklearn标准化和归一化方法汇总(1):标准化 / 标准差归一化 / Z-Score归一化

Sklearn中与特征缩放有关的五个函数和类&#xff0c;全部位于sklearn.preprocessing包内。作为一个系列文章&#xff0c;我们将逐一讲解Sklearn中提供的标准化和归一化方法&#xff0c;以下是本系列已发布的文章列表&#xff1a; Sklearn标准化和归一化方法汇总(1)&#xff1a…

ESP32 (WIFI)-AP、STA模式(13)

提示&#xff1a;本博客作为学习笔记&#xff0c;有错误的地方希望指正 文章目录一、ESP32 WIFI模式概述二、ESP32 WIFI-AP初始化流程三、WIFI-AP示例四、ESP32 WIFI-STA初始化流程五、WIFI-STA示例一、ESP32 WIFI模式概述 参考资料&#xff1a;ESP IDF编程手册V4.4   WIFI主…

【GD32F427开发板试用】Systick系统定时器的使用

本篇文章来自极术社区与兆易创新组织的GD32F427开发板评测活动&#xff0c;更多开发板试用活动请关注极术社区网站。作者&#xff1a;HonestQiao 基于Arm Cortex-M系列内核的MCU&#xff0c;都包含了SysTick定时器。 所谓SysTick即为系统定时器&#xff0c;又称嘀嗒定时器&am…

Docker Swarm

Swarm 是什么&#xff1f; Docker Swarm 是Docker官方的跨节点的容器编排工具。用户只需要在单一的管理节点上操作&#xff0c;即可管理集群下的所有节点和容器。 主要解决什么问题 1. 解决docker server的集群化管理和部署。 2. Swarm通过对Docker宿主机上添加的标签信息来…

分享60个PHP源码,总有一款适合您

PHP源码 分享60个PHP源码&#xff0c;总有一款适合您 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c; 60个PHP源码下载链接&#xff1a;https://pan.baidu.com/s/1SvjbzolwuMrQyhVb_byG5Q?pwdx831 提取码&#xff…

生物素点击标记试剂:DBCO-SS-PEG3-biotin,1430408-09-5,生物素PEG3二硫键DBCO

1、理论分析&#xff1a;中文名&#xff1a;生物素-三聚乙二醇-二硫-二苯并环辛炔&#xff0c;生物素-PEG3-二硫-DBCO &#xff0c;生物素PEG3-二硫二苯并环辛炔英文名&#xff1a;DBCO-S-S-PEG3-biotin&#xff0c;Biotin-PEG3-SS-DBCOCAS号&#xff1a;1430408-09-5化学式&am…

如何使用ArcGIS进行点抽稀

01 概述对于制图工作者而言&#xff0c;遇到大量的点要素&#xff08;POI、村名等&#xff09;标注的时候往往非常的麻烦&#xff0c;因为这些点往往都是十分的密集&#xff0c;直接标注很影响制图的美观。如果直接去处理原始的数据&#xff0c;会导致后续的数据更新维护麻烦&a…

pdf合并在线,大家都在用的工具

工作和生活当中都有很多场景需要我们提交PDF文档&#xff0c;而且有时候要求仅能提交一份&#xff0c;如果这时候刚好你的文档分成了几份&#xff0c;就得先合并之后才能提交。要在线合并PDF并不麻烦&#xff0c;关键是用对工具。下面就来给大家介绍几款热门的软件&#xff0c;…

Spring Boot(五十五):基于redis防止接口恶意刷新和暴力请求

下面的教程&#xff0c;通过intercept和redis针对urlip在一定时间内访问的次数来将ip禁用&#xff0c;可以根据自己的需求进行相应的修改&#xff0c;来达到自己的目的 下面只讲解大致步骤&#xff0c;不详细讲解&#xff0c;需要完整代码的可以自行下载。 https://download.c…

数据结构之查找详解

一、什么是查找表&#xff1f; 1.1 定义 查找表是由同一类型的数据元素构成的集合。例如电话号码簿和字典都可以看作是一张查找表。 1.2 查找表的几种操作&#xff1a; 1&#xff09;在查找表中查找某个具体的数据元素&#xff1b; 2&#xff09;在查找表中插入数据元素&am…

win10环境使用nvm安装多版本nodejs并配置环境变量

win10环境使用nvm安装多版本nodejs并配置环境变量nvm安装环境变量配置测试安装全局模块对于旧版本的node&#xff0c;手动安装npm最近使用node工程&#xff0c;需要多版本&#xff0c;并且进行切换&#xff0c;来回安装卸载不同版本的node比较麻烦&#xff0c;后面自己就简单捯…

MySQL 5.5版本的两个执行引擎

目录执行引擎引入MySQL执行引擎生成的文件MyIsamInnoDB聚簇索引与非聚簇索引稀疏索引回表覆盖索引执行引擎引入 我们真正的索引结构要去落地的时候呢&#xff0c;也就是MySQL底层BTree数据结构要去落地的话&#xff0c;那么一定要和我们的存储引擎相结合。接下来我们会说MySQL…

【游戏逆向】老飞飞怀恋魅力爱玩等老飞飞瞬移分析代码

【游戏逆向】老飞飞怀恋魅力爱玩等老飞飞瞬移分析代码 在游戏中&#xff0c;每个人物都有一个坐标。x坐标和y坐标。老飞飞也一样&#xff0c;可能有些朋友用ce找到当前的人物坐标。然后修改坐标就能达到瞬移到效果。不过有些老飞飞是无法实现的。只要瞬移就会掉客户端。今天就…

3>2,看看U.3升级了啥

关注企业级NVMe SSD的小伙伴对U.2接口一定不会感到陌生。然而&#xff0c;在U.2之外&#xff0c;还存在一种名为“U.3”的硬盘接口&#xff0c;二者外观完全相同&#xff0c;接口性能也都一样&#xff0c;甚至不少客户直接将U.3的NVMe SSD部署在U.2服务器上使用。但既然3&#…

分布式应用解决方案之一致性Hash

什么是一致性Hash 一致性Hash就是将整个hash值空间按照顺时针方向形成一个虚拟的环&#xff0c;整个环状结构就称之为Hash环。那为什么叫做一致性Hash环&#xff1f;一致性是由于Hash环应用场景一般在分布式应用服务中&#xff0c;各个服务提供者分布在hash环中&#xff0c;当某…

【Qt】一文总结新工程的创建

文章目录一、导读二、浅谈开发方式&#xff08;2-1&#xff09;C开发方式&#xff08;2-2&#xff09;QtQuick qml开发方式&#xff08;2-3&#xff09;python开发方式三、新工程创建向导下的Library四、其他项目五、其他工程项目六、Import Project选项七、总结一、导读 在使…

Linux-Find命令

目录 Find 命令格式&#xff1a; 常用查找条件 案例展示&#xff1a; Find find 命令根据预设的条件递归查找文件或目录所在位置 命令格式&#xff1a; 命令格式&#xff1a;find 查找路径 查找条件1 查找条件2 .. [-exec 处理命令 {} \; ] –exec 可接额外的命令来处理查…

【Kubernetes 企业项目实战】03、基于 Alertmanager 发送报警到多个接收方(下)

目录 一、promethues 采集 tomcat 监控数据 1.1 制作 tomcat 镜像 1.2 基于上面的镜像创建一个 tomcat 实例 1.3 采集数据 二、promethues 采集 redis 监控数据 2.1 配置一个 Redis 的 exporter 2.2 查看 Prometheus 2.3 grafana 导入模板 三、Prometheus 监控 mysql …

【微服务】Nacos 前端设计

目录 一、背景 二、选型 React 1、Vue vs React vs Angular 1.1、npm trends 2、GitHub Stats 3、根据自身情况选型 4、现状 5、小结 6、React/Vue ⽣态 三、方案 &#x1f496;微服务实战 &#x1f496; Spring家族及微服务系列文章 一、背景 我们需要提供⼀个简单…

Xilinx关于Aurora IP核仿真和使用

平台&#xff1a;vivado2017.4芯片&#xff1a;xc7k325tfbg676-2 (active)关于Aurora的开发学习。使用xilinx官方提供的IP核。官方资料&#xff0c;pg046-aurora-8b10b.pdf和pg074-aurora-64b66b-en-us-12.0.pdf。IP核的生成步骤首先在IP Catalog中搜索Aurora IP核关于此IP有两…