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

【设计模式】原型模式

三、原型模式

3.2 原型模式

同工厂模式一样,原型(Prototype) 模式也是一种创建型模式。原型模式通过一个对象 (原型对象)克隆出多个一模一样的对象。实际上,该模式与其说是一种设计模式,不如说是 一种创建对象的方法(对象克隆),尤其是创建给定类的对象(实例)过程很复杂(例如,要设 置许多成员变量的值)时,使用这种设计模式就比较合适。

3.2.1 通过工厂方法模式演变到原型模式

回顾一下前面讲解工厂方法模式时的范例,由图3.2,可以看到:

  • 怪物相关类 M_Undead 、M_Element 、M_Mechanic 分别继承自怪物父类Monster;
  • 怪物工厂相关类 M_UndeadFactory 、M_ElementFactory 、M_MechanicFactory 分 别 继承自工厂父类 M_ParFactory;
  • 怪物工厂类 M_UndeadFactory 、M_ElementFactory 、M_MechanicFactory 中的成员 函数createMonster 分别用于创建怪物类 M_Undead 、M_Element 、M_Mechanic 对 象 。

现在,把上述类的层次结构(源码)进行一下变换,请读者仔细观察:

  • (1)把怪物父类 Monster 和工厂父类 M_ParFactory 合二为一 (或者说成是把 M_ParFactory 类中的能力搬到 Monster 中去并把 M_ParFactory 类删除掉),让怪物父类 Monster 本身具有克隆自己的能力。改造后的代码如下:
class Monster
{public:Monster(int life,int magic,int attack);virtual ~Monster();virtual Monster* createMonster() = 0;protected:int m_life;int m_magic;int m_attack;
};Monster::~Monster(){}
  • (2)遵从传统习惯(但不是一定要这样做),将上述成员函数createMonster 重新命名为 clone,clone 的中文翻译为“克隆”,意味着调用该成员函数就会从当前类对象复制出一个完 全相同的对象(通过克隆自己来创建出新对象),这当然也是一种创建该类所属对象的方式,虽然读者可能以往没见过这种创建对象的方式,但相信在将来阅读大型项目的源码时会遇 到这种创建对象的方式。改造后的代码如下:
virtual Monster* clone() = 0;
  • (3)把 M_UndeadFactory、M_ElementFactory、M_MechanicFactory 这3个怪物工厂 类中的createMonster 成员函数分别搬到M_Undead 、M_Element 、M_Mechanic 中并将该成 员函数重新命名为clone, 同时将M_UndeadFactory 、M_ElementFactory 、M_MechanicFactory类 删除掉。改造后的代码如下:

class M_Undead :public Monster {public:M_Undead(int life, int magic, int attack);virtual Monster* clone() override;
};class M_Element :public Monster {public:M_Element(int life, int magic, int attack);virtual Monster* clone() override;};class M_Mechanic :public Monster {public:M_Mechanic(int life, int magic, int attack);virtual Monster* clone() override;
};
  • (4)当然,既然是克隆,那么上述M Undead 、M Element 、M Mechanic 中 的clone 成员 函数的实现体是需要修改的。例如,某个机械类怪物因为被主角砍了一刀失去了100点生 命值,导致该怪物对象的m life 成员变量(生命值)从原来的400变成300,那么调用 clone 方法克隆出来的新机械类怪物对象也应该是300点生命值,所以此时 M_Mechanic 类中 clone 成员函数中的代码行
return new M_Mechanic(400,0,110);

就不合适,因为这样会 创建(克隆)出一个400点生命值的新怪物,不符合 clone这个成员函数的本意(复制出一个 完全相同的对象)。

克隆对象自身实际上是需要调用类的拷贝构造函数的。阅读过笔者的《C++ 新经典: 对象模型》的读者都知道:

  • ① 如果程序员在类中没有定义自己的拷贝构造函数,那么编译器会在必要的时候(但 不是一定)合成出一个拷贝构造函数;
  • ② 在某些情况下,程序员必须书写自己的拷贝构造函数,例如在涉及深拷贝的情形之 下,如果读者对深拷贝和浅拷贝这两个概念理解模糊,建议一定要通过搜索引擎或者《C++新 经典:对象模型》这本书理解清楚,因为这决定着你能否写出正确的程序代码。克隆对象意 味着复制出一个全新的对象,所以在涉及深拷贝和浅拷贝概念时都是要实现深拷贝的(这样 后续如果需要对克隆出来的对象进行修改才不会影响原型对象)。

为方便查看测试结果,笔者为M Element 类编写了一个拷贝构造函数供读者参考,在 M_Element 中,加入如下代码:

M_Element::M_Element(M_Element const& tmpobj):Monster(tmpobj)
{std::cout << "调用了M_Element的拷贝构造函数" << std::endl;
}Monster* M_Element::clone() {return new M_Element(*this);
}

对 M_Undead 、M_Element 、M_Mechanic 中的clone 成员函数的实现体分别进行修改, 通过调用类的拷贝构造函数的方式真正实现类对象的克隆,修改后的代码如下

class Monster
{public:Monster(int life, int magic, int attack);virtual ~Monster();virtual Monster* clone() = 0;protected:int m_life;int m_magic;int m_attack;
};class M_Undead :public Monster {public:M_Undead(int life, int magic, int attack);M_Undead(const M_Undead& tmpobj);virtual Monster* clone() override;
};class M_Element :public Monster {public:M_Element(int life, int magic, int attack);M_Element(M_Element const& tmpobj);virtual Monster* clone() override;};class M_Mechanic :public Monster {public:M_Mechanic(int life, int magic, int attack);M_Mechanic(M_Mechanic const& tmpobj);virtual Monster* clone() override;};Monster::Monster(int life, int magic, int attack):m_life(life), m_magic(magic), m_attack(attack)
{}Monster::~Monster()
{
}M_Undead::M_Undead(int life, int magic, int attack):Monster(life, magic, attack)
{std::cout << "一只亡灵怪物来到了这个世界" << std::endl;
}M_Undead::M_Undead(const M_Undead& tmpobj):Monster(tmpobj)
{std::cout << "调用M_Undead的拷贝构造函数" << std::endl;
}Monster* M_Undead::clone() {return new M_Undead(*this);
}M_Element::M_Element(int life, int magic, int attack):Monster(life, magic, attack)
{std::cout << "一只元素怪物来到了这个世界" << std::endl;
}M_Element::M_Element(M_Element const& tmpobj):Monster(tmpobj)
{std::cout << "调用了M_Element的拷贝构造函数" << std::endl;
}Monster* M_Element::clone() {return new M_Element(*this);
}M_Mechanic::M_Mechanic(int life, int magic, int attack):Monster(life, magic, attack)
{std::cout << "一只机械怪物来到了这个世界" << std::endl;
}M_Mechanic::M_Mechanic(M_Mechanic const& tmpobj):Monster(tmpobj)
{std::cout << "调用了M_Mechanic的拷贝构造函数" << std::endl;
}Monster* M_Mechanic::clone() {return new M_Mechanic(*this);
}
  • (5)如果在上述(4)中确定要编写自己的拷贝构造函数,应确保无误。因为只有拷贝构 造函数正确,调用clone 成员函数时才能正确地克隆出新的对象。

  • (6)在实际项目中,可以先创建出原型对象,原型对象一般只是用于克隆目的而存在, 然后就可以调用这些对象所属类的clone 成员函数来随时克隆出新的对象,并通过这些新 对象实现项目的业务逻辑。在main 主函数中,增加如下测试代码:

  Monster* pmyPropUndMonster = new M_Undead(300, 50, 80);Monster* pmyPropEleMonster = new M_Element(200, 80, 100);M_Mechanic myPropMecMonster(400, 0, 110);Monster* p_CloneObj1 = pmyPropUndMonster->clone();Monster* p_CloneObj2 = pmyPropEleMonster->clone();Monster* p_CloneObj3 = myPropMecMonster.clone();delete p_CloneObj1;delete p_CloneObj2;delete p_CloneObj3;delete pmyPropUndMonster;delete pmyPropEleMonster;

从代码中可以看到,分别在栈和堆上创建了一个原型对象以用于克隆的目的,当然,在 堆中创建的原型对象最终不要忘记释放对应的内存以防止内存泄漏。甚至可以根据项目的 需要,将多个原型对象保存在例如 map 容器中,甚至可以书写专门的管理类来管理这些原 型对象,当需要用这些原型对象创建(克隆)新对象时,可以从容器中取出来使用。在对原型 对象的使用方面,程序员完全可以发挥自己的想象力。

3.2.2 引入原型模式

在前面的范例中,原型对象通过clone 成员函数调用怪物子类的拷贝构造函数,可能有 些读者认为这有些多余——直接利用怪物子类的拷贝构造函数生成新对象不是更直接、更方便吗?例如在main 主函数利用代码行

Monster* p_CloneObj3 = new M_Mechanic(myPropMecMonst);

也可以克隆出一个新的对象。其实这样认为也没错,但读者要认识 到,设计模式是独立于计算机编程语言而存在的,这意味着虽然 C++ 语言中怪物子类的 clone 成员函数可以直接调用拷贝构造函数,但在其他计算机编程语言中可能并没有拷贝构 造函数这种概念,此时,本该在拷贝构造函数中的实现代码就必须放在clone 成员函数中实 现 了 。

引入“原型”(Prototype)模式的定义:用原型实例指定创建对象的种类,并且通过复制 这些原型创建新的对象。简单来说,就是通过克隆来创建新的对象实例。

前面范例中的main 主函数中,myPropMecMonster 对象就是原型实例,通过调用该对象的clone 成员函数就指定了所创建的对象种类——当然,创建的是M Mechanic 类型的 对象而不是M_Undead 或 M_Element 类型的对象。通过复制myPropMecMonster 这个原 型对象以及 pmyPropEleMonster 所指向的原型对象创建了两个新对象: 一个是机械类怪 物对象,一个是元素类怪物对象,指针 p_CloneObjl 和 p_CloneObj2 分别指向这两个新对象。

针对前面的代码范例绘制原型模式的 UML 图,如图3.7所示。

Monster
# m_life
# m_magic
# m_attack
+Monster()
+~Monster()
+clone()
M_Undead
+M_Undead()
+clone()
M_Element
+M_Element()
+clone()
M_Mechanic
+M_Mechanic()
+clone()

原型模式的 UML 图中,包含两种角色。

  • (1)Prototype (抽象原型类):所有具体原型类的父类,在其中声明克隆方法。这里指 Monster 类。
  • (2)ConcretePrototype (具体原型类):实现在抽象原型类中声明的克隆方法,在克隆方法 中返回自己的一个克隆对象。这里指M_Undead 类 、M_Element 类 和M_Mechanic 类。

和工厂方法模式相比,原型模式有什么明显的特点呢?什么情况下应该采用原型模式 来克隆对象呢?
设想一下,如果某个对象的内部数据比较复杂且多变,例如一个实际游戏中的怪物 对 象 :

  • 在战斗中它的生命值可能因为被玩家攻击而不断减少;
  • 如果这个怪物会魔法,那么它施法后自身的魔法值也会减少;
  • 在生命值过低时怪物还可能自己使用一些药剂类物品或者治疗类魔法来替自己增 加生命值;
  • 玩家也可能通过施法导致怪物产生一些负面效果,例如中毒会持续让怪物丢失生命 值、混乱会让怪物乱跑而无法攻击玩家、石化导致怪物完全原地不动若干秒等。

如果使用工厂方法模式创建这种怪物对象(战斗中的,自身生命值、魔法值、状态等数据 随时在变化的怪物对象),那么大概要执行的步骤是:

  • 先通过调用工厂方法模式中的 createMonster 创建出该怪物对象(实际上就是创建
    一个怪物对象);
  • 通过怪物所属类中暴露出的设置接口(成员函数)来设置该怪物当前的生命值、魔法 值、状态(例如,中毒、混乱、石化)等,这些程序代码可能会比较烦琐。

显然,在这种情形下,使用工厂模式创建当前这个怪物对象就不如使用克隆方式来克隆 当前怪物对象容易,如果采用克隆方式来克隆当前对象,仅仅需要调用clone 成员函数,那 么因为clone 调用的实际是类的拷贝构造函数,所以这个怪物对象当前的内部数据(生命 值、魔法值、状态等)都会被立即克隆到新产生的对象中而不需要程序员额外通过程序代码 设置这些数据,也就是说,在调用clone 成员函数的那个时刻,克隆出来的对象与原型对象 内部的数据是完全一样的。例如,当游戏中的一个 BOSS 级别的怪物被攻击失血到一定程 度时,它会产生自己的分身,这种情况下使用clone 成员函数来产生这个分身就很合适,当 然,一旦新的对象被克隆出来后依旧可以单独设置该克隆对象自身的数据而丝毫不会影响 原型对象。

所以,如果对象的内部数据比较复杂且多变并且在创建对象的时候希望保持对象的当 前的状态,那么用原型模式显然比用工厂方法模式更合适。

总结一下工厂方法模式和原型模式在创建对象时的异同点:

  • 在前面范例中创建怪物对象时,这两种模式其实都不需要程序员知道所创建对象所 属的类名;

  • 工厂方法模式是调用相应的创建接口,例如使用createMonster 接口来创建新的怪 物对象,该接口中采用代码行“new 类名(参数…);”来完成对象的最终创建工作,这 仍旧是属于根据类名来生成新对象;

  • 原型模式是调用例如 clone(程序员可以修改成任意其他名字)接口来创建新的怪物 对象,按照惯例,这个接口一般不带任何参数,以免破坏克隆接口的统一性。该接口 中采用的是代码行“new 类名(* this);” 完成对类拷贝构造函数的调用来创建对象, 所以这种创建对象的方式是根据现有对象来生成新对象。

当然,有些读者把原型模式看成是一种特殊的工厂方法模式(工厂方法模式的变体),这 也是可以的——把原型对象所属的类本身(例如,M_Undead、M_Element、M_Mechanic) 看 成是创建克隆对象的工厂,而工厂方法指的自然就是克隆方法(clone)。

看一看原型模式的优缺点:

  • (1)如果创建的新对象内部数据比较复杂且多变,那么使用原型模式可以简化对象的 创建过程,提高新对象的创建效率。设想一下,如果对象内部数据是通过复杂的算法(例如 通过排序、计算哈希值等)计算得到,或者是通过从网络、数据库、文件中读取得到,那么用原 型模式从原型对象中直接复制生成新对象而不是每次从零开始创建全新的对象,对象的创 建效率显然会提高很多。

  • (2)通过观察图3.2(工厂方法模式UML 图)可以发现,工厂方法模式往往需要创建一 个与产品等级结构(层次)相同的工厂等级结构,这当然是一种额外的开销,而原型模式不存 在这种额外的等级结构——原型模式不需要额外的工厂类,只要通过调用类中的克隆方法 就可以生产新的对象。

  • (3)在产品类中,必须存在一个克隆方法以用于根据当前对象克隆出新的对象(加重开发者负担,这算是缺点)。当然,不一定采用“new 类名(* this);” 的形式来调用所属类的拷 贝构造函数实现对原型对象自身的克隆,也可以采用“new 类名(参数…);”先生成新的对 象,然后通过调用类的成员函数、直接设置成员变量等手段把原型对象内部的所有当前数据 赋给新对象,例如,可以把 M_Undead 的 clone 成员函数实现成下面的样子:

M_Undead::M_Undead(const M_Undead& tmpobj):Monster(tmpobj)
{Monster* pmonster = new M_Undead(300, 50, 80);pmonster->setLife(m_life);pmonster->setMagic(m_magic);pmonster->setAttack(m_attack);std::cout << "调用M_Undead的拷贝构造函数" << std::endl;
}
  • (4)在某些情况下,产品类中存在一个克隆方法也会给开发提供一些明显的便利。设 想一个全局函数Gbl CreateMonster, 其形参为Monster* 类型的指针,如果期望创建一 个与该指针所指向的对象相同类型的对象,那么传统做法的代码可能如下:
void Gbl_CreateMonster(Monster *pMonster);void Gbl_CreateMonster(Monster* pMonster) {Monster* ptmpobj = nullptr;if (dynamic_cast<M_Undead*>(pMonster)) {ptmpobj = new M_Undead(300, 50, 80);}if (dynamic_cast<M_Element*>(pMonster)) {ptmpobj = new M_Element(200, 80, 100);}if (dynamic_cast<M_Mechanic*>(pMonster)) {ptmpobj = new M_Mechanic(300, 0, 110);}if (ptmpobj != nullptr) {std::cout << "实现相关业务" << std::endl;delete ptmpobj;}
}

但是,如果每一个Monster 子类(M_Undead 、M_Element 、M_Mechanic) 都提供一个克 隆方法,那么Gbl CreateMonster函数的实现就简单得多,此时根本不使用dynamic_cast 和通过类名进行类型判断就可以直接利用已有对象来创建新对象,新的实现代码如下:

void Gbl_CreateMonster(Monster *pMonster);void Gbl_CreateMonster(Monster* pMonster)
{Monster* ptmpobj = pMonster->clone();std::cout << "实现相关业务" << std::endl;delete ptmpobj;
}

从这个范例中不难看到,根本就不需要知道Gbl_CreateMonster 的形参 pMonster 所 指向的对象到底是什么类型就可以创建出新的该形参所属类型的对象,这也减少了Gbl_CreateMonster 函数中需要知道的产品类名的数目。

相关文章:

【设计模式】原型模式

三、原型模式 3.2 原型模式 同工厂模式一样&#xff0c;原型(Prototype) 模式也是一种创建型模式。原型模式通过一个对象 (原型对象)克隆出多个一模一样的对象。实际上&#xff0c;该模式与其说是一种设计模式&#xff0c;不如说是 一种创建对象的方法(对象克隆),尤其是创建给…...

力扣题目汇总 使用贪心算法解决问题

贪心算法是一种通过局部最优解来获得全局最优解的算法。它的核心思想是&#xff1a;在每一步中选择当前看起来最优的解&#xff0c;并希望通过一系列局部最优选择最终得到全局最优解。 121.买卖股票的最佳时机 分析&#xff1a; 在每一天求出当前最优的利润&#xff0c;也就…...

Mac下Ollama安装全攻略:开启本地大模型之旅

文章目录 Mac下Ollama安装全攻略&#xff1a;开启本地大模型之旅一、Ollama 是什么功能特点优势应用场景 二、安装前准备&#xff08;一&#xff09;系统要求&#xff08;二&#xff09;硬件要求 三、下载安装包&#xff08;一&#xff09;官网下载&#xff08;二&#xff09;其…...

[HelloCTF]PHPinclude-labs超详细WP-Level 1-FILE协议

源码分析 <?php include("get_flag.php");isset($_GET[wrappers]) ? include("file://".$_GET[wrappers]) : ;highlight_file(__FILE__); ?>第一句 include("get_flag.php");, 使代码包含了 get_flag.php 的内容 大概是生成 Flag 之类的…...

Skia 图形引擎介绍

文章目录 一、Skia 的基本概念1. 定位与作用2. 历史背景 二、Skia 的核心架构1. 模块化设计2. 渲染流程3. 跨平台适配 三、Skia 在 Flutter 中的角色1. 自绘 UI 的核心依赖2. 跨平台一致性3. 性能优化 四、Skia 的性能优势1. 高效的图形处理2. 与原生渲染的对比3. 性能瓶颈 五、…...

构建高可靠NFS存储:自动化挂载保障机制的设计与优势

一、背景与需求场景 在分布式系统或集群架构中&#xff0c;NFS&#xff08;Network File System&#xff09;是跨节点共享存储的经典方案。然而&#xff0c;传统/etc/fstab配置的静态挂载方式存在明显缺陷&#xff1a; 服务启动顺序不可控&#xff0c;网络未就绪时挂载失败临…...

Spring Boot对接twilio发送邮件信息

要在Spring Boot应用程序中对接Twilio发送邮件信息&#xff0c;您可以使用Twilio的SendGrid API。以下是一个简单的步骤指南&#xff0c;帮助您完成这一过程&#xff1a; 1. 创建Twilio账户并获取API密钥 注册一个Twilio账户&#xff08;如果您还没有的话&#xff09;。在Twi…...

如何创建并保存HTML文件?零基础入门教程

原文&#xff1a;如何创建并保存HTML文件&#xff1f;零基础入门教程 | w3cschool笔记 本文将以Windows系统为例&#xff0c;教你用最简单的记事本创建并保存第一个HTML网页。 &#x1f4dd; 第一步&#xff1a;准备工具 文本编辑器&#xff1a;使用系统自带的记事本&#xff…...

vue3 + css 列表无限循环滚动+鼠标移入停止滚动+移出继续滚动

1.动画文件.vue <template><div class"dashboard" click"setFullScreen"><div class"warp-box"><el-scrollbar ref"scrollRef" height"100%" scroll"handelScroll"><div class"…...

C#的简单工厂模式、工厂方法模式、抽象工厂模式

工厂模式是一种创建型设计模式&#xff0c;主要将对象的创建和使用分离&#xff0c;使得系统更加灵活和可维护。常见的工厂模式有简单工厂模式、工厂方法模式和抽象工厂模式&#xff0c;以下是 C# 实现的三个案例&#xff1a; 简单工厂模式 简单工厂模式通过一个工厂类来创建…...

Vue:Vue2和Vue3创建项目的几种常用方式以及区别

前言 Vue.js 和 Element UI 都是用 JavaScript 编写的。 1、Vue.js 是一个渐进式 JavaScript 框架。2、Element UI 是基于 Vue.js 的组件库。3、JavaScript 是这两个项目的主要编程语言。 而Element Plus是基于TypeScript开发的。 一、Vue2 1、基于vuecli工具创建 vue2 …...

C++ list类

C list类 目录 C list类引言1.list的使用1.1 list的构造1.2 list的iterator的使用1.3 list capacity1.4 list element acess1.5 list modifiers 2. list的迭代器失效3. list的模拟实现3.1 List.h文件3.2 List的反向迭代器 4.list与vector的对比 引言 在C标准库中&#xff0c;l…...

LeetCode 热题 100_跳跃游戏(78_55_中等_C++)(贪心算法)

LeetCode 热题 100_跳跃游戏&#xff08;78_55&#xff09; 题目描述&#xff1a;输入输出样例&#xff1a;题解&#xff1a;解题思路&#xff1a;思路一&#xff08;贪心算法&#xff09;&#xff1a; 代码实现代码实现&#xff08;思路一&#xff08;贪心算法&#xff09;&am…...

【Redis】Redis的数据删除(过期)策略,数据淘汰策略。

如果问到&#xff1a;假如Redis的key过期之后&#xff0c;会立即删除吗&#xff1f; 其实就是想问数据删除(过期)策略。 如果面试官问到&#xff1a;如果缓存过多&#xff0c;内存是有限的&#xff0c;内存被占满了怎么办&#xff1f; 其实就是问&#xff1a;数据的淘汰策略。…...

C++和标准库速成(八)——指针、动态数组、const、constexpr和consteval

目录 1. 指针和动态数组1.1 栈和自由存储区1.2 使用指针1.3 动态分配的数组1.4 空指针常量 2. const2.1 const修饰类型2.2 const与指针2.3 使用const保护参数2.4 const方法(建议&#xff09; 3. constexpr4. consteval参考 1. 指针和动态数组 动态内存允许所创建的程序具有在编…...

深入解析 Spring Boot 中的 FailureAnalyzer

深入解析 Spring Boot 中的 FailureAnalyzer 在 Spring Boot 应用中&#xff0c;我们难免会遇到启动失败的情况&#xff0c;而默认的异常信息往往过于复杂&#xff0c;导致排查问题变得困难。Spring Boot 提供了一套强大的 FailureAnalyzer 机制&#xff0c;能够捕获常见的异常…...

20. Excel 自动化:Excel 对象模型

一 Excel 对象模型是什么 Excel对象模型是Excel图形用户界面的层次结构表示&#xff0c;它允许开发者通过编程来操作Excel的各种组件&#xff0c;如工作簿、工作表、单元格等。 xlwings 是一个Python库&#xff0c;它允许Python脚本与Excel进行交互。与一些其他Python库&#x…...

【Matlab GUI】封装matlab GUI为exe文件

注&#xff1a;封装后的exe还是需要有matlab环境才能运行 &#xff08;1&#xff09;安装MCRinstaller.exe文件&#xff0c;在matlab安装目录下的toolbox/compiler/deploy/win64文件夹里 &#xff08;2&#xff09;安装完MCRinstaller.exe&#xff0c;字命令窗口输入&#x…...

ModBus TCP/RTU互转(主)(从)|| Modbus主动轮询下发的工业应用 || 基于智能网关的串口服务器进行Modbus数据收发的工业应用

目录 前言 一、ModBus TCP/RTU互转&#xff08;从&#xff09;及应用|| 1.1 举栗子 二、ModBus TCP/RTU互转&#xff08;主&#xff09; 2.1 举栗子 三、ModBus 主动轮询 3.1 Modbus主动轮询原理 3.2 Modbus格式上传与下发 3.2.1.设置Modbus主动轮询指令 3.2.2 设…...

Linux top 命令详解:从入门到高级用法

Linux top 命令详解&#xff1a;从入门到高级用法 在 Linux 系统中&#xff0c;top 是一个强大的实时监控工具&#xff0c;用于查看系统资源使用情况和进程状态。它可以帮助你快速了解 CPU、内存、负载等信息&#xff0c;是系统管理员和开发者的日常利器。本文将从基本用法开始…...

【网络协议】基于UDP的可靠协议:KCP

TCP是为流量设计的&#xff08;每秒内可以传输多少KB的数据&#xff09;&#xff0c;讲究的是充分利用带宽。而 KCP是为流速设计的&#xff08;单个数据包从一端发送到一端需要多少时间&#xff09;&#xff0c;以10%-20%带宽浪费的代价换取了比 TCP快30%-40%的传输速度。TCP信…...

【Docker入门】构建推送第一个Docker映像

【Docker入门】构建推送第一个Docker映像 Build and Push the First Docker Image By JacksonML Docker的容器(Container)映像是轻量级的可执行独立包&#xff0c;包含代码、运行时、库、环境变量以及配置文件&#xff0c;它对于运行软件至关重要。注册表可在团队间分享映像。…...

Python----计算机视觉处理(Opencv:图像颜色替换)

一、开运算 开运算就是对图像先进行腐蚀操作&#xff0c; 然后进行膨胀操作。开运算可以去除二值化图中的小的噪点&#xff0c;并分离相连的物体。 其主要目的就是消除那些小白点 在开运算组件中&#xff0c;有一个叫做kernel的参数&#xff0c;指的是核的大小&#xff0c;通常…...

搭建自己的OCR服务

网上看到相关文章&#xff0c;这里整理记录一下&#xff0c;仅供学习。 搭建自己的OCR服务&#xff0c;第一步&#xff1a;选择合适的开源OCR项目 - PandaCode辉 - 博客园 一、OCR是什么&#xff1f; 光学字符识别&#xff08;Optical Character Recognition, OCR&#xff09…...

vue:组件的使用

Vue&#xff1a;组件的使用 1、什么是组件 1.1、传统方式开发的应用 一个网页通常包括三部分&#xff1a;结构&#xff08;HTML&#xff09;、样式&#xff08;CSS&#xff09;、交互&#xff08;JavaScript&#xff09;。在传统开发模式下&#xff0c;随着项目规模的增大&a…...

leetcode日记(105)买卖股票的最佳时机Ⅱ

原本以为是一个很难想的动态规划&#xff0c;没想到是最简单的贪心…… 如果实在想不出就画个折线图&#xff0c;只买上涨的就行了&#xff0c;所有上涨的段都取到。 真的没想到会这么简单…… class Solution { public:int maxProfit(vector<int>& prices) {int …...

7种数据结构

7种数据结构 顺序表sqlite.hseqlite.c 单链表linklist.clinklist.h 双链表doulinklist.cdoulinklist.h 链式栈linkstack.clinkstack.h 队列SeqQueue.cSeqQueue.h 树tree.c 哈希表hash.c 顺序表 sqlite.h #ifndef __SEQLIST_H__ #define __SEQLIST_H__ typedef struct person…...

论文阅读:Deep Hybrid Camera Deblurring for Smartphone Cameras

今天介绍一篇 ACM SIGGRAPH 2024 的文章&#xff0c;关于手机影像中的去模糊的文章。 Deep Hybrid Camera Deblurring for Smartphone Cameras Abstract 手机摄像头尽管取得了显著的进步&#xff0c;但由于传感器和镜头较为紧凑&#xff0c;在低光环境下的成像仍存在困难&am…...

Redis 三主三从集群部署的完整方案

一、架构设计原理‌ 分布式数据分片 哈希槽机制‌&#xff1a;Redis Cluster 将数据划分为 16384 个槽位&#xff0c;每个主节点负责部分槽位&#xff08;如主节点1管理槽0-5460&#xff0c;主节点2管理5461-10922等&#xff09;。 自动负载均衡‌&#xff1a;数据按哈希值分配…...

C++项目:高并发内存池_上

目录 1. 项目介绍 2. 内存池概念 2.1 池化技术 2.2 内存池和内存碎片 2.3 细看malloc 3. 定长内存池的实现 ObjectPool.hpp 4. 高并发内存池框架 5. thread cache测试 5.1 thread cache框架 5.2 ConcurrentAlloc.hpp 6. central cache测试 6.1 central cache框架 …...

『Plotly实战指南』--折线图绘制基础篇

在数据分析的世界中&#xff0c;折线图是一种不可或缺的可视化工具。 它能够清晰地展示数据随时间或其他变量的变化趋势&#xff0c;帮助我们快速发现数据中的模式、趋势和异常。 无论是金融市场分析、气象数据监测&#xff0c;还是业务增长趋势预测&#xff0c;折线图都能以直…...

【css酷炫效果】纯CSS实现波浪形分割线

【css酷炫效果】纯CSS实现波浪形分割线 缘创作背景html结构css样式完整代码效果图 想直接拿走的老板&#xff0c;链接放在这里&#xff1a;https://download.csdn.net/download/u011561335/90492023 缘 创作随缘&#xff0c;不定时更新。 创作背景 刚看到csdn出活动了&…...

【资料分享】全志科技T113-i全国产(1.2GHz双核A7 RISC-V)工业核心板规格书

核心板简介 创龙科技SOM-TLT113 是一款基于全志科技T113-i 双核ARM Cortex-A7 玄铁C906 RISC-V HiFi4 DSP 异构多核处理器设计的全国产工业核心板&#xff0c;ARM Cortex-A7 处理单元主频高达1.2GHz。核心板 CPU、ROM、RAM、电源、晶振等所有元器件均采用国产工业级方案&…...

Coco AI 智能检索 Hugo Blog 集成指南

在此前的文章中&#xff0c;我们介绍了如何使用 Coco Server 连接 Notion&#xff0c;实现智能内容检索。本次&#xff0c;我们将进一步探索如何在 Coco Server 最新版本 中集成 Hugo Site&#xff0c;以便对 Hugo 站点 进行高效检索。 Coco Server 部署方式 要在本地或服务器…...

【MySQL数据库】多表查询(笛卡尔积现象,联合查询、内连接、左外连接、右外连接、子查询)-通过练习快速掌握法

在DQL的基础查询中&#xff0c;我们已经学过了多表查询的一种&#xff1a;联合查询&#xff08;union&#xff09;。本文我们将系统的讲解多表查询。 笛卡尔积现象 首先&#xff0c;我们想要查询emp表和stu表两个表&#xff0c;按照我们之前的知识栈&#xff0c;我们直接使用…...

jmeter将返回的数据写入csv文件

举例说明&#xff0c;我需要接口返回体中的exampleid与todoid的数据信息&#xff08;使用边界提取器先将其提取&#xff09;&#xff0c;并将其写入csv文件进行保存 使用后置处理器BeanShell 脚本实例如下 import java.io.*;// 设置要写入的文件路径 String filePath "…...

AI如何在财务工作中提升效率的一些看法

文章目录 1. 自动化重复性任务2. 财务预测与分析3. 欺诈检测与风险管理4. 智能报表与决策支持5. 税务管理优化6. 提升团队协作与客户体验未来的趋势与挑战结论 随着人工智能&#xff08;AI&#xff09;技术的迅猛发展&#xff0c;其正全方位地革新各行各业的运作模式&#xff0…...

OpenCV入门指南:从安装到基本操作

引言 OpenCV&#xff08;Open Source Computer Vision Library&#xff09;是一个开源的计算机视觉和机器学习软件库。它包含了数百种计算机视觉算法&#xff0c;广泛应用于图像处理、视频分析、物体检测、人脸识别等领域。本文将带你从安装OpenCV开始&#xff0c;逐步了解其基…...

简单以太网配置

display arp //查看路由器mac地址 交换机配置命令&#xff1a; system-view // 从用户视图进入系统视图 dis mac-address //查看mac地址表 路由器配置命令: system-view // 从用户视图进入系统视图 int GigabitEthernet 0/0/0 //进入G口 0/0/0 进入之后配置网关: ip addre…...

蓝桥杯嵌入式组第十四届省赛题目解析+STM32G431RBT6实现源码

文章目录 1.题目解析1.1 分而治之&#xff0c;藕断丝连1.2 模块化思维导图1.3 模块解析1.3.1 KEY模块1.3.2 LED模块1.3.3 LCD模块1.3.4 TIM模块1.3.4.1 频率变化处理1.3.4.1 占空比计算 1.3.5 ADC模块 2.源码2.1cubemx配置3.第十四届题目 前言&#xff1a;STM32G431RBT6实现嵌入…...

让双向链表不在云里雾里

又来博客留下我的足迹了&#xff0c;哈哈哈&#xff0c;这次是对于双向链表的理解 目录 创建双向链表&#xff1a; 申请结点&#xff1a; 双向链表初始化&#xff1a; 双向链表插入结点&#xff1a; 双向链表删除结点&#xff1a; 双向链表的打印&#xff1a; 双向链表…...

基于django+vue的购物商城系统

开发语言&#xff1a;Python框架&#xff1a;djangoPython版本&#xff1a;python3.8数据库&#xff1a;mysql 5.7数据库工具&#xff1a;Navicat11开发软件&#xff1a;PyCharm 系统展示 系统首页 热卖商品 优惠资讯 个人中心 后台登录 管理员功能界面 用户管理 商品分类管理…...

在Vue3中使用Echarts的示例

1.常用-引用ts文件方式 1.1 导出ts文件-一个简单的柱状图 export const baseBarChart (xdata: string[], data: number[][], legendData: string[]) > {if (data.length 0) {return noData;}// 定义颜色数组const color [#00CCCC,#FF9900,#1677DC,#FF6666,#B366FF,#666…...

TCP协议的多线程应用、多线程下的网络编程

DAY13.2 Java核心基础 多线程下的网络编程 基于单点连接的方式&#xff0c;一个服务端对应一个客户端&#xff0c;实际运行环境中是一个服务端需要对应多个客户端 创建ServerSocketNable类&#xff0c;多线程接收socket对象 public class ServerSocketNable implements Run…...

每日学习Java之一万个为什么(待补充)

Git分支操作 git branch 分支名 git branch -v git checkout -b 分支名 git checkout 分支名 git merge 分支名 git branch -d | -D 分支名Git冲突 git同名文件合并的最基本单位是行。同名文件同一行不同就会发生冲突。 解决办法&#xff1a;及时沟通&#xff0c;手动更改&…...

设计C语言的单片机接口

一、主要内容 (一)控制引脚 1、定义管脚 // 定义管脚的结构体 struct pin{ int id; // 管脚编号 int mode; // 模式&#xff0c;输入为1&#xff0c;输出为0 int pull; // 输入电阻 int driver; // 功率 } 2、输出电平 语法&#xff1a; void pin_output(s…...

博客迁移----宝塔面板一键迁移遇到问题

前景 阿里云轻量级服务器到期了&#xff0c;又免费领了个ESC&#xff0c; 安转了宝塔面板。现在需要迁移数据&#xff0c;使用宝塔面板一键迁移功能&#xff0c;完成了数据的迁移&#xff0c;改了域名的解析&#xff0c;现在进入博客是显示502 bad grateway 宝塔搬家参考链接…...

抽象工厂模式 (Abstract Factory Pattern)

抽象工厂模式 (Abstract Factory Pattern) 是一种创建型设计模式&#xff0c;它提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。 一、基础 1. 意图 提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无需指定它们具体的类。 2. …...

LeetCode 第14~16题

目录 LeetCode 第14题&#xff1a;最长公共前缀 LeetCode 第15题&#xff1a;三数之和 LeetCode 第16题&#xff1a;最接近的三数之和 LeetCode 第14题&#xff1a;最长公共前缀 题目描述 编写一个函数来查找字符数组中的最长公共前缀。如果不存在公共前缀&#xff0c;返回字符…...

深入了解Linux —— git三板斧

版本控制器git 为了我们方便管理不同版本的文件&#xff0c;就有了版本控制器&#xff1b; 所谓的版本控制器&#xff0c;就是能够了解到一个文件的历史记录&#xff08;修改记录&#xff09;&#xff1b;简单来说就是记录每一次的改动和版本迭代的一个管理系统&#xff0c;同…...