Qt案例 在对QGraphicsView视图修改和撤销修改图元操作时,使用命令模式实现。

当项目中有QGraphicsView视图直接修改图元的功能时,常会有Ctri+ZCtrl+Y这种执行与撤销图元修改的功能,以便于在修改图元后能够进行一个还原/执行操作,此时就适合使用命令模式设计来实现这个功能。
以下示例在WINDOWS系统,Qt Creator 5.13.1,MSCV2017 X64 Release环境实现,以及涉及QStack类的使用。

目录导读

    • 一、命令模式详解
    • 二、命令模式的应用场景
    • 二、QStack类详解
    • 三、QGraphicsView视图-修改还原-命令模式案例实现
      • 命令角色(Command) 接口实现
      • 请求者(Invoker)功能实现
      • 具体命令(ConcreteCommand)实现
        • 1. 窗体拖拽的上下左右平移命令对象
        • 2. 对场景类的缩放和放大命令
        • 3. 新增图元命令对象
        • 4. 删除图元命令对象
      • 接收者(Receiver)实现
      • 命令模式 缺点

一、命令模式详解

在诸多设计模式中,命令模式适用范围非常广泛,比如工业设计,CAD二开,流程图设计等,都经常能看到命令模式的设计模块和功能实现。
有时候在工作中为了尽快完成功能,基本都是面向过程开发,不会过多的使用一些设计模式,但是在面试中,只要工作年限有个两三年的,面试官都基本会问一句了解那些设计模式,谈谈具体的使用,无一例外!
所以日常中可以不用设计模式开发,但是不能不了解,特别是单例模式,提到的概率特别高!

命令模式是一种设计模式,它将请求或操作封装在对象中,并将这些对象传递给调用对象。该模式的意图是将请求发送者与实际执行操作的接收者解耦,使得发送者和接收者可以独立变化

在命令模式中,
主要有三个角色:

  • 命令(Command):定义了执行操作的接口,通常包含一个execute方法,用于调用具体的操作。
  • 具体命令(Concrete_Command):实现了命令接口,负责执行具体的操作。它通常包含了对接收者的引用,通过调用接收者的方法来完成请求的处理。
  • 接收者(Receiver):知道如何执行与请求相关的操作,实际执行命令的对象。

其他角色:

  • 调用者/请求者(Invoker):
    发送命令的对象,它包含了一个或多个命令对象并能触发命令的执行。调用者并不直接处理请求,而是通过将请求传递给命令对象来实现。
  • 客户端(Client):
    创建具体命令对象并设置其接收者,将命令对象交给调用者执行。

命令模式结构示意图(命令模式 | 菜鸟教程):
来自菜鸟教程-命令模式

命令模式的核心思想是将请求以命令的形式封装在对象中,并传给调用对象。调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令。这种设计使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。

命令模式的主要优点是降低了系统耦合度,使得请求的一方和接收的一方独立开来,可以独立变化。新的命令可以很容易添加到系统中去。但是,使用命令模式可能会导致某些系统有过多的具体命令类。

总之,命令模式是一种将请求或操作封装在对象中,并将这些对象传递给调用对象的设计模式,它使得请求的一方和接收的一方解耦,可以独立变化

文章参考:

  • 命令模式 | 菜鸟教程
  • 设计模式(行为型模式)之:命令模式(Command Pattern)
  • 【设计模式】命令模式 ( 简介 | 适用场景 | 优缺点 | 代码示例 )
  • 技术分享 | 什么是命令模式

二、命令模式的应用场景

命令模式的应用场景主要有以下几个方面:

  1. 多级撤销/重做: 如果系统需要实现多级撤销或重做操作,可以将命令对象存储在历史栈中,通过调用命令对象的undo和redo方法来实现撤销和重做。
  2. 事务处理: 借助命令模式,可以简单地实现一个具有原子事务的行为。
  3. 进度条: 如果系统需要按顺序执行一系列的命令操作,可以通过调用每个命令对象的getEstimatedDuration方法来评估执行状态并显示出合适的进度条。
  4. 网络通信: 通过网络发送命令到其他机器上运行。
  5. 日志系统: 可以将请求以命令的形式记录在日志中,方便日后审计和重放。
  6. 游戏开发: 在游戏开发中,命令模式常被用于实现游戏逻辑,如移动、攻击等。
  7. GUI开发: 在GUI开发中,命令模式可以用于实现撤销、重做等功能
  8. 自动化测试: 将测试用例封装为命令对象,方便测试的执行和管理。
  9. 批处理: 将一系列操作封装成命令对象,然后按照顺序或并行执行这些命令对象,以完成批量处理任务。
  10. 游戏AI: 在游戏AI中,可以使用命令模式来封装玩家的操作,然后由AI实体来解析和执行这些命令。
    总之,命令模式的应用场景非常广泛,只要是需要将请求或操作封装在对象中,并传递给调用对象的情况,都可以考虑使用命令模式。

应用场景说明来自于文言一心
如果工作中遇到以上应用场景示例,建议直接使用命令模式开发,别管会不会,多报错几次、多掉点头发就会了!

二、QStack类详解

在涉及到QGraphicsView视图修改图元操作,也就是 多级撤销/重做 这种应用场景,这种情况下需要一个堆栈来保存命令的多个对象,并且这个堆栈必须符合 后进先出 的特点,这样才能保证最后一次修改的功能最先被还原的。

QStack(Qt Stack)是一个来自于Qt框架的容器类,类似于C++标准库中的std::stackQStack容器是一种后进先出(LIFO)的数据结构,即最后一个进入堆栈的元素将最先被移除。QStack继承了QVector,所以它拥有QVector的所有功能,同时提供了堆栈的特定操作。
具体关于QStack 内容建议参考:
Qt QStack 详解:从底层原理到高级用法
描述的相当详细,老厉害了!

三、QGraphicsView视图-修改还原-命令模式案例实现

QGraphicsView视图对图元进行一个增加、删除、改变大小、移动图元等一系列操作时,使用命令模式执行/撤销修改动作命令,也就是常说的修改还原功能。

命令角色(Command) 接口实现

在操作图元时,增加、删除、改变大小、移动图元等就是一个个命令,而这些命令只有两个相同的动作,撤销命令执行命令
所以声明一个通用的 Command 接口类,

#include <QObject>
/// <summary>
/// 定义一个命令角色。一般是一个接口,为所有的命令对象声明一个接口,规范将要进行的命令操作。Command
/// 命令角色(Command): 定义命令的接口,声明执行的方法。这是一个抽象类。
/// </summary>
class Command : public QObject
{
    Q_OBJECT
public:
    explicit Command(QObject *parent = nullptr);
    virtual ~Command(){delete this;}

    ///
    /// \brief 执行命令
    ///
   virtual void execute() const=0; //MSCV写法 如果加上const 就必须赋值

    ///
    /// \brief 撤销命令
    ///
    virtual void undo() const=0;

    ///
    /// \brief 是否更新数据
    ///是否需要刷新数据
    ///用于一些操作需要更新数据源的实现
    /// 当前用处不大 如果涉及到数据的交互此时就需要,此处只是给个示范
    /// \return
    
    virtual bool   isrefresh_data() const{
        return false;
    }

};

需要注意的是:
在MSCV编译器中,无论是否添加const ,纯虚函数一定要加 =0,否则编译不通过。
如:virtual void undo() const=0;或者virtual void undo() =0;
而在Mingw编译器中不加。

请求者(Invoker)功能实现

请求者角色(Invoker):要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。 这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
这个类一般包含在QGraphicsView类的属性中,用于存储增加、删除、改变大小、移动图元等一系列命令对象。

Invoker_Command.h 头文件

#include <QObject>
#include <QWidget>
#include <QStack>
#include <QObject>
#include <QWidget>
#include <QMessageBox>
#include "command.h"
/// <summary>
/// 请求者角色(Invoker):要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。
/// 这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
///
/// 持有一个命令对象,有一个行动方法,在某个时间点调用命令对象的 execute() 方法,将请求付诸实行。
/// </summary>

class Invoker_Command:public QObject
{
    Q_OBJECT
public:
    Invoker_Command(QObject *parent = nullptr);
    ///撤销队列 先进后出
    QStack<Command*>* undoStack = new QStack<Command*>();
    ///执行队列 先进后出
    QStack<Command*>* redoStack = new QStack<Command*>();

    ///插入命令角色
    void SetCommad(Command* _com);

    ///
    /// \brief 撤销动作
    ///
    void UndoStack();

    ///
    /// \brief 执行动作
    ///
    void RedoStack();

    ///
    /// \brief 清除所有队列
    ///
    void clearall();

signals:
    ///发送撤销和还原的缓存数量 用于界面上使用,
    ///甚至可以扩展为选定某一端前还原,部分CAD软件软件就有这种功能
    void stack_count(int undocount,int redocount);
    ///数据刷新,修改界面一些属性
    void data_update();
};

Invoker_Command.cpp 头文件

#include "invoker_command.h"
#include <QDebug>
Invoker_Command::Invoker_Command(QObject *parent)
    :QObject(parent)
{

}


void Invoker_Command::SetCommad(Command* _com)
{
    redoStack->clear();
    undoStack->push(_com);
    if(_com->isrefresh_data())
        emit data_update();
    emit stack_count(undoStack->count(),redoStack->count());
}


void Invoker_Command::UndoStack()
{
    if (undoStack->count() == 0)
    {
        QMessageBox::information((QWidget *)this->parent(),tr("提示"),tr("无可撤销项!"));
        return;
    }

    Command* com = undoStack->pop();
    com->undo();
    if(com->isrefresh_data())
        emit data_update();

    redoStack->push(com);
    emit stack_count(undoStack->count(),redoStack->count());
}


void Invoker_Command::RedoStack()
{
    if (redoStack->count() == 0)
    {
        QMessageBox::information((QWidget *)this->parent(),tr("提示"),tr("无可执行项!"));
        return;
    }

    Command* com = redoStack->pop();
    com->execute();
    if(com->isrefresh_data())
        emit data_update();

    undoStack->push(com);
    emit stack_count(undoStack->count(),redoStack->count());
}

void Invoker_Command::clearall()
{
    redoStack->clear();
    undoStack->clear();
    emit stack_count(undoStack->count(),redoStack->count());
}

注意: 在添加一个命令对象时,需要清空可执行队列,否则会造成数据混乱。

具体命令(ConcreteCommand)实现

1. 窗体拖拽的上下左右平移命令对象

视窗显示内容的切换,实际就是两个的点之间的视窗移动,所以这里只需要记录前后两个基准点就行;
Order_View_Move.h

/窗体拖拽的上下左右平移 命令对象
/ 实际上是两个的点之间的视窗移动
class Order_View_Move:public Command
{
    Q_OBJECT
public:
    explicit Order_View_Move(QPointF _oldpos,QPointF _newpos,QObject *parent = nullptr);
    ~Order_View_Move() Q_DECL_OVERRIDE {}

    ///
    ///  执行命令
    ///
    void execute() const Q_DECL_OVERRIDE ;

    ///
    ///  撤销命令
    ///
    void undo() const Q_DECL_OVERRIDE ;
public:
    bool isrefresh_data() const Q_DECL_OVERRIDE{return  false;}
private:
    QPointF Oldpos;
    QPointF Newpos;
};

Order_View_Move.cpp

Order_View_Move::Order_View_Move(QPointF _oldpos,QPointF _newpos,QObject *parent )
    :Command(parent),Oldpos(_oldpos),Newpos(_newpos)
{

}

void Order_View_Move::execute() const
{
    if(this->parent()!=nullptr)
    {
    	//QGraphicsViewRefactor 继承的 QGraphicsView ,重构类
        QGraphicsViewRefactor* view=qobject_cast<QGraphicsViewRefactor*>(this->parent());
        if(view!=nullptr )
        {
            QPointF disPointF =Newpos - Oldpos ;
            //调整位置
            view->scene()->setSceneRect( view->scene()->sceneRect().x()-disPointF.x(),
                                         view->scene()->sceneRect().y()-disPointF.y(),
                                         view->scene()->sceneRect().width(),
                                         view->scene()->sceneRect().height());
            view->scene()->update();
        }
    }
}
void Order_View_Move::undo() const
{
    if(this->parent()!=nullptr)
    {
        QGraphicsViewRefactor* view=qobject_cast<QGraphicsViewRefactor*>(this->parent());
        if(view!=nullptr )
        {
            QPointF disPointF =Oldpos - Newpos ;
            //调整位置
            view->scene()->setSceneRect( view->scene()->sceneRect().x()-disPointF.x(),
                                         view->scene()->sceneRect().y()-disPointF.y(),
                                         view->scene()->sceneRect().width(),
                                         view->scene()->sceneRect().height());
            view->scene()->update();
        }
    }
}
2. 对场景类的缩放和放大命令

QGraphicsScene 场景类放大缩小需要保存三个参数;
int Delta; 判断放大还是缩小
double mom; 放大缩小倍数
QPointF Pos; 放大缩小的基准点
Order_View_Scale.h

/ <summary>
/ 命令对象
/ 对场景类的 缩放 和 放大
/ <summary>
class  Order_View_Scale:public Command
{
    Q_OBJECT
public:
    explicit Order_View_Scale(int _delta,double _mom,QPointF _Pos,QObject *parent = nullptr);
    ~Order_View_Scale() Q_DECL_OVERRIDE {}

    ///
    ///  执行命令
    ///
    void execute() const Q_DECL_OVERRIDE;

    ///
    ///  撤销命令
    ///
    void undo() const Q_DECL_OVERRIDE;
private:
    int Delta;
    double mom;
    QPointF Pos;

public:
    bool isrefresh_data() const Q_DECL_OVERRIDE{return  false;}
};

Order_View_Scale.cpp

Order_View_Scale::Order_View_Scale(int _delta,double _mom,QPointF _Pos,QObject *parent)
    :Command(parent),Delta(_delta),mom(_mom),Pos(_Pos)
{
}

void Order_View_Scale::execute() const
{
    if(this->parent()!=nullptr)
    {
        QGraphicsViewRefactor* ride=qobject_cast<QGraphicsViewRefactor*>(this->parent());
        if(Delta>0)
        {
            ride->scale_Sum=ride->scale_Sum*mom;
            ride->Setwheelscale(mom,mom,Pos);
            emit ride->Scale_Sum_Change(ride->scale_Sum);
        }
        else
        {
            ride->scale_Sum=ride->scale_Sum/mom;
            ride->Setwheelscale(1.0 / mom, 1.0 / mom,Pos);
            emit ride->Scale_Sum_Change(ride->scale_Sum);
        }
    }
}

void Order_View_Scale::undo() const
{
    if(this->parent()!=nullptr)
    {
        QGraphicsViewRefactor* ride=qobject_cast<QGraphicsViewRefactor*>(this->parent());
        if(!(Delta>0))
        {
            ride->scale_Sum=ride->scale_Sum*mom;
            ride->Setwheelscale(mom,mom,Pos);
            emit ride->Scale_Sum_Change(ride->scale_Sum);
        }
        else
        {
            ride->scale_Sum=ride->scale_Sum/mom;
            ride->Setwheelscale(1.0 / mom, 1.0 / mom,Pos);
            emit ride->Scale_Sum_Change(ride->scale_Sum);
        }
    }
}
3. 新增图元命令对象

新增图元执行动作就是将图元加进QGraphicsScene 场景类 中,撤销动作就是将新增图元从QGraphicsScene 场景类 中删除。
同时使用 QList<QGraphicsItem*> AddtoItems;保存图元数据。
Order_Append_FlowItem.h

/ <summary>
/ 命令对象
/ 各種图元的新增
/ <summary>
class  Order_Append_FlowItem:public Command
{
    Q_OBJECT
public:
    explicit Order_Append_FlowItem(QList<QGraphicsItem*> _AddtoItems,QObject *parent = nullptr);
    ~Order_Append_FlowItem() Q_DECL_OVERRIDE {}

    ///
    ///  执行命令
    ///
    void execute() const Q_DECL_OVERRIDE;

    ///
    ///  撤销命令
    ///
    void undo() const Q_DECL_OVERRIDE;
private:
    QList<QGraphicsItem*> AddtoItems;

public:
    bool isrefresh_data() const Q_DECL_OVERRIDE{return  true;}
};

Order_Append_FlowItem.cpp


Order_Append_FlowItem::Order_Append_FlowItem(QList<QGraphicsItem*> _AddtoItems,QObject *parent)
    :Command(parent),AddtoItems(_AddtoItems)
{



}


void Order_Append_FlowItem::execute() const
{
    if(this->parent()!=nullptr)
    {
        QGraphicsViewRefactor* ride=qobject_cast<QGraphicsViewRefactor*>(this->parent());
        for(QGraphicsItem* item:qAsConst(AddtoItems))
        {
            if(item->type()==QGraphicsFlowTextItem::Type)
               ride->m_scene->Correlation_Text.append(qgraphicsitem_cast<QGraphicsFlowTextItem*>(item));
            if(item->type()==QGraphicsConnectingLineItem::Type)
                ride->m_scene->ConnectLints.append(qgraphicsitem_cast<QGraphicsConnectingLineItem*>(item));
             ride->m_scene->addItem(item);
        }
    }
}


void Order_Append_FlowItem::undo() const
{
    if(this->parent()!=nullptr)
    {
        QGraphicsViewRefactor* ride=qobject_cast<QGraphicsViewRefactor*>(this->parent());
        for(QGraphicsItem* item:qAsConst(AddtoItems))
        {
            if(item->type()==QGraphicsFlowTextItem::Type)
               ride->m_scene->Correlation_Text.removeAll(qgraphicsitem_cast<QGraphicsFlowTextItem*>(item));
            if(item->type()==QGraphicsConnectingLineItem::Type)
                ride->m_scene->ConnectLints.removeAll(qgraphicsitem_cast<QGraphicsConnectingLineItem*>(item));
            ride->m_scene->removeItem(item);
        }
    }
}

4. 删除图元命令对象

删除图元对象,就是与新增图元对象命令执行与撤销的动作相反,所以这里直接继承了
Order_Append_FlowItem 新增图元命令对象而不是 Command 对象;

Order_Delete_FlowItem.h


/ <summary>
/ 命令对象
/ 图元对象的删除
/ <summary>
class Order_Delete_FlowItem:public Order_Append_FlowItem
{
    Q_OBJECT
public:
    explicit Order_Delete_FlowItem(QList<QGraphicsItem*> _AddtoItems,QObject *parent = nullptr);
    ~Order_Delete_FlowItem() Q_DECL_OVERRIDE {}

    ///
    ///  执行命令
    ///
    void execute() const Q_DECL_OVERRIDE;

    ///
    ///  撤销命令
    ///
    void undo() const Q_DECL_OVERRIDE;
private:
    QList<QGraphicsItem*> AddtoItems;

public:
    bool isrefresh_data() const Q_DECL_OVERRIDE{return  true;}
};

Order_Delete_FlowItem.cpp

Order_Delete_FlowItem::Order_Delete_FlowItem(QList<QGraphicsItem*> _AddtoItems,QObject *parent)
    :Order_Append_FlowItem(_AddtoItems,parent),AddtoItems(_AddtoItems)
{


}

void Order_Delete_FlowItem::execute() const
{
    Order_Append_FlowItem::undo();
}


void Order_Delete_FlowItem::undo() const
{
    Order_Append_FlowItem::execute();
}

接收者(Receiver)实现

知道如何执行与请求相关的操作,实际执行命令的对象就是接收者
在这个案例中通过重构QGraphicsView类,监控键盘事件【Ctrl+Z与Ctrl+Y 】来执行 请求者(Invoker_Command) 的函数,请求者(Invoker_Command) 将请求传递给 命令对象QGraphicsView类就是接受者,也是客户端(Client)

命令模式 缺点

扩展命令会导致类的数量增加 , 增加了系统实现的复杂程度 ,需要针对每个命令都要开发一个与之对应的命令类 ;在这个示例中包括修改图元,新增图元,删除图元都需要创建一系列的命令类,在实际开发中这种命令操作只会更多!!!

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

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

相关文章

Stable Diffusion 模型下载:EnvyHyperrealXL01

模型介绍 一个基于 EnvyHyperdrive 和 NewReality 的超写实模型&#xff0c;使生成的照片级真实感模型在主题和视觉上与我的其他模型相似&#xff0c;除了&#xff0c;你知道&#xff0c;照片级真实感。 &#x1f603; 条目内容类型大模型基础模型SDXL 1.0来源CIVITAI作者_En…

SpinrgBoot-Mybatis基础

​ JDBCMyBatis概述MyBatis基础应用MyBatis动态标签 一、JDBC( Java DataBase Connectivity )&#xff1a;Java连接数据库的解决方案 概念&#xff1a;JDBC就是Java后端操作数据库的解决方案&#xff0c;操作数据的指令应该来自于前端&#xff0c;前端把数据提交到后端Java代…

CHS_09.2.3.6_2+多生产者-多消费者

CHS_09.2.3.6_2多生产者-多消费者 问题描述问题分析如何实现如何实现假如我们把盘子的容量设为二知识回顾 在这个小节中 我们会学习一个多生产者 多消费者的这样一个问题模型 问题描述 先来看一下问题的描述 假设桌子上面有一个盘子 每次只能向这个盘子里放一个水果 有四个人…

搜索专项---Flood Fill

文章目录 池塘计数城堡问题山峰与山谷 一、池塘计数OJ链接 1.BFS做法 #include <bits/stdc.h>#define x first #define y secondtypedef std::pair<int,int> PII;constexpr int N1010;int n,m; char g[N][N]; bool st[N][N];//用来表示已经记录过的 std::queue&…

3D力导向树插件-3d-force-graph学习002

一、实现效果&#xff1a;节点文字同时展示 节点显示不同颜色节点盒label文字并存节点上添加点击事件 二、利用插件&#xff1a;CSS2DRenderer 提示&#xff1a;以下引入文件均可在安装完3d-force-graph的安装包里找到 三、关键代码 提示&#xff1a;模拟数据可按如下格式填…

笔记---容斥原理

AcWing,890.能被整除的数 给定一个整数 n n n 和 m m m 个不同的质数 p 1 , p 2 , … , p m p_{1},p_{2},…,p_{m} p1​,p2​,…,pm​。 请你求出 1 ∼ n 1∼n 1∼n 中能被 p 1 , p 2 , … , p m p_{1},p_{2},…,p_{m} p1​,p2​,…,pm​ 中的至少一个数整除的整数有多少…

SemiDrive E3 MCAL 开发系列(1) – 环境搭建

一、 概述 本文将会介绍 SemiDrive E3 系列 MCU 的MCAL 开发环境搭建&#xff0c;包括如何获取及安装 EB 和 MCAL&#xff0c;E3 Gateway 开发板介绍&#xff0c;MCAL 工程的编译、下载等。 二、 EB 和 MCAL 的获取及安装 2.1 软件获取 EB Tresos 是用于进行 MCAL 配置…

Android Split APK介绍

文章目录 Split APKSplit APK 详细介绍概念Android App Bundle&#xff08;AAB&#xff09;Split APK 的优势动态分发减小安装包大小模块化和渠道分发 Split APK 的类型基于屏幕密度### 基于 CPU 架构基于语言 实现 Split APK Split APK Split APK 是 Android 中一种应用程序安…

【测试运维】web自动化全知识点笔记第1篇:什么是Web自动化测试(已分享,附代码)

本系列文章md笔记&#xff08;已分享&#xff09;主要讨论Web自动化测试相关知识。了解什么是自动化&#xff0c;理解什么是自动化测试以及为什么要使用自动化测试。具体包含&#xff1a;WebDriver的基本操作&#xff0c;WebDriver的鼠标、键盘操作&#xff0c;下拉选择框、警告…

2024.2.4日总结(小程序开发1)

小程序开发和普通网页开发的区别 运行环境不同 网页运行在浏览器环境中&#xff0c;小程序运行在微信环境中 API不同 由于运行的环境不同&#xff0c;所以小程序中无法调用DCM和BOM的API&#xff0c;但是可以调用微信环境提供的各种API&#xff0c;如&#xff1a;地理定位&…

Python 数据分析(PYDA)第三版(一)

原文&#xff1a;wesmckinney.com/book/ 译者&#xff1a;飞龙 协议&#xff1a;CC BY-NC-SA 4.0 关于开放版本 第 3 版的《Python 数据分析》现在作为“开放获取”HTML 版本在此网站wesmckinney.com/book上提供&#xff0c;除了通常的印刷和电子书格式。该版本最初于 2022 年…

微服务基础(持续更新中)

安装SSH以及虚拟机&#xff0c;Centos具体步骤见 https://b11et3un53m.feishu.cn/wiki/FJAnwOhpIihMkLkOKQocdWZ7nUc

vulhub中Adminer ElasticSearch 和 ClickHouse 错误页面SSRF漏洞复现(CVE-2021-21311)

Adminer是一个PHP编写的开源数据库管理工具&#xff0c;支持MySQL、MariaDB、PostgreSQL、SQLite、MS SQL、Oracle、Elasticsearch、MongoDB等数据库。 在其4.0.0到4.7.9版本之间&#xff0c;连接 ElasticSearch 和 ClickHouse 数据库时存在一处服务端请求伪造漏洞&#xff08…

202416读书笔记|《总有人会拥抱满身带刺的你》——今天我请客,想请你快乐

202416读书笔记|《总有人会拥抱满身带刺的你》——今天我请客&#xff0c;想请你快乐 这是一篇暖萌轻松的绘本推荐记录书评&#xff0c;《总有人会拥抱满身带刺的你》纳米著&#xff0c;《今天我请客&#xff0c;想请你快乐》燕七著&#xff0c;都还不错&#xff0c;截取摘录了…

从搜索引擎到答案引擎:LLM驱动的变革

在过去的几周里&#xff0c;我一直在思考和起草这篇文章&#xff0c;认为谷歌搜索正处于被颠覆的边缘&#xff0c;它实际上可能会影响 SEO 作为业务牵引渠道的可行性。 考虑到谷歌二十多年来的完全统治地位&#xff0c;以及任何竞争对手都完全无力削弱它&#xff0c;坦率地说&…

解析 JavaScript 异步编程:从回调地狱到 Promise 和 Async/Await

在现代的JavaScript开发中&#xff0c;处理异步任务变得愈发重要&#xff0c;因为它们允许我们在等待I/O、网络请求或定时器等事件时继续执行其他任务&#xff0c;以提高程序的性能和响应能力。本文将介绍JavaScript中异步编程的演变过程&#xff0c;从最初的回调地狱到后来的P…

【数据结构与算法】(9)基础数据结构 之 阻塞队列的单锁实现、双锁实现详细代码示例讲解

目录 2.8 阻塞队列1) 单锁实现2) 双锁实现 2.8 阻塞队列 之前的队列在很多场景下都不能很好地工作&#xff0c;例如 大部分场景要求分离向队列放入&#xff08;生产者&#xff09;、从队列拿出&#xff08;消费者&#xff09;两个角色、它们得由不同的线程来担当&#xff0c;…

使用绿联私有云Docker搭建自动化实时网页监控工具,实现降价提醒/RSS监控等

使用绿联私有云Docker搭建自动化实时网页监控工具&#xff0c;实现降价提醒/RSS监控等 哈喽小伙伴们好&#xff0c;我是Stark-C~ 之前老是有小伙伴们在评论区说我分享的Docker容器都是通过Docker run命令部署的&#xff0c;能不能照顾下像绿联私有云这种新势力NAS的新手用户&…

C# CAD界面-自定义工具栏(三)

运行环境 vs2022 c# cad2016 调试成功 一、引用 二、开发代码进行详细的说明 初始化与获取AutoCAD核心对象&#xff1a; Database db HostApplicationServices.WorkingDatabase;&#xff1a;这行代码获取当前工作中的AutoCAD数据库对象。在AutoCAD中&#xff0c;所有图形数…

【Git】01 Git介绍与安装

文章目录 一、版本控制系统二、Git三、Windows安装Git3.1 下载Git3.2 安装3.3 检查 四、Linux安装Git4.1 YUM安装4.2 源码安装 五、配置Git5.1 配置用户名和邮箱5.2 配置级别5.3 查看配置 六、总结 一、版本控制系统 版本控制系统&#xff0c;Version Control System&#xff…