主頁 > 後端開發 > Qt 事件系統總結

Qt 事件系統總結

2023-06-13 07:46:08 後端開發

參考:

  • (35條訊息) Qt事件回圈及QEventLoop的使用_kupeThinkPoem的博客-CSDN博客
  • (35條訊息) Qt訊息機制:事件分發和事件過濾_qt 訊息過濾_SOC羅三炮的博客-CSDN博客

Qt 事件系統總結

Qt 事件

  • 在 Qt 中,事件(event)是一些物件,它們都派生自抽象類 QEvent

  • 事件是應用程式所關心的,程式內部發生的事或是外部行動的結果

  • 當一個事件發生,Qt 會創建一個事件物件,它是一個派生自抽象類 QEvent 的類的實體,用來代表發生的事件

  • 有時一個事件包含多個事件型別,比如滑鼠事件 QMouseEvent 又可以分為滑鼠按下雙擊滾輪滾動移動等多種操作

  • 事件由誰接收:事件可以被任何派生自 QObject 的型別的實體接收和處理

    • QObject 類的三大核心功能其中之一就是:事件處理,QObject通過 event() 函式獲取和分發事件,
  • 事件由誰產生:

    • 由作業系統或應用程式內部產生
    • 使用 bool QEvent::spontaneous() const 判斷事件是否來自于應用程式外部,如果事件來自于外部回傳 true,否則回傳 false

Qt 事件回圈

主事件回圈

  • 每一個 Qt 程式,main 函式中一般都有唯一的 QCoreApplication/QGuiApplication/QApplication,并在末尾呼叫 exec(),這樣就開始 Qt 的事件回圈
  • 事件回圈的本質是無限回圈,使用 exec() 開啟事件回圈,如果事件回圈不結束,exec() 后面的代碼永遠不會執行,
  • 在執行 exec() 函式之后,程式將進入事件回圈來監聽應用程式的事件,事件多數情況下是被分發到一個佇列中(事件佇列),當佇列中有事件時就不停的將佇列中的事件發送給 QObject 物件,當佇列為空時就回圈等待事件,
  • 當事件發生時,Qt 將創建一個事件物件,Qt 中所有事件類都繼承于 QEvent ,這也是事件不同于信號(信號與槽中的信號)的一點 —— 事件是類具有特定型別, 而信號是信號函式
  • QCoreApplication 中提供了一下處理事件的函式:
/// 給任何執行緒的任何物件發送任何事件都會呼叫該函式,可以重寫該函式來達到全域的事件處理與控制的功能,
[virtual] bool QCoreApplication::notify(QObject *receiver, QEvent *event)

/// 直接使用 notify() 將事件發送給事件的接收者,回傳事件處理程式回傳的值,事件被發送后并不會被自動被銷毀,因此事件物件常常可以宣告在堆疊上作為自動變數,
[static] bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
    
/// 添加事件到事件佇列然后立即回傳,事件必須宣告在堆上,當控制回傳到主事件回圈時,所有存盤在事件佇列中的事件都將使用 notify 函式發送出去,
/// 事件按優先級排隊,高優先級的事件先入隊,事件優先級是一個整機變數,
/// 函式是【執行緒安全】的
[static] void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority = Qt::NormalEventPriority)
    
/// 立即分派在事件佇列中的所有事件接收物件為 receiver 事件型別為 event_type 的事件,
/// 如果 receiver = nullptr ,所有事件型別為 event_type 都會被立即發送給接收者
/// 如果 event_type = 0, 所有發送給 receiver 的事件都會被立即發送給它
[static] void QCoreApplication::sendPostedEvents(QObject *receiver = nullptr, int event_type = 0)
    
/// 告訴應用以指定的回傳碼退出事件回圈,exec() 將結束并回傳該回傳碼,任何非零回傳碼意味著錯誤,
[static] void QCoreApplication::exit(int returnCode = 0)

/// 告訴應用正常退出事件回圈,相當于 exit(0),通常信號與該槽應該進行[佇列連接],因為如果在主事件回圈開始之前,信號發送導致的 quit() 回呼是無效的(事件回圈沒有開始,何談退出)
/// 使用佇列連接確保槽函式不會再事件回圈開始前執行,
[static slot] void QCoreApplication::quit()
    

QEventLoop 類

/// 開啟事件回圈 
int exec(QEventLoop::ProcessEventsFlags flags = AllEvents)
void exit(int returnCode = 0)
/// 如果事件回圈是運行著的,回傳 true,否則回傳 false,事件回圈在 exec() 和 exit() 之間被認為是運行的
bool isRunning() const
[slot] void QEventLoop::quit()
  • 事件回圈是可以嵌套的,當在子事件回圈中的時候,父事件回圈中的事件實際上處于中斷狀態,這就相當于回圈嵌套,

  • 當子事件回圈結束,exec() 回傳之后才可以執行父回圈中的事件,當然,這不代表在執行子回圈的時候,類似父回圈中的界面回應會被中斷,因為往往子回圈中也會有父回圈的大部分事件,執行QMessageBox::exec(),QEventLoop::exec()的時候,雖然這些exec()打斷了main()中的QApplication::exec(),但是由于GUI界面的回應已經被包含到子回圈中了,所以GUI界面依然能夠得到回應,

  • 如果某個子事件回圈仍然有效,但其父回圈被強制跳出,此時父回圈不會立即執行跳出,而是等待子事件回圈跳出后,父回圈才會跳出,

事件的轉發與處理流程

Qt 程式需要在 main() 函式創建一個 QApplication 物件,然后呼叫它的 exec() 函式,這個函式就是開始 Qt 的事件回圈,在執行 exec() 函式之后,程式將進入事件回圈來監聽應用程式的事件

image-20230608165523292
事件回圈與事件佇列
image-20230608203644315
事件的轉發與處理流程

同步與異步事件

  • 同步事件: 呼叫 QCoreApplication::sendEvent() ,會直接使用 QCoreApplication::notify 將事件發送給事件接收方,事件會立即被執行,
  • 異步事件:呼叫 QCoreApplication::postEvent(), 會將事件加入到事件佇列,等待事件回圈進行處理,

事件分發器

Qt 中每個事件型別都有一個列舉型別 QEvent::Type 的資料成員,通過該列舉型別,在程式中可以區分不同的事件型別,根據不同的事件型別進行不同的動作,如下即為 QObject::event 的原始碼:

  • 事件分發器根據事件的不同,將事件發送給不同的事件處理器進行處理,因此可以通過重寫事件處理器函式,讓指定事件發生發生時,執行我們想要的事件處理動作,
bool QObject::event(QEvent *e)
{
    switch (e->type()) {
    case QEvent::Timer:
        timerEvent((QTimerEvent *)e);
        break;

    case QEvent::ChildAdded:
    case QEvent::ChildPolished:
    case QEvent::ChildRemoved:
        childEvent((QChildEvent *)e);
        break;

    case QEvent::DeferredDelete:
        qDeleteInEventHandler(this);
        break;

    case QEvent::MetaCall:
        {
            QAbstractMetaCallEvent *mce = static_cast<QAbstractMetaCallEvent*>(e);

            if (!d_func()->connections.loadRelaxed()) {
                QBasicMutexLocker locker(signalSlotLock(this));
                d_func()->ensureConnectionData();
            }
            QObjectPrivate::Sender sender(this, const_cast<QObject*>(mce->sender()), mce->signalId());

            mce->placeMetaCall(this);
            break;
        }

    case QEvent::ThreadChange: {
        Q_D(QObject);
        QThreadData *threadData = https://www.cnblogs.com/Critical-Thinking/archive/2023/06/12/d->threadData.loadRelaxed();
        QAbstractEventDispatcher *eventDispatcher = threadData->eventDispatcher.loadRelaxed();
        if (eventDispatcher) {
            QList timers = eventDispatcher->registeredTimers(this);
            if (!timers.isEmpty()) {
                // do not to release our timer ids back to the pool (since the timer ids are moving to a new thread).
                eventDispatcher->unregisterTimers(this);
                QMetaObject::invokeMethod(this,"_q_reregisterTimers", Qt::QueuedConnection,
                                          Q_ARG(void*, (new QList<QAbstractEventDispatcher::TimerInfo>(timers))));
            }
        }
        break;
    }

    default:
        if (e->type() >= QEvent::User) {
            customEvent(e);
            break;
        }
        return false;
    }
    return true;
}
  • 如果希望在事件分發之前做一些操作,就可以重寫這個 event() 函式,
  • 如果傳入的事件已被識別并且處理,則需要回傳 true,否則回傳 false,如果回傳值是 true,那么 Qt 會認為這個事件已經處理完畢,不會再將這個事件發送給其它物件,而是會繼續處理事件佇列中的下一事件

???在 event() 函式中,呼叫事件物件的 accept()ignore() 函式是沒有作用的,不會影響到事件的傳播

事件過濾器

事件過濾器可以對其他組件接收到的事件進行監控

事件過濾器使用步驟如下:

  1. 創建一個事件過濾器

    • 任意的 QObject 物件都可以作為事件過濾器使用

    • 事件過濾器物件需要重寫 eventFilter() 函式

      • eventFilter() 中可以決定是否將事件傳遞給組件物件,事件處理程式也可以提前寫在事件過濾器中,
      • 不讓事件繼續轉發回傳 true, 否則回傳 false
  2. 被監控物件安裝事件過濾器

    • void QObject::installEventFilter(QObject *filterObj)

img

事件過濾器的呼叫時間是目標物件(也就是 eventFilter() 引數里面的 watched 物件)接收到事件物件之前,如果事件被過濾掉(回傳 true) 那么組件物件就不會收到該事件,

??? 事件過濾器和被安裝過濾器的組件必須在同一執行緒,否則,過濾器將不起作用,另外,如果在安裝過濾器之后,這兩個組件到了不同的執行緒,那么,只有等到二者重新回到同一執行緒的時候過濾器才會有效

全域事件過濾器QAppliaction::instance()QCoreApplication::instance() 上安裝事件過濾器,那么任何事件在通過 notify() 函式發送給其他物件之前都要先傳給事件過濾器,

Qt 事件處理的 5 個層次

  1. 重寫 paintEvent()mousePressEvent() 等事件處理函式,最普通、最簡單的形式,
  2. 重寫 event() 函式,event() 是任何 Qt 物件的所有事件的入口,默認是根據事件型別的不同將事件分發給不同的事件處理函式
  3. 在特定物件上安裝事件過濾器,該事件過濾器僅過濾該物件接收到的事件
  4. 使用全域事件過濾器,在 QAppliaction::instance()QCoreApplication::instance() 上安裝事件過濾器,事件過濾器可以安裝多個(多個事件過濾器會按安裝順序逆序激活),相比重寫 notify() 更加靈活,全域過濾器有一個問題:只能用在主執行緒,
  5. 重寫 QCoreApplication::notify() 這是最強大的,和全域事件過濾器一樣提供完全控制,并且不受執行緒的限制,但是全域范圍內只能有一個被使用(因為QCoreApplication是單例的),

事件(QEvent)與信號(SIGNAL)的區別

事件 信號
本質區別 事件是物件,都是派生自 QEvent 的類的實體 信號是QObject 或是其派生類的函式成員
與 QObject 的關系 事件由 QObject 及其派生類的實體物件接收并進行處理 信號由QObject 或是其派生類的實體物件發出(emit
對程式影響 改寫事件處理函式可能導致程式行為發生改變 如果將信號與不同的槽函式連接,會導致不同的行為
  • 兩者的聯系:
    • 事件的轉發和處理,信號與槽實作的物件間通訊,都依靠于 QObject,都依靠 Qt 的事件回圈,
    • 一些信號,是在事件處理函式中發出的,

QPushButton 事件處理分析

示例代碼:

#ifndef MYAPPLICATION_H
#define MYAPPLICATION_H
#include <QApplication>

class MyApplication : QApplication
{
    Q_OBJECT
public:
    MyApplication(int &argc, char **argv);
    bool notify(QObject *receiver, QEvent *event) override;
    void installEventFilter(QObject *filter);
    int exec();
protected:
    bool event(QEvent *e) override;
};

#endif // MYAPPLICATION_H
#include "myapplication.h"
#include "qdebug.h"


MyApplication::MyApplication(int &argc, char **argv)
    : QApplication{argc, argv}
{

}

bool MyApplication::notify(QObject *receiver, QEvent *event)
{
    if(event->type() == QEvent::MouseButtonPress){
        qDebug() << "MyApplication::notify():發布滑鼠按下事件給型別為 " << receiver->metaObject()->className()
                 << " 的物件;";
    }
    return QApplication::notify(receiver, event);
}

void MyApplication::installEventFilter(QObject *filter)
{
    QApplication* a = static_cast<QApplication*>(this);
    a->installEventFilter(filter);
}

int MyApplication::exec()
{
    return QApplication::exec();
}

bool MyApplication::event(QEvent *e)
{
    if(e->type() == QEvent::MouseButtonPress){
        qDebug() << "MyApplication::event(): 分發滑鼠按下事件";
    }
    return QApplication::event(e);
}
#ifndef MYBUTTON_H
#define MYBUTTON_H

#include <QPushButton>

class MyButton : public QPushButton
{
    Q_OBJECT
public:
    MyButton(QWidget* parent = nullptr);
    MyButton(QString const& text, QWidget* parent = nullptr);
protected:
    void mousePressEvent(QMouseEvent *e) override;
    bool event(QEvent* e) override;
};

#endif // MYBUTTON_H
#include "mybutton.h"
#include <QDebug>
#include <QEvent>
#include <QMouseEvent>

MyButton::MyButton(QWidget* parent)
    : QPushButton{parent}
{

}

MyButton::MyButton(const QString &text, QWidget *parent)
    : QPushButton{text, parent}
{

}

void MyButton::mousePressEvent(QMouseEvent *e)
{
    qDebug() << "MyButton::mousePressEvent() 按鈕按下事件被處理";

    QPushButton::mousePressEvent(e);// 默認的事件處理函式中,會發出信號: emit pressed()

    qDebug() << "槽函式回呼回傳后,執行發送信號(emit)后面的代碼";
}

bool MyButton::event(QEvent *e)
{
    if(e->type() == QEvent::MouseButtonPress){
        qDebug() << "MyButton::event(): 按鈕點擊事件被分發";
    }
    return QPushButton::event(e);
}

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include "mybutton.h"

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

protected:
    bool event(QEvent *e) override;
    bool eventFilter(QObject* watched, QEvent *event) override;
private:
    MyButton *button;
private slots:
    void buttonPressedSlot();
};
#endif // WIDGET_H
#include "widget.h"
#include <QEvent>
#include <QMouseEvent>
#include <QDebug>
#include <QPushButton>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
{
    button = new MyButton(">>> 按鈕 <<<",this);
    button->installEventFilter(this);
    connect(button, &MyButton::pressed, this, &Widget::buttonPressedSlot);
}

Widget::~Widget()
{
}

bool Widget::event(QEvent *e)
{
    if(e->type() == QEvent::MouseButtonPress){
        qDebug() << "Widget::event(): 按鈕按下事件被分發";
    }
    return QWidget::event(e);
}

bool Widget::eventFilter(QObject* watched, QEvent *event)
{
    if(watched == this->button && event->type() == QEvent::MouseButtonPress){
        qDebug() << "在滑鼠按下事件發送給 button 之前,事件過濾器 Widget::eventFilter() 先對事件進行處理";
    }
    return QWidget::eventFilter(watched, event);
}

void Widget::buttonPressedSlot(){
    qDebug() << "Widget::buttonPressedSlot(): 按下按鈕的槽函式被呼叫";
}
#include "widget.h"
#include "myapplication.h"
#include <QDebug>
#include <memory.h>

class GlobalFilter : public QObject
{
public:
    GlobalFilter(QObject *parent = nullptr) : QObject{parent}{}
protected:
    bool eventFilter(QObject* watched, QEvent *event) override{
        if(event->type() == QEvent::MouseButtonPress){
            qDebug() << "在滑鼠按下事件發送給型別為 " << watched->metaObject()->className()
                     << " 的物件之前,全域事件過濾器 GlobalFilter::eventFilter() 先對事件進行處理;";
        }
        return QObject::eventFilter(watched, event);
    }
};

int main(int argc, char *argv[])
{
    MyApplication a(argc, argv);
    std::unique_ptr<GlobalFilter> uptr_filter(new GlobalFilter);
    a.installEventFilter(uptr_filter.get());
    Widget w;
    w.show();
    return a.exec();
}

image-20230609162238213

輸出:

# 點擊按鈕輸出如下資訊:
MyApplication::notify():發布滑鼠按下事件給型別為  QWidgetWindow  的物件;
在滑鼠按下事件發送給型別為  QWidgetWindow  的物件之前,全域事件過濾器 GlobalFilter::eventFilter() 先對事件進行處理;
MyApplication::notify():發布滑鼠按下事件給型別為  MyButton  的物件;
在滑鼠按下事件發送給型別為  MyButton  的物件之前,全域事件過濾器 GlobalFilter::eventFilter() 先對事件進行處理;
在滑鼠按下事件發送給 button 之前,事件過濾器 Widget::eventFilter() 先對事件進行處理
MyButton::event(): 按鈕點擊事件被分發
MyButton::mousePressEvent() 按鈕按下事件被處理
Widget::buttonPressedSlot(): 按下按鈕的槽函式被呼叫
槽函式回呼回傳后,執行發送信號(emit)后面的代碼

示例:查看圖片的簡單應用

功能如下:

  • 選擇和打開圖片:點擊按鈕,選擇打開一個圖片
  • 縮放圖片:通過滑鼠滾輪能夠縮放圖片,縮放中心為圖片繪制視窗的幾何中心
    • 相關事件:wheelEvent()QEvent::ResizeQEvent::Paint
  • 拖動圖片:通過滑鼠左鍵按住來拖動圖片
    • 相關事件:mousePressEvent()mouseMoveEvent()QEvent::Paint
image-20230612105958478
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
protected:
    /// 重寫事件過濾器,在事件過濾器中來處理子視窗的繪圖事件
    /// 默認的事件過濾器默會把把父視窗下子控制元件的繪圖事件過濾掉,因此重新父視窗的 paintEvent 是無法在子控制元件上繪圖的,
    /// 因此,直接在事件過濾器中處理繪圖事件
    bool eventFilter(QObject *watched, QEvent *event) override;
    void mousePressEvent(QMouseEvent *event) override;
    void mouseMoveEvent(QMouseEvent *event) override;
    void wheelEvent(QWheelEvent *event) override;

private:
    /// 設定圖片的縮放中心
    /// 輸入縮放中心在圖窗中的坐標,來求更新 zoomCenter 和 zoomCenterPic
    void setZoomCenter(const QPointF& zoomCenter);
    /// 因為圖片縮放默認以圖片坐標系原點(圖片左上角頂點)為縮放中心
    /// 為了以 zoomCenter 為縮放中心,需要修改圖片的繪制位置,
    /// 通過縮放 + 移動使得圖片相當于以指定的縮放中心縮放
    void correctImagPosition();
    Ui::Widget *ui;
    QPixmap pixmap;
    double scaleFactor;     ///< 縮放因子
    QPointF zoomCenter;     ///< 圖片縮放中心在圖窗坐標系下的坐標
    QPointF zoomCenterPic;  ///< 當縮放因子為 1 時,縮放中心相對于圖片坐標系的坐標
    QPointF posit;          ///< 圖片繪制位置(為圖片左上角頂點在圖窗中的坐標)
    bool isImgError;        ///< 讀取圖片是否錯誤的標志
    QPoint lastDragPos;     ///< 暫存滑鼠最新的拖動位置


private slots:
    void openImg();
};
#endif // WIDGET_H
#include "widget.h"
#include "ui_widget.h"
#include <QFileDialog>
#include <QPainter>
#include <QMouseEvent>
#include <cmath>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
    , scaleFactor(1)
    , isImgError(false)
{
    ui->setupUi(this);
    ui->widget->installEventFilter(this);// 為圖片顯示視窗安裝事件過濾器
    connect(ui->pushButton, &QPushButton::clicked, this, &Widget::openImg);
}

Widget::~Widget()
{
    delete ui;
}

bool Widget::eventFilter(QObject *watched, QEvent *event)
{
    if(watched == ui->widget && event->type() == QEvent::Paint){
        // 處理子視窗 ui->widget 的 Paint 事件
        QPainter painter(ui->widget);
        if(pixmap.isNull()){
            painter.fillRect(rect(), Qt::black);
            painter.setPen(Qt::white);
            if(isImgError){
                painter.drawText(rect(), Qt::AlignCenter, tr("無法打開圖片"));
                return true;
            }else{
                painter.drawText(rect(), Qt::AlignCenter, tr("請選擇圖片"));
                return true;
            }
        }
        // 縮放圖片
        QPixmap img = pixmap.scaled(pixmap.width() / scaleFactor, pixmap.height() / scaleFactor, Qt::KeepAspectRatio/*, Qt::SmoothTransformation*/);
        correctImagPosition();// 修正圖片位置
        painter.drawPixmap(posit, img);// 繪制圖片
    }else if(watched == ui->widget && event->type() == QEvent::Resize){
        // 處理子視窗 ui->widget 的 Resize 事件
        QResizeEvent* re = static_cast<QResizeEvent*>(event);
        setZoomCenter(QPointF(re->size().width() / 2.0, re->size().height() / 2.0));// 更新縮放中心位置
        return false;
    }else{

        return QWidget::eventFilter(watched,event);//其它事件交給父類事件過濾器處理
    }
}

void Widget::mousePressEvent(QMouseEvent *event)
{
    if(event->button() == Qt::LeftButton)
        lastDragPos = event->pos();// 暫存滑鼠按下時的位置
}

void Widget::mouseMoveEvent(QMouseEvent *event)
{
    if (event->buttons() & Qt::LeftButton) {
        QPoint delta = event->pos() - lastDragPos;// 計算滑鼠拖拽時的相對位置變化
        posit.rx() += delta.x();
        posit.ry() += delta.y();
        // 因為 posit 改變,縮放中心相對于圖片坐標系的位置也發生了改變
        zoomCenterPic -= delta * scaleFactor;//或:setZoomCenter(QPointF(this->width() / 2.0, this->height() / 2.0));
        lastDragPos = event->pos();
        update();
    }
}

void Widget::wheelEvent(QWheelEvent *event)
{
    // 滾輪朝前推為正,朝后推為負
    // 滑鼠滾輪每滾動 1°,angleDelta() 值加或減 8
    const int numDegrees = event->angleDelta().y() / 8;
    // 實際中,滾輪每滾動一格,角度變化為 15°
    const double numSteps = numDegrees / double(15);
    // 根據滾輪的移動格數修改縮放因子
    double tmp = scaleFactor * pow(0.8, numSteps);
    
    // 限制縮放因子的范圍
    if(tmp < 0.1)
        scaleFactor = pow(0.8, 10);
    else if(tmp > 10)
        scaleFactor = pow(0.8, -10);
    else
        scaleFactor = tmp;
    
    update();
}

void Widget::setZoomCenter(const QPointF& zoomCenter)
{
    this->zoomCenter = zoomCenter;
    this->zoomCenterPic = (zoomCenter - posit) * scaleFactor;
}

void Widget::correctImagPosition()
{
    // 縮放時修正圖片繪制位置
    posit = zoomCenter - zoomCenterPic / scaleFactor;
}

void Widget::openImg()
{
    QString fileName = QFileDialog::getOpenFileName(this, tr("選擇圖片"),
                                                    QDir::homePath(),
                                                    tr("Images (*.png *.xpm *.jpg)"));
    if(!pixmap.load(fileName)){
        isImgError = true;
    }else{
        isImgError = false;
        // 每次打開圖片,設定初始縮放因子為 1
        scaleFactor = 1;
        // 設定圖片初始在圖窗中心顯示
        posit.rx() = (this->width() - pixmap.width()) / 2.0;
        posit.ry() = (this->height() - pixmap.height()) / 2.0;
        // 設定圖窗中心為縮放中心
        setZoomCenter(QPointF(this->width() / 2.0, this->height() / 2.0));
    }
    update();// 更新視窗顯示
}
image-20230612105833568

轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/554954.html

標籤:其他

上一篇:一篇文章帶你入門HBase

下一篇:返回列表

標籤雲
其他(160822) Python(38222) JavaScript(25492) Java(18225) C(15237) 區塊鏈(8270) C#(7972) AI(7469) 爪哇(7425) MySQL(7247) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5874) 数组(5741) R(5409) Linux(5347) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4589) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2435) ASP.NET(2404) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) .NET技术(1984) 功能(1967) HtmlCss(1962) Web開發(1951) C++(1933) python-3.x(1918) 弹簧靴(1913) xml(1889) PostgreSQL(1881) .NETCore(1863) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 【C++】Microsoft C++、C 和匯編程式檔案

    ......

    uj5u.com 2020-09-10 00:57:23 more
  • 例外宣告

    相比于斷言適用于排除邏輯上不可能存在的狀態,例外通常是用于邏輯上可能發生的錯誤。 例外宣告 Item 1:當函式不可能拋出例外或不能接受拋出例外時,使用noexcept 理由 如果不打算拋出例外的話,程式就會認為無法處理這種錯誤,并且應當盡早終止,如此可以有效地阻止例外的傳播與擴散。 示例 //不可 ......

    uj5u.com 2020-09-10 00:57:27 more
  • Codeforces 1400E Clear the Multiset(貪心 + 分治)

    鏈接:https://codeforces.com/problemset/problem/1400/E 來源:Codeforces 思路:給你一個陣列,現在你可以進行兩種操作,操作1:將一段沒有 0 的區間進行減一的操作,操作2:將 i 位置上的元素歸零。最終問:將這個陣列的全部元素歸零后操作的最少 ......

    uj5u.com 2020-09-10 00:57:30 more
  • UVA11610 【Reverse Prime】

    本人看到此題沒有翻譯,就附帶了一個自己的翻譯版本 思考 這一題,它的第一個要求是找出所有 $7$ 位反向質數及其質因數的個數。 我們應該需要質數篩篩選1~$10^{7}$的所有數,這里就不慢慢介紹了。但是,重讀題,我們突然發現反向質數都是 $7$ 位,而將它反過來后的數字卻是 $6$ 位數,這就說明 ......

    uj5u.com 2020-09-10 00:57:36 more
  • 統計區間素數數量

    1 #pragma GCC optimize(2) 2 #include <bits/stdc++.h> 3 using namespace std; 4 bool isprime[1000000010]; 5 vector<int> prime; 6 inline int getlist(int ......

    uj5u.com 2020-09-10 00:57:47 more
  • C/C++編程筆記:C++中的 const 變數詳解,教你正確認識const用法

    1、C中的const 1、區域const變數存放在堆疊區中,會分配記憶體(也就是說可以通過地址間接修改變數的值)。測驗代碼如下: 運行結果: 2、全域const變數存放在只讀資料段(不能通過地址修改,會發生寫入錯誤), 默認為外部聯編,可以給其他源檔案使用(需要用extern關鍵字修飾) 運行結果: ......

    uj5u.com 2020-09-10 00:58:04 more
  • 【C++犯錯記錄】VS2019 MFC添加資源不懂如何修改資源宏ID

    1. 首先在資源視圖中,添加資源 2. 點擊新添加的資源,復制自動生成的ID 3. 在解決方案資源管理器中找到Resource.h檔案,編輯,使用整個專案搜索和替換的方式快速替換 宏宣告 4. Ctrl+Shift+F 全域搜索,點擊查找全部,然后逐個替換 5. 為什么使用搜索替換而不使用屬性視窗直 ......

    uj5u.com 2020-09-10 00:59:11 more
  • 【C++犯錯記錄】VS2019 MFC不懂的批量添加資源

    1. 打開資源頭檔案Resource.h,在其中預先定義好宏 ID(不清楚其實ID值應該設定多少,可以先新建一個相同的資源項,再在這個資源的ID值的基礎上遞增即可) 2. 在資源視圖中選中專案資源,按F7編輯資源檔案,按 ID 型別 相對路徑的形式添加 資源。(別忘了先把檔案拷貝到專案中的res檔案 ......

    uj5u.com 2020-09-10 01:00:19 more
  • C/C++編程筆記:關于C++的參考型別,專供新手入門使用

    今天要講的是C++中我最喜歡的一個用法——參考,也叫別名。 參考就是給一個變數名取一個變數名,方便我們間接地使用這個變數。我們可以給一個變數創建N個參考,這N + 1個變數共享了同一塊記憶體區域。(參考型別的變數會占用記憶體空間,占用的記憶體空間的大小和指標型別的大小是相同的。雖然參考是一個物件的別名,但 ......

    uj5u.com 2020-09-10 01:00:22 more
  • 【C/C++編程筆記】從頭開始學習C ++:初學者完整指南

    眾所周知,C ++的學習曲線陡峭,但是花時間學習這種語言將為您的職業帶來奇跡,并使您與其他開發人員區分開。您會更輕松地學習新語言,形成真正的解決問題的技能,并在編程的基礎上打下堅實的基礎。 C ++將幫助您養成良好的編程習慣(即清晰一致的編碼風格,在撰寫代碼時注釋代碼,并限制類內部的可見性),并且由 ......

    uj5u.com 2020-09-10 01:00:41 more
最新发布
  • Qt 事件系統總結

    > 參考: > > - [(35條訊息) Qt事件回圈及QEventLoop的使用_kupeThinkPoem的博客-CSDN博客](https://blog.csdn.net/kupepoem/article/details/121844578) > - [(35條訊息) Qt訊息機制:事件分發和 ......

    uj5u.com 2023-06-13 07:46:08 more
  • 一篇文章帶你入門HBase

    本文已收錄至Github,推薦閱讀 👉 [Java隨想錄](https://github.com/ZhengShuHai/JavaRecord) 微信公眾號:[Java隨想錄](https://mmbiz.qpic.cn/mmbiz_jpg/jC8rtGdWScMuzzTENRgicfnr91C5 ......

    uj5u.com 2023-06-13 07:45:54 more
  • 分庫分表用這個就夠了

    # 一、前言 2018年寫過一篇分庫分表的文章《[SpringBoot使用sharding-jdbc分庫分表](https://www.cnblogs.com/2YSP/p/9746981.html)》,但是存在很多不完美的地方比如: - sharding-jdbc的版本(1.4.2)過低,現在gi ......

    uj5u.com 2023-06-13 07:45:45 more
  • 掌握Python檔案操作:從基礎到高階的全方位探索

    **在本篇博客中,我們將全面、深入地探討Python中的檔案操作。檔案操作在Python編程中是不可或缺的一部分,它包含了打開、讀取、寫入和關閉檔案等各種操作。我們將從基礎的檔案操作講解到高級的檔案處理技巧,以及如何優雅地使用Python進行檔案操作。每一部分我們都會分享一些獨特的用法,并且附有具體 ......

    uj5u.com 2023-06-13 07:45:39 more
  • 【技識訓累】Java中的泛型【一】

    博客推行版本更新,成果積累制度,已經寫過的博客還會再次更新,不斷地琢磨,高質量高數量都是要追求的,工匠精神是學習必不可少的精神。因此,大家有何建議歡迎在評論區踴躍發言,你們的支持是我最大的動力,你們敢投,我就敢肝 ......

    uj5u.com 2023-06-13 07:44:25 more
  • CoaXpress downlink資料決議方法

    ## 什么是downlink資料 downlink指的是相機傳輸到host采集卡的高速鏈路,其中包含了如下型別的資料: 1、Stream Data 2、Trigger Ack, Trigger; 3、Ack (reply data); 4、Event, Heartbeat ![](https://i ......

    uj5u.com 2023-06-13 07:43:58 more
  • 【python基礎】復雜資料型別-字典(遍歷)

    一個字典可能只包含幾個鍵值對,也可能包含數百萬個鍵值對,所以Python支持字典遍歷。字典可用于以各種方式存盤資訊,因此有多種遍歷字典的方式:可遍歷字典的所有鍵值對、鍵或值。 # 1.遍歷所有的鍵值對 其語法格式: ![image](https://img2023.cnblogs.com/blog/ ......

    uj5u.com 2023-06-13 07:43:14 more
  • 掌握Python檔案操作:從基礎到高階的全方位探索

    **在本篇博客中,我們將全面、深入地探討Python中的檔案操作。檔案操作在Python編程中是不可或缺的一部分,它包含了打開、讀取、寫入和關閉檔案等各種操作。我們將從基礎的檔案操作講解到高級的檔案處理技巧,以及如何優雅地使用Python進行檔案操作。每一部分我們都會分享一些獨特的用法,并且附有具體 ......

    uj5u.com 2023-06-13 07:42:50 more
  • 【python基礎】復雜資料型別-字典(增刪改查)

    # 1.初識字典 字典,是另外一種復雜的資料型別,相較于串列,字典可以將相關資訊關聯起來。比如說一個人的資訊有名字、年齡、性別等,如果用串列存盤的話,不能表示他們之間是相關聯的,而字典可以,字典是一個或多個鍵值對組成 串列的標志是[ ],**字典的標志是{ }**,其語法格式: **{** 鍵1:值 ......

    uj5u.com 2023-06-13 07:42:41 more
  • php檔案上傳之白名單00截斷實驗

    # %00截斷 **介紹:** > 0x00,%00,/00 在url中 %00 表示ascll碼中的 0 ,而ascii中0作為特殊字符保留,表示字串結束,所以當url中出現%00時就會認為讀取已結束。但是所謂的if攔截仍會讀取后面的后綴達到繞過白名單的效果。 當前版本環境: PHP版本低于5. ......

    uj5u.com 2023-06-13 07:42:14 more