【C++】内存管理,RALL原则和智能指针

news2024/12/25 9:05:40

1 C++资源管理

C++资源管理是确保程序运行效率和稳定性的关键。资源管理涉及变量、参数的存储和生命周期控制,以及动态内存的分配和释放。C++通过一套内存管理机制来实现资源的有效分配和管理。

1.1 内存管理

为适用不同场景,C++提供了多种内存管理方式,以适用不同的使用场景。

1.1.1 内存分区

栈:自动分配和释放,存储静态局部变量、函数参数、返回值等,栈向下增长;

堆:手动分配和释放,用于程序运行时动态内存分配,堆向上增长;

数据段:用于存储全局数据和静态数据,即全局变量或static修饰的变量;

代码段:可执行的代码(以二进制形式存储)和只读常量;

内存映射段:是高效的I/O隐射方式,用于装载一个共享的动态内存库,用户可使用系统接口创建共享内存,做进程间通信。

1.1.2 内存分配方式

1.1.2.1 自动分配

由栈管理的内存可进行自动内存管理,由编译器自动处理。如局部变量和函数参数等,当函数调用时被创建,其生命周期与函数执行时间相同,当函数返回时,这些变量自动销毁,内存自动释放。

1.1.2.2 手动分配

由堆管理的内存需进行手动分配,由程序员进行控制。在C++中通过new和delete操作符进行动态内存管理。

  1. new/delete操作内置类型

    申请和释放单个元素的空间,使用new和delete操作符,申请和释放连续的空间,使用new[]和delete[]。对用内置类型而言,用malloc和new,除了用法不同,没有什么区别。

  2. new/delete操作自定义类型

    申请空间时,malloc只开空间,new既开空间又调用构造函数初始化(先调用operator new开空间,再调用构造进行初始化);释放空间时,delete会调用析构函数(先调用析构函数,再调用operator delete),free不会

1.1.3 内存管理问题

内存泄漏: 内存泄漏发生在分配了内存但未正确释放的情况下。

悬空指针: 野指针是指向已经释放或无效内存的指针,使用野指针可能导致程序奔溃或未定义行为。

内存碎片: 在堆上频繁的使用new和delete分配和释放内存可能会导致内存碎片,影响程序性能,使用内存池或大块内存分配策略可以减少碎片。

1.2 RALL原则

RALL是为高效管理内存资源提出的一种编程范式,利用对象的生命周期管理资源的获取和释放。RALL(Resource Acquisition is Initialization)资源获取即初始化。核心思想是将资源的获取与对象的初始化绑定,将资源的释放与对象的销毁绑定。这样做的目的是确保在任何情况下,一旦资源被成功获取,它最终都会被正确释放,从而避免资源泄漏和重复释放的问题。

1.2.1 工作原理

在C++中,对象的生命周期是明确的:对象在创建时构造函数被调用,在对象生命周期结束时析构函数被调用。RAII利用这一特性,通过在构造函数中获取资源,在析构函数中释放资源,来管理资源的生命周期。其基本工作流程是:创建对象时,先开辟空间,再调用构造进行初始化,对象销毁时,先调用析构函数,再释放空间。

1.2.2 堆上内存的深浅拷贝问题

1.2.2.1 浅拷贝(shallow copy)

在浅拷贝中,只复制对象中的值,而不复制指向动态分配内存的指针。这意味着两个对象共享相同的内存资源。如果其中一个对象修改了共享的资源,另一个对象也会受到影响。

*data
*data
obj1
堆区
obj2
#include <iostream>

class ShallowCopyExample {
public:
    int *data;

    ShallowCopyExample(const ShallowCopyExample &other) {
        // 浅拷贝
        data = other.data;
    }
};

int main() {
    ShallowCopyExample obj1;
    obj1.data = new int(42);

    ShallowCopyExample obj2 = obj1;  // 浅拷贝

    // 修改obj2的data,将影响obj1
    *(obj2.data) = 84;

    std::cout << *(obj1.data) << std::endl;  // 输出 84
    std::cout << *(obj2.data) << std::endl;  // 输出 84

    delete obj1.data;

    return 0;
}

1.2.2.2 深拷贝(Deep copy)

​ 在深拷贝中,不仅复制对象的值,还复制指向动态分配内存的指针所指向的实际数据。这样,两个对象将拥有彼此独立的内存副本,修改一个对象不会影响另一个对象。

*data
*data
obj1
堆区
obj2
新堆区
#include <iostream>

class DeepCopyExample {
public:
    int *data;

    DeepCopyExample(const DeepCopyExample &other) {
        // 深拷贝
        data = new int(*(other.data));
    }

    ~DeepCopyExample() {
        delete data;
    }
};

int main() {
    DeepCopyExample obj1;
    obj1.data = new int(42);

    DeepCopyExample obj2 = obj1;  // 深拷贝

    // 修改obj2的data,不会影响obj1
    *(obj2.data) = 84;

    std::cout << *(obj1.data) << std::endl;  // 输出 42
    std::cout << *(obj2.data) << std::endl;  // 输出 84

    delete obj1.data;
    delete obj2.data;

    return 0;
}

1.2.2.3 两种拷贝的区别
  1. 在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的;但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象,所以,此时,必须采用深拷贝。
  2. 深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝

1.2.3 RALL原则和构造函数

1.2.3.1 三法则
  1. 析构函数:如果类中存在指针(即堆上资源),你在类中需要手动定义了析构函数,这种情况下可能还需要定义复制构造函数和赋值运算符。
  2. 拷贝构造函数:拷贝构造主要用途是复制对象,确保类中指针在拷贝构造函数中进行深拷贝,以避免资源共享问题,完成新资源副本的创建。
  3. 赋值运算符重载:确保在赋值运算符重载中进行深拷贝和资源释放,防止资源泄漏。
#include <iostream>

class MyClass {
public:
    // 构造函数
    MyClass(int value) {
        data = new int(value);
        std::cout << "Constructor" << std::endl;
    }

    // 复制构造函数
    MyClass(const MyClass& other) {
        // 深拷贝资源
        data = new int(*(other.data));
        std::cout << "Copy Constructor" << std::endl;
    }

    // 赋值运算符重载
    MyClass& operator=(const MyClass& other) {
        if (this != &other) {
            // 释放已有资源
            delete data;

            // 深拷贝资源
            data = new int(*(other.data));
        }
        std::cout << "Copy Assignment Operator" << std::endl;
        return *this;
    }

    // 析构函数
    ~MyClass() {
        // 释放资源
        delete data;
        std::cout << "Destructor" << std::endl;
    }

    // 显示数据
    void displayData() const {
        std::cout << "Data: " << *data << std::endl;
    }

private:
    int* data; // 示例成员,假设是一个动态分配的整数
};

int main() {
    // 创建对象1
    MyClass obj1(42);
    obj1.displayData();

    // 使用复制构造函数创建对象2
    MyClass obj2 = obj1;
    obj2.displayData();

    // 使用赋值运算符创建对象3
    MyClass obj3(100);
    obj3 = obj1;
    obj3.displayData();

    return 0;
}
1.2.3.2 五法则

在C++11及以上的标准中,为了解决大数据对象拷贝时资源消耗的问题,引入了移动语义,通过转移资源所有权的方式,完成新对象的创建,这种情况下不在需要堆对象进行深拷贝。而移动语义主要是基于右值引用来实现的。

右值引用:

我们通常说的变量、解引用的指针,这种可以取地址,可以赋值,可以在赋值号左边或者右边的值称为左值,而右值,通常是字面常量,函数返回值、表达式的返回值,是一个即将被销毁的临时变量,不能进行取地址操作,且只能出现在赋值号右侧。

//左值
int* p=new int(0);
int b=1int a=b;
const int c=3;
//右值
int&& ri=10;  //10不能出现在赋值号左侧
int&& ri1=x+y;	//同理x+y也不行
int&& ri2=fun(x,y)

左值引用和右值引用就是分别给左值和右值取别名,如上述ri、ri1、ri2。左值和右值在引用的时候有如下特点:

  • 左值引用只能引用左值
int a=10;
int& ra1=a;//左值引用
//int& ra2=10; //10是右值,不能给左值引用
  • const左值引用可以引用左值,也可以引用右值(右值通常不可修改,使用可以用const左值引用)
const int& ra3=10;//const左值引用右值
const int& ra4=a;//const左值引用左值
  • 右值只能引用右值,左值可以通过move来转换为右值进行引用
int&& ri1=10;//右值引用右值
int&& ri2=std::move(a)//右值引用move的左值
  • const右值引用:右值是不能取地址的,但是通过右值引用给右值起别名后,导致右值有了特定的存储位置,是可以去到地址的,这时右值被右值引用后变成了左值,为了避免这种情况下发生对右值的修改,这个时候就要用const右值引用
int&& ri3=20;
ri3++;
const int&& ri4=30;
//ri4++;//不可修改

说清楚右值和右值引用后,再来讨论右值引用和移动构造的关系。

对于函数需要返回操作结果的问题,通常有两种处理方式:设置返回值通过左值引用设置输出型参数

  • 设置返回值的情况:返回值出了函数作用域就会被销毁,因此,需先有个临时变量去接受函数的返回值,然后再将返回值赋值给主函数的接受对象,这个过程涉及多次拷贝,销毁较大,效率较低。

  • 设置输出型参数的情况:通过左值引用设置输出型参数的方式可以避免拷贝的问题,但是会导致函数参数过多,函数定义臃肿。(目前只知道这些,知道的可以补充)。

针对上述情况,右值引用就发挥了其价值,右值引用可以直接接受函数返回值,通过移动对象所有权的方式,避免中间的一系列拷贝操作,大大提高新能。对于自定义的类对象,通过定义拷贝构造函数来实现右值引用,实现性能优化的效果

基于上述内容,又有了两个新的规则,将“三法则”扩展为“五法则”:

  1. 移动构造函数:移动构造用于转移对象资源的所有权,而不是复制,可以避免不必要的资源复制,定义一个移动构造函数,以支持资源的高效转移。

  2. 移动赋值运算符重载:定义一个移动赋值运算符,以支持资源的高效转移。

    #include <iostream>
    
    class MyClass {
    public:
        // 构造函数
        MyClass(int value) : data(new int(value)) {
            std::cout << "Constructor" << std::endl;
        }
    
        // 移动构造函数
        MyClass(MyClass&& other) noexcept : data(other.data) {
            other.data = nullptr;  // 确保源对象处于有效但未指定的状态
            std::cout << "Move Constructor" << std::endl;
        }
    
        // 移动赋值运算符重载
        MyClass& operator=(MyClass&& other) noexcept {
            if (this != &other) {
                delete data;  // 释放已有资源
    
                data = other.data;
                other.data = nullptr;  // 确保源对象处于有效但未指定的状态
            }
            std::cout << "Move Assignment Operator" << std::endl;
            return *this;
        }
    
        // 析构函数
        ~MyClass() {
            delete data;
            std::cout << "Destructor" << std::endl;
        }
    
        // 显示数据
        void displayData() const {
            std::cout << "Data: " << (data ? *data : 0) << std::endl;
        }
    
    private:
        int* data;  // 示例成员,假设是一个动态分配的整数
    };
    
    int main() {
        // 创建对象1
        MyClass obj1(42);
        obj1.displayData();
    
        // 移动构造函数创建对象2
        MyClass obj2 = std::move(obj1);
        obj2.displayData();
    
        // 移动赋值运算符创建对象3
        MyClass obj3(100);
        obj3 = std::move(obj2);
        obj3.displayData();
    
        return 0;
    }
    

这五法则确保了对于拥有资源管理责任的类,它们能够正确地进行资源的获取、释放和转移,从而实现了更安全和更高效的编程。RAII的使用通常与智能指针、容器等C++标准库组件一起,以提供更好的资源管理。

[注:]noexcept 是一个C++11引入的关键字,用于指示一个函数是否可能抛出异常。noexcept 并不是完全禁止函数抛出异常,而是告诉编译器在异常发生时如何处理。如果函数确实抛出异常,而 noexcept 有一个 false 的参数,编译器会调用 std::terminate 来终止程序。

1.2.4 RALL的优点

资源管理: 利用对象的生命周期自动管理资源,减少了手动管理资源的复杂性;

异常安全: 即使在代码执行过程中发生异常,由于析构函数总是会被调用,资源也能被正确释放。

1.3 RALL原则的应用

RALL的典型应用是智能指针和内存池,用于安全的进行堆上内存的分配和释放。

1.3.1 智能指针

智能指针是C++标准库提供的一种模板类,是RALL原则的具体实现,用于自动管理动态分配的内存,以防止内存泄漏和其他与动态内存分配相关的问题。C++标准库提供了四种智能指针:auto_ptr、unique_ptr、shared_ptr、weak_ptr。其中后三种是C++11支持的,并且第一个已经在C++11弃用。

1.3.1.1 auto_ptr

auto_ptr采用管理权转移的方法进行赋值和拷贝构造,假设原先有一个auto_ptr对象p1,要通过p1构造p2,当拷贝构造完成后,用于拷贝构造传参的对象p1中管理资源的指针会被更改为nullptr,赋值也一样,假设p2=p1,p1中资源的管理权会转移给p2,p2原本的资源会被释放。转移管理权的方式容易出现悬空指针的问题,auto_ptr在C++11中已经被摒弃,很多公司命令禁止使用。

1.3.1.2 unique_ptr

独占对象所有权,直接将拷贝构造和赋值禁止,不存在浅拷贝多次释放同一块空间的问题。相比于shared_ptr,由于没有引用计数,性能较好。虽然unique_ptr禁止拷贝和赋值,但可以利用move通过返回值的方式实现拷贝。

unique_ptr<T> ptr_a(new T);
unique_ptr<T> ptr_b=std::move(ptr_a);
1.3.1.3 shared_ptr

共享对象的所有权,通过引用计数的方式较好的解决了拷贝和赋值的问题,相对于unique_ptr,性能较差。share_ptr指向同一个对象,当进行拷贝和赋值时,通过应用计数来实现,当最后一个shared_ptr释放时,资源才会释放。

  • shared_ptr环形引用问题:
//循环引用
struct ListNode
{
    shared_ptr<ListNode> _pre;
    shared_ptr<ListNode> _next;
};
int main(){
    shared_ptr<ListNode> node1(new ListNode);
    shared_ptr<ListNode> node2(new ListNode);
    node1->_next=node2;
    node2->_pre=node1;
    cout<<"node1引用计数:"<<node1.use_count()<<endl;
    cout<<"node2引用计数:"<<node2.use_count()<<endl;
}
1.3.1.4 weak_ptr

不增加引用计数,为解决shared_ptr环形引用的问题而提出,weak_ptr不参与资源的管理和释放,可以使用shared_ptr对象来构造weak_ptr对象,但是不能直接使用指针来构造weak_ptr对象,在weak_ptr中,也没有operator*函数和operator->成员函数,不具有一般指针的行为,因此,weak_ptr严格意义上并不是智能指针,weak_ptr的出现,就是为了解决shared_ptr的循环引用问题。

  • 环形引用问题解决方案:
//weak_ptr不管资源的释放
struct ListNode
{
    weak_ptr<ListNode> _pre;
    weak_ptr<ListNode> _next;
};
int main(){
    shared_ptr<ListNode> node1(new ListNode);
    shared_ptr<ListNode> node2(new ListNode);
    node1->_next=node2;
    node2->_pre=node1;
    cout<<"node1引用计数:"<<node1.use_count()<<endl;
    cout<<"node2引用计数:"<<node2.use_count()<<endl;
}

1.3.2 内存池

内存池(Memory Pool)是一种用于优化动态内存分配和释放操作的技术。它的基本原理是在程序启动时或在明确的时机预先分配一大块内存,然后将这块内存分割成多个固定大小的小块,存储在自由列表(free list)中,当程序需要分配内存时,直接从自由列表中获取一个内存块,这样可以显著减少对操作系统内存分配器的调用次数,从而提高内存分配的效率。

1.3.2.1 内存池的工作原理

内存池的工作原理可以分为以下几个步骤:

  1. 预分配内存:在程序开始时,预先分配一块较大的内存区域。
  2. 管理空闲块:使用链表、栈或数组等数据结构管理可用内存块。
  3. 分配和释放:提供分配和释放接口,让用户从内存池中获取和释放内存。
  4. 回收机制:当内存块被释放时,将其返回到内存池中,便于后续使用。
#include <iostream>
#include <vector>
#include <cassert>

class MemoryPool {
public:
    MemoryPool(size_t blockSize, size_t blockCount)
        : blockSize(blockSize), blockCount(blockCount), freeBlocks(blockCount) {
        // 分配内存池
        pool = ::operator new(blockSize * blockCount);
        char* current = static_cast<char*>(pool);
        
        // 初始化空闲块列表
        for (size_t i = 0; i < blockCount; ++i) {
            freeBlocks[i] = current + i * blockSize;
        }
    }

    ~MemoryPool() {
        // 释放内存池
        ::operator delete(pool);
    }

    void* allocate() {
        if (freeBlocks.empty()) {
            return nullptr; // 没有可用的块
        }
        // 从空闲块列表中取出一个块
        char* block = freeBlocks.back();
        freeBlocks.pop_back();
        return block;
    }

    void deallocate(void* block) {
        assert(block != nullptr); // 确保要释放的块不是nullptr
        // 添加到空闲块列表
        freeBlocks.push_back(static_cast<char*>(block));
    }

    size_t getBlockSize() const {
        return blockSize;
    }

    size_t getBlockCount() const {
        return blockCount;
    }

    size_t getFreeBlockCount() const {
        return freeBlocks.size();
    }

private:
    size_t blockSize;           // 每个内存块的大小
    size_t blockCount;          // 内存池中的块数量
    void* pool;                 // 内存池的起始地址
    std::vector<char*> freeBlocks; // 存储空闲块的列表
};

// 示例使用
int main() {
    const size_t BLOCK_SIZE = 32; // 每个块32字节
    const size_t BLOCK_COUNT = 10; // 总共10个块
    MemoryPool pool(BLOCK_SIZE, BLOCK_COUNT); // 创建内存池

    // 分配内存块
    void* block1 = pool.allocate();
    void* block2 = pool.allocate();
    std::cout << "Allocated blocks: " << block1 << ", " << block2 << std::endl;

    std::cout << "Free blocks: " << pool.getFreeBlockCount() << std::endl;

    // 释放内存块
    pool.deallocate(block1);
    pool.deallocate(block2);
    std::cout << "Free blocks after deallocation: " << pool.getFreeBlockCount() << std::endl;

    return 0;
}
1.3.2.2 内存池的优势
  • 性能提升:通过减少系统调用,提高内存分配和释放的速度。
  • 内存碎片减少:通过统一管理,减少内存碎片的问题。
  • 简化内存管理:可以设计为自动回收机制,降低内存泄漏的风险。
1.3.2.3 内存池的劣势
  • 内存浪费:如果分配的块未被充分利用,可能会造成内存浪费。
  • 复杂性增加:需要额外的代码管理内存池,增加了系统的复杂性。

参考资料

1.C / C++ 内存管理_c和c++的动态管理内存方法-CSDN博客

2.【C进阶】动态内存管理(2)_(char *)malloc(100)-CSDN博客

3.【C++】右值引用(极详细版)-CSDN博客

4.C++:智能指针_c++智能指针-CSDN博客

5.C++ — 智能指针 - 流水灯 - 博客园 (cnblogs.com)

6.【c++复习笔记】——智能指针详细解析(智能指针的使用,原理分析)-CSDN博客

7.c++深拷贝和浅拷贝-CSDN博客

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

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

相关文章

基于Springboot+Vue的校园消费点评系统(含源码数据库)

1.开发环境 开发系统:Windows10/11 架构模式:MVC/前后端分离 JDK版本: Java JDK1.8 开发工具:IDEA 数据库版本: mysql5.7或8.0 数据库可视化工具: navicat 服务器: SpringBoot自带 apache tomcat 主要技术: Java,Springboot,mybatis,mysql,vue 2.视频演示地址 3.功能 这个系…

大顶堆优化dp,带限制的子序列

前言&#xff1a;看到这个题目的时候我们可以用大顶堆记录前面的最大值&#xff0c;这样我们转移的时候就少了很多繁琐的查询 题目地址 class Solution { public:int constrainedSubsetSum(vector<int>& nums, int k) {int n nums.size();vector<int> ans nu…

论文Idea | 人工智能与建筑的交叉领域探索

在人工智能&#xff08;AI&#xff09;与建筑领域的交叉研究中&#xff0c;学者们探索了如何利用AI技术优化建筑设计、施工、管理以及运营等各个环节。以下是关于这一交叉领域的一些关键内容、研究方向、代表性论文及资源。 1.人工智能在建筑领域的应用概述 人工智能技术的快速…

Dev-Cpp 5.11 安装教程【保姆级】

目录 1. 准备软件安装包 2. 软件安装 3. 软件使用过程中必备的预先设置 写在前面 1. 如果你的电脑是Win 7&#xff0c;建议安装Dev-Cpp 5.11&#xff0c;解决软件和系统的兼容问题。具体安装步骤按照本教程进行即可。 2. 如果你的电脑是Win 10&#xff0c;建议安装Embarca…

QML6 项目生成缓存文件取消办法

有时候我们创建QML项目会在 C:\Users\85720\AppData\Local 文件夹下生成临时缓存文件&#xff0c;不注意的话&#xff0c;文件会越来越多&#xff0c;很烦人。 可以试试通过下面方法取消生成qml缓存文件&#xff0c;配置-》构建和运行 这里选 Use qmlcachegen: 禁用

java web 之过滤器Filter

1、概念 当访问服务器的资源时&#xff0c;Filter过滤器可以将请求拦截下来&#xff0c;完成一些特殊的功能。 通常都是用来拦截request进行处理的&#xff0c;也可以对返回的response进行拦截处理。 一般用于完成通用的操作。如&#xff1a;登录验证、统一编码处理、敏感字…

丰田,也杀入料箱AGV市场

导语 大家好&#xff0c;我是社长&#xff0c;老K。专注分享智能制造和智能仓储物流等内容。 新书《智能物流系统构成与技术实践》人俱乐部 在全球仓储自动化领域的竞争日益激烈之际&#xff0c;一场引人注目的合作正在悄然展开。 丰田旗下的物料搬运巨头范德兰德(Vanderlande)…

NSSCTF-WEB-pklovecloud

目录 前言 正文 思路 尝试 结尾 前言 许久未见,甚是想念. 今天来解一道有意思的序列化题 正文 思路 <?php include flag.php; class pkshow {function echo_name(){return "Pk very safe^.^";} }class acp {protected $cinder;public $neutron;public $…

安当数据库透明加密组件:守护您的 ClickHouse 数据安全

数据库简介 ClickHouse 是一款高性能的列式数据库管理系统&#xff0c;专为在线分析处理&#xff08;OLAP&#xff09;设计。它以其卓越的查询性能、强大的 SQL 支持和灵活的可扩展性&#xff0c;在大数据分析领域广受赞誉。无论是实时数据分析、业务智能还是广告技术&#xf…

栈溢出0x0D ret2_dl_runtime_resolve

实际上&#xff0c;dl_runtime_resolve 是通过最后的 字符串 来确定执行那一个函数的&#xff0c;也就是说&#xff0c;可以通过控制这个地址的内容来执行任意函数&#xff0c;比如&#xff1a;system 而 reloc_arg 是我们可控的&#xff0c;我们需要控制reloc_arg 间接控制 最…

【096】基于SpringBoot+Vue实现的私人健身与教练预约管理系统

系统介绍 视频演示 基于SpringBootVue实现的私人健身与教练预约管理系统 文档 PPT 源码 数据库脚本 课程设计 基于SpringBootVue实现的私人健身与教练预约管理系统采用前后端分离的架构方式开发&#xff0c;系统整体设计了管理员、教练、用户三种角色&#xff0c;实现了用户查…

根据Vue对比来深入学习React 下 props 组件传值 插槽 样式操作 hooks 高阶组件 性能优化

文章目录 函数组件的特点props组件间的传值父传子看上例子传父兄弟组件传值祖先组件传值 插槽基础插槽具名插槽作用域插槽 样式操作**CSS Modules** 生命周期useRef常用hookuseStateuseEffectuseContextuseReduceruseMemouseCallback 高阶组件什么时候使用 react性能问题和优化…

LeetCode讲解篇之2266. 统计打字方案数

文章目录 题目描述题解思路题解代码题目链接 题目描述 题解思路 我们使用逆向思维发现如果连续按存在三个字母的按键&#xff0c;最后一个按键表示的字母可以是某个字母连续出现一次、两次、三次这三种情况的方案数之和 我们发现连续按存在三个字母的按键&#xff0c;当连续按…

数据治理中的核心 元数据

数据治理中的核心元素——元数据 一、关于元数据 1、什么是元数据 元数据&#xff08;metadata&#xff09;是关于数据的组织、数据域及其关系的信息&#xff0c;简单来说&#xff0c;元数据就是被用来描述数据的数据。 概念阐述总归生涩&#xff0c;下面用几个简单的例子来…

【千图网-登录_注册安全分析报告】

前言 由于网站注册入口容易被黑客攻击&#xff0c;存在如下安全问题&#xff1a; 1. 暴力破解密码&#xff0c;造成用户信息泄露 2. 短信盗刷的安全问题&#xff0c;影响业务及导致用户投诉 3. 带来经济损失&#xff0c;尤其是后付费客户&#xff0c;风险巨大&#xff0c;造…

【双指针算法】移动零

1.题目解析 2.算法分析 可以归结为数组划分/数组分块&#xff08;采用双指针算法&#xff09;-->利用数组下标充当指针 &#xff08;1&#xff09;首先定义两个指针 dest&#xff1a;已处理的区间内&#xff0c;非零元素的最后一个位置cur&#xff1a;从左往右扫描数组&…

Stm32+Esp8266连接阿里云程序移植教程(MQTT协议)

Stm32Esp8266连接阿里云程序移植教程&#xff08;MQTT协议&#xff09; 一、前期准备二、移植过程三、程序的使用3.1 连接上阿里云3.2 传输用户数据到阿里云3.3 解析从阿里云下发给用户的数据3.4 关于调试接口 一、前期准备 自己要的工程文件移植所需的文件&#xff08;如下图&…

CentOS 7.9安装MySQL

下载Linux版MySQL安装包 下载地址https://downloads.mysql.com/archives/community/ 下载解压后 安装&#xff0c;按照从上至下顺序&#xff0c;一条一条执行即可安装完毕。 进入到rpm所在目录rpm -ivh mysql-community-common-8.0.26-1.el7.x86_64.rpm rpm -ivh mysql-comm…

计算机网络——CDN

空间编码例子&#xff1a;不是发送N个相同颜色值&#xff0c;而是仅发送2个值&#xff0c;颜色和重复个数 时间编码例子&#xff1a;不是发送i1帧的全部编码&#xff0c;而是仅发送帧i差别的地方 视频播放时&#xff0c;先下载manifest file文件——>解析&#xff08;不…

vscode中关闭cmake自动配置

前言 最近误触了一个操作&#xff0c;导致&#xff0c;一旦使用vscode打开项目&#xff0c;就会去配置cmake。或者你一旦更改cmakelists.txt&#xff0c;就会去配置cmake。 这个操作&#xff0c;结果对不对还另说&#xff0c;关键是增加计算机开销&#xff0c;使得vscode打开后…