C++智能指针的用法(全)

news2024/11/25 16:37:18

一、智能指针概念

        C/C++ 语言最为人所诟病的特性之一就是存在内存泄露问题,因此后来的大多数语言都提供了内置内存分配与释放功能,有的甚至干脆对语言的使用者屏蔽了内存指针这一概念。这里不置贬褒,手动分配内存与手动释放内存有利也有弊,自动分配内存和自动释放内存亦如此,这是两种不同的设计哲学。有人认为,内存如此重要的东西怎么能放心交给用户去管理呢?而另外一些人则认为,内存如此重要的东西怎么能放心交给系统去管理呢?在 C/C++ 语言中,内存泄露的问题一直困扰着广大的开发者,因此各类库和工具的一直在努力尝试各种方法去检测和避免内存泄露,如 boost,智能指针技术应运而生。

        智能指针主要用于管理在堆上分配的内存,它将普通的指针封装为一个栈对象。当栈对象的生存周期结束后,会在析构函数中释放掉申请的内存,从而防止内存泄漏。简要的说,智能指针利用了 C++ 的 RAII 机制,在智能指针对象作用域结束后,会自动做内存释放的相关操作,不需要我们再手动去操作内存。

C++ 中有四种智能指针:

  • auto_ptr:已经废弃
  • unique_ptr:独占式指针,同一时刻只能有一个指针指向同一个对象
  • shared_ptr:共享式指针,同一时刻可以有多个指针指向同一个对象
  • weak_ptr:用来解决shared_ptr相互引用导致的死锁问题

二、auto_ptr

auto_ptr 是 C++98 中引入的智能指针,用于自动管理动态分配的对象的生命周期。然而,它在 C++11 中已被标记为已废弃,并且在 C++17 中已被移除,因为它存在一些严重的缺陷和安全问题。在此我们不做过多讲述,只针对其缺陷加以说明。

缺陷:

  1. 没有共享所有权auto_ptr 不能共享所有权,这意味着多个指针不能指向同一个对象
  2. 不支持拷贝语义auto_ptr 在拷贝或者赋值时进行的是资源所有权转移,而并不是真正的拷贝和赋值

三、unique_ptr

1.unique_ptr的使用

用unique_ptr来替代new关键字创建出来的动态对象;

class Person
{
public:
    Person(){cout << "构造函数" << endl;}

    void Demo(){cout << "Person Demo" << endl;}

    ~Person(){cout << "析构函数" << endl;}
};

// 1. unique_ptr 创建方式
void test01()
{
    //管理单一动态对象
    unique_ptr<Person> up1(new Person);
    up1->Demo();

    //管理动态对象数组
    unique_ptr<Person[]> up2(new Person[2]);
    up2[0].Demo();
    up2[1].Demo();
}

// 2. unique_ptr 操作函数
void test02()
{
    unique_ptr<Person> up(new Person);

    //get 成员函数返回 unique_ptr 管理的动态对象指针
    Person* person1 = up.get();

    //release 成员函数使 unique_ptr 不再持有动态对象指针(并不销毁管理的对象),并返回其管理的动态指针
    Person* person2 = up.release();
    delete person2;

    //reset 成员函数有两个重载的版本,具体功能如下:
    up.reset();  // 释放并销毁 unique_ptr 所管理的动态对象指针。
    up.reset(new Person);  // 释放并销毁原来的管理的动态对象,并重新持有新创建的动态对象

    //swap 成员函数交换两个 unique_ptr 对象管理的动态对象
    unique_ptr<Person> sp(new Person);
    up.swap(sp);
}

2.unique_ptr的特性

unique_ptr 特点:

  1. 同时只能有一个 unique_ptr 对象来持有动态对象资源
  2. unique_ptr 对象不支持默认拷贝、默认赋值语义
  3. unique_ptr 对象支持移动拷贝、移动赋值语义
  4. unique_ptr 对象不能以值的方式用做函数参数,也不能存储在STL的容器中(容器要求元素必须能够被拷贝)
class Person
{
public:
    Person(){cout << "构造函数" << endl;}

    ~Person(){cout << "析构函数" << endl;}
};

void test()
{
    unique_ptr<Person> up1(new Person);
    unique_ptr<Person> up2(new Person);

    // 1. 禁止拷贝、赋值
    // unique_ptr(const unique_ptr&)            = delete;
    // unique_ptr& operator=(const unique_ptr&) = delete;

    // 2. 允许移动拷贝、赋值
    unique_ptr<Person> up3(move(up1));  // 移动拷贝
    up2 = move(up3);  // 移动赋值

    // unique_ptr 不允许作为容器元素
    // vector<unique_ptr<Person>> vec;
    // vec.push_back(up1);
}

3.unique_ptr自定义删除器

unique_ptr 可用于管理 new 出来的动态对象,也可以管理其他需要手动关闭的资源。例如:文件对象。

由于 unique_ptr 默认使用 delete、delete[] 来释放被管理的资源。所以,当管理的对象不能通过 delete、delete[] 来释放时,就需要自定义删除器。

class Person
{
public:
    Person(){cout << "构造函数" << endl;}

    ~Person(){cout << "析构函数" << endl;}
};

struct Deleter
{
    void operator()(FILE* fp)
    {
        cout << "文件被自动关闭" << endl;
        if (fp != nullptr)
        {
            fclose(fp);
            fp = nullptr;
        }
    }
};

void my_deleter(FILE* fp)
{
    cout << "文件被自动关闭" << endl;
    if (fp != nullptr)
    {
        fclose(fp);
        fp = nullptr;
    }
}

void test()
{
    // 1. 函数对象作为删除器
    // unique_ptr<FILE, Deleter> up(fopen("./demo.txt", "w"), Deleter());
    // unique_ptr<FILE, function<void(FILE*)>> up(fopen("./demo.txt", "w"), Deleter());
    
    // 2. 普通函数作为删除器
    // unique_ptr<FILE, decltype(&my_deleter)> up(fopen("./demo.txt", "w"), my_deleter);
    // unique_ptr<FILE, void(*)(FILE *)> up(fopen("./demo.txt", "w"), my_deleter);
    // unique_ptr<FILE, function<void(FILE*)>> up(fopen("./demo.txt", "w"), my_deleter);
    
    // 3. 匿名函数作为删除器
    unique_ptr<FILE, function<void(FILE *)>> up(fopen("./demo.txt", "w"), [](FILE *fp) {
            cout << "文件被自动关闭" << endl;

            if (fp != nullptr)
            {
                fclose(fp);
                fp = nullptr;
            }
        });

    if (!up)
    {
        cout << "文件打开失败" << endl;
        return;
    }
    fputs("hello world\n", up.get());
}

四、shared_ptr

C++ 的 shared_ptr 是 C++11 标准引入的智能指针之一,用于管理动态分配的对象的所有权。它允许多个 shared_ptr 实例共享对同一对象的所有权,而不会出现内存泄漏或者悬空指针的情况。shared_ptr 使用引用计数技术来跟踪有多少个 shared_ptr 实例指向同一个对象,并在最后一个实例销毁时自动释放对象。

1.share_ptr使用

  • shared_ptr 的创建和使用方法。
  • 支持管理动态对象和对象数组。
  • 提供多种操作函数,如 get(), swap(), 和 reset()。
class Person
{
public:
    Person(){cout << "无参构造函数" << endl;}

    Person(int, int){cout << "有参构造函数" << endl;}

    ~Person(){cout << "析构函数" << endl;}
};

// 1. 创建 shared_ptr 对象
void test01()
{
    // 1.1 使用 shared_ptr 的构造函数创建对象
    shared_ptr<Person> sp1(new Person(10, 20));

    // 1.2 使用 shared_ptr 管理动态对象数组
    shared_ptr<Person[]> sp2(new Person[5]);
}


// 2. shared_ptr 操作函数
void test02()
{
    shared_ptr<Person> sp1(new Person(10, 20));
    shared_ptr<Person> sp2(new Person(100, 200));

    // 2.1 get 成员函数可以获得 shared_prt 管理的动态对象指针
    Person* person = sp1.get();

    // 2.2 swap 成员函数可以交换两个 shared_ptr 管理的动态对象指针
    sp1.swap(sp2);

    // 2.3 reset 成员函数存在两个重载版本的函数,其作用分别如下:
    sp1.reset();  // 释放其管理动态指针,此时 sp1 对象管理的动态指针指向为 nullptr
    sp1.reset(new Person(1, 2));  // 释放原来的动态对象,并指向新的动态对象
}

2.share_ptr特性

  • 允许多个 shared_ptr 对象同时管理同一动态对象。
  • 支持对象拷贝和赋值,以及对象移动拷贝和赋值。
  • 可以将 shared_ptr 存储在容器中,如 vector。
class Person
{
public:
    Person(){cout << "无参构造函数" << endl;}

    ~Person(){cout << "析构函数" << endl;}
};

void test()
{
    shared_ptr<Person> sp1(new Person);

    // 1. 允许对象拷贝、对象赋值
    shared_ptr<Person> sp2(sp1);

    shared_ptr<Person> sp3;
    sp3 = sp1;  // 对象赋值

    // 2. 允许对象移动拷贝、移动赋值
    shared_ptr<Person> sp4 = move(sp3);

    shared_ptr<Person> sp5;
    sp5 = move(sp1);  // 移动赋值

    cout << "sp1:" << sp1.get() << endl;  // 已被移动
    cout << "sp2:" << sp2.get() << endl;
    cout << "sp3:" << sp3.get() << endl;  // 已被移动
    cout << "sp4:" << sp4.get() << endl;
    cout << "sp5:" << sp5.get() << endl;

    // 允许存储到容器中
    vector<shared_ptr<Person>> vec;
    vec.push_back(sp2);
    vec.push_back(sp4);
    vec.push_back(sp5);
}

3.share_ptr引用计数

  • shared_ptr 通过内部维护的引用计数来实现对象共享。
  • 当 shared_ptr 的生命周期结束时,不会导致其他 shared_ptr 管理无效指针。
  • 提供 use_count() 方法查看当前引用计数。
struct Person
{
    Person(){cout << "Person 构造函数" << endl;}

    ~Person(){cout << "Person 析构函数" << endl;}
};


void test()
{
    // 初始化智能指针,引用计数为 0
    shared_ptr<Person> sp1(new Person);
    cout << "sp1:" << sp1.use_count() << endl;
    
    // 发生拷贝,引用计数 +1
    shared_ptr<Person> sp2(sp1);
    cout << "sp2:" << sp2.use_count() << endl;

    // 发生赋值,引用计数 + 1
    shared_ptr<Person> sp3;
    sp3 = sp2;
    cout << "sp3:" << sp3.use_count() << endl;
    
    // 判断是否独占资源
    cout << "sp1 是否独占资源:" << sp1.unique() << endl;

    // sp2 释放资源所有权,通过该对方访问的引用次数为 0
    sp2.reset();
    cout << "sp2:" << sp2.use_count() << endl;

    // sp1 和 sp2 引用计数为 2
    cout << "sp1:" << sp1.use_count() << endl;
    cout << "sp3:" << sp3.use_count() << endl;
}

4.share_ptr自定义删除器

shared_ptr 和 unique_ptr 一样,并不仅仅管理 new 出来的动态对象,但是,智能指针默认使用 delete、delete[] 来释放被管理的对象。

此时,如果被管理的对象并不是 new、new[] 出来的,需要自定义删除器。其删除器可以是以下任何一种形式:

  1. 普通函数
  2. 函数对象
  3. lambda 匿名函数对象
void my_deleter(FILE* fp)
{
    cout << "文件自动关闭" << endl;
    fclose(fp);
    fp = nullptr;
}

struct MyDeleter
{
    void operator()(FILE* fp)
    {
        cout << "文件自动关闭" << endl;
        fclose(fp);
        fp = nullptr;
    }
};

void test()
{
    // 1. 使用普通函数作为自定义删除器
    shared_ptr<FILE> sp1(fopen("./demo.txt", "w"), my_deleter);

    // 2. 使用函数对象作为自定义删除器
    shared_ptr<FILE> sp2(fopen("./demo.txt", "w"), MyDeleter());

    // 3. 使用 lambda 匿名函数对象作为自定义删除器
    shared_ptr<FILE> sp3(fopen("./demo.txt", "w"), [](FILE* fp) {
            cout << "文件自动关闭" << endl;
            fclose(fp);
            fp = nullptr;
        });
}

五、weak_ptr

std::weak_ptr 是 C++ 标准库中的一个智能指针类,用于解决 std::shared_ptr 可能引发的循环引用问题。循环引用可能导致内存泄漏,因为引用计数无法降为零,从而无法释放对象。

std::weak_ptr是一种弱引用,它允许你观测由 std::shared_ptr 管理的对象,但不会增加对象的引用计数。换句话说,std::weak_ptr 不拥有所指向对象的所有权,因此不会影响对象的生命周期。当 std::shared_ptr 管理的对象被销毁后,对应的 std::weak_ptr 会自动失效,指向空值。

1.weak_ptr创建和使用

weak_ptr 是作为 shared_ptr 辅助角色存在,不会被直接用来管理动态对象。所以:

  1. 我们不会直接创建 weak_ptr 去管理动态对象
  2. weak_ptr 只能通过 shared_ptr 对象创建
  3. weak_ptr 引用 shared_ptr 对象时,并不会增加引用计数
  4. weak_ptr 不直接操作 shared_ptr 管理的对象,但允许间接操作 shared_ptr 管理的对象
class Person
{
public:
    Person(int, int){cout << "构造函数" << endl;}

    void show(){cout << "Person::show 函数" << endl;}

    ~Person(){cout << "析构函数" << endl;}
};

void test()
{
    // weak_ptr 是对 shared_ptr 的辅助,其自身并不拥有资源所有权
    shared_ptr<Person> sp1 = make_shared<Person>(10, 20);
    
    // 通过拷贝 shared_ptr 对象创建 weak_ptr 对象
    weak_ptr<Person> wp1(sp1);

    // weak_ptr 使用时,不能直接访问对象成员
    // 必须使用 lock 方法返回一个 shared_ptr 对象(会增加引用)
    if (wp1.expired())
    {
        return;
    }
    auto sp2 = wp1.lock();
    sp2->show();
    
    // 可以将 shared_ptr 赋值给 weak_ptr 对象
    weak_ptr<Person> wp2;
    wp2 = sp1;

    // weak_ptr 相当于一个不增加引用的 shared_ptr
    cout << "sp1:" << sp1.use_count() << endl;
    cout << "sp2:" << sp2.use_count() << endl;
    cout << "wp1:" << wp1.use_count() << endl;
    cout << "wp2:" << wp2.use_count() << endl;
}

2.shared_ptr 引用计数的缺陷

shared_ptr 使用引用计数来管理对象,但在双向链表的情况下,两个节点互相引用会导致循环引用,导致内存泄漏。

在这种情况下,两个对象的引用计数都保持在 2,导致它们的析构函数不会被调用。

例如:

// 双向链表节点
class LinkNode
{
public:
    LinkNode(int value)
    {
        cout << "LinkNode 构造函数" << endl;
        data = value;
    }

    ~LinkNode()
    {
        cout << "LinkNode 析构函数" << endl;
    }

public:
    int data;
    shared_ptr<LinkNode> prev;
    shared_ptr<LinkNode> next;
};


void test()
{
    // 创建两个链表节点
    shared_ptr<LinkNode> node1(new LinkNode(10));
    shared_ptr<LinkNode> node2(new LinkNode(20));

    // 建立节点之间的关系
    node1->next = node2;
    node2->prev = node1;
}

        我们对链表节点使用 shared_ptr 进行了管理,防止出现忘记释放而导致的内存泄漏。但是,通过程序运行的结果看到,两个 LinkNode 只调用了构造函数,并没有调用析构函数,出现了内存泄漏产生。

        这是由于什么原因导致的呢?

        上图中, node1 对象内部引用 node2 对象,node2 对象内部引用 node1 这种现象,叫做循环引用。

  1. 由于循环引用,导致两个 LinkNode 关联的引用计数为 2
  2. 当 test 函数生命周期结束,node1 和 node2 的析构函数调用,两个 LinkNode 对象引用计数 -1,引用计数为 1
  3. 只有当 LinkNode 实际被销毁时,才会调用 prev 和 next 的析构函数。

第 2、3 点是一对矛盾的存在:

  1. 只有 prev 和 next 被销毁,LinkNode 才会被真正释放
  2. 只有两个 LinkNode 被释放,prev 和 next 才会被销毁

3.weak_ptr 使用示例

为了解决循环引用的问题,可以在节点之间使用 weak_ptr 替代 shared_ptr。这样可以打破循环引用,确保对象能够正确释放。

示例代码展示了如何使用 weak_ptr 来管理双向链表节点,从而避免内存泄漏。

// 双向链表节点
class LinkNode
{
public:
    LinkNode(int value)
    {
        cout << "LinkNode 构造函数" << endl;
        data = value;
    }

    ~LinkNode(){cout << "LinkNode 析构函数" << endl;}

public:
    int data;
    // 使用 weak_ptr 代替 shared_ptr
    weak_ptr<LinkNode> prev;
    weak_ptr<LinkNode> next;
};


void test()
{
    // 创建两个链表节点
    shared_ptr<LinkNode> node1(new LinkNode(10));
    shared_ptr<LinkNode> node2(new LinkNode(20));

    // 建立节点之间的关系
    node1->next = node2;
    node2->prev = node1;
}

六、工厂函数

1.make_unique

std::make_unique 是一个 C++14 中引入的函数模板,用于创建 std::unique_ptr 实例。它的目的是简化创建动态分配对象的过程,并且提供了更好的异常安全性。在使用 std::make_unique 时,你只需提供要动态分配的对象的类型和构造函数参数,函数会返回一个包装了这个动态分配对象的 std::unique_ptr

class Person
{
public:
    Person(){cout << "构造函数" << endl;}

    Person(int, int){cout << "构造函数" << endl;}

    ~Person(){cout << "析构函数" << endl;}
};

void test()
{    
    // 使用默认构造
    unique_ptr<Person> up1 = make_unique<Person>();
    // 使用有参构造
    unique_ptr<Person> up2 = make_unique<Person>(10, 20);
    // 创建对象数组
    unique_ptr<Person[]> up3 = make_unique<Person[]>(3);
}

make_unique函数和unique_ptr构造函数创建智能对象时的区别是什么呢?

1. 从语法角度:make_unique 函数比 unique_ptr 语法更加简洁
2. 从安全角度:make_unique 是异常安全的,而 unique_ptr 构造函数创建方式则不是。

        异常安全性是指当函数在执行过程中发生异常时,程序依然能够保持数据结构的一致性和资源的正确释放。对于 make_unique,它保证了在异常发生时内存会被正确释放,即使在内存分配后抛出异常也不会导致内存泄漏。

异常安全

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

class Person
{
public:
    Person()
    {
        cout << "Person 构造函数" << endl;
    }

    ~Person()
    {
        cout << "Person 析构函数" << endl;
    }
};

void do_logic(unique_ptr<Person> uq, int number) {}


int get_number()
{
    cout << "get_number" << endl;
    throw exception();
    return 100;
}


int main()
{
    try
    {
        // 1. 构造函数 异常不安全
        do_logic(unique_ptr<Person>(new Person), get_number());
        // 2. make_unique 异常安全
        // do_logic(make_unique<Person>(), get_number());
    }
    catch (...)
    {
        cout << "错误处理..." << endl;
    }

    return 0;
}

我们运行这段代码时发现:

在vc++里输出结果为:

// unique_ptr
Person 构造函数
get_number
错误处理...

// make_unique
get_number
错误处理...

对于上面的运行结果,显然发生了内存泄漏。这是因为 do_logic 函数在执行前,要先构造参数,而参数的顺序并没有规定,会导致不同的编译器不同的顺序。

2.make_shared

class Person
{
public:
    Person(int, int) {cout << "无参构造函数" << endl;}

    ~Person(){cout << "析构函数" << endl;}
};

void test()
{
    shared_ptr<Person> sp = make_shared<Person>(10, 20);
}

注意make_shared 函数并不支持创建用于管理动态对象数组的 shared_ptr 对象,这个和 make_unique 是有区别的。

1.make_shared 函数和 shared_ptr 构造函数创建的 shared_ptr 对象有什么不同呢?

  • make_shared 是异常安全的,而构造函数创建方式并不总是异常安全
  • make_shared 创建 shared_ptr 对象的效率更高

我们着重了解一下为什么make_shared 创建 shared_ptr 对象的效率更高?

shared_ptr<Person> sp(new Person(10, 20));

当创建 sp 对象时,过程如下:

  1. 首先,在堆上创建 Person 动态对象,需要分配一次内存。
  2. 然后,在堆上创建引用计数对象,需要分配一次内存。
  3. 最后,在栈上创建 shared_ptr 对象。

从上面过程来看,很显然,我们需要为 Person 动态对象、引用计数对象动态分配两次内存。

而当使用 make_shared 函数时:

  1. 一次性分配 Person 动态对象和引用计数对象所需要的内存,并在该内存中构建 Person 对象和引用计数对象。
  2. 然后,在栈上创建 shared_ptr 对象。

从该过程中,我们发现 make_shared 函数会比 shared_ptr 构造函数创建方式减少一次动态内存的申请和释放,进而提升了 shared_ptr 的构建效率。

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

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

相关文章

普元EOS-基于CriteriaEntity进行数据查询

1 前言 普元EOS内置了一系列数据库的操作类&#xff0c;本文介绍其中的一个类 CriteriaEntity的使用方法。 CriteriaEntity是进行组织数据库查询条件的类&#xff0c;基于该类配合DataObject&#xff0c;实现对数据库的查询。 2 CriteriaType类的实例化 要利用Criteria进行查…

LlamaIndex 实现 RAG (一)

理解过 LlamaIndex 的功能之后&#xff0c;本文通过 LlamaIndex 快速实现一个简单的 RAG 应用&#xff0c;主要包括以下几个部分&#xff1a; 创建知识库&#xff0c;并进行 Embedding集成本地 Ollama 模型或者 Qwen 模型通过 Streamlit 可视化 RAG 文末提供了源代码地址 创…

HarmonyOS开发实战:应用权限/通知设置跳转方案

场景描述 引导用户跳转到系统设置页进行权限&#xff0c;通知的相关设置&#xff0c;类似android和iOS应用中常见的应用内跳转到设置进行通知开启或权限设置的操作。 应用经常会遇到如下的业务诉求&#xff1a; 场景一&#xff1a;如果应用首次拒绝了消息通知&#xff0c;应…

免费高效:2024年四大视频剪辑软件推荐!

不管是不是专业人士&#xff0c;相信大家多多少少都会有视频剪辑的需求&#xff0c;对于很多新手来说&#xff0c;一款好用且免费的视频剪辑工具十分必要&#xff0c;接下来就为大家推荐几个好用的视频剪辑免费软件&#xff01; 福昕视频剪辑 链接&#xff1a;www.pdf365.cn/…

Linux(CentOS7)虚拟机安装教程

创建虚拟机 自定义高级&#xff0c;就下一步 选择Workstation 17.x,完好后就继续下一步,下面就如图所示 虚拟机内存看情况加 磁盘大小也看情况加 完成&#xff01; 开启此虚拟机 鼠标放进去直接回车 可能有点慢&#xff0c;请耐心等待 一.进入日期时间 二.进入软件选择 三.配置…

[创业之路-138] :产品需求、产品研发、产品生产、库存管理、品控、售后全流程 - 时序图

目录 一、产品研发全流程 1. 客户/市场需求 2. 供应链采购 3. 设计研发 4. 库房管理 5. 品控质检 6. 物流运输 7. 客户现场验证 8. 返修售后 二、产品生产全流程 1. 客户/市场需求 2. 供应链采购 3. 生产加工 4. 库房管理 5. 品控质检 6. 物流运输 7. 客户现场…

物理可微分神经算法:深度学习与物理世界的桥梁

物理可微分神经算法&#xff1a;深度学习与物理世界的桥梁 前言物理可微分神经算法的核心PyTorch中的实现讨论与展望结语 前言 在这个信息爆炸的时代&#xff0c;人工智能&#xff08;AI&#xff09;已成为推动技术革新的关键力量。深度学习&#xff0c;作为AI领域的一个重要分…

CAPL如何实现在网络节点中添加路由Entry

其实不只是CANoe的网络节点,所有设备的应用程序如果要通过Socket套接字发送报文,在网络层都需要根据路由表里配置的路由条目选择发送路径。这个路由条目可以是静态配置,也可以是自动添加。 如果CANoe的网络节点添加一个网络接口,配置IP地址和子网掩码: 说明此网络节点在1…

外挂程序:增强点及辅助

1.关于前几篇介绍的外挂程序,SAP中的业务单据还是要区分具体的操作人员。如建立财务凭证,工号A,B,C使用相同的SAP账号,那就没办法知道是谁操作的了啊,所以sap的业务单据需要细分到具体人员的都要增强实现以下: 如生产工单: 具体的增强点: 2.辅助程序:SAP账号自动锁定功…

【Redis】基本全局命令

Redis的基本全局命令 keysexistsdelexpirettltype Redis 有 5 种数据结构&#xff0c;但它们都是键值对种的值&#xff0c;对于键来说有⼀些通⽤的命令。 keys 返回所有满足样式 &#xff08;pattern&#xff09;的key。支持如下统配样式。 h?llo 匹配 hello , hallo 和 hxl…

D-ID 推出人工智能视频翻译工具,拥有语音克隆和口型同步等功能

D-ID公司以其创新的人工智能技术在视频创作领域取得了突破性进展。这家人工智能视频创作平台最近推出了一项新工具&#xff0c;允许用户将视频翻译成多达30种不同的语言&#xff0c;包括阿拉伯语、普通话、日语、印地语、西班牙语和法语等。这项技术不仅能够自动翻译视频内容&a…

面试题 08.06. 汉诺塔问题(整活版)(不讲武德)

题目具体要求看面试题 08.06. 汉诺塔问题(递归法)-CSDN博客 class Solution { public:void hanota(vector<int>& A, vector<int>& B, vector<int>& C) {CA;A.clear();} };

Blender新手入门笔记收容所(二)

材质篇 学习来源&#xff1a;B站 【Kurt】Blender零基础入门教程 | Blender中文区新手必刷教程(已完结) Blender材质基础 PBR(Physically Based Rendering)&#xff1a;基于物理的渲染BSDFBRDF(反射)BTDF(透射) 原理化BSDF详解 中间部分利用率80% 材质篇第一节课笔记 纹…

健身房预约小程序,提高市场竞争力

随着“全民健身”的风靡&#xff0c;各大健身场所受到了较大的关注&#xff0c;健身市场的发展迎来了爆发期&#xff01;健身房预约系统是一个在线预约管理系统&#xff0c;对于健身房来说&#xff0c;一个操作简单、功能齐全的预约系统至关重要&#xff0c;他不仅可以帮助学员…

代码随想录打卡第六十一天

代码随想录–图论部分 day 62 图论第十一天&#xff08;完结&#xff09; 文章目录 代码随想录–图论部分一、卡码网97--小明逛公园二、卡码网126--骑士的攻击总结 一、卡码网97–小明逛公园 代码随想录题目链接&#xff1a;代码随想录 给定一个公园景点图&#xff0c;图中有…

Flink常用转换(transformation)算子使用教程(DataSTream API)

前言 一个 Flink 程序,其实就是对 DataStream 的各种转换。具体来说,代码基本上都由以下几部分构成,如下图所示: 获取执行环境(execution environment)读取数据源(source)定义基于数据的转换操作(transformations)定义计算结果的输出位置(sink)触发程序执行(exec…

Linux入门——06 基础IO

1.什么是当前路径 exe -> /home/lin/Desktop/Linux_learn/fork_learn/test 当前进程执行是磁盘路径下的哪一个程序 cwd -> /home/lin/Desktop/Linux_learn/fork_learn 当前进程的工作目录------》当前进程 1.1当前路径这个地址能改吗&#xff1f; 可以&#xff0c;使…

spring-boot-maven-plugin:repackage分析

springboot项目打包插件为&#xff1a;spring-boot-maven-plugin,使用如下: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance&…

【python实现通过复数进程互相检测防止关闭和删除】

python实现通过复数进程互相检测防止关闭和删除 要使用 Python 实现通过多个进程互相检测来防止关闭和删除&#xff0c;可以使用 multiprocessing 模块来创建多个进程&#xff0c;并通过进程间通信来实现心跳检测。以下是一个简单的示例代码&#xff0c;展示了如何使用两个进程…

【学术前沿】基于非易失性存储器硬件特性的存算一体神经网络设计方法

【学术前沿】基于非易失性存储器硬件特性的存算一体神经网络设计方法 Lixia HAN, Peng HUANG, Yijiao WANG, Zheng ZHOU, Haozhang YANG, Yiyang CHEN, Xiaoyan LIU & Jinfeng KANG, Mitigating Methodology of Hardware Non-ideal Characteristics for Non-volatile Memo…