【Qt学习】06:事件与事件过滤器

news2024/11/15 15:34:04

OVERVIEW

  • 事件与事件过滤器
      • 一、事件
        • 1.鼠标事件
          • 创建子类MyLabel
          • 重写鼠标事件
          • 提升Label控件为MyLabel
        • 2.定时器事件
          • timerEvent
          • QTimer
        • 3.事件分发器(event函数)
          • event函数重写
          • event函数深入
      • 二、事件过滤器
        • 1.事件过滤器
        • 2.事件处理的五个层次

事件与事件过滤器


一、事件

事件event是由系统或者Qt本身在不同的时刻发出的,主要包括用户事件与定时器事件,

当用户按下鼠标、敲下键盘或者是窗口需要重新绘制的时候,都会发出一个相应的事件,一些事件是在对用户操作做出响应时进行发出(如键盘事件等);另一些事件则是由系统自动发出(如计时器事件)。

  1. Qt 程序需要在main()函数创建一个QApplication对象然后调用其exec()函数,这个函数就是开始Qt的事件循环。在执行exec()函数之后,程序将进入事件循环来监听应用程序的事件
  2. 当事件发生时 Qt 将创建一个事件对象(Qt 中所有事件类都继承于QEvent),在事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。
  3. event()函数并不直接处理事件,而是按照事件对象的类型分派给特定的事件处理函数event handler

在所有组件的父类QWidget中,定义了很多事件处理的回调函数,如

  • keyPressEvent()
  • keyReleaseEvent()
  • mouseDoubleClickEvent()
  • mouseMoveEvent()
  • mousePressEvent()
  • mouseReleaseEvent()等

1.鼠标事件

在这里插入图片描述

鼠标双击、鼠标按下、鼠标释放、鼠标移动、鼠标轨迹追踪,

在这里插入图片描述

enterEvent进入控件Widget事件,

以上这些函数都是protected virtual的,可以在子类中重新实现,重写的鼠标事件的主要步骤如下:

  1. 创建label关联提升的子类
  2. 创建label并提升
  3. 重写鼠标事件
    • 鼠标进入
    • 鼠标离开
    • 鼠标按下
    • 鼠标释放
    • 鼠标移动
    • 追踪状态激活

如果想要对控件进行构造、析构、事件捕捉等自定义操作,需要利用自定义控件来实现。这里省略对控件ui界面的设计,直接对widget控件的功能进行自定义操作,操作如下:

创建子类MyLabel

创建QLabel的子类MyLabel类,右键项目添加新文件,选择C++中的C++class不带有ui界面,如下图所示:

在这里插入图片描述

设置好需要继承的父类以及,根据语义确定需要自定义的类名称(这里起名为MyLabel)

在这里插入图片描述

重写鼠标事件

在MyLabel中重写鼠标的各种事件,包括

void enterEvent(QEvent *event);
void leaveEvent(QEvent *event);
//重写虚函数
virtual void mousePressEvent(QMouseEvent *ev);
virtual void mouseReleaseEvent(QMouseEvent *ev);
virtual void mouseMoveEvent(QMouseEvent *ev);

具体代码如下所示:

#ifndef MYLABEL_H
#define MYLABEL_H

#include <QMainWindow>
#include <QLabel>

class MyLabel : public QLabel {
    Q_OBJECT
public:
    explicit MyLabel(QWidget *parent = nullptr);
    void enterEvent(QEvent *event);
    void leaveEvent(QEvent *event);
    //重写虚函数
    virtual void mousePressEvent(QMouseEvent *ev);
    virtual void mouseReleaseEvent(QMouseEvent *ev);
    virtual void mouseMoveEvent(QMouseEvent *ev);
signals:
public slots:
};

#endif // MYLABEL_H
void MyLabel::enterEvent(QEvent *event) {
    qDebug() << "mouse entered.";
}

void MyLabel::leaveEvent(QEvent *event) {
    qDebug() << "mouse leave.";
}

void MyLabel::mousePressEvent(QMouseEvent *ev) {
    qDebug() << "mouse pressed.";
    QString str1 = QString("mouse pressed at position(%1, %2).").arg(ev->x()).arg(ev->y());
    qDebug() << str1;
}

void MyLabel::mouseReleaseEvent(QMouseEvent *ev) {
    qDebug() << "mouse released.";
    QString str1;
    str1.sprintf("mouse released at position(%1, %2).", ev->x(), ev->y());
    qDebug() << str1;
}

void MyLabel::mouseMoveEvent(QMouseEvent *ev) {
    qDebug() << "mouse moved.";
    QString str1 = QString("mouse moved at position(%1, %2).").arg(ev->x()).arg(ev->y());
    qDebug() << str1;
}
  1. MyLabel继承了QLabel,覆盖了mousePressEvent()、mouseMoveEvent()和MouseReleaseEvent()三个函数。并没有添加什么功能,只是在鼠标按下(press)、鼠标移动(move)和鼠标释放(release)的时候,把当前鼠标的坐标值打印出来。

  2. QString的arg()函数可以自动替换掉QString中出现的占位符。其占位符以 % 开始,后面是占位符的位置,例如 %1,%2 这种。

    QString("[%1, %2]").arg(x).arg(y); 语句将会使用x替换 %1,y替换 %2,因此,生成的QString为[x, y]。

  3. 在mouseReleaseEvent()函数中,使用了另外一种QString的构造方法。类似 C 风格的格式化函数sprintf()来构造QString。

在上面的代码中,只有在点击鼠标之后移动鼠标,才能在mouseMoveEvent()函数中显示鼠标坐标值:

这是因为QWidget中有一个mouseTracking属性,该属性用于设置是否追踪鼠标。只有鼠标被追踪时,mouseMoveEvent()才会发出。如果mouseTracking是 false(默认),组件在至少一次鼠标点击之后,才能够被追踪(发出mouseMoveEvent()事件)。如果mouseTracking为 true,则mouseMoveEvent()直接可以被发出。

可以在main()函数中添加如下代码:label->setMouseTracking(true); 这样鼠标移动操作就可以直接被捕获,而不用先点击鼠标了。

MyLabel::MyLabel(QWidget *parent) : QLabel(parent) {
    setMouseTracking(true);//设置鼠标追踪状态
}
提升Label控件为MyLabel

在需要利用MyLabel控件的ui界面中,加入Label控件,并有点点击将其提升为MyLabel控件,从而重写的各种方法可以该控件上起作用,

最后达到捕获鼠标事件的效果,

在这里插入图片描述

2.定时器事件

由定时器timer定时触发的时间叫做timerEvent定时器事件,

timerEvent
class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    //重写定时器的事件
    void timerEvent(QTimerEvent *);
    int tid1;
    int tid2;
    int tid3;
private:
    Ui::MainWindow *ui;
};
MainWindow::MainWindow(QWidget *parent):QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    tid1 = startTimer(1000);
    tid2 = startTimer(2000);
    tid3 = startTimer(3000);
}

void MainWindow::timerEvent(QTimerEvent *ev) {
    if (ev->timerId() == tid1) {
        static int num1 = 1;
        ui->label2->setText(QString::number(num1++));
    } else if (ev->timerId() == tid2) {
        static int num2 = 1;
        ui->label3->setText(QString::number(num2++));
    } else if (ev->timerId() == tid3) {
        static int num3 = 1;
        ui->label4->setText(QString::number(num3++));
    }
}
QTimer

定时器的使用除了timerEvent的方式,还有一个专门的定时器类QTimer,该类非常的强大提供了很多的功能,

MainWindow::MainWindow(QWidget *parent):QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    QTimer *timer = new QTimer(this);
    timer->start(500);
    connect(timer, &QTimer::timeout, this, [=](){
        static int num4 = 1;
        ui->label5->setText(QString::number(num4++));
    });
    connect(ui->btn, &QPushButton::clicked, this, [=](){
        if (timer->isActive()) timer->stop();
        else timer->start();
    });
}

3.事件分发器(event函数)

事件分发eventDispatch

在这里插入图片描述

事件对象创建完毕后,Qt 将这个事件对象传递给QObject的event()函数。event()函数并不直接处理事件,而是将这些事件对象按照它们不同的类型,分发给不同的事件处理器event handler(event()函数主要用于事件的分发)。

event函数重写

如果开发者希望在事件不向下分发之,而是做一些自定义的操作,则需要重写event()函数。

通过自定义的event函数处理过感兴趣的事件之后,可以直接返回 true,表示我们已经对此事件进行了处理;对于其它我们不关心的事件,则需要调用父类的event()函数继续转发,否则这个组件就只能处理我们定义的事件了。

例1:要在某个QWidget组件中监听鼠标点击的按下,就可以继承QWidget并重写它的event()函数,截断事件分发的流程来达到目的:

void MyLabel::mousePressEvent(QMouseEvent *ev) {
    qDebug() << "mouse pressed.";
    QString str1 = QString("mouse pressed at position(%1, %2).").arg(ev->x()).arg(ev->y());
    qDebug() << str1;
}

bool MyLabel::event(QEvent *e) {
    //如果ev->typy为鼠标按下事件 则在event事件分发中做拦截操作
    if (e->type() == QEvent::MouseButtonPress) {
        QMouseEvent *ev = static_cast<QMouseEvent *>(e);
        QString str1 = QString("event dispatch catched: mouse pressed at position(%1, %2).").arg(ev->x()).arg(ev->y());
        qDebug() << str1;
        return true;//true代表用户自己处理不向下分发
    }
    //其他的事件交给父类处理 默认处理
    return QLabel::event(e);
}

例2:要在某个QWidget组件中监听 tab 键的按下,就可以继承QWidget并重写它的event()函数,来达到这个目的:

bool CustomWidget::event(QEvent *e) {
    if (e->type() == QEvent::KeyPress) {
        QKeyEvent *keyEvent = static_cast<QKeyEvent *>(e);
        if (keyEvent->key() == Qt::Key_Tab) {
            qDebug() << "You press tab.";
            return true;
        }
    }
    return QWidget::event(e);
}

CustomWidget是QWidget的子类,这里重写了其event()函数,event函数有一个QEvent对象作为参数(需要转发的事件对象)。

  • 返回true:表示传入的事件已被识别并且处理,不会再将这个事件发送给其它对象,会继续处理事件队列中的下一事件。
  • 在event()函数中,调用事件对象的accept()和ignore()函数是没有作用的,不会影响到事件的传播。

可以通过使用QEvent::type()函数可以检查事件的实际类型,其返回值是QEvent::Type类型的枚举。

event函数深入

Qt的event函数实际上本质是使用QEvent::type()判断事件类型,然后根据类型调用对应事件处理器,

其实际是通过事件处理器来响应一个具体的事件,这相当于event()函数将具体事件的处理“委托”给具体的事件处理器。

而这些事件处理器是 protected virtual 的,因此只要重写了某个事件处理器,即可让 Qt 调用开发者自己实现的版本。由此可以见,event()是一个集中处理不同类型的事件的地方。

switch (event->type()) {
    case QEvent::MouseMove:
        mouseMoveEvent((QMouseEvent*)event);
        break;
    // ...
}

结论

  1. 如果不想重写一大堆事件处理器,就可以重写这个event()函数,通过QEvent::type()判断不同的事件。
  2. 由于重写event()函数需要十分小心注意父类的同名函数调用(容易出问题),所以一般还是建议只重写事件处理器(当然也必须记得是不是应该调用父类的同名处理器)。
  3. 这其实暗示了event()函数的另外一个作用:屏蔽掉某些不需要的事件处理器。正如前面的CustomTextEdit例子看到的那样,创建了一个只能响应 tab 键的组件。这种作用是重写事件处理器所不能实现的。

二、事件过滤器

1.事件过滤器

有时候对象需要查看、甚至要拦截发送到另外对象的事件。如对话框可能想要拦截按键事件,不让别的组件接收到;或者要修改回车键的默认处理。Qt 创建了QEvent事件对象之后,会调用QObject的event()函数处理事件的分发,显然可以在event()函数中实现拦截的操作。

event()函数会有两个问题:

  1. event()函数是protected的函数:
    • 这意味着要想重写event()必须继承一个已有的类,如果程序不需要鼠标事件(程序中所有组件都不允许处理鼠标事件)那就得继承所有组件,并重写其event()函数(这显然当然相当麻烦,更不用说重写event()函数还得小心一堆问题)。
    • 如果程序基于第三方库开发,在不知道第三方库源码的情况下,无法进行这种组件继承操作。
  2. event()函数的确有一定的控制,但是组件仍然接收到了QMouseEvent对象(感知到了事件的发生)
    • 事件过滤器在目标对象接收到事件之前进行处理,目标对象根本不会见到这个事件。

这两个问题是event()函数无法处理的,于是Qt 提供了另外一种机制来解决:事件过滤器。

在这里插入图片描述

QObject的eventFilter()函数,用于建立事件过滤器,函数原型如下:

virtual bool QObject::eventFilter ( QObject * watched, QEvent * event );

函数解释:

  • 事件过滤器会检查接收到的事件,如果该事件是感兴趣的类型就进行自定义处理,否则继续转发。
  • 函数返回值为true表示事件已处理,返回值为false表示继续进行事件转发。
  • 事件过滤器的调用时间是目标对象接收到事件对象之前,如果在事件过滤器中停止了某事件,那么watched对象以及以后所有的事件过滤器也不会知道这么一个事件(已经被过滤)。

事件过滤器使用步骤:1.为控件安装事件过滤器,2.重写eventFilter事件,

class MainWindow : public QMainWindow {
    Q_OBJECT
public:
    MainWindow(QWidget *parent = nullptr);
    ~MainWindow();
    //重写事件过滤器
    bool eventFilter(QObject *watched, QEvent *event);
private:
    Ui::MainWindow *ui;
};

MainWindow::MainWindow(QWidget *parent):QMainWindow(parent), ui(new Ui::MainWindow) {
    ui->setupUi(this);
    //步骤1 为label1安装事件过滤器
    ui->label1->installEventFilter(this);//传递需要安装事件过滤器的父类
}

//步骤2 重写eventfilter事件
bool MainWindow::eventFilter(QObject *watched, QEvent *event) {
    if (watched == ui->label1 && event->type() == QEvent::MouseButtonPress) {
        QMouseEvent *ev = static_cast<QMouseEvent*>(event);
        QString str1 = QString("eventFilter: mouse pressed at position(%1, %2).").arg(ev->x()).arg(ev->y());
        qDebug() << str1;
        return true;
    }
    return QMainWindow::eventFilter(watched, event);
}
  1. 重写了MainWindow类中的eventFilter()函数,为了过滤特定组件上的事件,首先判断该对象是否为感兴趣的组件,然后判断这个事件的类型。watched == ui->label1 && event->type() == QEvent::MouseButtonPress

  2. 对鼠标点击事件进行拦截,如果这个事件是目标事件则进行自定义操作后直接返回 true(过滤掉了这个事件),

  3. 其他事件需要继续处理所以返回 false。对于其它的组件并不能保证是否还有过滤器,于是最保险的方式是调用父类的函数。

  4. 安装过滤器需要调用QObject::installEventFilter()函数。函数的原型如下:

    void QObject::installEventFilter(QObject * filterObj);
    

    该函数接受一个QObject *类型的参数,eventFilter()函数是QObject的一个成员函数,因此任意QObject都可以作为事件过滤器(如果没有重写eventFilter()函数,这个事件过滤器是没有任何作用的,默认什么都不会过滤)。已经存在的过滤器则可以通过QObject::removeEventFilter()函数移除。

  5. 可以向1个对象上面安装多个事件处理器,只要调用多次installEventFilter()函数。如果一个对象存在多个事件过滤器,那么最后一个安装的会第一个执行,即后进先执行的顺序。

特别注意:事件处理器必须在同一线程才能使用生效,

  1. 件过滤器和被安装过滤器的组件必须在同一线程,否则过滤器将不起作用。
  2. 如果在安装过滤器之后,这两个组件到了不同的线程,只有等到二者重新回到同一线程的时候过滤器才会有效
  3. installEventFilter()函数是QObject的函数,QApplication或者QCoreApplication对象都是QObject的子类,因此可以向QApplication或者QCoreApplication添加事件过滤器,这种全局的事件过滤器将会在所有其它特性对象的事件过滤器之前调用。尽管这种全局事件过滤器很强大,但会严重降低整个应用程序的事件分发效率,因此一般不会这么处理。

2.事件处理的五个层次

QCoreApplication::notify()

事件拦截实际上还有一种方法,Qt 事件的调用最终都会追溯到QCoreApplication::notify()函数,因此最大的控制权实际上是重写QCoreApplication::notify()。该函数声明:

virtual bool QCoreApplication::notify(QObject *receiver, QEvent *event);

该函数会将event发送给receiver,也就是调用receiver->event(event),其返回值就是来自receiver的事件处理器。

该函数为任意线程的任意对象的任意事件调用,因此不存在事件过滤器的线程的问题。不过并不推荐这么做,因为notify()函数只有一个,而事件过滤器要灵活得多。

Qt 的事件处理,实际上是有五个层次

  1. 重写paintEvent()、mousePressEvent()等事件处理函数。这是最普通、最简单的形式,同时功能也最简单。
  2. 重写event()函数。event()函数是所有对象的事件入口,QObject和QWidget中的实现,默认是把事件传递给特定的事件处理函数。
  3. 在特定对象上面安装事件过滤器。该过滤器仅过滤该对象接收到的事件。
  4. 在QCoreApplication::instance()上面安装事件过滤器。该过滤器将过滤所有对象的所有事件,因此和notify()函数一样强大,但是它更灵活,因为可以安装多个过滤器。全局的事件过滤器可以看到 disabled 组件上面发出的鼠标事件。全局过滤器有一个问题:只能用在主线程。
  5. 重写QCoreApplication::notify()函数。这是最强大的和全局事件过滤器一样提供完全控制,并且不受线程的限制。但是全局范围内只能有一个被使用(因为QCoreApplication是单例的)。

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

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

相关文章

VMVareC++开发环境快速配置

OVERVIEW VMVareC开发环境快速配置ipgitvimgithubzshgcc&g&cmakesshifconfigmysqlnginxredisgdb VMVareC开发环境快速配置 VMVareC开发环境快速配置&#xff0c;为了省时间快速整理出文档方便以后快速配置&#xff0c; 按照这个流程直接可以快速得到一个舒适的C/C开发…

[论文阅读笔记25]A Comprehensive Survey on Graph Neural Networks

这是一篇GNN的综述, 发表于2021年的TNNLS. 这篇博客旨在对GNN的基本概念做一些记录. 论文地址: 论文 1. 引言, 背景与定义 对于图像数据来说, CNN具有平移不变性和局部连接性, 因此可以在欧氏空间上良好地学习. 然而, 对于具有图结构的数据(例如社交网络 化学分子等)就需要用…

用AI + Milvus Cloud搭建着装搭配推荐系统

在上一篇文章中,我们学习了如何利用人工智能技术(例如开源 AI 向量数据库 Milvus Cloud 和 Hugging Face 模型)寻找与自己穿搭风格相似的明星。在这篇文章中,我们将进一步介绍如何通过对上篇文章中的项目代码稍作修改,获得更详细和准确的结果,文末附赠彩蛋。 注:试用此…

Excel 打开文件提示内存或磁盘不足

Excel表格打开文件时&#xff0c;提示内存或磁盘空间不足&#xff0c;Microsoft Excel 无法再次打开或保存任何文档&#xff0c;这是很多人都会遇到的问题&#xff0c;该如何解决这个问题呢&#xff1f;如果你是用Excel表格打开某个文件时遇到提示内存或磁盘空间不足&#xff0…

学好嵌入式,未来能干啥?

很多对嵌入式行业不了解的人会以为嵌入式就是单纯搞单片机的工作。甚至有很多专业学生也抱有这种观念。 这种现象的原因在于大学专业中没有专门针对嵌入式行业的完善专业体系。嵌入式的知识体系庞大&#xff0c;不同的方向需要的知识差异很大。关于嵌入式学习路线&#xff0c;网…

Django(4)-Django 管理页面

创建一个管理员账号 python manage.py createsuperuser运行项目&#xff0c;访问http://127.0.0.1:8080/admin&#xff0c;可以看到管理员界面 管理页面加上投票应用 polls/admin.py from django.contrib import admin# Register your models here. from .models import …

npm和yarn的区别?

文章目录 前言npm和yarn的作用和特点npm和yarn的安装的机制npm安装机制yarn安装机制检测包解析包获取包链接包构建包 总结后言 前言 这一期给大家讲解npm和yarn的一些区别 npm和yarn的作用和特点 包管理&#xff1a;npm 和 yarn 可以用于安装、更新和删除 JavaScript 包。它们提…

腾讯云服务器可用区是什么?可用区怎么选择?

腾讯云服务器可用区是什么意思&#xff1f;可用区是指同一地域内电力和网络互相独立的物理数据中心&#xff0c;腾讯云每个地域下都有多个可用区供选择&#xff0c;将应用部署到不同可用区能够做到故障隔离&#xff0c;提升应用的可靠性和容灾性&#xff0c;阿腾云来详细说下什…

SpringMVC 第二天

第 1 章 ModelAttribute 和 SessionAttribute[ 应 用 ] 1.1ModelAttribute 1.1.1 使用说明 作用&#xff1a; 该注解是 SpringMVC4.3 版本以后新加入的。它可以用于修饰方法和参数。 出现在方法上&#xff0c;表示当前方法会在控制器的方法执行之前&#xff0c;先执行…

综合能源系统(9)——综合能源系统运行管控平台技术

综合能源系统关键技术与典型案例  何泽家&#xff0c;李德智主编 1、综合能源系统运行管控平台技术发展现状 在综合能源服务蓝海市场驱动下&#xff0c;作为能源和互联网跨界融合中枢产品&#xff0c;综合能源服务平台取得了较大进展。综合能源服务平台属性示意图如图3-47所…

Jvm之JIT优化详细解释

文章目录 一、JIT 产生的背景二、HotSpot虚拟机内置JIT编译器1. Client Compiler2. Server Compiler3. 查看本地编译器模式 三、常见热点探测技术1. 基于计数器的热点探测2. 基于采样的热点探测2.1 方法调用计数器2.2 回边计数器 四、常见JIT优化手段1. 公共子表达式消除2. 方法…

vue登录验证码组件,前端验证

效果图 点击可以切换验证码 自定义组件 <template><div class"s-canvas"><canvas id"s-canvas" :width"contentWidth" :height"contentHeight"></canvas></div> </template> <script> e…

左偏树\可并堆

https://www.luogu.com.cn/problem/P3377 作用&#xff1a;可并堆 形态&#xff1a;堆满二叉树 即左节点最小深度大于等于右节点最小深度 合并过程&#xff1a;

谷歌浏览器响应Failed to load response data: no resource with given identifer found

1、如下问题展示&#xff0c;这个是新版谷歌浏览器的不知道啥时候出现的问题&#xff0c;以前旧版未出现过&#xff0c;所以降版本浏览器可能可以 2、但是&#xff0c;博主的方法是换一个浏览器&#xff0c;换成edge就没问题了&#xff0c;由于用习惯了谷歌&#xff0c;所以这…

算法竞赛入门【码蹄集新手村600题】(MT1220-1240)C语言

算法竞赛入门【码蹄集新手村600题】(MT1220-1240&#xff09;C语言 目录MT1221 分数的总和MT1222 等差数列MT1223 N是什么MT1224 棋盘MT1225 复杂分数MT1226 解不等式MT1227 宝宝爬楼梯MT1228 宝宝抢糖果MT1229 搬家公司MT1230 圆周率MT1231圆周率IIMT1232 数字和MT1233 数字之…

OLED透明屏:在广告领域中的应用,为品牌注入更强的视觉冲击

OLED透明屏作为一项引人注目的技术创新&#xff0c;其独特的透明度和高清晰度为各行各业带来了全新的展示和创意空间。 本文将详细介绍其透明度、高对比度、超薄柔性设计以及强大的颜色表现力&#xff0c;并探讨其在零售、汽车和建筑等领域的应用前景。 一、透明度&#xff1a…

大数据Flink(六十七):SQL Table 简介及运行环境

文章目录 SQL & Table 简介及运行环境 一、​​​​​​​​​​​​​​简介 二、案例

Ansible 生成主机文件

生成主机文件 将一个初始模板文件从 http://materials/hosts.j2 下载到 /home/greg/ansible 完成该模板&#xff0c;以便用它生成以下文件&#xff1a;针对每个清单主机包含一行内容&#xff0c;其格式与 /etc/hosts 相同 创建名为 /home/greg/ansible/hosts.yml 的 playboo…

谁才是Python最强IDE?

本文概述了大量优秀的 Python IDE 和其他工具。决定使用哪一种工具取决于工具的便利性和对工具的熟练程度。当然&#xff0c;也有大家都喜欢的工具&#xff08;如 PyCharm&#xff09;&#xff0c;但是你可以多尝试几种工具&#xff0c;看看哪种最适合你。 每个人都知道这一点…

张量分解--CP、Tucker分解

目录 符号和准备工作 张量 实例 Fiber Slice norm 张量内积 秩一张量 回顾一下SVD分解 对角张量 张量的矩阵化与向量化 Kolda水平展开 具体例子 mode-n product CP分解 张量CP分解的定义 向量的外积 CP秩 张量CP分解的理解 Tucker分解 线性变化改变向量维度…