RAII与智能指针

news2025/2/23 20:18:22

RAII与智能指针

  • 1.RAII
    • 1.1RAII理解
    • 1.2RAII的原理
      • 1.2.1简单的例子说明局部对象的自动销毁的特性
    • 1.2.2 RAII 过程
  • 2.智能指针
    • 2.1 auto_ptr
      • 2.1.1auto_ptr的使用
        • 构造函数与析构函数
        • 拷贝构造函数与赋值
        • 提领操作
        • auto ptr其它函数
      • 2.1.2autoptr使用的注意事项
    • 2.2 unique_ptr
      • 2.2.1unique_ptr的使用
        • unique_ptr的创建
        • unique_ptr不能进行拷贝构造和赋值操作
        • unique_ptr可以进行移动构造和移动赋值操作
        • unique_ptr虽然没有拷贝操作,但是可以从函数中返回unique_ptr
      • 2.2.2unique_ptr使用场景
    • 2.3 shared_ptr(共享指针)
      • 2.3.1 shared_ptr的使用
        • shared_ptr的创建
        • shared_ptr的访问
        • shared_ptr的拷贝和赋值操作
        • shared_ptr的引用计数
      • 2.3.2 shared_ptr的线程安全
      • 2.3.3 shared_ptr与unordered map使用
    • 2.4 weak_ptr
      • 2.4.1 weak_ptr理解
      • 2.4.2 为什么会有weak_ptr
      • 2.3.3 weak_ptr使用
        • 创建weak_ptr实例
        • 判断weak_ptr指向对象是否存在
        • weak_ptr使用
      • 2.5 几种智能指针的比较

1.RAII

1.1RAII理解

RAII(Resource Acquisition ls lnitialization) ,资源获取即初始化,是由 c++之父 Biarne Stroustrup 提出的。
使用局部对象管理资源的技术称为资源获取即初始化,这里的资源主要是指操作系统中有限的东西如内存、网络套接字,互斥量,文件句柄等等,局部对象是指存储在栈的对象,它的生命周期是由操作系统来管理的,无需人工介入。

1.2RAII的原理

资源的使用一般经历三个步骤:

  1. 获取资源(创建对象),
  2. 使用资源,
  3. 销毁资源 (析构对象)。

但是资源的销毁往往是程序员经常忘记的一个环节,所以程序界就想如何在程序员中让资源自动销毁呢?解决问题的方案是: RAIl,它充分的利用了 C++语言局部对象自动销毁的特性来控制资源的生命周期。

1.2.1简单的例子说明局部对象的自动销毁的特性

#include <iostream>
#include <string>

using namespace std;

class Student
{
private:
    const string s_name;
    int s_age;
public:
    Student(const string name = "",int age = 0):
        s_name(name),s_age(age){
            cout<<"Construct a Student"<<endl;
        }
    ~Student(){
        cout<<"Destory a Student"<<endl;
    }
};

int main(){
    Student stu1; #局部对象

    return 0;
}

在这里插入图片描述
从 Student类可以看出,当我们在 main 函数中声明一个局部对象的时候,会自动调用构造函数进行对象的初始化,当整个 main 函数执行完成后,自动调用析构函数来销毁对象,整个过程无需人工介入,由操作系统自动完成。
于是,基于上述实现方式,可以想到,当我们在使用资源的时候,在构造函数中进行初始化,在析构函数中进行销毁

1.2.2 RAII 过程

  1. 设计一个类封装资源
  2. 在构造函数中初始化
  3. 在析构函数中执行销毁操作
  4. 使用时声明一个该类的对象

2.智能指针

智能指针其实是将指针进行了封装,可以像普通指针一样进行使用,同时可以自行进行释放,避免忘记释放指针指向的内存地址造成内存泄漏。

C11 里面的四个智能指针:

  1. auto ptr,
  2. unique ptr,
  3. shared ptr,
  4. weak ptr
    下面对这四个智能指针进行一一介绍:

2.1 auto_ptr

auto_ptr是较早版本的智能指针,已经被 C11 弃用,C98 中 auto_ptr 所做的事情,就是动态分配对象以及当对象不再需要时自动执行清理。

2.1.1auto_ptr的使用

构造函数与析构函数

auto_ptr 在构造时获取对某个对象的所有权(ownership),在析构时释放该对象,提高代码安全性,因为我们不必关系应该如何释放auto ptr,也不用担心发生异常时会有内存泄漏。

int *p = new int(10);

auto_ptr<int> ap(p);

注意点

  1. auto ptr 析构的时候会删除他所拥有的那个对象,所以我们要注意两个auto_ptr 不能同时拥有同一个对象。
int *p = new int(10);

auto_ptr<int> ap1(p);
auto_ptr<int> ap2(p);

因为 ap1 与 ap2 都认为指针 p 是归它管的,在析构时都试图删除 p,两次删除同一个对象的行为在C++标准中是未定义的。所以我们必须防止这样使用 auto ptr

  1. 不应该用 auto ptr 来管理一个数组指针
string * sar = new string[10];

auto_ptr<string> ap3(sar);
  1. 构造函数的 explicit 关键词有效阻止从一个“裸指针隐式转换成 auto ptr 类型
拷贝构造函数与赋值

auto ptr 要求其对“裸”指针的完全占有性。也就是说一个“裸”指针不能同时被两个以上的 auto ptr 所拥有。那么,在拷贝构造或赋值操作时,就必须作特殊的处理来保证这个特性。
auto ptr的做法是“所有权转移”,即拷贝或赋值的源对象将失去对“裸”指针的所有权,所以,与一般拷贝构造函数,赋值承数不同,auto ptr 的拷贝构造函数,赋值函数的参数为引用而不是常引用(const reference).
当然,一个 auto ptr 也不能同时拥有两个以上的“裸”指针,所以,拷贝或赋值的目标指针将先释放其所拥有的对象,然后管理旧指针的资源,因此旧指针即指向nullptr,在访问旧指针时会出现悬空现象
注意点:

  1. auto ptr 被拷贝或被赋值后,其已经失去对原对象的所有权,这个时候,对这个 auto ptr 的提领(dereference)操作是不安全的。
int *p = new int(10);
auto_ptr<int> ap1(p);
auto_ptr<int> ap2 = ap1;

cout<<*ap1<<endl; //error,此时ap1已经失去对p指针的拥有权
  1. 将 auto_ptr 作为函数参数按值传递时,函数调用过程中在函数的作用域中会产生一个局部对象来接收传入的 auto ptr(拷贝构造),这样,传入的实参 auto ptr 就失去了其对原对象的所有权,而该对象会在函数退出时被局部 auto ptr 删除。
void fun(auto_ptr<int> ap)
{
    cout<< *ap<< endl;
}

int main(){

    
    auto_ptr<int> ap1(new int(10));

    fun(ap1);

    cout<<*ap1<<endl; //error,经过fun(ap1)函数调用,ap1已经不再拥有任何对象了

    return 0;
}

这种情况容易出错,所以 auto ptr 作为函数参数按值传递是一定要避免的。或许用auto ptr 的指针或引用作为函数参数或许可以,但是我们并不知道在函数中对传入的 auto ptr 做了什么,如果当中某些操作使其失去了对对象的所有权,那么这还是可能会导致致命的执行期错误。也许用 const reference 的形式来传递 auto ptr 会是一个不错的选择。

  1. auto ptr在基类和子类隐式转换
class Object{ };

class Base: public Object{ };

auto_ptr<Object> apobj = auto_ptr<Base>(new Base);
提领操作

提领操作有两个操作:

  1. 返回其所拥有的对象的引用,
  2. 实现了通过 auto ptr 调用其所拥有的对象的成员(首先要确保这个智能指针确实拥有某个对象,否则,这个操作的行为即对空指针的提领是未定义)。
auto ptr其它函数
  1. get 用来显式的返回 auto ptr 所拥有的对象指针。我们可以发现,标准库提供的 auto ptr 既不提供从裸”指针到auto _ptr 的隐式转换(构造函数为 explicit),也不提供 auto ptr 到指针的隐式转换,从使用上来讲可能不那么的灵活,考虑到其所带来的安全性还是值得的。
  2. release,用来转移所有权。
  3. reset,用来接收所有权,如果接收所有权的 auto ptr 如果已经拥有某对象,必须先释放该对象.

2.1.2autoptr使用的注意事项

  1. auto ptr 不能指向数组
  2. auto_ptr 不能共享所有权
  3. auto_ptr 不能通过复制操作来初始化
  4. auto_ptr 不能放入容器中使用
  5. auto ptr 不能作为容器的成员

2.2 unique_ptr

C11 中使用 unique_ptr 替代auto_ptr
unique 是独特的、唯一的意思,故名思议,unique ptr 可以“独占”地拥有它所指向的对象,是一种定义在<memory中的智能指针(smart pointer),保证一个对象同一时间只有一个智能指针。
unique_ptr 对象中保存指向某个对象的指针,当它本身被删除或者离开其作用域时会自动释放其指向对象所占用的资源。

2.2.1unique_ptr的使用

unique_ptr的创建

要想创建一个 unique ptr,需要将一个 new 操作符返回的指针传递给 unique ptr 的构造函数.

int main()
{    
    unique_ptr<int> pt(new int(10));

    cout<< *pt<< endl;  //10

    return 0;
}
unique_ptr不能进行拷贝构造和赋值操作
unique_ptr& operator=( const unique_ptr& ) = delete;

在这里插入图片描述

unique_ptr可以进行移动构造和移动赋值操作

unique ptr 虽然没有支持普通的拷贝和赋值操作,但却提供了一种移动机制来将指针的所有权从一个 unique ptr转移给另一个 unique ptr。如果需要转移所有权,可以使用 std:.move()函数

int main(){
    unique_ptr<int> pt1(new int(10));

    cout<< *pt1<< endl;  //10

    unique_ptr<int> pt2(move(pt1)); 
    cout<<"--------------"<<endl;
    //cout<< *pt1<< endl;  //出错,为空
    cout<< *pt2<< endl;  //10

    unique_ptr<int> pt3 = move(pt2); 
    cout<<"~~~~~~~~~~~~~~~~"<<endl;
    //cout<< *pt1<< endl;  //出错,为空
    //cout<< *pt2<< endl;  //出错,为空
    cout<< *pt3<< endl;  //10

    return 0;
}

在这里插入图片描述

unique_ptr虽然没有拷贝操作,但是可以从函数中返回unique_ptr
unique_ptr<int> clone(int a)
{
    unique_ptr<int> ptr(new int(a));

    return ptr;//返回unique_ptr
}

int main()
{
    int val = 11;

    unique_ptr<int> pt1 = clone(val);

    cout<< *pt1<< endl;  //11

    return 0;
}

2.2.2unique_ptr使用场景

  1. 为动态申请的资源提供异常安全保证
void fun_(int a)
{
    int *p = new int(5);

    //.....(抛出异常)
}

void fun(int a)
{
    unique_ptr<int> ptr(new int(a));

    //.....(抛出异常)
    
}

fun_是传统的写法:在动态申请内存后,有可能接下来的代码由于抛出异常或者提前退出 (if 语句)而没有执行 delete 操作。
解决的方法是使用 unique ptr 来管理动态内存,只要 unique ptr 指针创建成功,其析构函数都会被调用。确保动态资源被释放。
2. 返回函数内动态申请资源的所有权

unique_ptr<int> fun(int a)
{
    unique_ptr<int> ptr(new int(a));

    return ptr;//返回unique_ptr
}

int main()
{
    int a = 12;
    unique_ptr<int> ret = fun(a);
    
    cout<< *ret<< endl; //12
    
    //函数结束后,自动释放资源

    return 0;
}
  1. 在容器中保存指针
int main()
{
    vector<unique_ptr<int>> vec;
    unique_ptr<int> pt(new int(5));
    
    vec.push_back(move(pt));

    for(int i = 0;i < vec.size();i++){
        cout<< *vec[i]<< endl;  // 5
    }
    

    return 0;
}
  1. 管理动态数组
int main()
{   

    unique_ptr<vector<int>[]> pt(new vector<int> {1,2,3,4,5});
        
    
    for(int i = 0;i < pt.get()->size();i++){
        cout<<pt.get()->at(i)<<endl;    //1,2,3,4,5
    }

    return 0;
}

2.3 shared_ptr(共享指针)

shared ptr 是一个引用计数的智能指针,用于共享对象的所有权。也就是说它允许多个指针指向同一个对象,并且维护了一个共享的引用计数器,当这个对象所有的智能指针被销毁时(引用计数器==0)就会自动进行回收。

class Object
{
private:
    int val;
public:
    Object(int x = 0):val(0){
        cout<< "Construct Object"<< endl;
    }
    ~Object(){
        cout<< "Destory Object"<< endl;
    }

};

int main()
{
        
    shared_ptr<Object> pobj(new Object(13));
    //指针引用对象的个数
    cout<< " pobj:"<< pobj.use_count()<<endl; //1

    shared_ptr<Object> pobj1 = pobj;
    
    cout<< " pobj:"<< pobj.use_count()<<endl; //2
    cout<< " pobj1:"<< pobj1.use_count()<<endl; //2


    return 0;
}

在这里插入图片描述
一方面,跟 STL 中大多数容器类型一样,shared ptr 也是模板类,因此在创建 shared ptr 时需要指定其指向的类型。另一方面,shared ptr 指针允许让多个该类型的指针共享同一堆分配对象。同时 shared ptr 使用经典的“引用计数”方法来管理对象资源,每个 shared ptr 对象关联一个共享的引用计数。

2.3.1 shared_ptr的使用

shared_ptr的创建
  1. 调用 make shared 库函数,该函数会在堆中分配一个对象并初始化,最后返回指向此对象的share ptr 实例(安全、高效
  2. 先 new 出一个对象,然后把其原始指针传递给 share ptr 的构造函数
shared_ptr<int> ptm = make_shared<int>(16);

cout<< *ptm<< endl; //16

shared_ptr<int> ptn(new int(15));
    
cout<< *ptn<< endl; //15

shared_ptr的访问
  1. 解引用操作符*获得原始对象进而访问其各个成员,
  2. 指针访问符->来访问原始对象的各个成员。
shared_ptr的拷贝和赋值操作

对于 shared ptr 在拷贝和赋值时的行为是,每个 shared ptr 都有一个关联的计数值,通常称为引用计数。无论何时我们拷贝一个 shared ptr,计数器都会加1。当我们将一个指针的对象交给另一个指针管理后,其关联的引用计数就会减1。
例如,当用一个 shared ptr 初始化另一个 shred ptr时,或将它当做参数传递给一个函数以及作为函数的返回值时它所关联的计数器就会递增。
当我们给 shared ptr 赋予一个新值或是 shared ptr 被销(例如一个局部的 shared ptr 离开其作用域)时,计数器就会递减。
shared ptr 对象的计数器变为 0,它就会自动释放自己所管理的对象。

class Object
{
private:
    int val;
public:
    Object(int x = 0):val(0){
        cout<< "Construct Object"<< endl;
    }
    ~Object(){
        cout<< "Destory Object"<< endl;
    }

};

int main()
{
        
    shared_ptr<Object> pobj = make_shared<Object>(13);
    //指针引用对象的个数
    cout<< " pobj:"<< pobj.use_count()<<endl; //1

    shared_ptr<Object> pobj1 = pobj;
    
    cout<< " pobj:"<< pobj.use_count()<<endl; //2
    cout<< " pobj1:"<< pobj1.use_count()<<endl; //2


    return 0;
}

对比我们上面的代码可以看到: 接下来,我们用 pob初始化 pobit1,两者关联的引用计数值增加为 2。随后,函数结束,pObi和 PObi2 相继离开函数作用域,相应的引用计数值分别自减 1 最后变为 0,于是 Obiect 对象被自动释放(调用其析构函数)。

shared_ptr的引用计数

shared _ptr 提供了两个函数来检查其共享的引用计数值,分别是

  1. unique()函数用来测试该 shared ptr 是否是原始指针唯一拥有者,也就是 use count()的返回值为 1时unique()返回 true,否则返回 false。
  2. use count()函数,该函数返回当前指针的引用计数值。值得注意的是 use count()函数能效率很低,应该只把它用于测试或调试。

2.3.2 shared_ptr的线程安全

  1. (shared_ptr) 的引用计数本身是线程安全(引用计数是原子操作)
  2. 多个线程同时读同一个 shared ptr 对象是线程安全的。
  3. 如果是多个线程对同一个 shared ptr 对象进行读和写,则需要加锁
  4. 多线程读写 shared_ptr 所指向的同一个对象,不管是相同的 shared ptr 对象,还是不同的 shared ptr 对象,也需要加锁保护。

2.3.3 shared_ptr与unordered map使用

如果把 shared_ptr 放到 unordered set 中,或者用于 unrdered map 的 key, 那么要小心 hash table 退化为链表。但是其 hash value 是 shared_ptr 隐式转换为 bool 的结果。也就是说,如果不自定义 hash 函数,那么unordered {set/map] 会退化为链表。
为什么要尽量使用 make shared()? 申请被管理对象以及引用计数的内存:调用适当的构造函数初始化对象,返回shared_ptr。
为了节省一次内存分配,原来 shared_ptr x(new Obiect (10) ; 需要为 Obiect对象 和 RefCnt 各分配次内存,现在用 make shared() 的话,可以一次分配一块足够大的内存,供 Obiect 和 RefCnt 对象容身。不过Obiect 的构造函数所需的参数要传给 make shared(),后者再传给 Obiect; : Obiect),这只有在 C++11 里通过perfect forwarding(完美转发) 才能完美解决。

2.4 weak_ptr

2.4.1 weak_ptr理解

weak_ptr 是为了配合 shared ptr 而引入的一种智能指针,它指向一个由 shared ptr 管理的对象而不影响所指对象的生命周期,也就是将一个 weak_ptr 绑定到一个 shared_ptr 不会改变 shared_ ptr 的引用计数
即weak_ptr是为了协助shared_ptr而出现的。它不能访问对象,只能观测shared_ptr的引用计数,防止出现死锁。
不论是否有 weak ptr 指向,一旦最后一个指向对象的 shared_ptr 被销毁,对象就会被释放。从这个角度看weak_ptr 更像是 shared_ptr 的一个助手而不是智能指针。

2.4.2 为什么会有weak_ptr

在出现了循环引用(或环形引用)的情况下,shared_ptr。

class Child;

class Parent
{
public:
    shared_ptr<Child> child;
    ~Parent(){
        cout<< "Destory Parent"<< endl;
    }
    void Priant() const{
        cout<< "Parent-----------"<< endl;
    }
};

class Child
{
public:
    shared_ptr<Parent> parent;
    ~Child(){
        cout<< "Destory Child"<< endl;
    }
};

int main()
{
        
    shared_ptr<Parent> parent = make_shared<Parent>();
    shared_ptr<Child> child = make_shared<Child>();

    parent->child = child;
    child->parent = parent;

    child->parent->Priant();

    return 0;
}

在这里插入图片描述
上面代码的运行结果,只打印出”Parent-----------”,而并没有打印出"Destory Parent”或”Destory Child",说明 Parent 和 Child 的析构函数并没有调用到。这是因为Parent 和 Child 对象内部,具有各自指向对方的 shared_ptr,加上 parent和 child 这两个shared_ptr,说明每个对象的引用计数都是 2。当程序退出时,即使 parent 和 child 被销毁,也仅仅是导致引用计数变为了1,因此并未销毁 Parent 和 Child 对象。

2.3.3 weak_ptr使用

创建weak_ptr实例

创建一个weak_ptr时,需要用一个 shared_ptr 实例来初始化weak_ptr,由于是弱共享,weak_ptr的创建并不会影响 shared_ptr 的引用计数值

int main()
{
        
    shared_ptr<int> sp = make_shared<int>(5);

    cout<< sp.use_count()<< endl; //1
    
    weak_ptr<int> wp(sp);
    cout<< sp.use_count()<< endl; //1
    cout<< wp.use_count()<< endl; //1
    

    return 0;
}
判断weak_ptr指向对象是否存在

weak_ptr 并不改变其所共享的 shared_ptr 实例的用计数,那就可能存在 weak ptr 指向的对象被释放掉这种情况。这时,我们就不能使用 weak ptr 直接访问对象。那么我们如何判断 weak ptr 指向对象是否存在呢?C++中提供了 lock 函数来实现该功能。
如果对象存在,lock(函数返回一个指向共享对象的 shared ptr,否则返回一个空 shared ptr。

shared_ptr<int> sp = make_shared<int>(5);

//sp = nullptr;
    
weak_ptr<int> wp(sp);
    
if(shared_ptr<int> pa = wp.lock()){
    cout<< *pa <<endl;
}
else{
    cout<< wp.expired()<< endl; weak_ptr 还提供了 expired0函数来判断所指对象是否已经被销毁
    cout<< "*wp is nullptr"<< endl;
}
weak_ptr使用

weak_ptr 并没有重载 operator->和 operator 操作符,因此不可直接通过 weak_ptr 使用对象,典型的用法是调用其lock 函数来获得 shared_ptr 示例,进而访问原始对象。
最后,我们来看看如何使用 weak_ptr 来改造最前面的代码,打破循环引用问题。

class Child;

class Parent
{
public:
    weak_ptr<Child> child;
    
    ~Parent(){
        cout<< "Destory Parent"<< endl;
    }
    void Priant() {
        cout<< "Parent-----------"<< endl;
    }
};

class Child
{
public:
    weak_ptr<Parent> parent;
    ~Child(){
        cout<< "Destory Child"<< endl;
    }
};

int main()
{
        
    shared_ptr<Parent> parent1 = make_shared<Parent>();
    shared_ptr<Child> child1 = make_shared<Child>();

    weak_ptr<Parent> parent(parent1);
    weak_ptr<Child> child(child1);

    parent1->child = child1;
    child1->parent = parent1;

    //child1->parent.lock()->Priant();
    
    if(!child.expired()){
        child1->parent.lock()->Priant();
    }
    

    return 0;
}

2.5 几种智能指针的比较

unique_ptr和 shared ptr 类型指针有很大的不同: shared ptr 允许多个指针指同一对象,而 nique_ptr 在某时刻只能有一个指针指向该对象(两个 unique ptr 不能指向同一个对象)。

  1. 使用场景:
    如果程序要使用多个指向同一个对象的指针,应该选择 shared_ptr;
    如果程序要使用一个指向一个对象的指针,则可以使用 unique_ptr;
    如果使用 new [] 分配内存,应该选择 unique_ptr;
    如果函数使用 new 分配内存,并返回指向该内存的指针,将其返回类型声明为 unique_ptr 是不错的选择。

  2. 智能指针实现原理:建立所有权(ownership)概念。
    auto_ptr 和 unique_ptr 的策略:对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的析构函数会删除该对象。然后,让赋值操作转让所有权。但 unique_ptr 的策略更严格,unique_ptr 能够在编译期识别错误。
    shared_ptr 采用的策略。:跟踪引用特定对象的智能指针计数,这称为引用计数(reference counting)。例如,赋值时,计数将加 1,而指针过期时,计数将减 1. 仅当最后一个指针过期时,才调用 delete。

  3. 线程安全:
    shared_ptr:引用计数在手段上使用了 atomic 原子操作,只要 shared_ptr 在拷贝或赋值时增加引用,析构时减少引用就可以了。首先原子是线程安全的,所有 shared_ptr 智能指针在多线程下引用计数也是安全的,也就是说 shared_ptr 智能指针在多线程下传递使用时引用计数是不会有线程安全问题的。 但是指向对象的指针不是线程安全的,使用 shared_ptr 智能指针访问资源不是线程安全的,需要手动加锁解锁。智能指针的拷贝也不是线程安全的。

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

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

相关文章

一元函数极值问题

一元函数极值问题 0 引言 在高等数学课程中&#xff0c;我们应该都学习过一元函数的极值问题&#xff0c;这篇文章我们再来回顾一下相关知识点。为什么要对一元函数的极值问题进行回顾&#xff1f;因为后面我会出一篇非线性规划问题的极值问题&#xff0c;其中会涉及到多元函…

【视觉算法系列1】使用 KerasCV YOLOv8 进行红绿灯检测(下)

提示&#xff1a;免费获取本文涉及的完整代码与数据集&#xff0c;请联系助理老师peaeci122 使用最新“KerasCV YOLOv8”模型进行红绿灯检测的综合指南 YOLO目标检测模型已经进入了无数的应用领域&#xff0c;从监控系统到自动驾驶汽车。那么&#xff0c;如果在KerasCV框架下…

4.DApp-MetaMask怎么连接本地Ganache

题记 用metamask连接本地ganache&#xff0c;以下是全部操作流程 下载Ganache ganache是一个以太坊的个人开发环境&#xff0c;可以在上面部署合约、开发程序和进行测试。 ganache官网&#xff1a;Ganache - Truffle Suite 可以点击下面的按钮直接下载 &#xff0c;下载速度…

怎样自动开始播放网页视频?

有些视频网站&#xff0c;网页打开后&#xff0c;并不会自动播放视频&#xff0c;需要人工点击视频或者播放器的播放按钮&#xff0c;才能进入视频播放状态。有没有办法一打开页面就自动播放视频呢&#xff1f;下面我们就来分析研究一下。 首先使用浏览器的开发者工具查看&am…

MybatisPlus多表关联分页返回结果异常

1. 按照该博客进行多表关联分页查询&#xff1a; https://blog.csdn.net/code_ang/article/details/116448694 2.在实际测试过程中&#xff0c;发现异常&#xff0c;分页返回的结果时而正确&#xff0c;时而错误。 count函数满足预期 count函数不满足预期 只是count了主表的…

pytorch 入门(二)

本文为&#x1f517;小白入门Pytorch内部限免文章 &#x1f368; 本文为&#x1f517;小白入门Pytorch中的学习记录博客&#x1f366; 参考文章&#xff1a;【小白入门Pytorch】教案二&#x1f356; 原作者&#xff1a;K同学啊 目录 一、神经网络的组成部分1. 神经元2. 神经网络…

热成像仪的工作原理及在工业设备状态监测中的应用

前面我们介绍过>>热分析技术在工业设备状态监测中的应用&#xff0c;下面我们将深入探讨热成像仪的工作原理及在工业设备状态监测中的应用。 近年来&#xff0c;热成像仪作为一种先进的检测工具&#xff0c;在工业设备状态监测领域得到了广泛的应用。热成像仪能够通过探测…

oauth2和knife4j结合

1.先说knife4j是个什么东西 他是swagger的升级版&#xff0c;在有swagger的调试功能以及接口描述的基础上&#xff0c;让人看着更加一目了然。 这次可能说的比较浅&#xff0c;主要是说怎么使用以及简单配置&#xff0c;还有我自己踩过的坑&#xff0c;因为这个东西我也第一次…

软件环境基础(ROS、CMake)

参考视频&#xff1a;【全】无人驾驶系列知识入门到提高 本文旨在对视频内容规划控制方面做一些学习记录&#xff0c;希望帮助有需要的人学习提高。不对处&#xff0c;望指正。 0 ROS介绍 ROS特点&#xff1a; 点对点设计&#xff08;比如一个结点读取相机数据&#xff0c;传…

yolo配置(windows)

文章目录 一、下载Anaconda和pycharm1 、Anaconda官方下载地址&#xff1a;1.2 Anaconda 的安装 下载好之后双击打开可执行安装文件&#xff1a;1.3 进入到安装界面&#xff1a;1.4 这里建议两个都勾选&#xff08;第一个选项是将 Anaconda 添加到环境变量中&#xff0c;不勾选…

火山引擎 ByteHouse:双十一即将到来,直播商家如何用数据“点播成金”?

更多技术交流、求职机会&#xff0c;欢迎关注字节跳动数据平台微信公众号&#xff0c;回复【1】进入官方交流群 “双十一”电商大促脚步渐近&#xff0c;各大平台的战火又将燃起。直播电商以低成本、高转化率等优势备受商家青睐。据智研咨询数据显示&#xff0c;2022 年我国直播…

iOS——JSONModel的使用与JSONModel的嵌套

什么是JSONModel JSONModel是一个解析JSON数据的开源库&#xff0c;可以将JSON数据直接解析成自定义的model 使用 JSONModel 非常简单,只需要将你的 model 类继承自 JSONModel ,而同时 model 中的属性名又恰巧可以和 JSON 数据中的 key 名字一样的话,那么非常恭喜你,你的工作…

10.16作业

头文件 #ifndef MYWIDGET_H #define MYWIDGET_H#include <QWidget> #include<QDebug> #include<QIcon> #include<QPushButton> #include<QLineEdit> #include<QLabel> #include<QMovie> #include<QCheckBox> #include<QPa…

MyCAT:回顾当年的热潮,探寻这款备受欢迎的数据库中间件的核心特性

什么是 MyCAT &#xff1f; 根据 MyCAT 官网 - http://mycat.io/ 的描述可以知道&#xff0c; MyCAT 是如下的一个东东&#xff1a; 一个彻底开源的&#xff0c;面向企业应用开发的大数据库集支持事务、ACID、可以替代MySQL的加强版数据库一个可以视为MySQL集群的企业级数据库…

银河麒麟你服务x86访问ftp服务器上的文件

打开我的电脑 地址栏输入 ftp地址 可以选择需要的文件复制出来了

PyQt 小程序

设备管理程序 v0.0.1.0, 终于出了一个基础版本,… … 两个字典的键值判断 辛亏用的是Python 这个编码时间大大缩短了

node+vue+mysql后台管理系统

千千博客系统&#xff0c;该项目作为一套多功能的后台框架模板&#xff0c;适用于绝大部分的后台管理系统开发。基于 vue.js&#xff0c;使用 vue-cli3 脚手架&#xff0c;引用 Element UI 组件库&#xff0c;数据库直连mysql方便开发快速简洁好看的组件。 功能包含如下&#…

电子杂志制作不求人:简单易用的工具推荐

​如果你想要制作一份精美的电子杂志&#xff0c;但是又不想花费太多的时间和金钱&#xff0c;也不想求及朋友帮忙制作&#xff0c;那么可以试试这个网站制作电子杂志&#xff0c;展现出的效果跟专业级设计师的效果没什么区别哦 赶快收藏吧-------FLBOOK在线制作电子杂志平台&a…

iOS代码混淆和加固技术详解

目录 摘要&#xff1a; 本文介绍了iOS开发中常用的代码混淆和加固技术&#xff0c;包括数据加密、应用加壳和代码混淆。其中&#xff0c;重点讨论了代码混淆的实现方法和注意事项&#xff0c;并推荐了一些相关的工具和库。 引言 代码混淆和加固 数据加密 应用加壳 代码混…

易基因:细菌微生物基因表达调控表观研究方案|原核三代甲基化+转录组

1、原核甲基化 原核生物中的DNA甲基化 原核生物甲基化为什么基于三代测序&#xff1f; 第三代DNA测序为原核细菌的甲基化和表观遗传的研究开辟了一条新的途径&#xff0c;能够在基因组的水平上获取整个表观遗传的序列信息&#xff0c;绘制全基因组甲基化组。 细菌中DNA甲基化…