QT事件循环

文章目录

    • 主事件循环
    • 事件循环
    • 事件调度器
    • 事件处理
      • 投递事件
      • 发送事件
    • 事件循环的嵌套
    • 线程的事件循环
    • deleteLater与事件循环
    • QEventLoop类
    • QEventLoop应用
      • 等待一段时间
      • 同步操作
      • 模拟模态对话框
    • 参考

本文主要对QT中的事件循环做简单介绍和使用

Qt作为一个跨平台的UI框架,其事件循环实现原理, 就是把不同平台的事件循环进行了封装,并提供统一的抽象接口。

主事件循环

一个最简单的qt程序:

int main(int argc, char *argv[])
{QCoreApplication app(argc, argv);return app.exec();
}

QCoreApplication app(argc, argv)这一步,主要是创建并初始化了一个QCoreApplication对象。最主要的是设置一些线程相关的数据(QThreadData),比如事件调度器(QAbstractEventDispatcher)等。
app.exec()则是为了启动一个事件循环来分发事件。如果没有事件循环或事件循环没有启动,则对象永远不会接收到事件。
由QCoreApplication启动的事件循环也叫作主事件循环,所有的事件分发、事件处理都从这里开始。

查看exec的代码(qtbase/src/corelib/kernel/qcoreapplication.cpp):

int QCoreApplication::exec()
{if (!QCoreApplicationPrivate::checkInstance("exec"))return -1;QThreadData *threadData = self->d_func()->threadData;if (threadData != QThreadData::current()) {qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());return -1;}if (!threadData->eventLoops.isEmpty()) {qWarning("QCoreApplication::exec: The event loop is already running");return -1;}threadData->quitNow = false;QEventLoop eventLoop;self->d_func()->in_exec = true;self->d_func()->aboutToQuitEmitted = false;int returnCode = eventLoop.exec();threadData->quitNow = false;if (self)self->d_func()->execCleanup();return returnCode;
}

可以看到,基本就是启动一个QEventLoop。

事件循环

继续查看QEventLoop的代码(qtbase/src/corelib/kernel/qeventloop.cpp):

int QEventLoop::exec(ProcessEventsFlags flags)
{Q_D(QEventLoop);auto threadData = d->threadData.loadRelaxed();//we need to protect from race condition with QThread::exitQMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(threadData->thread.loadAcquire()))->mutex);if (threadData->quitNow)return -1;if (d->inExec) {qWarning("QEventLoop::exec: instance %p has already called exec()", this);return -1;}struct LoopReference {QEventLoopPrivate *d;QMutexLocker &locker;bool exceptionCaught;LoopReference(QEventLoopPrivate *d, QMutexLocker &locker) : d(d), locker(locker), exceptionCaught(true){d->inExec = true;d->exit.storeRelease(false);auto threadData = d->threadData.loadRelaxed();++threadData->loopLevel;threadData->eventLoops.push(d->q_func());locker.unlock();}~LoopReference(){if (exceptionCaught) {qWarning("Qt has caught an exception thrown from an event handler. Throwing\n""exceptions from an event handler is not supported in Qt.\n""You must not let any exception whatsoever propagate through Qt code.\n""If that is not possible, in Qt 5 you must at least reimplement\n""QCoreApplication::notify() and catch all exceptions there.\n");}locker.relock();auto threadData = d->threadData.loadRelaxed();QEventLoop *eventLoop = threadData->eventLoops.pop();Q_ASSERT_X(eventLoop == d->q_func(), "QEventLoop::exec()", "internal error");Q_UNUSED(eventLoop); // --release warningd->inExec = false;--threadData->loopLevel;}};LoopReference ref(d, locker);// remove posted quit events when entering a new event loopQCoreApplication *app = QCoreApplication::instance();if (app && app->thread() == thread())QCoreApplication::removePostedEvents(app, QEvent::Quit);#ifdef Q_OS_WASM// Partial support for nested event loops: Make the runtime throw a JavaSrcript// exception, which returns control to the browser while preserving the C++ stack.// Event processing then continues as normal. The sleep call below never returns.// QTBUG-70185if (threadData->loopLevel > 1)emscripten_sleep(1);
#endifwhile (!d->exit.loadAcquire())processEvents(flags | WaitForMoreEvents | EventLoopExec);ref.exceptionCaught = false;return d->returnCode.loadRelaxed();
}

可见QEventLoop::exec()是通过循环不断地调用QEventLoop::processEvents()来分发事件队列中的事件。
而最终完成事件分发的是事件调度器。通过下面的代码我们可以看出,事件调度器存在于各自线程相关数据中。也就是说每个线程都可以有、并且只使用自己专属的事件调度器。

bool QEventLoop::processEvents(ProcessEventsFlags flags)
{Q_D(QEventLoop);auto threadData = d->threadData.loadRelaxed();if (!threadData->hasEventDispatcher())return false;return threadData->eventDispatcher.loadRelaxed()->processEvents(flags);
}

事件调度器

事件调度器就比较依赖于各个平台的实现了,各平台上的事件调度器实现都不尽相同。Linux下是QEventDispatcherUNIX:

bool QEventDispatcherUNIX::processEvents(QEventLoop::ProcessEventsFlags flags)
{Q_D(QEventDispatcherUNIX);d->interrupt.storeRelaxed(0);// we are awake, broadcast itemit awake();auto threadData = d->threadData.loadRelaxed();QCoreApplicationPrivate::sendPostedEvents(nullptr, 0, threadData);const bool include_timers = (flags & QEventLoop::X11ExcludeTimers) == 0;const bool include_notifiers = (flags & QEventLoop::ExcludeSocketNotifiers) == 0;const bool wait_for_events = flags & QEventLoop::WaitForMoreEvents;const bool canWait = (threadData->canWaitLocked()&& !d->interrupt.loadRelaxed()&& wait_for_events);if (canWait)emit aboutToBlock();if (d->interrupt.loadRelaxed())return false;timespec *tm = nullptr;timespec wait_tm = { 0, 0 };if (!canWait || (include_timers && d->timerList.timerWait(wait_tm)))tm = &wait_tm;d->pollfds.clear();d->pollfds.reserve(1 + (include_notifiers ? d->socketNotifiers.size() : 0));if (include_notifiers)for (auto it = d->socketNotifiers.cbegin(); it != d->socketNotifiers.cend(); ++it)d->pollfds.append(qt_make_pollfd(it.key(), it.value().events()));// This must be last, as it's popped off the end belowd->pollfds.append(d->threadPipe.prepare());int nevents = 0;switch (qt_safe_poll(d->pollfds.data(), d->pollfds.size(), tm)) {case -1:perror("qt_safe_poll");break;case 0:break;default:nevents += d->threadPipe.check(d->pollfds.takeLast());if (include_notifiers)nevents += d->activateSocketNotifiers();break;}if (include_timers)nevents += d->activateTimers();// return true if we handled events, false otherwisereturn (nevents > 0);
}

QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData)会派发当前线程事件队列中所有的事件。

void QCoreApplicationPrivate::sendPostedEvents(QObject *receiver, int event_type,QThreadData *data)
{if (event_type == -1) {// we were called by an obsolete event dispatcher.event_type = 0;}if (receiver && receiver->d_func()->threadData != data) {qWarning("QCoreApplication::sendPostedEvents: Cannot send ""posted events for objects in another thread");return;}++data->postEventList.recursion;auto locker = qt_unique_lock(data->postEventList.mutex);// by default, we assume that the event dispatcher can go to sleep after// processing all events. if any new events are posted while we send// events, canWait will be set to false.data->canWait = (data->postEventList.size() == 0);if (data->postEventList.size() == 0 || (receiver && !receiver->d_func()->postedEvents)) {--data->postEventList.recursion;return;}data->canWait = true;// okay. here is the tricky loop. be careful about optimizing// this, it looks the way it does for good reasons.int startOffset = data->postEventList.startOffset;int &i = (!event_type && !receiver) ? data->postEventList.startOffset : startOffset;data->postEventList.insertionOffset = data->postEventList.size();// Exception-safe cleaning up without the need for a try/catch blockstruct CleanUp {QObject *receiver;int event_type;QThreadData *data;bool exceptionCaught;inline CleanUp(QObject *receiver, int event_type, QThreadData *data) :receiver(receiver), event_type(event_type), data(data), exceptionCaught(true){}inline ~CleanUp(){if (exceptionCaught) {// since we were interrupted, we need another pass to make sure we clean everything updata->canWait = false;}--data->postEventList.recursion;if (!data->postEventList.recursion && !data->canWait && data->hasEventDispatcher())data->eventDispatcher.loadRelaxed()->wakeUp();// clear the global list, i.e. remove everything that was// delivered.if (!event_type && !receiver && data->postEventList.startOffset >= 0) {const QPostEventList::iterator it = data->postEventList.begin();data->postEventList.erase(it, it + data->postEventList.startOffset);data->postEventList.insertionOffset -= data->postEventList.startOffset;Q_ASSERT(data->postEventList.insertionOffset >= 0);data->postEventList.startOffset = 0;}}};CleanUp cleanup(receiver, event_type, data);while (i < data->postEventList.size()) {// avoid live-lockif (i >= data->postEventList.insertionOffset)break;const QPostEvent &pe = data->postEventList.at(i);++i;if (!pe.event)continue;if ((receiver && receiver != pe.receiver) || (event_type && event_type != pe.event->type())) {data->canWait = false;continue;}if (pe.event->type() == QEvent::DeferredDelete) {// DeferredDelete events are sent either// 1) when the event loop that posted the event has returned; or// 2) if explicitly requested (with QEvent::DeferredDelete) for//    events posted by the current event loop; or// 3) if the event was posted before the outermost event loop.int eventLevel = static_cast<QDeferredDeleteEvent *>(pe.event)->loopLevel();int loopLevel = data->loopLevel + data->scopeLevel;const bool allowDeferredDelete =(eventLevel > loopLevel|| (!eventLevel && loopLevel > 0)|| (event_type == QEvent::DeferredDelete&& eventLevel == loopLevel));if (!allowDeferredDelete) {// cannot send deferred deleteif (!event_type && !receiver) {// we must copy it first; we want to re-post the event// with the event pointer intact, but we can't delay// nulling the event ptr until after re-posting, as// addEvent may invalidate pe.QPostEvent pe_copy = pe;// null out the event so if sendPostedEvents recurses, it// will ignore this one, as it's been re-posted.const_cast<QPostEvent &>(pe).event = nullptr;// re-post the copied event so it isn't lostdata->postEventList.addEvent(pe_copy);}continue;}}// first, we diddle the event so that we can deliver// it, and that no one will try to touch it later.pe.event->posted = false;QEvent *e = pe.event;QObject * r = pe.receiver;--r->d_func()->postedEvents;Q_ASSERT(r->d_func()->postedEvents >= 0);// next, update the data structure so that we're ready// for the next event.const_cast<QPostEvent &>(pe).event = nullptr;locker.unlock();const auto relocker = qScopeGuard([&locker] { locker.lock(); });QScopedPointer<QEvent> event_deleter(e); // will delete the event (with the mutex unlocked)// after all that work, it's time to deliver the event.QCoreApplication::sendEvent(r, e);// careful when adding anything below this point - the// sendEvent() call might invalidate any invariants this// function depends on.}cleanup.exceptionCaught = false;
}

事件处理

Application还提供了sendEvent和poseEvent两个函数,分别用来发送事件。sendEvent发出的事件会立即被处理,也就是“同步”执行。postEvent发送的事件会被加入事件队列,在下一轮事件循环时才处理,也就是“异步”执行。还有一个特殊的sendPostedEvents,是将已经加入队列中的准备异步执行的事件立即同步执行。

QCoreApplication::sendPostedEvents()会清空事件队列并立即返回。

void QCoreApplication::sendPostedEvents(QObject *receiver, int event_type)
{// ### Qt 6: consider splitting this method into a public and a private//           one, so that a user-invoked sendPostedEvents can be detected//           and handled properly.QThreadData *data = QThreadData::current();QCoreApplicationPrivate::sendPostedEvents(receiver, event_type, data);
}

而QCoreApplication::processEvents()相较于QCoreApplication::sendPostedEvents()相当于多了等待事件发生这一步。也就是说它在清空事件队列后不会立即返回。

void QCoreApplication::processEvents(QEventLoop::ProcessEventsFlags flags)
{QThreadData *data = QThreadData::current();if (!data->hasEventDispatcher())return;data->eventDispatcher.loadRelaxed()->processEvents(flags);
}

投递事件

当通过QCoreApplication::postEvent()投递一个事件的时候,会将其加入到事件队列中,并唤醒事件调度器。具体来说,是向eventfd()系统调用获取的文件描述符写入数据,使其状态为数据可读(POLLIN)。
You can manually post events to any object in any thread at any time using the thread-safe function QCoreApplication::postEvent(). The events will automatically be dispatched by the event loop of the thread where the object was created.

void QCoreApplication::postEvent(QObject *receiver, QEvent *event, int priority)
{Q_TRACE_SCOPE(QCoreApplication_postEvent, receiver, event, event->type());if (receiver == nullptr) {qWarning("QCoreApplication::postEvent: Unexpected null receiver");delete event;return;}auto locker = QCoreApplicationPrivate::lockThreadPostEventList(receiver);if (!locker.threadData) {// posting during destruction? just delete the event to prevent a leakdelete event;return;}QThreadData *data = locker.threadData;// if this is one of the compressible events, do compressionif (receiver->d_func()->postedEvents&& self && self->compressEvent(event, receiver, &data->postEventList)) {Q_TRACE(QCoreApplication_postEvent_event_compressed, receiver, event);return;}if (event->type() == QEvent::DeferredDelete)receiver->d_ptr->deleteLaterCalled = true;if (event->type() == QEvent::DeferredDelete && data == QThreadData::current()) {// remember the current running eventloop for DeferredDelete// events posted in the receiver's thread.// Events sent by non-Qt event handlers (such as glib) may not// have the scopeLevel set correctly. The scope level makes sure that// code like this://     foo->deleteLater();//     qApp->processEvents(); // without passing QEvent::DeferredDelete// will not cause "foo" to be deleted before returning to the event loop.// If the scope level is 0 while loopLevel != 0, we are called from a// non-conformant code path, and our best guess is that the scope level// should be 1. (Loop level 0 is special: it means that no event loops// are running.)int loopLevel = data->loopLevel;int scopeLevel = data->scopeLevel;if (scopeLevel == 0 && loopLevel != 0)scopeLevel = 1;static_cast<QDeferredDeleteEvent *>(event)->level = loopLevel + scopeLevel;}// delete the event on exceptions to protect against memory leaks till the event is// properly owned in the postEventListQScopedPointer<QEvent> eventDeleter(event);Q_TRACE(QCoreApplication_postEvent_event_posted, receiver, event, event->type());data->postEventList.addEvent(QPostEvent(receiver, event, priority));eventDeleter.take();event->posted = true;++receiver->d_func()->postedEvents;data->canWait = false;locker.unlock();QAbstractEventDispatcher* dispatcher = data->eventDispatcher.loadAcquire();if (dispatcher)dispatcher->wakeUp();
}

发送事件

postEvent()、sendEvent()是Qt事件循环中两个不同的阶段。如果简化掉一切中间流程,可以认为QCoreApplication::sendEvent()是在直接调用接受者的QObject::event()方法(QGuiApplication及QApplication实现不尽相同)。
Event filters are supported in all threads, with the restriction that the monitoring object must live in the same thread as the monitored object. Similarly, QCoreApplication::sendEvent() (unlike postEvent()) can only be used to dispatch events to objects living in the thread from which the function is called

bool QCoreApplication::sendEvent(QObject *receiver, QEvent *event)
{Q_TRACE(QCoreApplication_sendEvent, receiver, event, event->type());if (event)event->spont = false;return notifyInternal2(receiver, event);
}bool QCoreApplication::notifyInternal2(QObject *receiver, QEvent *event)
{bool selfRequired = QCoreApplicationPrivate::threadRequiresCoreApplication();if (!self && selfRequired)return false;// Make it possible for Qt Script to hook into events even// though QApplication is subclassed...bool result = false;void *cbdata[] = { receiver, event, &result };if (QInternal::activateCallbacks(QInternal::EventNotifyCallback, cbdata)) {return result;}// Qt enforces the rule that events can only be sent to objects in// the current thread, so receiver->d_func()->threadData is// equivalent to QThreadData::current(), just without the function// call overhead.QObjectPrivate *d = receiver->d_func();QThreadData *threadData = d->threadData;QScopedScopeLevelCounter scopeLevelCounter(threadData);if (!selfRequired)return doNotify(receiver, event);return self->notify(receiver, event);
}bool QCoreApplication::notify(QObject *receiver, QEvent *event)
{// no events are delivered after ~QCoreApplication() has startedif (QCoreApplicationPrivate::is_app_closing)return true;return doNotify(receiver, event);
}static bool doNotify(QObject *receiver, QEvent *event)
{if (receiver == nullptr) {                        // serious errorqWarning("QCoreApplication::notify: Unexpected null receiver");return true;}#ifndef QT_NO_DEBUGQCoreApplicationPrivate::checkReceiverThread(receiver);
#endifreturn receiver->isWidgetType() ? false : QCoreApplicationPrivate::notify_helper(receiver, event);
}bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
{// Note: when adjusting the tracepoints in here// consider adjusting QApplicationPrivate::notify_helper too.Q_TRACE(QCoreApplication_notify_entry, receiver, event, event->type());bool consumed = false;bool filtered = false;Q_TRACE_EXIT(QCoreApplication_notify_exit, consumed, filtered);// send to all application event filters (only does anything in the main thread)if (QCoreApplication::self&& receiver->d_func()->threadData.loadRelaxed()->thread.loadAcquire() == mainThread()&& QCoreApplication::self->d_func()->sendThroughApplicationEventFilters(receiver, event)) {filtered = true;return filtered;}// send to all receiver event filtersif (sendThroughObjectEventFilters(receiver, event)) {filtered = true;return filtered;}// deliver the eventconsumed = receiver->event(event);return consumed;
}

事件循环的嵌套

事件循环是可以嵌套的,当在子事件循环中的时候,父事件循环中的事件实际上处于中断状态,当子循环跳出exec之后才可以执行父循环中的事件。当然,这不代表在执行子循环的时候,类似父循环中的界面响应会被中断,因为往往子循环中也会有父循环的大部分事件,执行QMessageBox::exec(),QEventLoop::exec()的时候,虽然这些exec()打断了main()中的QApplication::exec(),但是由于GUI界面的响应已经被包含到子循环中了,所以GUI界面依然能够得到响应。如果某个子事件循环仍然有效,但其父循环被强制跳出,此时父循环不会立即执行跳出,而是等待子事件循环跳出后,父循环才会跳出。

如果子widget没有accept或ignore该事件,则该事件会被传递给其父窗口。那么:对于一个继承而来的类,只要我们重写实现了其各个事件处理函数,则对应的事件肯定无法传递给其父widget! 哪怕重写的该事件处理函数的函数体为空!如果是标准的控件对象,则其肯定没重写各个事件处理函数。那消息能不能传递到父widget中,则取决于中途有没有使用事件过滤器等将该信号拦截下来了。

案例:
在一个QWidget上建了一个QLabel。而后实现父QWidget的mousePressEvent(), 然后跟一下发现:当我click这个label时:居然能进入到父QWidget的mousePressEvent()中,但是如果把子改成QPushButton则进入不了。
(1)对于QLabel: 如果不重写mouse处理函数,也没有设置事件过滤器等操作的话,则相当于:其对mouse这个事件一直没有进行处理,那没有进行处理的话,相当于上边所说的情况,此时该事件会被传递给其parent。
(2)而对于QPushButton而言:当click它时:其会发射clicked()信号,其实这就相当于它对该事件的一个operator过程。所以:这里它accept该事件并进行了对应处理。从而:无法传递给其父窗口。

线程的事件循环

Threads and QObjects
QThread::exec()将开启一个新的事件循环,每个线程的事件循环独立,并且只为存在于其线程内的QObjects对象传递事件,这些对象分为两类,一类是在其线程内创建的,一类是通过moveToThread()移动到该线程内的。可通过thread()方法查看对象依附的线程(thread affinity)。
对象、线程、事件循环的关系如下图:
在这里插入图片描述

deleteLater与事件循环

deleteLater()将发送一个QEvent::DeferredDelete事件到事件循环中。
deleteLater()真正删除对象:

  • 当控制返回事件循环时,该对象将被删除
  • 如果调用deleteLater()时事件循环未运行,一旦事件循环开始,该对象将被删除
  • 如果在循环停止后调用deleteLater(),该对象将在线程完成时被销毁(Qt 4.8以后)

注意,进入和离开新的事件循环(例如QDialog::exec())将不会执行延迟删除,控制权必须返回到调用deleteLater()时所在的事件循环时,对象才会被删除。

QEventLoop类

QEventLoop即Qt中的事件循环类,主要接口如下:

// 进入事件循环直到exit被调用
int exec(QEventLoop::ProcessEventsFlags flags = AllEvents)
// 通知事件循环退出
void exit(int returnCode = 0)
// 判断事件循环是否运行
bool isRunning() const
// 处理符合标志的待处理事件,直到没有更多事件需要处理为止。
bool processEvents(QEventLoop::ProcessEventsFlags flags = AllEvents)
// 处理与标志匹配且最多处理 maxTime 毫秒的待处理事件,或者直到没有更多事件可处理为止,以较短者为准。如果有一个长时间运行的操作,并且希望在不允许用户输入的情况下显示其进度(即通过使用 ExcludeUserInputEvents 标志),则此函数特别有用。
// 注意事项:
// 此函数不会持续处理事件;它会在所有可用事件处理完毕后返回。
// 指定 WaitForMoreEvents 标志没有意义,将被忽略。
void processEvents(QEventLoop::ProcessEventsFlags flags, int maxTime)
// 唤醒事件循环
void wakeUp()

QEventLoop应用

等待一段时间

    QEventLoop loop;QTimer::singleShot(1000, &loop, SLOT(quit()));qInfo() << QTime::currentTime();loop.exec();qInfo() << QTime::currentTime();

同步操作

经常会有这种场景: “触发 ”了某项操作,必须等该操作完成后才能进行“ 下一步 ”,但在等待该操作完成期间不能阻塞主线程。比如:数据获取,向服务器发起登录请求后,必须等收到服务器返回的数据,才决定下一步如何执行。这种场景,如果设计成异步调用,直接用Qt的信号/槽即可,如果要设计成同步调用,就可以使用本地QEventLoop。

void A::onFinish()
{//槽中退出事件循环 loop.quit(); 
}void A::work(){ // 1.声明本地EventLoop QEventLoop loop;// 2. 先连接好信号 connect(pwaitPeer, &waitPeer::finished,this,&A::onFinish);// 3. 发起操作...
// 4. 启动事件循环。阻塞当前函数调用,但是事件循环还能运行。这里不会再往下运行,直到前面的槽中,调用loop.quit之后,才会继续往下走loop.exec();
}

模拟模态对话框

QDialog dlg;
dlg.show();
QEventLoop loop;
connect(&dlg, SIGNAL(finished(int)), &loop, SLOT(quit()));
loop.exec(QEventLoop::ExcludeUserInputEvents);

参考

QEventLoop
Threads and QObjects

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

解决DeepSeek服务器繁忙问题的实用指南

目录 简述 1. 关于服务器繁忙 1.1 服务器负载与资源限制 1.2 会话管理与连接机制 1.3 客户端配置与网络问题 2. 关于DeepSeek服务的备用选项 2.1 纳米AI搜索 2.2 硅基流动 2.3 秘塔AI搜索 2.4 字节跳动火山引擎 2.5 百度云千帆 2.6 英伟达NIM 2.7 Groq 2.8 Firew…

进程等待和进程程序替换

进程控制 进程等待进程程序替换 进程等待 如果子进程没有退出 而父进程在进行执行waitpid进行等待&#xff0c;阻塞等待&#xff0c; 进程阻塞了 在等待某种条件发生&#xff08;子进程退出&#xff09; 进程程序替换 1 #include <stdio.h>2 #include <unistd.h>3…

UEFI Spec 学习笔记---6 - Block Translation Table (BTT) Layout

6.1 Block Translation Table (BTT) Background 定义个一个连续地址的非易失性的namespace&#xff0c;就是将一整个namespace 拆分成一个个block&#xff0c;其中的地址保存至BBT&#xff08;块转换表&#xff09;&#xff0c;这样可以防止扇区撕裂&#xff08;由于电源问题导…

SAP 代码扫描工具

描述&#xff1a; ZSCANNER是一个先进的代码分析工具&#xff0c;旨在提供对程序和功能模块内部工作的全面见解。它揭示了代码的技术细节&#xff0c;包括正在创建、读取、更新或删除的数据表&#xff08;CRUD操作&#xff09;&#xff0c;以及正在调用的类、功能模块和BAPI&a…

c语言基础第12节《函数的调用》

c语言基础10 函数 函数的调用 调用方式 ①函数语句&#xff1a; test(); // 对于无返回值的函数&#xff0c;直接调用 int res max(2,4); // 对于有返回值的函数&#xff0c;一般需要再主调函数中接收被调函数的返回值。②函数表达式&#xff1a; 4 max(2,4) scanf(&qu…

C++:iterator迭代器失效

说明&#xff1a;这里拿 vector 举例。 原因一&#xff1a;扩容导致迭代器失效 // 迭代器失效 void insert(iterator pos, const T& x) {assert(pos > _start);assert(pos < _finish);// 满了就扩容if (_finish _end_of_storage){reserve(capacity() 0 ? 4 : ca…

QT之改变鼠标样式

QT改变鼠标图片 资源路径如下 代码实现 QPixmap customCursorPixmap(":/images/mouse.png");QCursor customCursor(customCursorPixmap);QWidget::setCursor(customCursor); // 可以设置为整个窗口或特定控件QWidget::setCursor(); // 设置为透明光标&#xff0c…

用DeepSeek零基础预测《哪吒之魔童闹海》票房——从数据爬取到模型实战

系列文章目录 1.元件基础 2.电路设计 3.PCB设计 4.元件焊接 5.板子调试 6.程序设计 7.算法学习 8.编写exe 9.检测标准 10.项目举例 11.职业规划 文章目录 **一、为什么要预测票房&#xff1f;****二、准备工作****三、实战步骤详解****Step 1&#xff1a;数据爬取与清洗&am…

高并发下秒杀系统的设计

文章目录 1 业界通用做法1.1 压力分摊1.2 RedisMySQL1.3 Inventory Hint1.4 压力分摊RedisMQ 2 Redis MQ 解决高并发下的秒杀场景2.1 Redis库存预扣减2.1.1 lua脚本执行流程&#xff1a;2.1.2 Lua脚本主要做了几件事&#xff1a; 2.2 MySQL库存扣减2.3 记录操作流水的原因 3 I…

双重差分学习笔记

双重差分适用的研究场景&#xff1a; 研究某项政策或者冲击造成的影响 例如&#xff0c;某某小学在2024.12.12日颁布了小红花激励措施&#xff0c;我们要研究这项措施对学生成绩的影响&#xff0c;此时&#xff0c;就可以使用双重差分模型。 双重差分适用的数据类型&#xf…

深入理解 C++17 中的 std::atomic<T>::is_always_lock_free

文章目录 原子操作与锁无关性&#xff08;Lock-Free&#xff09;锁无关性&#xff08;Lock-Free&#xff09;无锁&#xff08;Lock-Free&#xff09;与无阻塞&#xff08;Wait-Free&#xff09; std::atomic<T>::is_always_lock_free 是什么&#xff1f;truefalse与 is_l…

VSCode 中 Git 添加了多个远端,如何设置默认远端

VSCode 中 Git 添加了多个远端&#xff0c;如何设置默认远端 查看分支&#xff1a;设置默认远端手动指定远端 查看分支&#xff1a; * 表示当前默认远端 git branch -vv* master a1b2c3d [origin/main] Fix typo dev d4e5f6g [upstream/dev] Add feature设置默认远端 将本…

一文讲清 AIO BIO NIO的区别

引言 在 Java 编程中&#xff0c;BIO&#xff08;Blocking I/O&#xff09;、NIO&#xff08;Non-blocking I/O&#xff09;和 AIO&#xff08;Asynchronous I/O&#xff09;是三种不同的 I/O 模型&#xff0c;它们在处理输入输出操作时有着不同的机制和特点&#xff0c;但是市…

使用(xshell+xftp)将前端项目部署到服务器

一.以vue项目为例 将项目打包生成dist文件 二.下载载安装xshell和xftp 下载地址&#xff1a;家庭/学校免费 - NetSarang Website 三.连接服务器 在xshell新建会话&#xff08;需要用到服务器、用户名、密码、端口号&#xff09;正确输入后连接到服务器 使用命令连接&#x…

硬件岗位是否适合你?

在当今科技飞速发展的时代,硬件行业作为技术创新的基石,始终扮演着至关重要的角色。无论是智能手机、自动驾驶汽车,还是人工智能服务器,硬件都是这些技术的核心支撑。然而,硬件岗位是否适合你?作为一名硬件专家,我将从多个角度为你分析,帮助你判断自己是否适合从事硬件…

Linux基本指令(二)

文章目录 基本指令echocat&#xff08;输入重定向&#xff09;history日志moretail和headmv&#xff08;重要&#xff09;时间相关的指令查找的命令 知识点Linux下一切皆文件为什么计算机关机了&#xff0c;开机后还能准确地记录时间呢&#xff1f; 基本指令 echo 1. echo&…

【Blender】二、建模篇--05,阵列修改器与晶格形变

阵列修改器是bender里面一个比较常用的修改器,所以我们单独开口来讲,我们会先从几片树叶出发,然后我们用阵列修改器把这几片树叶变成这样的造型和这样的造型。这两个造型分别就代表着阵列修改器最常用的两种偏移方法,我们现在就开始我们先来做几个树叶。 1.树叶建模 首先…

fpga助教面试题

第一题 module sfp_pwm( input wire clk, //clk is 200M input wire rst_n, input wire clk_10M_i, input wire PPS_i, output reg pwm ) reg [6:0] cunt ;always (posedge clk ) beginif(!rst_n)cunt<0;else if(cunt19) //200M是10M的20倍cunt<0;elsecunt<cunt1;…

SpringAI系列 - ToolCalling篇(二) - 如何设置应用侧工具参数ToolContext(有坑)

目录 一、引言二、集成ToolContext示例步骤1: 在`@Tool`标注的工具方法中集成`ToolConext`参数步骤2:`ChatClient`运行时动态设置`ToolContext`参数三、填坑一、引言 在使用AI大模型的工具调用机制时,工具参数都是由大模型解析用户输入上下文获取的,由大模型提供参数给本地…

Jest单元测试

由于格式和图片解析问题&#xff0c;可前往 阅读原文 前端自动化测试在提高代码质量、减少错误、提高团队协作和加速交付流程方面发挥着重要作用。它是现代软件开发中不可或缺的一部分&#xff0c;可以帮助开发团队构建可靠、高质量的应用程序 单元测试&#xff08;Unit Testi…