淺談Qt事件子系統——以可拖動的通用Widget為例子

淺談Qt事件子系統——以可拖動的通用Widget為例子

這一篇文章是一個通過實現可拖動的通用Widget為引子簡單介紹一下我們的事件對象子系統的事情
代碼和所有的文檔

1:Qt側的API介紹和說明

? 這個是每一個小項目的慣例,我會介紹大部分Qt程序中使用到的細節,比如說,本項目當中就是eventFilter和事件處理隊列的Qt編程技術。這個也是我們編程Qt的一個重點。

? 本項目打算介紹的是——Qt的事件處理機制,以及對象事件監聽機制。如果您很熟悉了,可以考慮直接跳過本篇。

所以,Qt的事件處理機制

? 我喜歡寫一個東西的時候,直接說明我要寫什么。很簡單。

  • Qt是如何實現事件處理的?技術的要點有哪些?
  • 我們作為開發人員,重點關心的接口有哪些?
  • 如何監聽,甚至是攔截其他對象的事件處理呢【這個是本項目的實現要點】

Qt是如何實現事件處理的

? 毫無疑問,事件驅動處理是GUI的一個命根子,我們的GUI接受事件,展示對應的變化;同時我們的用戶跟GUI交互,將用戶的意圖傳遞給我們的后臺。這就是GUI的一個最大的要點。

所以,我們關心事件驅動的對象有哪些呢?

? 我們的事件隊列的處理主要依賴一個重要的概念,叫“事件循環”(Event Loop)。事件循環是一個持續運行的循環,它不斷檢測、分發并處理各種事件,包括用戶輸入(如鍵盤、鼠標事件)、系統消息以及自定義事件。主要過程大致如下:

  1. 事件產生:當用戶操作或系統狀態變化時,Qt會生成一個對應的事件對象(QEvent的子類實例)。
  2. 事件隊列:事件對象被放入事件隊列中等待處理。
  3. 事件分發:事件循環依次從隊列中取出事件,分發給相應的對象處理。
  4. 事件響應:目標對象在其事件處理函數中對事件作出響應,更新界面或執行其他邏輯。

? 我們分析事件,也是主要抓手這四個部分進行學習。

? 我們事件隊列處理的開始,在QApplication::exec上,調用這個,我們的全應用程序的事件隊列就開始工作了。下面,我們來看看一些API函數:

? 對于框架層次,你需要知道這員工的一些函數:

QCoreApplication::notify

Qt的事件分發機制主要依賴于QCoreApplication類中的notify()方法。每當一個事件需要傳遞給某個QObject對象時,都會經過該方法。其主要職責是:

  1. 統一調度:集中管理所有事件的發送和轉發。
  2. 異常處理:對事件處理過程中可能出現的異常進行捕獲和處理,保證整個事件循環的穩定性。
  3. 事件過濾:在正式分發事件前,提供預處理的機會(見下文的事件過濾機制)。

? 我們一般不會跑去重寫notify(至少筆者沒見過特殊到要重寫notify的)

事件隊列與異步處理

Qt支持將事件異步投遞到目標對象中,通過QCoreApplication::postEvent()方法將事件放入事件隊列,等待事件循環處理。這種方式使得事件發送和處理解耦,避免在調用過程中產生阻塞,提升了系統響應能力。

與之對應的同步事件發送方式為QCoreApplication::sendEvent(),該方法直接調用目標對象的事件處理函數,在調用者線程中立刻執行。這種方式適用于對時序和結果有嚴格要求的情況,但需注意同步調用可能會引發遞歸調用或死鎖問題。

事件循環(Event Loop)

每個Qt應用程序通常都有一個主事件循環,通過調用QCoreApplication::exec()啟動。事件循環在不斷地檢測、分發和處理事件的同時,也會處理定時器、信號等異步任務。

  • 阻塞與非阻塞:事件循環既能阻塞等待事件,也能在無事件時進入休眠狀態,保證資源利用率。
  • 嵌套事件循環:在某些對話框或模態窗口中,Qt會啟動嵌套事件循環,保證界面依然響應用戶操作。需要注意的是,嵌套循環可能會帶來事件處理順序和狀態管理方面的復雜性。

討論事件的類型

? 事件事件,啥事件呢?這就是事件的類型。Qt中的所有事件都以QEvent為基類,其派生類涵蓋了豐富的事件類型,如:

  • 用戶輸入事件:QMouseEvent、QKeyEvent、QWheelEvent等。
  • 窗口系統事件:QResizeEvent、QCloseEvent等。
  • 自定義事件:開發人員可以繼承QEvent,定義屬于自己的事件類型,實現特定業務邏輯的事件傳遞。

? 這些事件呢,就在我們后面的開發接口上埋下了伏筆,所以,讓我們馬上進入第二個部分

開發人員關心的關鍵接口

? 我們的一個大頭中的大頭,是QObject的一個重要的函數,或者說,QT元對象系統的一個重要的特化于事件處理的核心,就是我們的一個虛函數event(QEvent *event),這是所有事件最終處理的入口函數。每個QObject子類都可以重寫這個函數,根據事件類型作出不同的響應。

? 我們需要注意的是——event()函數并不直接處理事件,而是將這些事件對象按照它們不同的類型,**分發給不同的事件處理器(event handler)。**重寫一個event事件,我們往往可能是要特化一部分操作。當然,往往我們的功能是——需要在原先擁有事件處理的基礎上,進一步擴展通用事件處理的能力,比如說要做薄記,比如說統一的處理,這個時候重寫event就是一個很明智的選擇了!

? 例如,在自定義控件中,可以重寫event()函數,對特定事件(如鼠標點擊、鍵盤輸入)進行處理,從而實現自定義行為。當然!這只是一個例子,實際上沒人這樣寫!我們會有專門的函數來處理,這是我們下面會提到的議題!

bool MyWidget::event(QEvent *event) {if (event->type() == QEvent::MouseButtonPress) {my_process_of_mouseEvent(event);return true;}// 調用基類的事件處理,保證其他事件正常分發return QWidget::event(event);
}

? 你需要注意的是——請看,這里函數返回的是一個Bool值,這個bool值的含義是什么呢?答案是——當你返回了true的時候,就說明你的事件已經處理結束,Qt 將會檢查這個函數的返回值,如果是true,說明這個事件已經被處理完成,會轉而取事件隊列的下一個進行預取,如果返回的是false,那么會繼續把這個事件傳遞給其他的組件讓他們接著處理

專用事件處理函數

為了簡化事件處理,Qt為常見的事件提供了專用的虛函數,舉個例子看看:

  • mousePressEvent(QMouseEvent *event):處理鼠標按下事件。
  • keyPressEvent(QKeyEvent *event):處理鍵盤按下事件。
  • resizeEvent(QResizeEvent *event):處理窗口尺寸變化事件。

這些函數通常在對應的控件類中重寫,目的是對特定事件進行精細控制。需要注意的是,如果同時重寫了event()函數和專用事件函數,則通常應保證事件在其中一個函數中得到完整處理,避免重復調用。這些在源碼中的表先就是:判斷事件的Type,然后依據事件的類型轉發給對應的回調函數,就是這樣簡單!

事件發送接口

QCoreApplication::sendEvent()

同步事件發送接口sendEvent()直接調用目標對象的事件處理函數,并返回處理結果。這種方式適用于需要立即獲得事件處理結果的情況。但由于它是在當前線程中執行的,因此要注意防止在事件處理過程中產生阻塞或遞歸調用。

QCoreApplication::postEvent()

異步事件投遞接口postEvent()將事件放入目標對象所在線程的事件隊列中,由事件循環在合適的時機進行分發。常見的應用場景包括跨線程通信、延遲處理等。由于postEvent()并不會立即調用事件處理函數,開發人員在設計邏輯時應考慮事件延時帶來的影響。

自定義事件

在許多場景中,內置的事件類型無法滿足特定需求,開發者可以通過繼承QEvent來定義自定義事件。常見步驟如下:

  1. 定義新的事件類型(通常選用Qt::User 類型及之后的值)。
  2. 創建自定義事件類,包含特定數據和處理邏輯。
  3. 通過postEvent()或sendEvent()將自定義事件投遞到目標對象中。
  4. 在目標對象的event()函數中進行識別和處理。

這種方式提供了極大的擴展性,使得復雜的應用邏輯可以通過事件機制進行模塊化解耦。

事件過濾器

Qt還提供了事件過濾器機制,使得開發人員可以在事件傳遞前攔截、監控或修改事件。關鍵接口是QObject的installEventFilter(QObject *filterObj)eventFilter(QObject *watched, QEvent *event)函數。通過在某個對象上安裝事件過濾器,過濾器對象可以提前捕獲并處理目標對象的事件。

例如,在全局日志記錄、調試或臨時修改事件響應邏輯時,事件過濾器是一種非常有效的手段。下列代碼展示了如何為一個窗口安裝事件過濾器:

// 在構造函數中安裝過濾器
myWidget->installEventFilter(this);// 重寫eventFilter函數
bool MyClass::eventFilter(QObject *watched, QEvent *event) {if (watched == myWidget && event->type() == QEvent::KeyPress) {// 對鍵盤事件進行特殊處理qDebug() << "捕獲到鍵盤事件";return true;  // 返回true表示事件已經被處理,不再傳遞}// 調用基類實現,確保其他事件可以正常傳遞return QObject::eventFilter(watched, event);
}

通過上述接口,開發者可以在不改動原有對象代碼的前提下,實現對事件的監聽和攔截。


如何監聽和攔截其他對象的事件(本次文檔的重點)

在實際開發中,經常需要對已有控件或對象的事件進行監聽、修改甚至攔截。Qt提供了非常方便的事件過濾機制,使得這一需求得以高效實現。

? 事件過濾器的核心在于:每個QObject對象都有一個內部列表,用于存儲安裝到該對象上的過濾器。當事件到達目標對象前,系統會先依次調用每個過濾器對象的eventFilter()方法。

  • 如果某個過濾器返回true,表示該事件已經被處理,后續的過濾器和目標對象本身將不再接收到此事件。
  • 如果所有過濾器都返回false,事件則繼續傳遞給目標對象進行正常處理。

這種機制使得開發人員可以在不侵入原對象邏輯的情況下,對事件進行預處理,甚至阻斷事件傳遞。

安裝和使用事件過濾器

要實現對其他對象事件的監聽和攔截,主要步驟如下:

  1. 編寫過濾器類
    通常通過繼承QObject并重寫eventFilter()方法,編寫自定義過濾器類。在該方法中,根據watched參數判斷當前捕獲的事件屬于哪個對象,并根據事件類型進行處理。
  2. 安裝過濾器
    在需要監控的對象上調用installEventFilter()方法,將自定義過濾器對象注冊到該對象上。一個對象可以安裝多個過濾器,調用順序與安裝順序有關。
  3. 事件攔截與傳遞控制
    在eventFilter()中,當檢測到感興趣的事件后,可以選擇返回true(表示事件已處理,不繼續傳遞),也可以返回false(讓目標對象繼續處理)。

例如,假設我們需要攔截某個QLineEdit控件中的鼠標事件,可以這樣實現:

class MyEventFilter : public QObject {Q_OBJECT
protected:bool eventFilter(QObject *watched, QEvent *event) override {if (watched->inherits("QLineEdit")) {if (event->type() == QEvent::MouseButtonDblClick) {// 攔截雙擊事件qDebug() << "QLineEdit雙擊事件被攔截";return true;  // 阻止事件繼續傳遞}}// 其他情況繼續傳遞事件return QObject::eventFilter(watched, event);}
};

在程序初始化時,為目標對象安裝過濾器:

QLineEdit *edit = new QLineEdit(this);
MyEventFilter *filter = new MyEventFilter();
edit->installEventFilter(filter);

這樣,當用戶對該QLineEdit進行雙擊操作時,MyEventFilter將捕獲并攔截該事件,而QLineEdit本身不會收到雙擊事件。

動態監聽與跨對象事件監控

有時,我們不僅需要攔截單個對象的事件,還需要在全局范圍內對多個對象進行統一監控。例如,在大型應用中調試或記錄日志時,可以為整個應用安裝一個全局事件過濾器。通常的做法是將過濾器安裝在QCoreApplication對象上,這樣所有事件都會先經過該過濾器的檢測。

class GlobalEventFilter : public QObject {Q_OBJECT
protected:bool eventFilter(QObject *watched, QEvent *event) override {// 可以對所有對象和事件進行日志記錄或特定處理qDebug() << "全局過濾器捕獲到事件:" << event->type() << "來自對象:" << watched;// 根據需求選擇是否攔截或繼續傳遞return QObject::eventFilter(watched, event);}
};// 在main()函數中安裝全局過濾器
int main(int argc, char *argv[]) {QApplication app(argc, argv);GlobalEventFilter *globalFilter = new GlobalEventFilter();app.installEventFilter(globalFilter);// 后續創建的所有對象的事件均會經過globalFilter的檢測// …return app.exec();
}

這種全局過濾器的使用,尤其適用于調試階段,對復雜交互過程中的事件進行全面記錄和分析,或在某些特殊情況下統一攔截某類事件。

注意事項與最佳實踐

? 當然這里說一些重點的事情。在使用事件過濾器時,還需要注意以下幾點:

  • 性能問題:全局事件過濾器會處理所有事件,因此在實現中要避免執行過于耗時的操作,防止影響界面響應。
  • 返回值控制:返回true表示事件被完全攔截,可能導致目標對象無法得到響應;返回false則允許事件繼續傳遞。開發人員需要仔細判斷實際需求。
  • 層次關系:如果一個對象安裝了多個事件過濾器,事件會按照安裝順序依次經過各過濾器,過濾器之間可能存在相互影響,因此在設計時要考慮好先后次序。
  • 安全釋放:當過濾器對象不再需要時,必須及時調用removeEventFilter()方法,或者在對象銷毀時自動移除,避免懸掛指針問題。

本項目的實現的重要文檔思路

? 注意,這個文檔可能不會跟我們的源碼有一定保證的同步,只是提供一種參考!

如何讓Widgets跟隨鼠標移動呢

? 一種辦法,是讓我們創建一個SubWidget,這個SubWidget負責一對一的維護一個目標控件。比如說一個按鈕,或者是任何一個其他的控件,當我們的的目標事件傳遞到這個控件的時候,會優先的投射到我們的這個widgets上來。通過調用控件的 installEventFilter() 方法,將當前對象(this)作為過濾器安裝到 holding_widget 上。安裝事件過濾器后,該控件產生的所有事件都會首先傳遞到當前對象的 eventFilter() 方法中進行預處理。如果在 eventFilter() 中返回了 true,那么該事件就不會繼續傳遞到控件自身的事件處理函數中;如果返回 false,則事件會繼續傳遞。

? 這樣,我們就可以寫自己的一個eventFilter來控制目標widget的行為。而不需要重載我們的對象添加一個Movable或者是其他任何的屬性,這樣看就會非常的方便。

? 下面我們要做的就是準備處理我們的move行為

bool CCMovableWidget::eventFilter(QObject *watched, QEvent *event) {if (!holding_widget || watched != holding_widget) {return false;}QMouseEvent *mouseEvent = dynamic_cast<QMouseEvent *>(event);if (!mouseEvent) {return false;}// here we handle the mouse events// this will promise the future extensionsswitch (event->type()) {case QEvent::MouseButtonPress:handling_mousePressEvent(mouseEvent);break;case QEvent::MouseButtonRelease:handling_mouseReleaseEvent(mouseEvent);break;case QEvent::MouseMove:handling_mouseMoveEvent(mouseEvent);break;default:break;}// back the default behaviorreturn QObject::eventFilter(watched, event);
}

? 這是筆者的處理方式,依次對這個事件的MouseButtonPress,MouseButtonRelease和MouseMove進行了傳遞。這也就意味著這里它的事件就傳遞進來了進行了處理,當然處理結束后,我們還希望讓它做進一步的處理,所以我們讓他進一步維護其默認的實現。不要更改控件原來的行為。

剩下的內容

? 剩下的內容就沒什么新鮮的了,這里就讓AI幫我代勞吧!

// widget is pressed by the mouse, so this means we shell start our moving
void CCMovableWidget::handling_mousePressEvent(QMouseEvent *event) {qDebug() << "Mouse pressed";if (!holding_widget)return; // no widget to hold, reject processif (accept_buttons.size() > 0 && !accept_buttons.contains(event->button()))return;                            // the button is not acceptable, reject processwidget_state.lastPoint = event->pos(); // memorize the last pointwidget_state.pressed   = true;
}

handling_mousePressEvent(QMouseEvent *event) 是用戶按下鼠標時觸發的事件處理函數,是整個拖動行為的起點。當鼠標點擊到控件上時,首先通過日志輸出來表明事件已經被捕獲。接著,程序判斷 holding_widget 是否存在,如果為空,則說明當前沒有設置任何需要被移動的目標控件,因此直接返回,放棄此次操作。隨后,如果開發者為這個類設定了一個特定可接受的鼠標按鈕列表 accept_buttons,而當前觸發事件的按鈕不在該列表中,也同樣視為無效事件,拒絕處理。只有當這些條件都滿足后,事件才被視為有效操作。此時程序記錄當前鼠標點擊的位置,保存在 widget_state.lastPoint 中,用于后續計算移動偏移量,并將 widget_state.pressed 標志設為 true,表明控件已被點擊按住,準備進行拖動。

void CCMovableWidget::handling_mouseReleaseEvent(QMouseEvent *event) {qDebug() << "Mouse released";if (!holding_widget)return; // no widget to hold, reject processwidget_state.pressed = false;
}

handling_mouseReleaseEvent(QMouseEvent *event) 則是用戶釋放鼠標按鈕時調用的函數,它的作用相對簡單。同樣以日志開始,表示捕獲了釋放事件。隨后依舊先檢查是否存在 holding_widget,如果當前并未綁定任何控件,則此次釋放事件無需處理。若控件存在,則將 widget_state.pressed 設為 false,這一行為本質上是標記當前已結束拖動操作,后續的鼠標移動將不再引起控件的位置變化。

void CCMovableWidget::handling_mouseMoveEvent(QMouseEvent *event) {qDebug() << "Mouse moved";if (!holding_widget)return; // no widget to hold, reject processif (!widget_state.pressed)return; // the widget is not pressed, reject process// calculate the offsetint offsetX = event->pos().x() - widget_state.lastPoint.x();int offsetY = event->pos().y() - widget_state.lastPoint.y();// calculate the new positionint x = holding_widget->x() + offsetX;int y = holding_widget->y() + offsetY;// check if the widget should be in the parentif (widget_state.inParent) {QWidget *w = dynamic_cast<QWidget *>(holding_widget->parent());if (w && (sizeIsOutlier(QPoint(x, y), w) || positionIsOutlier(QPoint(x, y)))) {return;}// move the widgetholding_widget->move(x, y);}
}

handling_mouseMoveEvent(QMouseEvent *event) 是核心函數,它在用戶拖動鼠標時不斷被調用,從而持續地更新控件位置,完成“隨鼠標移動”的視覺效果。函數首先打印出“鼠標移動”的日志,確認事件的發生。緊接著,它做出兩個防御性檢查。第一,是否存在 holding_widget,否則自然不該響應移動。第二,判斷是否存在 widget_state.pressed 為真的狀態,這是防止控件在未被按住的情況下跟隨鼠標移動,確保只有在“鼠標按下后并且未釋放”的情形下才進入后續邏輯。接下來,程序通過當前位置與上次記錄的鼠標按下點 lastPoint 計算出一個偏移量 offsetXoffsetY,這是拖動過程中控件應該移動的距離。然后,根據當前控件的原始位置加上偏移量,計算出控件新的坐標 xy

但并非所有位置更新都是合理的,因此函數中還加入了一道邏輯判斷,即如果當前設置了 widget_state.inParent 為真(意味著控件應保持在其父組件內),就需要判斷新位置是否越界。這里調用了 sizeIsOutlier(QPoint(x, y), w)positionIsOutlier(QPoint(x, y)) 兩個函數,前者大概是判斷控件在給定位置上是否尺寸越界,后者則可能是判斷位置是否超出允許的邊界。這一檢查使得控件不能被拖出其父容器或顯示區域之外。如果這兩個函數判定位置無效,則不執行移動操作,函數直接返回。

最后,如果所有條件都滿足,程序調用 holding_widget->move(x, y) 將控件平滑地移動到新位置上。這一行為便是“拖動”體驗的實現者,控件就隨著鼠標游走而流暢移動。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/898766.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/898766.shtml
英文地址,請注明出處:http://en.pswp.cn/news/898766.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

[入門]NUC13配置Ubuntu20.04詳細步驟

文章目錄 1. 安裝Ubuntu20.041.1 制作系統啟動盤1.1.1 下載鏡像文件1.1.2 配置啟動盤 1.2 安裝內存條、硬盤1.3 安裝系統 2. 網卡驅動配置2.1 關閉安全啟動2.2 安裝intel官方網卡驅動backport2.2.1 第四步可能會出現問題 2.3 ubuntu官方的驅動2.4 重啟 3. 軟件安裝3.1 錄屏軟件…

(七)Reactor響應式編程框架

一、簡介 Reactor 是運行在 JVM 上的編程框架&#xff0c;最大特點是完全非阻塞&#xff0c;能高效控制 “背壓”&#xff0c;簡單來說就是處理數據傳輸時速度不匹配的問題 。它能和 Java 8 里的一些功能直接搭配使用&#xff0c;像處理異步結果的 CompletableFuture、處理數據…

從邊緣到核心:群聯云防護如何重新定義安全加速邊界?

一、安全能力的全方位碾壓 1. 協議層深度防護 四層防御&#xff1a; 動態過濾畸形TCP/UDP包&#xff08;如SYN Flood&#xff09;&#xff0c;傳統CDN僅限速率控制。技術示例&#xff1a;基于AI的協議指紋分析&#xff0c;攔截異常連接模式。 七層防御&#xff1a; 精準識別業…

【Linux】Ubuntu 24.04 LTS 安裝 OpenJDK 8

目錄 通過 apt-get 直接安裝 JDK 1. 更新 apt 軟件源 2. 檢查 JDK 是否已安裝 3. 安裝OpenJDK 4. 檢查 JDK 是否成功安裝 5. 設置 JAVA_HOME 環境變量 找到需要設置的 Java 路徑 使用文本編輯器打開/etc/environment文件 添加 Java 安裝路徑 應用更改和驗證配置 通過…

Java 方法執行原理底層解析

java 文件經過javac編譯后&#xff0c;變成了存儲了一系列指令的.class文件。本文從指令層面分析Java 方法從解析、調用到執行的過程。 1 指令 一般格式&#xff1a;操作碼 [操作數1] [操作數2] ... 操作碼 1個字節的無符號整數&#xff08;范圍&#xff1a;0x00 ~ 0xFF&…

【數學建模】最大最小值模型詳解

數學建模中的最大最小值模型詳解 文章目錄 數學建模中的最大最小值模型詳解引言最大最小值模型的基本概念最大化問題最小化問題 常見的求解方法1. 微積分法2. 線性規劃3. 非線性規劃4. 動態規劃 實際應用案例案例1&#xff1a;生產規劃問題案例2&#xff1a;投資組合優化 最大最…

C#的List和DIctionary實現原理(手搓泛型類以及增刪查改等功能)

這里寫自定義目錄標題 ListDIctionary List MyList類&#xff1a;這是一個泛型類&#xff0c;能夠存儲任意類型的元素。 _items數組&#xff1a;用于實際存儲元素。 _size變量&#xff1a;記錄當前列表中的元素數量。 構造函數&#xff1a;初始化數組容量為 4。 Count屬性&…

Linux系統管理與編程08:任務驅動綜合應用

蘭生幽谷&#xff0c;不為莫服而不芳&#xff1b; 君子行義&#xff0c;不為莫知而止休。 [環境] windows11、centos9.9.2207、zabbix6、MobaXterm、Internet環境 [要求] zabbix6.0安裝環境&#xff1a;Lamp&#xff08;linux httpd mysql8.0 php&#xff09; [步驟] 3 …

數據結構之基本隊列-順序結構實現-初始化-判斷隊列是否為空(front=rear)-出隊-入隊-隊尾滿了,調整隊列-獲取隊頭元素

數據結構之基本隊列-順序結構實現-初始化-判斷隊列是否為空(frontrear)-出隊-入隊-隊尾滿了&#xff0c;調整隊列-獲取隊頭元素——完整可運行代碼 #include <stdio.h>#define MAXSIZE 100 typedef int ElemType;typedef struct {ElemType data[MAXSIZE];int front;int…

基于LabVIEW的Windows平臺高速閉環控制

在Windows系統下&#xff0c;通過LabVIEW實現高速閉環控制面臨兩大核心挑戰&#xff1a;非實時操作系統的調度延遲與硬件接口的傳輸速度限制。以USB-6351&#xff08;NI USB-6351 DAQ卡&#xff09;為例&#xff0c;其理論采樣率可達1.25 MS/s&#xff08;單通道&#xff09;&a…

Java面試黃金寶典8

1. 什么是 Spring MVC 定義 Spring MVC 是 Spring 框架里用于構建 Web 應用程序的模塊&#xff0c;它嚴格遵循 MVC&#xff08;Model - View - Controller&#xff09;設計模式。這種設計模式把應用程序清晰地劃分成三個主要部分&#xff1a; Model&#xff08;模型&#xff0…

【 <二> 丹方改良:Spring 時代的 JavaWeb】之 Spring Boot 中的 RESTful API 設計:從上手到骨折

<前文回顧> 點擊此處查看 合集 https://blog.csdn.net/foyodesigner/category_12907601.html?fromshareblogcolumn&sharetypeblogcolumn&sharerId12907601&sharereferPC&sharesourceFoyoDesigner&sharefromfrom_link <今日更新> 一、開篇整活…

分享最近前端面試遇到的一些問題

前情提要&#xff08;分享個人情況&#xff0c;可以直接跳過&#xff09; 先說一下我的個人情況&#xff0c;我是2026屆的&#xff0c;目前是在找前端實習。 3月初&#xff0c;從3月3日開始在Boss上投簡歷。 分享我的個人故事&#xff0c;不想看可以直接滑到下面&#xff0c;…

rip 協議詳細介紹

以下是關于 RIP&#xff08;Routing Information Protocol&#xff0c;路由信息協議&#xff09; 的詳細介紹&#xff0c;涵蓋其工作原理、版本演進、配置方法、優缺點及實際應用場景。 1. RIP 協議概述 類型&#xff1a;動態路由協議&#xff0c;基于距離矢量算法&#xff08…

scrapy入門(深入)

Scrapy框架簡介 Scrapy是:由Python語言開發的一個快速、高層次的屏幕抓取和web抓取框架&#xff0c;用于抓取web站點并從頁面中提取結構化的數據&#xff0c;只需要實現少量的代碼&#xff0c;就能夠快速的抓取。 新建項目 (scrapy startproject xxx)&#xff1a;新建一個新的…

KiLog2MaximumIncrement的由來和KiMaximumIncrementReciprocal的由來

第一部分&#xff1a;KiLog2MaximumIncrement的由來 i 1; j KeMaximumIncrement; while ((1UI64<<i) < KeMaximumIncrement) { i; } KiLog2MaximumIncrement i; 2^17131072 2^18262144 i18KiLog2MaximumIncrement 中…

數據結構-ArrayList

文章目錄 1. 線性表2. 順序表3. ArrayList4. ArrayList的問題以及思考4.2 增容的性能消耗問題4.3 空間浪費問題 1. 線性表 線性表&#xff08;Linear List&#xff09;是n個具有相同特性的數據元素的有限序列。線性表是一種在實際中廣泛使用的數據結構&#xff0c;常見線性表&…

FastGPT 社區版快速部署指南

產品簡介 FastGPT 是基于大語言模型的智能知識庫系統&#xff0c;提供以下核心能力&#xff1a; ? 開箱即用 - 內置數據預處理、多模型對接、權限管理 ? 可視化編排 - 通過 Flow 工作流實現復雜問答邏輯設計 ? 多場景適配 - 支持客服機器人/知識檢索/數據分析等場景 &…

【css酷炫效果】純CSS實現科技感網格背景

【css酷炫效果】純CSS實現科技感網格背景 緣創作背景html結構css樣式完整代碼基礎版進階版(3D光線掃描版) 效果圖 想直接拿走的老板&#xff0c;鏈接放在這里&#xff1a;上傳后更新 緣 創作隨緣&#xff0c;不定時更新。 創作背景 剛看到csdn出活動了&#xff0c;趕時間&a…

Android BLE 權限管理

前言 android 權限一直是比較活躍的 在藍牙權限這一塊又分新版和舊版 新版權限 android.Manifest.permission.BLUETOOTH_SCAN, android.Manifest.permission.BLUETOOTH_ADVERTISE, android.Manifest.permission.BLUETOOTH_CONNECT舊版權限如9.0以下 Manifest.permission.A…