单例模式 C++

6 种 单例 的手写,都是懒汉(饿汉代码在 “懒汉 / 饿汉的区别”)

目录

✊前言 

🌼GPT解析

🌼概念解析

RAII

懒汉 / 饿汉的区别

特点

举例

单例 -- 伪代码

适用场景

单例 -- 实现方式

优缺点

🎂手写 6 种单例模式

🐬(一)懒汉 -- 内存泄露

🐬(二)懒汉 -- 解决内存泄漏

🐆(三)懒汉 -- 双检锁

🐆(四)原子操作

🐘(五)C++11 -- magic static

🐘(六)模板类


“单例模式”实质上就是创建全局对象(全局访问节点 + 一个类一个实例)。如同普通的使用全局变量,在绝大多数场景下这并不是一个好做法,应当先检查一下设计是否合理
当你确认要在C++11及以后的版本中使用单例模式时,最佳的实现只有一个:magic static

被很多博客津津乐道的双重检查锁定是过时、复杂且错误的,原因参见👇

C++和双重检查锁定模式(DCLP)的风险_双检测锁单例 volatail c++-CSDN博客
(什么?你问C++11之前?对不起,由于标准还没有规定内存模型,所以做不到严格的线程安全)
最后重复强调一次,单例模式在绝大多数情况下是糟糕的设计,对着单例模式的八百种实现方法雕花更是一件浪费生命的事情

✊前言 

TinyWebServer 涉及到单例模式,花个 5 小时学习一下

参考文章👇 + GPT

【C++】C++ 单例模式总结(5种单例实现方法)_单例模式c++实现-CSDN博客

InterviewGuide大厂面试真题

C++ 线程安全的单例模式总结 - 掘金 (juejin.cn)

C++ 单例模式总结与剖析 - 行者孙 - 博客园 (cnblogs.com)

C++ 与 设计模式(比较重要👇)

单例设计模式 (refactoringguru.cn)

🌼GPT解析

概念

单例模式是一种创建型设计模式,确保类只有一个实例,并提供一个全局访问点来访问该实例

通过将类的构造函数私有化,限制外部代码无法直接实例化该类,然后通过静态方法或静态成员变量来返回类的 唯一 实例

优势

  1. 全局访问点:通过单例模式,可以在程序中任何地方访问该单例对象,方便统一管理
  2. 节约资源:由于单例模式只创建一个实例,可以节约资源,避免多次创建对象
  3. 避免竞争条件:单例模式可以避免多线程环境下的竞争条件,确保线程安全
  4. 延迟初始化:可以延迟创建实例,只有在需要时才进行实例化

劣势

  1. 隐藏依赖关系:单例模式会引入全局状态,增加模块之间的耦合,使得代码更难测试和维护
  2. 不适合动态配置:当需要变化的对象时,单例模式可能无法满足需求
  3. 可能导致性能问题:在高并发场景下,单例模式可能成为瓶颈,需要考虑并发访问时的性能问题

应用

  1. 日志系统:在Web服务器中,可以使用单例模式来实现日志系统,确保所有模块都可以共享同一个日志对象
  2. 数据库连接池数据库连接池是Web服务器中经常使用的组件,可以使用单例模式确保连接池的 唯一 性
  3. 配置管理器:使用单例模式来管理服务器的配置信息,保证配置信息的 一致性和全局可访问性

🌼概念解析

RAII

RAII 是 Resource Acquisition Is Initialization 的缩写,指的是资源获取即初始化。它是一种 C++ 中的编程技术,用于管理资源的生命周期

RAII 的基本思想是将资源的获取和释放操作封装在对象的构造函数和析构函数中。通过在对象的构造函数中获取资源,在析构函数中释放资源,可以确保资源在对象生命周期内始终正确地被获取和释放

这种技术的好处是可以避免因为忘记手动释放资源而导致的资源泄露问题。当对象离开作用域时,其析构函数会自动调用,从而触发资源的释放操作。这种自动化的资源管理能够提高代码的可靠性和可维护性

在版本二的示例中就使用了 RAII 技术,通过在单例类的构造函数中获取实例,在析构函数中释放实例。这样可以确保单例对象在程序退出时被正确释放,避免内存泄漏问题

懒汉 / 饿汉的区别

  • 懒汉式 -- 时间换空间 -- 访问量较(推荐内部静态变量的懒汉单例,代码量少)
  • 饿汉式 -- 空间换时间 -- 访问量较OR 线程较多的的情况
  1. 懒汉模式(Lazy Initialization)

    • 在懒汉模式中,单例对象在第一次调用 GetInstance() 函数时才被创建。也就是说,单例对象的实例化被推迟到需要使用时才进行
    • 这种延迟加载的方式可以节省一些初始资源和提高程序的启动速度
    • 然而,懒汉模式需要在多线程环境下考虑线程安全性。例如,当多个线程同时调用 GetInstance() 函数时,可能会导致创建多个实例的问题。因此,需要额外的同步措施来保证线程安全
  2. 饿汉模式(Eager Initialization)

    • 在饿汉模式中,单例对象在程序启动时就被创建,并且在整个程序运行期间都存在
    • 这种方式确保了在任何时刻都能够访问到单例对象,而不需要考虑多线程环境下的同步问题
    • 然而,饿汉模式可能会带来一些性能开销,因为单例对象的初始化发生在程序启动时

懒汉模式

// Singleton类的定义
class Singleton {
public:
    // 获取单例对象的静态成员函数
    static Singleton* GetInstance() {
        // 如果instance为空,即还没有创建单例对象
        if (instance == nullptr) {
            // 创建一个新的Singleton对象
            instance = new Singleton();
        }
        // 返回单例对象的指针
        return instance;
    }

private:
    // 构造函数和析构函数,设置为默认实现
    Singleton() = default;
    ~Singleton() = default;
    // 禁用拷贝构造函数和赋值运算符重载,确保单例对象唯一性和不会被复制
    Singleton(const Singleton&) = delete;
    Singleton& operator=(const Singleton&) = delete;

    // 静态成员变量,指向单例对象。初始值为nullptr,表示还未创建单例对象
    static Singleton *instance;
};

// 初始化静态成员变量
Singleton* Singleton::instance = nullptr;

懒汉 -- 解释

  1. Singleton::instance:这表示instanceSingleton类的静态成员变量。静态成员变量只有 一份内存,属于整个类的 共享资源

  2. Singleton*:这表示instance是一个指向Singleton对象的指针。因为instanceSingleton类的静态成员,所以它是一个类范围内的静态指针

  3. Singleton::GetInstance():这是一个静态成员函数,用于获取单例对象的指针。在函数内部,如果instance为空(即指向nullptr),则创建一个新的Singleton对象并将其赋值给instance,然后返回instance的值

  4. Singleton()~Singleton():这是构造函数和析构函数的定义,在这里被设置为默认实现

  5. Singleton(const Singleton&) = delete;Singleton& operator=(const Singleton&) = delete;:这两行代码禁用了拷贝构造函数和赋值运算符重载,确保单例对象不会被复制或赋值

需要时才创建单例对象,同时通过禁用拷贝构造和赋值操作来确保单例的唯一性和不被复制

饿汉模式

class Singleton {
public:
    static Singleton* GetInstance() {
        return instance;
    }

private:
    Singleton() = default; // 默认构造函数,设为 private,防止外部实例化对象
    ~Singleton() = default; // 默认析构函数,设为 private,防止外部删除对象
    Singleton(const Singleton&) = delete; // 删除拷贝构造函数,防止对象被复制
    Singleton& operator=(const Singleton&) = delete; // 删除赋值运算符重载,防止对象被赋值

    static Singleton *instance; // 静态成员变量,指向单例对象

};

Singleton* Singleton::instance = new Singleton(); // 在程序启动时即创建单例对象

特点

  • 将默认构造函数设为私有, 防止其他对象使用单例类的 new运算符
  • 新建一个静态构建方法作为构造函数。 该函数会 “偷偷” 调用私有构造函数来创建对象, 并将其保存在一个静态成员变量中。 此后所有对于该函数的调用都将返回这一缓存对象

举例

政府是单例模式的一个很好的示例。 一个国家只有一个官方政府。 不管组成政府的每个人的身份是什么, ​ “某政府” 这一称谓总是鉴别那些掌权者的全局访问节点

单例 -- 伪代码

/* 
数据库类会对`getInstance() 获取实例`方法
进行定义,以便客户端在程序各处都能访问
相同的数据库连接实例
*/
class Database is
    // 保存单例实例的成员变量 -- 声明为静态
    private static field instance : Database

    // 单例的构造函数 -- 私有,防止`new`运算符
    // 直接调用构造方法
    private constructor Database() is
        // 初始化代码(eg:数据库服务器的实际连接)

    // 用于控制对单例实例的访问权限的静态方法
    public static method getInstance() is
        if (Database.instance == null) 
            acquireThreadLock() 
                // 确保在该线程等待解锁时
                // 其他线程没有初始化该实例
                if (Database.instance == null) 
                    Database.instance = new Database()
        return Database.instance

    // 最后,任何单例都必须定义一些可在其实例上执行的业务逻辑
    public method query(sql) is
        // eg: 所有数据库查询请求都需通过该方法进行
        // 因此,可以在这里添加限流或缓冲逻辑

class Application is
    method main() is
        Database foo = Database.getInstance()
        foo.query("SELECT ...")

        Database bar = Database.getInstance()
        bar.query("SELECT ...")
        // 变量 `bar` 和 `foo` 中包含同一个对象

适用场景

🧜‍1)某个类,对于所有客户端,只有一个可用实例

单例模式禁止通过,特殊构建方法以外的方式,创建自身类的对象

特殊构建方法可以创建一个新对象,如果该对象已被创建,则返回已有对象

🤴2)需要严格控制全局变量

单例模式保证类只存在一个实例

除了单例类自身,无法通过任何方式,替换缓存的实例

PS:通过修改 getInstance() 获取实例的代码,可以设置生成单例的数量

单例 -- 实现方式

1,私有静态成员变量 -- 保存实例

2,公有静态构建方法 -- 获取实例

3,静态方法中,实现延迟初始化 -- 首次被调用创建新对象,存储在静态成员变量中,此后每次被调用都返回该实例

4,私有类的构造函数 -- 只有类的静态方法,可以调用该构造函数

5,检查客户端代码,构造函数的调用 -- 替换 为,静态构建方法的调用

优缺点

单例拥有与全局变量相同的优缺点。 尽管它们非常有用, 但却会破坏代码的模块化特性 

优点 

1)一个类一个实例

2)指向该实例的全局访问节点

3)仅在首次请求单例对象时初始化

缺点

1)违反 “单一职责原则”,单例模式同时解决了 2 个问题

2)可能掩盖不良设计,eg:各组件间了解过多

3)多线程下,需特殊处理,避免多个线程多次创建单例对象

4)单例客户端代码的单元测试,较为困难 -- 许多测试框架,基于继承的方式创建模拟对象

但是单例模式的构造函数 -- 私有 && 大部分语言无法重写静态方法

--> 所以,单例模式与测试代码不要共存最好

🎂手写 6 种单例模式

大厂面试真题

🐬(一)懒汉 -- 内存泄露

// 版本一:简单懒汉模式,存在内存泄漏和线程安全问题

class Singleton1 {
public:
    // 获取单例实例的静态方法
    // 用于获取 Singleton1 类的唯一实例
    static Singleton1* GetInstance() { 
        if (instance == nullptr)
            instance = new Singleton1(); // 创建新实例
        return instance; // 返回 Singleton1 实例
    }

private:
    // 防止外界 构造/删除 对象
    Singleton1() = default; // 私有构造
    ~Singleton1() = default; // 私有析构函数
    Singleton1(const Singleton1&) = default; // 禁止拷贝构造
    // 禁止赋值
    Singleton1& operator = (const Singleton1&) = default;

    // 静态成员指针,指向唯一实例
    static Singleton1 *instance; 
};

Singleton1* Singleton1::instance = nullptr;
/* 
存在内存泄漏问题,instance 在堆上,需要手动释放
但是外界无法调用delete释放对象(私有静态成员指针)
&& 
线程安全问题 -- 多个线程同时分配内存
*/
  • 这是一个懒汉模式,即在需要时才创建实例
  • 存在内存泄漏问题,因为instance是在堆上分配的,需要手动释放,但外部无法调用delete来释放对象

🐬(二)懒汉 -- 解决内存泄漏

补充知识👇

1)

std::atexit - cppreference.com

2) C/C++程序终止时执行的函数——atexit()函数详解-腾讯云开发者社区-腾讯云 (tencent.com)

3)

atexit()函数的参数是一个函数指针,函数指针指向一个没有参数没有返回值的 函数

-- 成功时返回零

4)

先返回 #2,再返回 #1,是C++标准库的规定

代码 

// 版本二:解决内存泄漏,但仍存在线程安全问题
class Singleton2 {
public:
    // 获取单例实例的静态方法
    static Singleton2* GetInstance() {
        if (instance == nullptr) {
            instance = new Singleton2();
            atexit(Destructor); // 程序退出时释放
        }
        return instance;
    }

private:
    // 防止外界 构造/删除 对象
    Singleton2() = default; // 私有构造
    ~Singleton2() = default; // 私有析构
    Singleton2(const Singleton2&) = default; // 禁止拷贝构造
    // 禁止赋值 -- 通过定义为私有,避免外界调用,达到了禁止的目的
    Singleton2& operator=(const Singleton2&) = default;

    // 释放实例资源的静态方法
    static void Destructor() {
        if (instance != nullptr) {
            delete instance;
            instance = nullptr;
        }
    }

    // 静态成员指针,指向唯一实例
    static Singleton2 *instance; 
};

Singleton2* Singleton2::instance = nullptr;
/*
解决了内存泄漏,程序退出时释放对象
但仍存在多个线程同时分配内存的问题
*/
  • 在程序退出时通过atexit注册析构函数,解决了内存泄漏问题

🐆(三)懒汉 -- 双检锁

需要注意的是,双检锁 -- Java下正确,Cpp下错误。具体参考👇

Ubuntu Pastebin

补充解释

<mutex> 是C++标准库中用于多线程编程的头文件,提供了互斥量(mutex)和其他与线程同步相关的类和函数

std::lock_guard 是一个RAII风格的互斥量封装类,它可以确保在其作用域结束时释放互斥量

代码中,lock_guard 用于对互斥量进行加锁操作,并且在作用域结束时会自动释放该锁,从而避免了忘记解锁的问题

std::mutex 是C++11引入的互斥量类,用于实现线程之间的互斥访问

mutex_ 是一个全局的 std::mutex 对象,用于保护 instance 的访问,确保在多线程环境下对 instance 进行安全的初始化

lock_guard<mutex> lock(mutex_) 

创建了一个 lock_guard 对象,并在构造函数中对 mutex_ 进行加锁操作

<mutex> 表示使用 std::mutex 作为互斥量类型

这样可以确保在同一时间只有一个线程能够进入临界区,避免了多个线程同时对 instance 进行初始化的问题

关于 双检锁 两个 if 的解释

第一个 if 语句会检查指针 instance 是否为空,如果为空,则表示还没有创建单例实例

进入第一个 if 语句块后,会获取互斥锁 lock,确保只有一个线程可以进入临界区

第二个 if 语句再次检查指针 instance 是否为空

防止多个线程,通过第一个 if 语句的互斥锁后,同时创建实例

第二个 if 的 instance 仍然为空,表示通过的是第一个线程,单例实例可以安全的创建

双检锁 -- 最终效果:只有第一个线程加锁,并创建单例实例

代码

/*
版本三
双重检查锁(double-checked-locking)实现线程安全
*/

class Singleton3 {
public:
    // 获取单例实例的静态方法
    // 静态成员函数 返回 指向 Singleton3 类型对象的指针
    static Singleton3* GetInstance() {
        // 双检锁 - 两个 if,只有指针为空才加锁,避免每次调用
        // GetInstance() 都加锁,降低加锁的开销
        if (instance == nullptr) {
            lock_guard<mutex> lock(mutex_); // 第一个线程加锁
            if (instance == nullptr) {
                instance = new Singleton3(); // 第一个线程创建实例
                atexit(Destructor);
            }
        }
        return instance;
    }

private:
    // 防止外界 构造/删除 对象
    Singleton3() = default; // 私有构造
    ~Singleton3() = default; // 私有析构
    Singleton3(const Singleton3&) = default; // 禁止拷贝
    Singleton3& operator=(const Singleton3&) = default; // 禁止赋值

    // 释放实例资源的静态方法
    static void Destructor() {
        if (instance != nullptr) {
            delete instance;
            instance = nullptr;
        }
    }

    static Singleton3 *instance; // 静态成员指针,指向唯一实例
    static mutex mutex_; // 互斥锁,用于线程安全
}

// 定义静态指针成员 instance,用于保存 Singleton3 类的唯一实例
Singleton3* Singleton3::instance = nullptr;
// nullptr 表示未创建实例
  • 添加互斥锁确保多线程情况下只有一个线程能够创建实例
  • 通过双重检查锁定(double-checked locking)避免不必要的锁开销
  • new的时候的操作分为三步:分配内存、调用构造函数、返回指针

    有可能先返回指针当还没有调用构造函数导致对象没有初始化,其他线程获取到没有初始化的对象

🐆(四)原子操作

补充解释

1)memory_order - cppreference.com

2)std::lock_guard - cppreference.com

3)atomic_thread_fence - cppreference.com

4)

instance.load(std::memory_order_relaxed) 方法来加载 Singleton4 类的静态原子指针 instance

std::memory_order_relaxed,表示对内存访问的轻量级约束,不会引入额外的内存屏障或同步操作

5)

atomic_thread_fence(std::memory_order_acquire): 这是一个原子操作,会创建一个内存屏障(memory fence)以确保前面的读取操作 instance.load 在内存中完成后,后续的读取操作不会受到影响

std::memory_order_acquire 表示获取操作的内存序,用于确保前面的加载操作不会被重排序

6)

lock_guard<mutex> lock(mutex_),创建了一个 lock_guard 对象 lock,它使用了 mutex_ 互斥锁进行初始化

保证在当前作用域中,只有一个线程能够获得 mutex_ 锁的所有权,避免多个线程同时修改共享资源

<mutex> 表示使用 std::mutex 作为互斥量类型

代码 

// 版本四:原子操作 + 内存屏障 保证线程安全

class Singleton4 {
public:
    // 获取实例的静态方法
    // 静态成员函数 返回 指向 Singleton4 类型对象的指针
    static Singleton4* GetInstance() {
        // 加载单例实例指针
        Singleton4* tmp = instance.load(std::memory_order_relaxed);
        // 获取内存屏障 - 确保前面操作完成
        atomic_thread_fence(std::memory_order_acquire);
        if (tmp == nullptr) {
            // 加锁保证线程安全
            std::lock_guard<std::mutex> lock(mutex_); // 加锁
            // 重新加载单例实例指针
            tmp = instance.load(std::memory_order_relaxed);
            if (tmp == nullptr) {
                tmp = new Singleton4; // 创建实例
                // 释放内存屏障 - 确保存储操作不被重排序
                atomic_thread_fence(std::memory_order_release);
                // 存储新创建的实例指针
                instance.store(tmp, std::memory_order_relaxed);
                atexit(Destructor); // 注册析构函数
            }
        }
        return tmp;
    }

private:
    // 防止外界 构造/删除 对象
    Singleton4() = default; // 私有构造
    ~Singleton4() = default; // 私有析构
    Singleton4(const Singleton4&) = default; // 拷贝构造
    Singleton4& operator=(const Singleton4&) = default; // 赋值

    // 释放实例资源的静态方法
    static void Destructor() {
        if (instance != nullptr) {
            delete instance; // 删除实例
            instance = nullptr; // 重置指针
        }
    }

    static std::atomic<Singleton4*> instance; // 原子指针 - 线程安全
    static std::mutex mutex_; // 互斥锁 - 用于双检锁
};

std::atomic<Singleton4*> Singleton4::instance; 
std::mutex Singleton4::mutex_;
  • 使用原子操作保证在多线程环境下的正确性
  • 通过原子操作和锁来保证线程安全
  • 使用原子操作代替互斥锁,提高了并发性能

🐘(五)C++11 -- magic static

补充解释

关于 new 操作带来的 cpu reorder 操作 

简而言之 -- new 和 多线程 可能会产生冲突(需要采取其他措施来规避 BUG)

当程序执行到包含 new 操作符的语句时,需要进行动态内存分配。动态内存分配可能涉及到内存管理的复杂操作,因此编译器和处理器在执行这些操作时会进行一些优化,其中就包括CPU重新排序(CPU reordering)

CPU重新排序是指处理器在执行指令时,可能会对指令的执行顺序进行优化和重新排列,以提高指令执行的效率。这种优化可以通过并行执行指令、延迟加载数据等方式来实现,从而减少处理器的空闲时间,提高整体的执行效率

然而,对于涉及到内存操作的指令(比如动态内存分配和释放),处理器的重新排序可能会导致意想不到的结果。例如,在多线程环境下,如果一个线程执行了内存分配操作,而另一个线程同时进行了访问该内存的操作,由于处理器的重新排序,可能导致访问到未初始化的内存,或者看到不一致的数据,从而引发程序的错误行为

因此,针对使用 new 操作符进行动态内存分配的代码,需要特别注意处理器的重新排序可能带来的影响,确保在多线程环境下能够正确地进行内存访问和操作。而利用静态局部变量实现单例模式可以避免使用 new 操作符,从而避免了因为 new 操作带来的 CPU 重新排序可能带来的潜在问题

代码

有了 C++11 magic static 后,就不用在 懒汉模式 下加锁了,因为静态成员只初始化 1 次

// 版本五:C++11 magic static 特性实现线程安全

class Singleton5 {
public:
    // 获取单例实例
    static Singleton5& GetInstance() {
        /*
        (1)静态局部变量初始化时,并发线程同时进入声明语句,
        并发线程会阻塞等待其初始化结束,保证线程安全
        (2)静态局部变量首次经过它的声明才会被初始化,在其后所有调用中,
        声明都会被跳过
        (3)So, 定义在栈上的局部静态变量保存单例对象的
        优势:延迟加载;系统自动调用析构函数;回收内存;
        没有 new 操作 带来的 cpu reorder 操作;线程安全
        */
        static Singleton5 instance; // magic static 特性
        return instance;
    }
private:
    // 防止外界 构造/删除 对象
    Singleton5() = default; // 私有构造
    ~Singleton5() = default; // 私有析构
    Singleton5(const Singleton5&) = default; // 拷贝构造
    Singleton5& operator=(const Singleton5&) = default; // 赋值
};
  • 利用C++11的magic static特性,确保并发线程等待初始化结束
  • 不需要显式加锁来保证线程安全
  • c++11 magic static 特性:如果当变量在初始化的时候,并发同时进⼊声明语句,并发线程将会阻塞等待初始化结束

🐘(六)模板类

补充解释

Singleton6<DesignPattern> 是将 DesignPattern 类作为模板参数传递给 Singleton6 类,表示 DesignPattern 是 Singleton6 的一个实例化对象。通过这种方式,可以在 Singleton6 类中定义静态的 GetInstance() 方法来获取 DesignPattern 类的唯一实例

 模板复习

(B站黑马C++)模板_c++模板-CSDN博客

👆挑重点敲一遍

代码

// 版本六:模板实现单例模式

template<typename T>
class Singleton6 {
public:
    // 获取单例实例
    static T& GetInstance() {
        static T instance;
        return instance;
    }

protected: // 为了基类可以 构造/析构(仅限类内部和基类使用)
    // 析构虚函数, 支持多态,确保子类析构
    virtual ~Singleton6() = default; 
    Singleton6() = default; // 私有构造
    Singleton6(const Singleton6&) = default; // 禁止拷贝构造
    Singleton6& operator=(const Singlton6&) = default; // 禁止赋值
};

// Singleton6<DesignPattern> 指定模板类型为 DesingnPattern
class DesignPattern : public Singleton6<DesignPattern> {
    // 友元:基类访问父类私有不受限制
    friend class Singleton6<DesignPattern>;

public:
    ~DesignPattern() = default; // 析构在public, 便于手动删除对象

private:
    DesignPattern() = default; // 私有构造
    DesignPattern(const DesignPattern&) = default; // 禁止拷贝
    // 禁止赋值
    DesignPattern& operator=(const Designpattern&) = default;
};

int main()
{
    // 获取单例实例
    DesignPattern &d1 = DesignPattern::Getinstance();
}
  • 利用模板实现单例模式,避免了重复编写单例模式的代码,提高代码复用性

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

JavaScript 的ArrayBuffer 和二进制数组

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 ​ ✨ 正文 简介 在 JavaScript 中&#xff0c;ArrayBuffer 对象代…

Java学习-常用API-新增时间

1.学习JDK8新增时间的原因&#xff1f; 2.JDK8新增了那些时间&#xff1f; 代替calendar的 localDate localTime localDateTime 常用APi及代码示例&#xff1a; ZoneIdZonedDateTime 常用方法 代码示例&#xff1a; 代替Date的 Instant常见方法及其代码示例&#xff1a; 注…

GEE:CART(Classification and Regression Trees)回归教程(样本点、特征添加、训练、精度、参数优化)

作者:CSDN @ _养乐多_ 对于分类问题,这个输出通常是一个类别标签 ,而对于回归问题,输出通常是一个连续的数值。回归可以应用于多种场景,包括预测土壤PH值、土壤有机碳、土壤水分、碳密度、生物量、气温、海冰厚度、不透水面积百分比、植被覆盖度等。 本文将介绍在Google…

Vue3快速上手(三)Composition组合式API及setup用法

一、Vue2的API风格 Vue2的API风格是Options API,也叫配置式API。一个功能的数据&#xff0c;交互&#xff0c;计算&#xff0c;监听等都是分别配置在data, methods&#xff0c;computed, watch等模块里的。如下&#xff1a; <template><div class"person"…

Imgui(1) | 基于imgui-SFML改进自由落体小球

Imgui(1) | 基于imgui-SFML改进自由落体小球 0. 简介 使用 SFML 做2D图形渲染的同时&#xff0c;还想添加一个按钮之类的 GUI Widget, 需要用 Dear Imgui。由于 Imgui 对于2D图形渲染并没有提供类似 SFML 的 API, 结合它们两个使用是一个比较好的方法, 找到了 imgui-SFML 这个…

阿里云服务器公网带宽收费价格表_2024更新报价

阿里云服务器公网带宽怎么收费&#xff1f;北京地域服务器按固定带宽计费一个月23元/M&#xff0c;按使用流量计费0.8元/GB&#xff0c;云服务器地域不同实际带宽价格也不同&#xff0c;阿里云服务器网aliyunfuwuqi.com分享不同带宽计费模式下带宽收费价格表&#xff1a; 公网…

Android---QUMI实现沉浸式状态栏

沉浸式状态栏是指将 App 的状态栏与应用界面进行融合&#xff0c;使得应用界面能够占据整个屏幕的控件&#xff0c;从而提供更加沉浸式的用户体验。通过使用沉浸式状态栏&#xff0c;应用界面可以延伸到状态栏的区域&#xff0c;使得应用界面的内容更加丰富&#xff0c;同时也能…

【开源】SpringBoot框架开发校园疫情防控管理系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 学生2.2 老师2.3 学校管理部门 三、系统展示四、核心代码4.1 新增健康情况上报4.2 查询健康咨询4.3 新增离返校申请4.4 查询防疫物资4.5 查询防控宣传数据 五、免责说明 一、摘要 1.1 项目介绍 基于JAVAVueSpringBoot…

web3知识体系汇总

web3.0知识体系 1.行业发展 2. web3的特点&#xff1a; 1、统一身份认证系统 2、数据确权与授权 3、隐私保护与抗审查 4、去中心化运行 Web3.0思维技术思维✖金融思维✖社群思维✖产业思维”&#xff0c;才能从容理解未来Web3.0时代的大趋势。 3.技术栈 Web3.jsSolidit…

【大数据】Flink on Kubernetes 原理剖析

Flink on Kubernetes 原理剖析 1.基本概念2.架构图3.核心概念4.架构5.JobManager6.TaskManager7.交互8.实践8.1 Session Cluster8.2 Job Cluster 9.问题解答 Kubernetes 是 Google 开源的 容器集群管理系统&#xff0c;其提供应用部署、维护、扩展机制等功能&#xff0c;利用 K…

深入理解梯度加权类激活热图(Grad-CAM)

深入理解梯度加权类激活热图&#xff08;Grad-CAM&#xff09; 项目背景与意义 在深度学习领域&#xff0c;模型的预测能力往往是黑盒子&#xff0c;难以解释。梯度加权类激活热图&#xff08;Grad-CAM&#xff09;作为一种可解释性技术&#xff0c;能够帮助模型开发者更好地…

js中事件循环的详解

文章目录 一、是什么二、宏任务与微任务微任务宏任务 三、async与awaitasyncawait 四、流程分析 一、是什么 首先&#xff0c;JavaScript是一门单线程的语言&#xff0c;意味着同一时间内只能做一件事&#xff0c;但是这并不意味着单线程就是阻塞&#xff0c;而实现单线程非阻…

java数据结构与算法刷题-----LeetCode18. 四数之和

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 解题思路 此题为三数之和的衍生题&#xff0c;代码完全一样&#xff0c;只…

Github 2024-02-13 开源项目日报 Top9

根据Github Trendings的统计&#xff0c;今日(2024-02-13统计)共有9个项目上榜。根据开发语言中项目的数量&#xff0c;汇总情况如下&#xff1a; 开发语言项目数量JavaScript项目2Python项目2C项目2TypeScript项目2Rust项目1Go项目1Dart项目1Java项目1C项目1 系统设计指南 …

JavaScript 的点击劫持(Clickjacking)

&#x1f9d1;‍&#x1f393; 个人主页&#xff1a;《爱蹦跶的大A阿》 &#x1f525;当前正在更新专栏&#xff1a;《VUE》 、《JavaScript保姆级教程》、《krpano》、《krpano中文文档》 ​ ​ ✨ 前言 点击劫持是一种恶意攻击&#xff0c;攻击者会在用户不知情的情况下诱…

Stable Diffusion 模型下载:majicMIX sombre 麦橘唯美

本文收录于《AI绘画从入门到精通》专栏,专栏总目录:点这里。 文章目录 模型介绍生成案例案例一案例二案例三案例四案例五案例六案例七案例八案例九案例十

LeetCode、452. 用最少数量的箭引爆气球【中等,贪心,区间问题】

文章目录 前言LeetCode、452. 用最少数量的箭引爆气球【中等&#xff0c;贪心&#xff0c;区间问题】题目链接与分类思路贪心&#xff0c;连续区间数量问题 资料获取 前言 博主介绍&#xff1a;✌目前全网粉丝2W&#xff0c;csdn博客专家、Java领域优质创作者&#xff0c;博客…

ARMv8-AArch64 的异常处理模型详解之异常处理概述Handling exceptions

异常处理模型详解之异常处理概述 一&#xff0c;异常处理相关概念二&#xff0c;异常处理概述 一&#xff0c;异常处理相关概念 在介绍异常处理之前&#xff0c;有必要了解一些关于异常处理状态的术语&#xff1a; 当处理器响应一个异常时&#xff0c;我们称该异常被获取了&a…

python 经典老人言

python 经典老人言 import tkinter as tkclass FlipBook:def __init__(self, master):self.master master master.title("经 典 老 人 言")self.pages ["经 典 老 人 言","求学无笨者&#xff0c;努力就成功","读 书 百 遍&am…

数据结构在JavaScript中的体现

一.概述 数据结构是计算机中存储、组织数据的方式。通常情况下&#xff0c;精心选择的数据结构可以带来最优效率的算法&#xff0c;其实算法并不是一个很高级的东西&#xff0c;它充斥在每一种代码组织方式中&#xff1b;而且各种语言关于数据结构方面的内容都是大同小异的&…