当前位置: 首页 > news >正文

创建型模式--1.单例模式【巴基速递】

1. 巴基的订单

在海贼世界中,巴基速递是巴基依靠手下强大的越狱犯兵力,组建的集团海贼派遣公司,它的主要业务是向世界有需要的地方输送雇佣兵(其实是不干好事儿)。

在这里插入图片描述

自从从特拉法尔加罗路飞同盟击败了堂吉诃德家族 ,战争的市场对雇佣兵的依赖越来越大。订单便源源不断的来了。此时我们来分析一个问题:巴基是怎么接单并且派单的呢?

简单来说,巴基肯定是有一个账本用于记录下单者信息,下单者的需求以及下单的时间,然后根据下单的先后顺序选择合适的人手进行派单。从程序猿的视角可以这样认为,这个账本其实就相当于一个任务队列:

  • 有一定的容量,可以存储任务
  • 按照下单的先后顺序存储并处理任务 – 典型的队列特性:先进先出

对于巴基来说把所有的订单全部记录到一个账本上就够了,如果将其平移到项目中,也就意味着应用程序在运行过程中存储任务的任务队列一个足矣,弄太多反而冗余,不太好处理了。

在一个项目中,全局范围内,某个类的实例有且仅有一个,通过这个唯一实例向其他模块提供数据的全局访问,这种模式就叫单例模式。单例模式的典型应用就是任务队列。

在这里插入图片描述

2. 独生子女

如果使用单例模式,首先要保证这个类的实例有且仅有一个,也就是说这个对象是独生子女,如果我们实施计划生育只生一个孩子,不需要也不能给再他增加兄弟姐妹。因此,就必须采取一系列的防护措施。对于类来说以上描述同样适用。涉及一个类多对象操作的函数有以下几个:

  • 构造函数:创建一个新的对象
  • 拷贝构造函数:根据已有对象拷贝出一个新的对象
  • 拷贝赋值操作符重载函数:两个对象之间的赋值

为了把一个类可以实例化多个对象的路堵死,可以做如下处理:

  1. 构造函数私有化,在类内部只调用一次,这个是可控的。
  • 由于使用者在类外部不能使用构造函数,所以在类内部创建的这个唯一的对象必须是静态的,这样就可以通过类名来访问了,为了不破坏类的封装,我们都会把这个静态对象的访问权限设置为私有的。
  • 在类中只有它的静态成员函数才能访问其静态成员变量,所以可以给这个单例类提供一个静态函数用于得到这个静态的单例对象。
  1. 拷贝构造函数私有化或者禁用(使用 = delete

  2. 拷贝赋值操作符重载函数私有化或者禁用(从单例的语义上讲这个函数已经毫无意义,所以在类中不再提供这样一个函数,故将它也一并处理一下。

由于单例模式就是给类创建一个唯一的实例对象,所以它的UML类图是很简单的:
在这里插入图片描述

因此,定义一个单例模式的类的示例代码如下:

// 定义一个单例模式的类
class Singleton
{
public:// = delete 代表函数禁用, 也可以将其访问权限设置为私有Singleton(const Singleton& obj) = delete;Singleton& operator=(const Singleton& obj) = delete;static Singleton* getInstance();
private:Singleton() = default;static Singleton* m_obj;
};

在实现一个单例模式的类的时候,有两种处理模式:

  • 饿汉模式
  • 懒汉模式

3. 饿汉模式

饿汉模式就是在类加载的时候立刻进行实例化,这样就得到了一个唯一的可用对象。关于这个饿汉模式的类的定义如下:

// 饿汉模式
class TaskQueue
{
public:// = delete 代表函数禁用, 也可以将其访问权限设置为私有TaskQueue(const TaskQueue& obj) = delete;TaskQueue& operator=(const TaskQueue& obj) = delete;static TaskQueue* getInstance(){return m_taskQ;}
private:TaskQueue() = default;static TaskQueue* m_taskQ;
};
// 静态成员初始化放到类外部处理
TaskQueue* TaskQueue::m_taskQ = new TaskQueue;int main()
{TaskQueue* obj = TaskQueue::getInstance();
}

第17行,定义这个单例类的时候,就把这个静态的单例对象创建出来了。当使用者通过getInstance()获取这个单例对象的时候,它已经被准备好了。

注意事项:类的静态成员变量在使用之前必须在类的外部进行初始化才能使用。

4. 懒汉模式

懒汉模式是在类加载的时候不去创建这个唯一的实例,而是在需要使用的时候再进行实例化。

4.1 懒汉模式类的定义

// 懒汉模式
class TaskQueue
{
public:// = delete 代表函数禁用, 也可以将其访问权限设置为私有TaskQueue(const TaskQueue& obj) = delete;TaskQueue& operator=(const TaskQueue& obj) = delete;static TaskQueue* getInstance(){if(m_taskQ == nullptr){m_taskQ = new TaskQueue;}return m_taskQ;}
private:TaskQueue() = default;static TaskQueue* m_taskQ;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;

在调用getInstance()函数获取单例对象的时候,如果在单线程情况下是没有什么问题的,如果是多个线程,调用这个函数去访问单例对象就有问题了。假设有三个线程同时执行了getInstance()函数,在这个函数内部每个线程都会new出一个实例对象。此时,这个任务队列类的实例对象不是一个而是3个,很显然这与单例模式的定义是相悖的。

4.2 线程安全问题

双重检查锁定

对于饿汉模式是没有线程安全问题的,在这种模式下访问单例对象的时候,这个对象已经被创建出来了。要解决懒汉模式的线程安全问题,最常用的解决方案就是使用互斥锁。可以将创建单例对象的代码使用互斥锁锁住,处理代码如下:

class TaskQueue
{
public:// = delete 代表函数禁用, 也可以将其访问权限设置为私有TaskQueue(const TaskQueue& obj) = delete;TaskQueue& operator=(const TaskQueue& obj) = delete;static TaskQueue* getInstance(){m_mutex.lock();if (m_taskQ == nullptr){m_taskQ = new TaskQueue;}m_mutex.unlock();return m_taskQ;}
private:TaskQueue() = default;static TaskQueue* m_taskQ;static mutex m_mutex;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
mutex TaskQueue::m_mutex;

在上面代码的10~13 行这个代码块被互斥锁锁住了,也就意味着不论有多少个线程,同时执行这个代码块的线程只能是一个(相当于是严重限行了,在重负载情况下,可能导致响应缓慢)。我们可以将代码再优化一下:

class TaskQueue
{
public:// = delete 代表函数禁用, 也可以将其访问权限设置为私有TaskQueue(const TaskQueue& obj) = delete;TaskQueue& operator=(const TaskQueue& obj) = delete;static TaskQueue* getInstance(){if (m_taskQ == nullptr){m_mutex.lock();if (m_taskQ == nullptr){m_taskQ = new TaskQueue;}m_mutex.unlock();}return m_taskQ;}
private:TaskQueue() = default;static TaskQueue* m_taskQ;static mutex m_mutex;
};
TaskQueue* TaskQueue::m_taskQ = nullptr;
mutex TaskQueue::m_mutex;

改进的思路就是在加锁、解锁的代码块外层有添加了一个if判断(第9行),这样当任务队列的实例被创建出来之后,访问这个对象的线程就不会再执行加锁和解锁操作了(只要有了单例类的实例对象,限行就解除了),对于第一次创建单例对象的时候线程之间还是具有竞争关系,被互斥锁阻塞。上面这种通过两个嵌套的 if 来判断单例对象是否为空的操作就叫做双重检查锁定

双重检查锁定的问题

假设有两个线程A、B,当线程A 执行到第 8 行时在线程A中 TaskQueue 实例对象 被创建,并赋值给 m_taskQ

static TaskQueue* getInstance()
{if (m_taskQ == nullptr){m_mutex.lock();if (m_taskQ == nullptr){m_taskQ = new TaskQueue;}m_mutex.unlock();}return m_taskQ;
}

但是实际上 m_taskQ = new TaskQueue; 在执行过程中对应的机器指令可能会被重新排序。正常过程如下:

  • 第一步:分配内存用于保存 TaskQueue 对象。
  • 第二步:在分配的内存中构造一个 TaskQueue 对象(初始化内存)。
  • 第三步:使用 m_taskQ 指针指向分配的内存。

但是被重新排序以后执行顺序可能会变成这样:

  • 第一步:分配内存用于保存 TaskQueue 对象。
  • 第二步:使用 m_taskQ 指针指向分配的内存。
  • 第三步:在分配的内存中构造一个 TaskQueue 对象(初始化内存)。

这样重排序并不影响单线程的执行结果,但是在多线程中就会出问题。如果线程A按照第二种顺序执行机器指令,执行完前两步之后失去CPU时间片被挂起了,此时线程B在第3行处进行指针判断的时候m_taskQ 指针是不为空的,但这个指针指向的内存却没有被初始化,最后线程 B 使用了一个没有被初始化的队列对象就出问题了(出现这种情况是概率问题,需要反复的大量测试问题才可能会出现)。

在C++11中引入了原子变量atomic,通过原子变量可以实现一种更安全的懒汉模式的单例,代码如下:

class TaskQueue
{
public:// = delete 代表函数禁用, 也可以将其访问权限设置为私有TaskQueue(const TaskQueue& obj) = delete;TaskQueue& operator=(const TaskQueue& obj) = delete;static TaskQueue* getInstance(){TaskQueue* queue = m_taskQ.load();  if (queue == nullptr){// m_mutex.lock();  // 加锁: 方式1lock_guard<mutex> locker(m_mutex);  // 加锁: 方式2queue = m_taskQ.load();if (queue == nullptr){queue = new TaskQueue;m_taskQ.store(queue);}// m_mutex.unlock();}return queue;}void print(){cout << "hello, world!!!" << endl;}
private:TaskQueue() = default;static atomic<TaskQueue*> m_taskQ;static mutex m_mutex;
};
atomic<TaskQueue*> TaskQueue::m_taskQ;
mutex TaskQueue::m_mutex;int main()
{TaskQueue* queue = TaskQueue::getInstance();queue->print();return 0;
}

上面代码中使用原子变量atomicstore() 方法来存储单例对象,使用load() 方法来加载单例对象。在原子变量中这两个函数在处理指令的时候默认的原子顺序是memory_order_seq_cst(顺序原子操作 - sequentially consistent),使用顺序约束原子操作库,整个函数执行都将保证顺序执行,并且不会出现数据竞态(data races),不足之处就是使用这种方法实现的懒汉模式的单例执行效率更低一些

静态局部对象

在实现懒汉模式的单例的时候,相较于双重检查锁定模式有一种更简单的实现方法并且不会出现线程安全问题,那就是使用静态局部局部对象,对应的代码实现如下:

class TaskQueue
{
public:// = delete 代表函数禁用, 也可以将其访问权限设置为私有TaskQueue(const TaskQueue& obj) = delete;TaskQueue& operator=(const TaskQueue& obj) = delete;static TaskQueue* getInstance(){static TaskQueue taskQ;return &taskQ;}void print(){cout << "hello, world!!!" << endl;}private:TaskQueue() = default;
};int main()
{TaskQueue* queue = TaskQueue::getInstance();queue->print();return 0;
}

在程序的第 9、10 行定义了一个静态局部队列对象,并且将这个对象作为了唯一的单例实例。使用这种方式之所以是线程安全的,是因为在C++11标准中有如下规定,并且这个操作是在编译时由编译器保证的:

如果指令逻辑进入一个未被初始化的声明变量,所有并发执行应当等待该变量完成初始化。

最后总结一下懒汉模式和饿汉模式的区别:

懒汉模式的缺点是在创建实例对象的时候有安全问题,但这样可以减少内存的浪费(如果用不到就不去申请内存了)。饿汉模式则相反,在我们不需要这个实例对象的时候,它已经被创建出来,占用了一块内存。对于现在的计算机而言,内存容量都是足够大的,这个缺陷可以被无视。

5. 替巴基写一个任务队列

作为程序猿的我们,如果想给巴基的账本升级成一个应用程序,首要任务就是设计一个单例模式的任务队列,那么就需要赋予这个类一些属性和方法:

  1. 属性:
    • 存储任务的容器,这个容器可以选择使用STL中的队列(queue)
    • 互斥锁,多线程访问的时候用于保护任务队列中的数据
  2. 方法:主要是对任务队列中的任务进行操作
    • 任务队列中任务是否为空
    • 往任务队列中添加一个任务
    • 从任务队列中取出一个任务
    • 从任务队列中删除一个任务

根据分析,就可以把这个饿汉模式的任务队列的单例类定义出来了:

#include <iostream>
#include <queue>
#include <mutex>
#include <thread>
using namespace std;class TaskQueue
{
public:// = delete 代表函数禁用, 也可以将其访问权限设置为私有TaskQueue(const TaskQueue& obj) = delete;TaskQueue& operator=(const TaskQueue& obj) = delete;static TaskQueue* getInstance(){return &m_obj;}// 任务队列是否为空bool isEmpty(){lock_guard<mutex> locker(m_mutex);bool flag = m_taskQ.empty();return flag;}// 添加任务void addTask(int data){lock_guard<mutex> locker(m_mutex);m_taskQ.push(data);}// 取出一个任务int takeTask(){lock_guard<mutex> locker(m_mutex);if (!m_taskQ.empty()){return m_taskQ.front();}return -1;}// 删除一个任务bool popTask(){lock_guard<mutex> locker(m_mutex);if (!m_taskQ.empty()){m_taskQ.pop();return true;}return false;}
private:TaskQueue() = default;static TaskQueue m_obj;queue<int> m_taskQ;mutex m_mutex;
};
TaskQueue TaskQueue::m_obj;int main()
{thread t1([]() {TaskQueue* taskQ = TaskQueue::getInstance();for (int i = 0; i < 100; ++i){taskQ->addTask(i + 100);cout << "+++push task: " << i + 100 << ", threadID: " << this_thread::get_id() << endl;this_thread::sleep_for(chrono::milliseconds(500));}});thread t2([]() {TaskQueue* taskQ = TaskQueue::getInstance();this_thread::sleep_for(chrono::milliseconds(100));while (!taskQ->isEmpty()){int data = taskQ->takeTask();cout << "---take task: " << data << ", threadID: " << this_thread::get_id() << endl;taskQ->popTask();this_thread::sleep_for(chrono::seconds(1));}});t1.join();t2.join();
}

在上面的程序中有以下几点需要说明一下:

  • 正常情况下,任务队列中的任务应该是一个函数指针(这个指针指向的函数中有需要执行的任务动作),此处进行了简化,用一个整形数代替了任务队列中的任务。
  • 任务队列中的互斥锁保护的是单例对象的中的数据也就是任务队列中的数据,上面所说的线程安全指的是在创建单例对象的时候要保证这个对象只被创建一次,和此处完全是两码事儿,需要区别看待。

相关文章:

创建型模式--1.单例模式【巴基速递】

1. 巴基的订单 在海贼世界中&#xff0c;巴基速递是巴基依靠手下强大的越狱犯兵力&#xff0c;组建的集团海贼派遣公司&#xff0c;它的主要业务是向世界有需要的地方输送雇佣兵&#xff08;其实是不干好事儿&#xff09;。 自从从特拉法尔加罗和路飞同盟击败了堂吉诃德家族 &…...

用 Wireshark 解码 H.264

H264&#xff0c;你不知道的小技巧-腾讯云开发者社区-腾讯云 这篇文章写的非常好 这里仅做几点补充 init.lua内容&#xff1a; -- Set enable_lua to false to disable Lua support. enable_lua trueif not enable_lua thenreturn end-- If false and Wireshark was start…...

鸿蒙TypeScript学习第10天:【String(字符串)】

1、TypeScript String&#xff08;字符串&#xff09; String 对象用于处理文本&#xff08;字符串&#xff09;。 语法 var txt new String("string"); 或者更简单方式&#xff1a; var txt "string"; 2、String 对象属性 下表列出了 String 对象支…...

【201】Java8读取JSON树形结构并插入到MySQL数据库表中

我写了一个 maven 项目的 Demo&#xff0c;用来演示 JAVA8 如何读取 JSON 文件树形结构&#xff0c;并将这种树形结构保存到 MySQL 中。 json文件 city.json {"name": "山东省","sub": [{"name": "青岛市","sub"…...

AI“复活”:慰藉心灵还是触碰禁忌?一文看懂技术与伦理的较量|TodayAI

随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;其应用领域也越来越广泛&#xff0c;不仅仅局限于数据分析、机器人自动化等传统领域&#xff0c;更是延伸到了一些人们曾经认为只存在于科幻小说中的领域。近年来&#xff0c;使用AI技术“复活”逝者的概念&a…...

Jackson @JsonUnwrapped注解扁平化 序列化反序列化数据

参考资料 Jackson 2.x 系列【7】注解大全篇三JsonUnwrapped 以扁平的数据结构序列化/反序列化属性Jackson扁平化处理对象 目录 一. 前期准备1.1 前端1.2 实体类1.3 Controller层 二. 扁平化序列反序列化数据2.1 序列化数据2.2 反序列化数据 三. 前缀后缀处理属性同名四. Map数…...

日期时间相关的类

分界线jdk8 jdk8之前和之后分别提供了一些日期和时间的类&#xff0c;推荐使用jdk8之后的日期和时间类 Date类型 这是一个jdk8之前的类型&#xff0c;其中有很多方法已经过时了&#xff0c;选取了一些没有过时的API //jdk1.8之前的日期 Date Date date new Date(); // 从1970年…...

微信小程序脚本的执行顺序

在小程序中的脚本执行顺序和浏览器中有所不同。 小程序的执行的入口文件是 app.js 。 并且会根据其中 require 的模块顺序决定文件的运行顺序&#xff0c;代码是一个 app.js 示例。 app.js /* a.js console.log(a.js) */ var a require(./a.js) console.log(app.js)/* b.js co…...

zabbix监控警告

监控概述 对服务的管理&#xff0c;不能仅限于可用性。 还需要服务可以安全、稳定、高效地运行。 监控的目的&#xff1a;早发现、早治疗。 被监控的资源类型&#xff1a; 公开数据&#xff1a;对外开放的&#xff0c;不需要认证即可获取的数据 私有数据&#xff1a;对外不…...

YOLOv9架构图分享

YOLOv9是YOLO (You Only Look Once)系列实时目标检测系统的最新迭代。它建立在以前的版本之上&#xff0c;结合了深度学习技术和架构设计的进步&#xff0c;以在目标检测任务中实现卓越的性能。通过将可编程梯度信息(PGI)概念与广义ELAN (GELAN)架构相结合&#xff0c;YOLOv9在…...

全自动封箱机的工作原理:科技与效率的完美结合

随着科技的不断发展&#xff0c;越来越多的自动化设备走进了我们的日常生活和工业生产中。其中&#xff0c;全自动封箱机作为物流包装领域的重要一环&#xff0c;凭借其高效、精准的工作性能&#xff0c;正逐渐成为提升生产效率、降低劳动成本的得力助手。星派就来与大家深入探…...

【管理咨询宝藏48】AA银行信息科技提升分析报告

本报告首发于公号“管理咨询宝藏”&#xff0c;如需阅读完整版报告内容&#xff0c;请查阅公号“管理咨询宝藏”。 【管理咨询宝藏48】AA银行信息科技提升分析报告 【格式】PPT版本&#xff0c;可编辑 【关键词】战略规划、商业分析、管理咨询 【强烈推荐】这是一套市面上非常…...

循序表实战——基于循序表的通讯录

前言&#xff1a;本篇文章主要是利用顺序表作为底层&#xff0c; 实现一个通讯录。偏向于应用&#xff0c; 对于已经学习过c的友友们可能没有难度了已经。没有学习过c的友友&#xff0c; 如果顺序表不会写&#xff0c; 或者说没有自己实现过&#xff0c; 请移步学习顺序表相关内…...

Java编程规范及最佳实践

文章目录 一、命名规范二、代码风格规范三、注释规范四、推荐的编程实践五、类和接口六、异常处理七、可见性八、并发九、代码复用十、代码组织和模块化十一、Java集合框架十二、输入验证十三、资源管理十四、文档和注释十五、测试和代码质量十六、代码可读性十七、性能优化十八…...

90天玩转Python—07—基础知识篇:Python中运算符详解

90天玩转Python系列文章目录 90天玩转Python—01—基础知识篇:C站最全Python标准库总结 90天玩转Python--02--基础知识篇:初识Python与PyCharm 90天玩转Python—03—基础知识篇:Python和PyCharm(语言特点、学习方法、工具安装) 90天玩转Python—04—基础知识篇:Pytho…...

C语言 位域

C 语言的位域&#xff08;bit-field&#xff09;是一种特殊的结构体成员&#xff0c;允许我们按位对成员进行定义&#xff0c;指定其占用的位数。 如果程序的结构中包含多个开关的变量&#xff0c;即变量值为 TRUE/FALSE&#xff0c;如下&#xff1a; struct {unsigned int w…...

【LeetCode热题100】【技巧】颜色分类

题目链接&#xff1a;75. 颜色分类 - 力扣&#xff08;LeetCode&#xff09; 只需排序三种&#xff0c;可以记录0和1的个数&#xff0c;然后直接原地赋值 class Solution { public:void sortColors(vector<int> &nums) {int zero 0, one 0;for (auto &num: n…...

笔记本电脑win7 Wireless-AC 7265连不上wifi6

1.背景介绍 旧路由器连接人数有限&#xff0c;老旧&#xff0c;信号不稳定更换了新路由器&#xff0c;如 TL-XDR5430易展版用户电脑连不上新的WIFI网络了&#xff0c;比较着急 核心问题&#xff1a;有效解决笔记本连接wifi上网问题&#xff0c;方法不限 2.环境信息 Windows…...

Linux gcc day5粘滞位

粘滞位 背景&#xff1a;一定时在一个公共目录&#xff08;root创建&#xff09;下。进行临时文件的操作 Linux系统中有很多人&#xff0c;我们需要在一个公共目录下&#xff0c;进行临时文件的操作&#xff08;增删查改&#xff09; 创建一个根目录下的dir&#xff08;mytmp…...

单片机按键消抖常用的软硬件方法

一&#xff1a;什么是开关抖动&#xff1f; 当我们按下按钮或拨动开关或微动开关时&#xff0c;两个金属部件会接触以短路电源。但它们不会立即连接&#xff0c;而是金属部件在实际稳定连接之前连接和断开几次。释放按钮时也会发生同样的事情。这会导致误触发或多次触发&#…...

不花一分钱!用闲置电脑搭建永久Mac远程控制台(VNC+cpolar固定TCP教程)

零成本打造24小时在线的Mac远程开发环境 你是否有一台闲置的Mac电脑放在角落积灰&#xff1f;或者需要随时随地访问家里的开发环境&#xff1f;将旧Mac改造成全天候在线的远程工作站&#xff0c;不仅能充分利用闲置资源&#xff0c;还能为移动办公提供极大便利。本文将手把手教…...

SiameseUIE参数详解:custom_entities与通用规则双模式解析

SiameseUIE参数详解&#xff1a;custom_entities与通用规则双模式解析 1. 核心功能概述 SiameseUIE作为信息抽取领域的实用模型&#xff0c;提供了两种截然不同的实体抽取模式&#xff0c;让用户可以根据实际需求灵活选择。这两种模式就像是给你的数据提取工作配备了两套不同…...

基于YOLOv5和swin-Unet的带钢缺陷智能识别系统

十一、基于YOLOv5和swin-Unet的带钢缺陷智能识别系统 1.带标签数据集&#xff0c;包括检测和分割数据集&#xff0c;其中检测数据共计6类&#xff0c;1800张图片。 2.含模型训练权重。 3.pyqt5设计的界面&#xff0c;带登录界面&#xff0c;注册界面和运行界面。 4.提供详细的环…...

Realistic Vision V5.1 Streamlit界面源码解析:如何扩展自定义摄影滤镜

Realistic Vision V5.1 Streamlit界面源码解析&#xff1a;如何扩展自定义摄影滤镜 1. 项目背景与技术特点 Realistic Vision V5.1是目前SD 1.5生态中最顶级的写实风格模型之一&#xff0c;能够生成媲美专业单反相机拍摄的人像作品。本项目通过Streamlit框架构建了直观的交互…...

光伏电站电流传感器选型与应用全解析

摘要&#xff1a;本文深入探讨光伏电站中电流传感器的选型要点与应用技术。阐述了电流传感器在光伏系统中的重要性&#xff0c;分析其不同技术原理及特点&#xff0c;并结合光伏电站实际工况&#xff0c;从量程、精度、隔离耐压等方面详细介绍选型方法&#xff0c;同时分享了电…...

Fastboot Enhance:高效Android刷机工具与Payload管理平台

Fastboot Enhance&#xff1a;高效Android刷机工具与Payload管理平台 【免费下载链接】FastbootEnhance A user-friendly Fastboot ToolBox & Payload Dumper for Windows 项目地址: https://gitcode.com/gh_mirrors/fa/FastbootEnhance 价值定位&#xff1a;重新定…...

番茄小说下载器:打造个人数字图书馆的完整攻略

番茄小说下载器&#xff1a;打造个人数字图书馆的完整攻略 【免费下载链接】Tomato-Novel-Downloader 番茄小说下载器不精简版 项目地址: https://gitcode.com/gh_mirrors/to/Tomato-Novel-Downloader 你是否曾遇到过网络信号不佳时无法追更小说的烦恼&#xff1f;或者希…...

用Multisim复刻经典24秒篮球计时器:从555时钟到数码管显示的保姆级仿真教程

用Multisim复刻经典24秒篮球计时器&#xff1a;从555时钟到数码管显示的保姆级仿真教程 篮球比赛中那令人窒息的最后24秒倒计时&#xff0c;不仅是球员的决胜时刻&#xff0c;也是电子爱好者眼中完美的数字电路实践案例。本文将带你用Multisim从零搭建一个完整的24秒计时系统&a…...

高效漫画收藏解决方案:打造你的离线数字漫画库

高效漫画收藏解决方案&#xff1a;打造你的离线数字漫画库 【免费下载链接】picacomic-downloader 哔咔漫画 picacomic pica漫画 bika漫画 PicACG 多线程下载器&#xff0c;带图形界面 带收藏夹&#xff0c;已打包exe 下载速度飞快 项目地址: https://gitcode.com/gh_mirrors…...

实测Claude 4.5 Opus重构“屎山”代码:手把手教你用AI给遗留项目做外科手术(附前后对比与单元测试生成)

实测Claude 4.5 Opus重构“屎山”代码&#xff1a;手把手教你用AI给遗留项目做外科手术&#xff08;附前后对比与单元测试生成&#xff09; 接手一个满是"祖传代码"的老旧项目&#xff0c;就像被丢进一座迷宫——变量命名像密码&#xff0c;函数逻辑像意大利面&…...