zoukankan      html  css  js  c++  java
  • 9、Qt 事件处理机制

    原文地址:http://mobile.51cto.com/symbian-272812.htm

    在Qt中,事件被封装成一个个对象,所有的事件均继承自抽象类QEvent. 接下来依次谈谈Qt中有谁来产生、分发、接受和处理事件。

    本篇来介绍Qt 事件处理机制。深入了解事件处理系统对于每个学习Qt人来说非常重要,可以说,Qt是以事件驱动的UI工具集。 大家熟知Signals/Slots在多线程的实现也依赖于Qt事件处理机制

    Qt中,事件被封装成一个个对象,所有的事件均继承自抽象类QEvent.  接下来依次谈谈Qt中有谁来产生、分发、接受和处理事件

    1、谁来产生事件: 最容易想到的是我们的输入设备,比如键盘、鼠标产生的

    keyPressEvent,keyReleaseEvent,mousePressEvent,mouseReleaseEvent事件(他们被封装成QMouseEvent和QKeyEvent),这些事件来自于底层的操作系统,它们以异步的形式通知Qt事件处理系统,后文会仔细道来。当然Qt自己也会产生很多事件,比如QObject::startTimer()会触发QTimerEvent. 
    用户的程序可还以自己定制事件

    2、谁来接受和处理事件:答案是QObject。在Qt的内省机制剖析一文已经介绍QObject 
    类是整个Qt对象模型的心脏,事件处理机制是QObject三大职责(内存管理、内省(intropection)与事件处理制)之一。任何一个想要接受并处理事件的对象均须继承自QObject,可以选择重载QObject::event()函数或事件的处理权转给父类。

    3、谁来负责分发事件:对于non-GUI的Qt程序,是由QCoreApplication负责将QEvent分发给QObject的子类-Receiver. 
    对于Qt GUI程序,由QApplication来负责

    接下来,将通过对代码的解析来看看QT是利用event loop从事件队列中获取用户输入事件,又是如何将事件转义成QEvents,并分发给相应的QObject处理。

    section1

    复制代码
    1 #include <QApplication>     
    2 #include "widget.h"    
    3 int main(int argc, char *argv[])     
    4 {         
    5         QApplication app(argc, argv); 
    6         Widget window;  // Widget 继承自QWidget
    7         window.show();
    8         return app.exec(); // 进入Qpplication事件循环,见section 2
    9 } 
    复制代码

    section2

    复制代码
    1 int QApplication::exec()
    2 {
    3 #ifndef QT_NO_ACCESSIBILITY
    4     QAccessible::setRootObject(qApp);
    5 #endif    //简单的交给QCoreApplication来处理事件循环=〉section 3
    6     return QCoreApplication::exec();
    7 }
    复制代码

    section3

    复制代码
     1 int QCoreApplication::exec()
     2 {
     3     if (!QCoreApplicationPrivate::checkInstance("exec"))
     4         return -1;
     5     //得到当前Thread数据  
     6     QThreadData *threadData = self->d_func()->threadData;
     7     if (threadData != QThreadData::current()) {
     8         qWarning("%s::exec: Must be called from the main thread", self->metaObject()->className());
     9         return -1;
    10     }
    //检查event loop是否已经创建  11 if (!threadData->eventLoops.isEmpty()) { 12 qWarning("QCoreApplication::exec: The event loop is already running"); 13 return -1; 14 } 15 16 threadData->quitNow = false; 17 QEventLoop eventLoop; 18 self->d_func()->in_exec = true; 19 self->d_func()->aboutToQuitEmitted = false;
    //委任QEventLoop 处理事件队列循环 ==> Section 4 20 int returnCode = eventLoop.exec(); 21 threadData->quitNow = false; 22 if (self) { 23 self->d_func()->in_exec = false; 24 if (!self->d_func()->aboutToQuitEmitted) 25 emit self->aboutToQuit(); 26 self->d_func()->aboutToQuitEmitted = true; 27 sendPostedEvents(0, QEvent::DeferredDelete); 28 } 29 30 return returnCode; 31 }
    复制代码


    section4

    复制代码
     1 int QEventLoop::exec(ProcessEventsFlags flags)
     2 {
     3     Q_D(QEventLoop);  //访问QEventloop私有类实例d
     4     //we need to protect from race condition with QThread::exit
     5     QMutexLocker locker(&static_cast<QThreadPrivate *>(QObjectPrivate::get(d->threadData->thread))->mutex);
     6     if (d->threadData->quitNow)
     7         return -1;
     8 
     9     if (d->inExec) {
    10         qWarning("QEventLoop::exec: instance %p has already called exec()", this);
    11         return -1;
    12     }
    13     d->inExec = true;
    14     d->exit = false;
    15     ++d->threadData->loopLevel;
    16     d->threadData->eventLoops.push(this);
    17     locker.unlock();
    18 
    19     // remove posted quit events when entering a new event loop
    20     QCoreApplication *app = QCoreApplication::instance();
    21     if (app && app->thread() == thread())
    22         QCoreApplication::removePostedEvents(app, QEvent::Quit);
    23     //这里的实现代码不少,最为重要的是以下几行 
    24 #if defined(QT_NO_EXCEPTIONS)
    25     while (!d->exit)
    26         processEvents(flags | WaitForMoreEvents | EventLoopExec);
    27 #else
    28     try {
    29         while (!d->exit)  //只要没有遇见exit,循环派发事件 
    30             processEvents(flags | WaitForMoreEvents | EventLoopExec);
    31     } catch (...) {
    32         qWarning("Qt has caught an exception thrown from an event handler. Throwing
    "
    33                  "exceptions from an event handler is not supported in Qt. You must
    "
    34                  "reimplement QApplication::notify() and catch all exceptions there.
    ");
    35 
    36         // copied from below
    37         locker.relock();
    38         QEventLoop *eventLoop = d->threadData->eventLoops.pop();
    39         Q_ASSERT_X(eventLoop == this, "QEventLoop::exec()", "internal error");
    40         Q_UNUSED(eventLoop); // --release warning
    41         d->inExec = false;
    42         --d->threadData->loopLevel;
    43 
    44         throw;
    45     }
    46 #endif
    47 
    48     // copied above
    49     locker.relock();
    50     QEventLoop *eventLoop = d->threadData->eventLoops.pop();
    51     Q_ASSERT_X(eventLoop == this, "QEventLoop::exec()", "internal error");
    52     Q_UNUSED(eventLoop); // --release warning
    53     d->inExec = false;
    54     --d->threadData->loopLevel;
    55 
    56     return d->returnCode;
    57 }
    复制代码

    section5

    复制代码
    1 bool QEventLoop::processEvents(ProcessEventsFlags flags)
    2 {
    3     Q_D(QEventLoop);
    4     if (!d->threadData->eventDispatcher)
    5         return false;
    6     if (flags & DeferredDeletion)
    7         QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
    8     return d->threadData->eventDispatcher->processEvents(flags);  //将事件派发给与平台相关的QAbstractEventDispatcher子类 =>Section 6
    9 }
    复制代码
    • // Section 6,QTDIRsrccorelibkernelqeventdispatcher_win.cpp     
    • // 这段代码是完成与windows平台相关的windows c++。 以跨平台著称的Qt同时也提供了对Symiban,Unix等平台的消息派发支持     
    • // 其事现分别封装在QEventDispatcherSymbian和QEventDispatcherUNIX     
    • // QEventDispatcherWin32派生自QAbstractEventDispatcher

     

    复制代码
      1 bool QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags flags)
      2 {
      3     Q_D(QEventDispatcherWin32);
      4 
      5     if (!d->internalHwnd)
      6         createInternalHwnd();
      7 
      8     d->interrupt = false;
      9     emit awake();
     10 
     11     bool canWait;
     12     bool retVal = false;
     13     bool seenWM_QT_SENDPOSTEDEVENTS = false;
     14     bool needWM_QT_SENDPOSTEDEVENTS = false;
     15     do {
     16         DWORD waitRet = 0;
     17         HANDLE pHandles[MAXIMUM_WAIT_OBJECTS - 1];
     18         QVarLengthArray<MSG> processedTimers;
     19         while (!d->interrupt) {
     20             DWORD nCount = d->winEventNotifierList.count();
     21             Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
     22 
     23             MSG msg;
     24             bool haveMessage;
     25 
     26             if (!(flags & QEventLoop::ExcludeUserInputEvents) && !d->queuedUserInputEvents.isEmpty()) {
     27                 // process queued user input events
     28                 haveMessage = true;
     29                 msg = d->queuedUserInputEvents.takeFirst(); //从处理用户输入队列中取出一条事件,处理队列里面的用户输入事件
     30             } else if(!(flags & QEventLoop::ExcludeSocketNotifiers) && !d->queuedSocketEvents.isEmpty()) {
     31                 // process queued socket events
     32                 haveMessage = true;
     33                 msg = d->queuedSocketEvents.takeFirst();  // 从处理socket队列中取出一条事件,处理队列里面的socket事件
     34             } else {
     35                 haveMessage = PeekMessage(&msg, 0, 0, 0, PM_REMOVE);
     36                 if (haveMessage && (flags & QEventLoop::ExcludeUserInputEvents)
     37                     && ((msg.message >= WM_KEYFIRST
     38                          && msg.message <= WM_KEYLAST)
     39                         || (msg.message >= WM_MOUSEFIRST
     40                             && msg.message <= WM_MOUSELAST)
     41                         || msg.message == WM_MOUSEWHEEL
     42                         || msg.message == WM_MOUSEHWHEEL
     43                         || msg.message == WM_TOUCH
     44 #ifndef QT_NO_GESTURES
     45                         || msg.message == WM_GESTURE
     46                         || msg.message == WM_GESTURENOTIFY
     47 #endif
     48                         || msg.message == WM_CLOSE)) {
     49                     // queue user input events for later processing
     50                     haveMessage = false;
     51                     d->queuedUserInputEvents.append(msg);  // 用户输入事件入队列,待以后处理 
     52                 }
     53                 if (haveMessage && (flags & QEventLoop::ExcludeSocketNotifiers)
     54                     && (msg.message == WM_QT_SOCKETNOTIFIER && msg.hwnd == d->internalHwnd)) {
     55                     // queue socket events for later processing
     56                     haveMessage = false;
     57                     d->queuedSocketEvents.append(msg);     // socket 事件入队列,待以后处理   
     58                 }
     59             }
     60             if (!haveMessage) {
     61                 // no message - check for signalled objects
     62                 for (int i=0; i<(int)nCount; i++)
     63                     pHandles[i] = d->winEventNotifierList.at(i)->handle();
     64                 waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, 0, QS_ALLINPUT, MWMO_ALERTABLE);
     65                 if ((haveMessage = (waitRet == WAIT_OBJECT_0 + nCount))) {
     66                     // a new message has arrived, process it
     67                     continue;
     68                 }
     69             }
     70             if (haveMessage) {
     71 #ifdef Q_OS_WINCE
     72                 // WinCE doesn't support hooks at all, so we have to call this by hand :(
     73                 (void) qt_GetMessageHook(0, PM_REMOVE, (LPARAM) &msg);
     74 #endif
     75 
     76                 if (d->internalHwnd == msg.hwnd && msg.message == WM_QT_SENDPOSTEDEVENTS) {
     77                     if (seenWM_QT_SENDPOSTEDEVENTS) {
     78                         // when calling processEvents() "manually", we only want to send posted
     79                         // events once
     80                         needWM_QT_SENDPOSTEDEVENTS = true;
     81                         continue;
     82                     }
     83                     seenWM_QT_SENDPOSTEDEVENTS = true;
     84                 } else if (msg.message == WM_TIMER) {
     85                     // avoid live-lock by keeping track of the timers we've already sent
     86                     bool found = false;
     87                     for (int i = 0; !found && i < processedTimers.count(); ++i) {
     88                         const MSG processed = processedTimers.constData()[i];
     89                         found = (processed.wParam == msg.wParam && processed.hwnd == msg.hwnd && processed.lParam == msg.lParam);
     90                     }
     91                     if (found)
     92                         continue;
     93                     processedTimers.append(msg);
     94                 } else if (msg.message == WM_QUIT) {
     95                     if (QCoreApplication::instance())
     96                         QCoreApplication::instance()->quit();
     97                     return false;
     98                 }
     99 
    100                 if (!filterEvent(&msg)) {
    101                     TranslateMessage(&msg);   //将事件打包成message调用Windows API派发出去
    102                     DispatchMessage(&msg);    //分发一个消息给窗口程序。消息被分发到回调函数,将消息传递给windows系统,windows处理完毕,会调用回调函数 => section 7 
    103                 }
    104             } else if (waitRet < WAIT_OBJECT_0 + nCount) {
    105                 d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
    106             } else {
    107                 // nothing todo so break
    108                 break;
    109             }
    110             retVal = true;
    111         }
    112 
    113         // still nothing - wait for message or signalled objects
    114         canWait = (!retVal
    115                    && !d->interrupt
    116                    && (flags & QEventLoop::WaitForMoreEvents));
    117         if (canWait) {
    118             DWORD nCount = d->winEventNotifierList.count();
    119             Q_ASSERT(nCount < MAXIMUM_WAIT_OBJECTS - 1);
    120             for (int i=0; i<(int)nCount; i++)
    121                 pHandles[i] = d->winEventNotifierList.at(i)->handle();
    122 
    123             emit aboutToBlock();
    124             waitRet = MsgWaitForMultipleObjectsEx(nCount, pHandles, INFINITE, QS_ALLINPUT, MWMO_ALERTABLE | MWMO_INPUTAVAILABLE);
    125             emit awake();
    126             if (waitRet < WAIT_OBJECT_0 + nCount) {
    127                 d->activateEventNotifier(d->winEventNotifierList.at(waitRet - WAIT_OBJECT_0));
    128                 retVal = true;
    129             }
    130         }
    131     } while (canWait);
    132 
    133     if (!seenWM_QT_SENDPOSTEDEVENTS && (flags & QEventLoop::EventLoopExec) == 0) {
    134         // when called "manually", always send posted events
    135         QCoreApplicationPrivate::sendPostedEvents(0, 0, d->threadData);
    136     }
    137 
    138     if (needWM_QT_SENDPOSTEDEVENTS)
    139         PostMessage(d->internalHwnd, WM_QT_SENDPOSTEDEVENTS, 0, 0);
    140 
    141     return retVal;
    142 }
    复制代码

    // Section 7 windows窗口回调函数 定义在QTDIRsrcguikernelqapplication_win.cpp 

    复制代码
    1 extern "C" LRESULT QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)     
    2 {
    3         ...
    4         //将消息重新封装成QEvent的子类QMouseEvent ==> Section 8
    5          result = widget->translateMouseEvent(msg);
    6          ...     
    7 }
    复制代码

    从Section 1~Section7, Qt进入QApplication的event loop,经过层层委任,最终QEventloop的processEvent将通过与平台相关的QAbstractEventDispatcher的子类QEventDispatcherWin32获得用户的用户输入事件,并将其打包成message后,通过标准Windows API ,把消息传递给了Windows OS,Windows OS得到通知后回调QtWndProc,  至此事件的分发与处理完成了一半的路程。

    在下文中,我们将进一步讨论当我们收到来在Windows的回调后,事件又是怎么一步步打包成QEvent并通过QApplication分发给最终事件的接受和处理者QObject::event

    事件的产生、分发、接受和处理,并以视窗系统鼠标点击QWidget为例,对代码进行了剖析,向大家分析了Qt框架如何通过Event 
    Loop处理进入处理消息队列循环,如何一步一步委派给平台相关的函数获取、打包用户输入事件交给视窗系统处理,函数调用栈如下:

    1 main(int, char **)   
    2 QApplication::exec()   
    3 QCoreApplication::exec()   
    4 QEventLoop::exec(ProcessEventsFlags )   
    5 QEventLoop::processEvents(ProcessEventsFlags )
    6 QEventDispatcherWin32::processEvents(QEventLoop::ProcessEventsFlags)

    本文将介绍Qt app在视窗系统回调后,事件又是怎么一步步通过QApplication分发给最终事件的接受和处理者QWidget::event, (QWidget继承Object,重载其虚函数event),以下所有的讨论都将嵌入在源码之中。

    复制代码
    1 QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) 
    2 bool QETWidget::translateMouseEvent(const MSG &msg)   
    3 bool QApplicationPrivate::sendMouseEvent(...)   
    4 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)   
    5 bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)   
    6 bool QApplication::notify(QObject *receiver, QEvent *e)   
    7 bool QApplicationPrivate::notify_helper(QObject *receiver, QEvent * e)   
    8 bool QWidget::event(QEvent *event) 
    复制代码

    section7 == section2-1

    复制代码
     1 QT_WIN_CALLBACK QtWndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)      
     2 {
     3     ...
     4     //检查message是否属于Qt可转义的鼠标事件
     5     if (qt_is_translatable_mouse_event(message)) {
     6         if (QApplication::activePopupWidget() != 0) { // in popup mode
     7             POINT curPos = msg.pt;
     8             //取得鼠标点击坐标所在的QWidget指针,它指向我们在main创建的widget实例
     9             QWidget* w = QApplication::widgetAt(curPos.x, curPos.y);
    10             if (w)
    11                 widget = (QETWidget*)w;
    12         }
    13 
    14         if (!qt_tabletChokeMouse) {
    15             //对,就在这里。Windows的回调函数将鼠标事件分发回给了Qt Widget
    16             // => Section 2-2
    17             result = widget->translateMouseEvent(msg);        // mouse event
    18         ...
    19 }
    复制代码
    • // Section 2-2  $QTDIRsrcguikernelqapplication_win.cpp     
    • //该函数所在与Windows平台相关,主要职责就是把已windows格式打包的鼠标事件解包、翻译成QApplication可识别的QMouseEvent,QWidget.
    复制代码
    1 bool QETWidget::translateMouseEvent(const MSG &msg)     
    2 {
    3           //.. 这里很长的代码给以忽略    
    4           // 让我们看一下sendMouseEvent的声明
    5           // widget是事件的接受者; e是封装好的QMouseEvent
    6           // ==> Section 2-3  
    7            res = QApplicationPrivate::sendMouseEvent(target, &e, alienWidget, this, &qt_button_down,  qt_last_mouse_receiver);
    8 }
    复制代码

    // Section 2-3 $QTDIRsrcguikernelqapplication.cpp  

    复制代码
     1 bool QApplicationPrivate::sendMouseEvent(QWidget *receiver, QMouseEvent *event,
     2                                          QWidget *alienWidget, QWidget *nativeWidget,
     3                                          QWidget **buttonDown, QPointer<QWidget> &lastMouseReceiver,
     4                                          bool spontaneous)
     5 {
     6     ...
     7     //至此与平台相关代码处理完毕
     8     //MouseEvent默认的发送方式是spontaneous, 所以将执行
     9     //sendSpontaneousEvent。 sendSpontaneousEvent() 与 sendEvent的代码实现几乎相同
    10     //除了将QEvent的属性spontaneous标记不同。 这里是解释什么spontaneous事件:如果事件由应用程序之外产生的,比如一个系统事件。
    11      //显然MousePress事件是由视窗系统产生的一个的事件(详见上文Section 1~ Section 7),因此它是   spontaneous事件 
    12     if (spontaneous)
    13         result = QApplication::sendSpontaneousEvent(receiver, event);
    14     else
    15         result = QApplication::sendEvent(receiver, event);
    16       
    17     ...
    18      
    19      return result;
    20 }
    复制代码

    // Section 2-4 C:Qt4.7.1-Vssrccorelibkernelqcoreapplication.h

    复制代码
    1 inline bool QCoreApplication::sendSpontaneousEvent(QObject *receiver, QEvent *event)
    2 { 
    3       //将event标记为自发事件
    4      //进一步调用 2-5 QCoreApplication::notifyInternal     
    5       if (event) 
    6           event->spont = true; 
    7       return self ? self->notifyInternal(receiver, event) : false; 
    8 }
    复制代码

    // Section 2-5:  $QTDIRguikernelqapplication.cpp     

    复制代码
     1 bool QCoreApplication::notifyInternal(QObject *receiver, QEvent *event)
     2 {
     3     // 几行代码对于Qt Jambi (QT Java绑定版本) 和QSA (QT Script for Application)的支持
     4     
     5     ...
     6     
     7     // 以下代码主要意图为Qt强制事件只能够发送给当前线程里的对象,也就是说receiver->d_func()->threadData应该等于QThreadData::current()。
     8     //注意,跨线程的事件需要借助Event Loop来派发
     9     QObjectPrivate *d = receiver->d_func();
    10     QThreadData *threadData = d->threadData;
    11     ++threadData->loopLevel;
    12 
    13     //哇,终于来到大名鼎鼎的函数QCoreApplication::nofity()了 ==> Section 2-6 
    14     QT_TRY {
    15         returnValue = notify(receiver, event);
    16     } QT_CATCH (...) {
    17         --threadData->loopLevel;
    18         QT_RETHROW;
    19     }
    20 
    21     ...
    22 
    23     return returnValue;
    24 }
    复制代码
    • // Section 2-6:  $QTDIRguikernelqapplication.cpp     
    • // QCoreApplication::notify和它的重载函数QApplication::notify在Qt的派发过程中起到核心的作用,Qt的官方文档时这样说的:
    • //任何线程的任何对象的所有事件在发送时都会调用notify函数。
    复制代码
     1 bool QCoreApplication::notify(QObject *receiver, QEvent *event)
     2 {
     3     Q_D(QCoreApplication);
     4     // no events are delivered after ~QCoreApplication() has started
     5     if (QCoreApplicationPrivate::is_app_closing)
     6         return true;
     7 
     8     if (receiver == 0) {                        // serious error
     9         qWarning("QCoreApplication::notify: Unexpected null receiver");
    10         return true;
    11     }
    12 
    13 #ifndef QT_NO_DEBUG
    14     d->checkReceiverThread(receiver);
    15 #endif
    16 
    17     return receiver->isWidgetType() ? false : d->notify_helper(receiver, event);
    18 }
    复制代码

    notify 调用 notify_helper()
    // Section 2-7:  $QTDIRguikernelqapplication.cpp     

    复制代码
     1 bool QCoreApplicationPrivate::notify_helper(QObject *receiver, QEvent * event)
     2 {
     3     // send to all application event filters
     4     if (sendThroughApplicationEventFilters(receiver, event))
     5         return true;
     6      // 向事件过滤器发送该事件,这里介绍一下Event Filters. 事件过滤器是一个接受即将发送给目标对象所有事件的对象。 
     7     //如代码所示它开始处理事件在目标对象行动之前。过滤器的QObject::eventFilter()实现被调用,能接受或者丢弃过滤
     8     //允许或者拒绝事件的更进一步的处理。如果所有的事件过滤器允许更进一步的事件处理,事件将被发送到目标对象本身。
     9     //如果他们中的一个停止处理,目标和任何后来的事件过滤器不能看到任何事件。
    10     if (sendThroughObjectEventFilters(receiver, event))
    11         return true;
    12     // deliver the event
    13     // 递交事件给receiver  => Section 2-8 
    14     return receiver->event(event);
    15 }
    复制代码

    // Section 2-8  $QTDIRguikernelqwidget.cpp  

    // QApplication通过notify及其私有类notify_helper,将事件最终派发给了QObject的子类- QWidget.

    复制代码
     1 bool QWidget::event(QEvent *event)
     2 {
     3     ...
     4 
     5     switch (event->type()) {
     6     case QEvent::MouseMove:
     7         mouseMoveEvent((QMouseEvent*)event);
     8         break;
     9 
    10     case QEvent::MouseButtonPress:
    11         // Don't reset input context here. Whether reset or not is
    12         // a responsibility of input method. reset() will be
    13         // called by mouseHandler() of input method if necessary
    14         // via mousePressEvent() of text widgets.
    15 #if 0
    16         resetInputContext();
    17 #endif
    18         mousePressEvent((QMouseEvent*)event);
    19         break;
    20 
    21         ...
    22 
    23 }
    复制代码

    转自:http://www.cnblogs.com/lfsblack/p/5338668.html

  • 相关阅读:
    利用dockerfile定制镜像
    发布Docker 镜像到dockerhub
    Docker 停止容器
    133. Clone Graph
    132. Palindrome Partitioning II
    131. Palindrome Partitioning
    130. Surrounded Regions
    129. Sum Root to Leaf Numbers
    128. Longest Consecutive Sequence
    127. Word Ladder
  • 原文地址:https://www.cnblogs.com/liushui-sky/p/6474122.html
Copyright © 2011-2022 走看看