消息处理过程

消息如何到达wxWidgets

Windows程序有其自身运行的一套规律,::SendMessage是MS提供的windows消息发送接口,用户调用这个接口后会进入到MS系统库程序,此接口指定了目标HWND和消息参数,Windows系统内部会查找指定HWND,然后通过gapfnScSendMessage接口调用用户的消息处理函数。

所以我们每次看到消息处理函数都是通过gapfnScSendMessage调用进入的。

Win32消息与wxWidgets消息的转换

wxWidgets注册窗口时同时指定了窗口处理函数wxWndProc,当收到消息后系统会调用此函数来处理消息。

处理过程如下:

  1. 调用wxFindWinFromHandle根据当前消息指定的HWND来查找对应的wxWindow,如果没有则需要与最近创建的一个窗口关联起来。
  2. 接着调用窗口的MSWWindowProc方法来进行消息处理。
  1. // Main window proc
  2. LRESULT WXDLLEXPORT APIENTRY _EXPORT wxWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  3. {
  4. wxWindowMSW *wnd = wxFindWinFromHandle(hWnd);
  5. // 关联窗口
  6. if ( !wnd && gs_winBeingCreated )
  7. {
  8. wxAssociateWinWithHandle(hWnd, gs_winBeingCreated);
  9. wnd = gs_winBeingCreated;
  10. gs_winBeingCreated = NULL;
  11. wnd->SetHWND((WXHWND)hWnd);
  12. }
  13. LRESULT rc;
  14. if ( wnd && wxGUIEventLoop::AllowProcessing(wnd) )
  15. rc = wnd->MSWWindowProc(message, wParam, lParam);
  16. else
  17. rc = ::DefWindowProc(hWnd, message, wParam, lParam);
  18. return rc;
  19. }

MSWWindowProc是在windows平台下特有的虚函数,对于Frame类来说就是wxFrame::MSWWindowProc,消息根据Message类型来执行不同的函数:

  1. WM_CLOSE: 退出操作,直接调用wxFrame::Close;
  2. WM_SIZE: 调用wxFrame::HandleSize;
  3. WM_COMMAND: 需要特殊处理,注释中写的很清楚,wxWidgets提供了一套自己的机制来进行父窗口和子窗口之间的消息调用,所以就不要再使用Win32提供的功能了。
  4. 如果自己没有处理,则给父类wxFrameBase::MSWWindowProc处理;
  1. WXLRESULT wxFrame::MSWWindowProc(WXUINT message, WXWPARAM wParam, WXLPARAM lParam)
  2. {
  3. WXLRESULT rc = 0;
  4. bool processed = false;
  5. switch ( message )
  6. {
  7. case WM_CLOSE:
  8. processed = !Close();
  9. break;
  10. case WM_SIZE:
  11. processed = HandleSize(LOWORD(lParam), HIWORD(lParam), wParam);
  12. break;
  13. case WM_COMMAND:
  14. {
  15. WORD id, cmd;
  16. WXHWND hwnd;
  17. UnpackCommand((WXWPARAM)wParam, (WXLPARAM)lParam,
  18. &id, &hwnd, &cmd);
  19. HandleCommand(id, cmd, (WXHWND)hwnd);
  20. processed = true;
  21. }
  22. break;
  23. }
  24. if ( !processed )
  25. rc = wxFrameBase::MSWWindowProc(message, wParam, lParam);
  26. return rc;
  27. }

wxFrame::Close函数举例,wxFrame直接使用父类的方法wxWindowBase::Close,内容就是构造wxWidgets的消息类型,然后调用HandleWindowEvent方法进行消息处理。

  1. bool wxWindowBase::Close(bool force)
  2. {
  3. wxCloseEvent event(wxEVT_CLOSE_WINDOW, m_windowId);
  4. event.SetEventObject(this);
  5. event.SetCanVeto(!force);
  6. // return false if window wasn't closed because the application vetoed the
  7. // close event
  8. return HandleWindowEvent(event) && !event.GetVeto();
  9. }

对于WM_SIZE,走的路要多一些,调用关系如下,最终由wxWindowMSW::HandleSize处理,这里会产生wxSizeEvent消息,随后将消息递交给HandleWindowEvent处理:

  1. wxFrame::MSWWindowProc()
  2. -> wxTopLevelWindowMSW::MSWWindowProc()
  3. -> wxWindowMSW::MSWWindowProc()
  4. -> wxWindowMSW::MSWHandleMessage()
  5. -> wxWindowMSW::HandleSize()
  6. bool wxWindowMSW::HandleSize(int WXUNUSED(w), int WXUNUSED(h), WXUINT wParam)
  7. {
  8. switch ( wParam )
  9. {
  10. case SIZE_RESTORED:
  11. wxSizeEvent event(GetSize(), m_windowId);
  12. event.SetEventObject(this);
  13. processed = HandleWindowEvent(event);
  14. }
  15. }

也就是不管是哪个消息,最终还是转换称wxEvent消息,然后调用当前窗口的HandleWindowEvent函数处理,要注意的是此时消息已经是wxWidgets内部的消息类型了。

菜单消息处理

接下来我们验证菜单消息在wxWidgets中的处理,首先在wxWidgets工程中增加静态消息映射表,并实现相应的代码:

  1. const long ID_MenuUser = wxNewId();
  2. BEGIN_EVENT_TABLE(debugWXFrame,wxFrame)
  3. EVT_MENU(ID_MenuUser, debugWXFrame::OnCheckMenu)
  4. EVT_UPDATE_UI(ID_MenuUser, debugWXFrame::OnCheckMenuUI)
  5. END_EVENT_TABLE()
  6. void debugWXFrame::OnCheckMenu(wxCommandEvent& event) {}
  7. void debugWXFrame::OnCheckMenuUI(wxUpdateUIEvent& event) {}

我们接着看下Command消息的处理,这个用的非常多,收到Command类型的消息后调用HandleCommand处理,Frame类只处理工具栏、菜单和加速键命令,实现过程:

  1. 调用FindItemInMenuBar根据消息中指定的ID来查找对应的wxMenuItem,这个函数的实现就是获取到当前wxFrame的MenuBar,然后循环查询,菜单越多查询的速度也就越慢;
  2. 找到MenuItem后则调用wxFrameBase::ProcessCommand(mitem)继续处理:
  1. bool wxFrame::HandleCommand(WXWORD id, WXWORD cmd, WXHWND control)
  2. {
  3. #if wxUSE_MENUS
  4. #if defined(WINCE_WITHOUT_COMMANDBAR)
  5. if (GetToolBar() && GetToolBar()->FindById(id))
  6. return GetToolBar()->MSWCommand(cmd, id);
  7. #endif
  8. // we only need to handle the menu and accelerator commands from the items
  9. // of our menu bar, base wxWindow class already handles the rest
  10. if ( !control && (cmd == 0 /* menu */ || cmd == 1 /* accel */) )
  11. {
  12. #if wxUSE_MENUS_NATIVE
  13. if ( !wxCurrentPopupMenu )
  14. #endif // wxUSE_MENUS_NATIVE
  15. {
  16. wxMenuItem * const mitem = FindItemInMenuBar((signed short)id);
  17. if ( mitem )
  18. return ProcessCommand(mitem);
  19. }
  20. }
  21. #endif // wxUSE_MENUS
  22. return wxFrameBase::HandleCommand(id, cmd, control);;
  23. }

wxFrame类本身没有实现ProcessCommand,所以将调用父类的方法wxFrameBase::ProcessCommand,关键流程代码部分调用wxMenu的SendEvent函数继续处理。

重点关注,这里调用的是menu->SendEvent,所以接下来的调用切换到wxMenu类中进行。

  1. bool wxFrameBase::ProcessCommand(wxMenuItem *item)
  2. {
  3. ...
  4. wxMenu* const menu = item->GetMenu();
  5. return menu->SendEvent(item->GetId(), checked);
  6. }

wxMenu的SendEvent实现是wxMenuBase::SendEvent方法,此时我们位于wxMenu对象中,所以调用GetEventHandler()获得的是wxMenu的EvntHandler。

重点关注win和mb两个变量,wxMenu首先使用自己的wxEvtHandler进行处理,然后检查它是否关联到了win或者menubar,如果有则它还增加了一个标记event.SetWillBeProcessedAgain(),也就是命令需要被wen或者menubar处理。

win和mb两个变量代表不同的菜单类型,mb是菜单条中的菜单,win是上下文菜单。

这里我们调用的是mb->HandleWindowEvent(event)

  1. bool wxMenuBase::SendEvent(int itemid, int checked)
  2. {
  3. wxCommandEvent event(wxEVT_MENU, itemid);
  4. event.SetEventObject(this);
  5. event.SetInt(checked);
  6. wxWindow* const win = GetWindow();
  7. wxMenuBar* const mb = GetMenuBar();
  8. wxEvtHandler *handler = GetEventHandler();
  9. if ( handler )
  10. {
  11. if ( win || mb )
  12. event.SetWillBeProcessedAgain();
  13. // 没有想到调用这个函数的场景?
  14. if ( handler->SafelyProcessEvent(event) )
  15. return true;
  16. }
  17. // If this menu is part of the menu bar, process the event there: this will
  18. // also propagate it upwards to the window containing the menu bar.
  19. if ( mb )
  20. return mb->HandleWindowEvent(event);
  21. // Try the window the menu was popped up from.
  22. if ( win )
  23. return win->HandleWindowEvent(event);
  24. // Not processed.
  25. return false;
  26. }

至此,我们将切换到wxMenuBar::HandleWindowEvent,所有者为wxMenuBarwxMenuBar继承自wxWindow类,它也是一个独立的窗口,所以这次调用的函数是wxWindowBase::HandleWindowEvent,调用过程如下:

  1. GetEventHandler()方法返回的就是自身,wxFrame本身就是继承自wxEvtHandler
  2. ProcessEvent方法是由父类wxEvtHandler提供的;
  1. bool wxWindowBase::HandleWindowEvent(wxEvent& event) const
  2. {
  3. return GetEventHandler()->SafelyProcessEvent(event);
  4. }
  5. bool wxEvtHandler::SafelyProcessEvent(wxEvent& event)
  6. {
  7. return ProcessEvent(event);
  8. }

接着调用ProcessEvent,这个函数是通用的,只要是继承自wxEvtHandler都会调用到这里,下面我们分两种情况来说明情况:

通用的处理:

  1. 处理全局过滤器,wxEvtHandler内部创建了静态变量ms_filterList,用于保存wxEventFilter列表,用户可以通过调用静态函数wxEvtHandler::AddFilter来向系统中增加过滤器,具体可参考过滤器使用章节;
  2. 调用TryBeforeAndHere仅对本对象处理,调用此函数需要依赖一个标记ShouldProcessOnlyIn,这个标记仅仅在DoTryChain中会被设置,也就是只有进入了DoTryChain函数才会有此标记;
  3. 调用ProcessEventLocally执行本对象处理;
  4. 调用TryAfter执行parent的处理;

第一种情况: 位于wxMenuBar中的处理:

此时我们位于wxMenuBar中,此类继承自wxEvtHandler,所以这里调用的实际是wxEvtHandler::ProcessEvent,处理过程:

  1. 此时是不会有ShouldProcessOnlyIn标记,所以不会执行TryBeforeAndHere
  2. 进入到ProcessEventLocally;由于wxMenuBar对象中并没有绑定此菜单的处理函数,所以ProcessEventLocally是不会处理的;
  3. 进入到TryAfter执行parent的处理;

第二种情况: 位于wxFrame中的处理:

对于wxFrame的ProcessEvent流程也有同样的效果,只不过会在ProcessEventLocally中处理。

  1. bool wxEvtHandler::ProcessEvent(wxEvent& event)
  2. {
  3. // 处理过滤器
  4. if ( !event.WasProcessed() )
  5. {
  6. for ( wxEventFilter* f = ms_filterList; f; f = f->m_next )
  7. {
  8. int rc = f->FilterEvent(event);
  9. if ( rc != wxEventFilter::Event_Skip )
  10. {
  11. return rc != wxEventFilter::Event_Ignore;
  12. }
  13. }
  14. }
  15. // 只有执行了 DoTryChain() 之后,ShouldProcessOnlyIn()方法才会返回true
  16. // 具体可以参考 wxEventProcessInHandlerOnly 辅助类
  17. if ( event.ShouldProcessOnlyIn(this) )
  18. return TryBeforeAndHere(event);
  19. // Try to process the event in this handler itself.
  20. if ( ProcessEventLocally(event) )
  21. {
  22. return !event.GetSkipped();
  23. }
  24. if ( TryAfter(event) )
  25. return true;
  26. // No handler found anywhere, bail out.
  27. return false;
  28. }

还是分成两种情况分别说明:

第一种情况: 位于wxMenuBar中的处理:

wxEvtHandler::TryBeforeAndHere会调用TryBefore||TryHereOnlyTryBefore我们暂时忽略,重点是TryHereOnly,在TryHereOnly函数中,首先超找动态绑定表,然后查找静态绑定表,如果表中存在处理函数则调用之,否则不会调用,对于本流程来将,wxMenubar中并没有绑定任何处理函数,所以TryHereOnly返回false,进而TryBeforeAndHere函数返回false,所以需要继续调用DoTryChain

第二种情况: 位于wxFrame中的处理:

对于wxFrame来说,本例中菜单的消息处理函数绑定在静态绑定区,所以会在if ( GetEventHashTable().HandleEvent(event, this) )中处理掉,返回true。

  1. bool wxEvtHandler::ProcessEventLocally(wxEvent& event)
  2. {
  3. return TryBeforeAndHere(event) || DoTryChain(event);
  4. }
  5. bool wxEvtHandler::TryBeforeAndHere(wxEvent& event)
  6. {
  7. return TryBefore(event) || TryHereOnly(event);
  8. }
  9. bool wxEvtHandler::TryHereOnly(wxEvent& event)
  10. {
  11. // Handle per-instance dynamic event tables first
  12. if ( m_dynamicEvents && SearchDynamicEventTable(event) )
  13. return true;
  14. // Then static per-class event tables
  15. if ( GetEventHashTable().HandleEvent(event, this) )
  16. return true;
  17. return false;
  18. }

继续进入入wxMenuBarDoTryChain,这里是通过设置EventHandlerChain来达到消息传递的目的,但是在wxWidgets系统中,wxWindow继承的过程中明确不使用这种方式进行消息传递,而是通过wxWindow自身的父子关系来进行消息传递,所以对于wxMenuBar来说,这个GetNextHandler必定返回是空的,所以DoTryChain返回false,进而wxMenuBarProcessEventLocally返回false。

  1. bool wxEvtHandler::DoTryChain(wxEvent& event)
  2. {
  3. for ( wxEvtHandler *h = GetNextHandler(); h; h = h->GetNextHandler() )
  4. {
  5. wxEventProcessInHandlerOnly processInHandlerOnly(event, h);
  6. if ( h->ProcessEvent(event) )
  7. {
  8. event.Skip(false);
  9. return true;
  10. }
  11. if ( !event.ShouldProcessOnlyIn(h) )
  12. {
  13. event.Skip();
  14. return true;
  15. }
  16. }
  17. return false;
  18. }

再回到前两步,此时我们只能通过调用wxMenuBarTryAfter(event)继续消息传递,前文有描述wxMenuBar继承自wxWindows,所以这里调用的是wxWindowBase::TryAfter,在下面的调用中,窗口只要可能正常接收消息,则会向上查找parent,然后调用父类的ProcessEvent继续处理。

在本例中,wxMenuBar的parent是wxFrame,所以会继续调用wxFrameProcessEvent继续处理

  1. bool wxWindowBase::TryAfter(wxEvent& event)
  2. {
  3. if ( event.ShouldPropagate() )
  4. {
  5. if ( !(GetExtraStyle() & wxWS_EX_BLOCK_EVENTS) )
  6. {
  7. wxWindow *parent = GetParent();
  8. if ( parent && !parent->IsBeingDeleted() )
  9. {
  10. wxPropagateOnce propagateOnce(event, this);
  11. return parent->GetEventHandler()->ProcessEvent(event);
  12. }
  13. }
  14. }
  15. return wxEvtHandler::TryAfter(event);
  16. }

wxFrameProcessEvent的调用顺序与wxMenuBar的相同,只不过wxFrame会在ProcessEventLocally方法中返回true,进而导致整个处理流程完成。

消息处理链(基于wxEvtHandler)

wxWidgets提供了一种手段,用户可以将消息处理函数注入到wxEvtHandler类中,而不需要使用继承方式,实现方法就是用户自定义一个wxEventHandler类,然后调用wxEvtHandler::SetNextHandler()将消息处理代码加入到指定的wxEvtHandler对象上,使用举例:

下面的代码用于将菜单处理放到独立的wxEvtHandler类中,通过wxEvtHandler::SetNextHandler方法将此Handler对象链接到wxFrame上:

注:必须使用wxEvtHandler::SetNextHandler方法注入,不能直接调用SetNextHandler,因为wxWindow重载了这个方法,直接调用不会生效。

  1. const long ID_MenuUser = wxNewId();
  2. class CMyEvtHandler : public wxEvtHandler {
  3. public:
  4. bool ProcessEvent(wxEvent& event) {
  5. if (event.GetEventType() == wxEVT_MENU && event.GetId() == ID_MenuUser) {
  6. wxMessageBox("Menu processed in chain");
  7. return true;
  8. }
  9. event.Skip();
  10. return false;
  11. }
  12. };
  13. debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id)
  14. {
  15. ...
  16. Menu1->Append(ID_MenuUser, _("Chain Menu"));
  17. wxEvtHandler::SetNextHandler(new CMyEvtHandler);
  18. }

实际调用时,会进入到wxFram::DoTryChain函数中,由于我们向wxFrame中增加了wxEvtHandler,此时会取出关系链上的wxEvtHandler逐个调用。

注意到使用这种方式调用时会预先设定只在当前对象中处理标记,通过wxEventProcessInHandlerOnly processInHandlerOnly(event, h);实现,当processInHandlerOnly销毁后标记消失,作用范围仅仅是在这个循环体内。

  1. bool wxEvtHandler::DoTryChain(wxEvent& event)
  2. {
  3. for ( wxEvtHandler *h = GetNextHandler(); h; h = h->GetNextHandler() )
  4. {
  5. wxEventProcessInHandlerOnly processInHandlerOnly(event, h);
  6. if ( h->ProcessEvent(event) )
  7. {
  8. event.Skip(false);
  9. return true;
  10. }
  11. if ( !event.ShouldProcessOnlyIn(h) )
  12. {
  13. event.Skip();
  14. return true;
  15. }
  16. }
  17. return false;
  18. }

消息处理链(基于wxWindow)

除了将消息处理类注入到当前wxEvtHandler对象中,还有一个办法就是调用wxWindow::PushEventHandler将消息处理类注入到当前windows的栈中,两种方式有区别:

  1. 注入到wxEvtHandler,wxWidgets会优先处理当前对象的wxEvtHandler,然后检查当前对象的wxEvtHandler是否有链接其他的wxEvtHandler,如果有则调用之;
  2. 通过wxWindow::PushEventHandler注入的是改写wxWindow类的当前消息处理对象,当其查找wxWindow对象的消息处理对象时,只调用最后插入的一个,所以,为了保证正常的消息能处理,我们必须在ProcessEvent()方法中调用下一个wxEvtHandler的方法。

举例:

  1. const long ID_MenuUser_wxWinChain = wxNewId();
  2. class CMyWinEvtHandler : public wxEvtHandler {
  3. public:
  4. bool ProcessEvent(wxEvent& event) {
  5. if (event.GetEventType() == wxEVT_MENU && event.GetId() == ID_MenuUser_wxWinChain) {
  6. wxMessageBox("Menu processed in chain, id="+wxString::Format("%d", event.GetId()));
  7. return true;
  8. }
  9. if (GetNextHandler())
  10. return GetNextHandler()->ProcessEvent(event);
  11. return false;
  12. }
  13. };
  14. debugWXFrame::debugWXFrame(wxWindow* parent,wxWindowID id)
  15. {
  16. ...
  17. Menu1->Append(ID_MenuUser_wxWinChain, _("Chain Menu"));
  18. PushEventHandler(new CMyWinEvtHandler);
  19. }

拿菜单处理举例,在wxMenuBar调用wxWindowBase::TryAfter查找父类调用时,会直接调用父类的方法,对于我们这个例子来说,会直接调用CMyWinEvtHandler::ProcessEvent方法,所以我们在实现ProcessEvent必须注意,需要调用GetNextHandler()->ProcessEvent(event)以保证其他消息的正常处理。

  1. if ( !(GetExtraStyle() & wxWS_EX_BLOCK_EVENTS) )
  2. {
  3. wxWindow *parent = GetParent();
  4. if ( parent && !parent->IsBeingDeleted() )
  5. {
  6. wxPropagateOnce propagateOnce(event, this);
  7. return parent->GetEventHandler()->ProcessEvent(event);
  8. }
  9. }

注:在测试过程中发现总会有the last handler of the wxWindow stack should have this window as next handler的提示,这个是wxWidgets库本身代码的Bug,窗口链不需要双向链表,窗口本身的wxEvtHandler不需要指向任何wxEvtHandler,因为它就是最后一个。

总结

本节中主要描述了wxWidgets的消息处理过程。

wxWidgets源码分析(4) - 消息处理过程的更多相关文章

  1. SOFA 源码分析 —— 服务引用过程

    前言 在前面的 SOFA 源码分析 -- 服务发布过程 文章中,我们分析了 SOFA 的服务发布过程,一个完整的 RPC 除了发布服务,当然还需要引用服务. So,今天就一起来看看 SOFA 是如何引 ...

  2. Dubbo 源码分析 - 服务调用过程

    注: 本系列文章已捐赠给 Dubbo 社区,你也可以在 Dubbo 官方文档中阅读本系列文章. 1. 简介 在前面的文章中,我们分析了 Dubbo SPI.服务导出与引入.以及集群容错方面的代码.经过 ...

  3. MyBatis 源码分析 - 配置文件解析过程

    * 本文速览 由于本篇文章篇幅比较大,所以这里拿出一节对本文进行快速概括.本篇文章对 MyBatis 配置文件中常用配置的解析过程进行了较为详细的介绍和分析,包括但不限于settings,typeAl ...

  4. 源码分析HotSpot GC过程(一)

    «上一篇:源码分析HotSpot GC过程(一)»下一篇:源码分析HotSpot GC过程(三):TenuredGeneration的GC过程 https://blogs.msdn.microsoft ...

  5. 源码分析HotSpot GC过程(三):TenuredGeneration的GC过程

    老年代TenuredGeneration所使用的垃圾回收算法是标记-压缩-清理算法.在回收阶段,将标记对象越过堆的空闲区移动到堆的另一端,所有被移动的对象的引用也会被更新指向新的位置.看起来像是把杂陈 ...

  6. 深入理解 spring 容器,源码分析加载过程

    Spring框架提供了构建Web应用程序的全功能MVC模块,叫Spring MVC,通过Spring Core+Spring MVC即可搭建一套稳定的Java Web项目.本文通过Spring MVC ...

  7. wifidog源码分析 - 用户连接过程

    引言 之前的文章已经描述wifidog大概的一个工作流程,这里我们具体说说wifidog是怎么把一个新用户重定向到认证服务器中的,它又是怎么对一个已认证的用户实行放行操作的.我们已经知道wifidog ...

  8. SOFA 源码分析 —— 服务发布过程

    前言 SOFA 包含了 RPC 框架,底层通信框架是 bolt ,基于 Netty 4,今天将通过 SOFA-RPC 源码中的例子,看看他是如何发布一个服务的. 示例代码 下面的代码在 com.ali ...

  9. 深入源码分析SpringMVC执行过程

    本文主要讲解 SpringMVC 执行过程,并针对相关源码进行解析. 首先,让我们从 Spring MVC 的四大组件:前端控制器(DispatcherServlet).处理器映射器(HandlerM ...

随机推荐

  1. c++bind函数使用

    总述     最近写代码的时候看到代码使用了bind,一个参数绑定的标准库函数.程序是这么写的, speaker_play_routine_ = new boost::thread (boost::b ...

  2. 翻译:《实用的Python编程》00_Setup

    课程设置与概述 欢迎访问本课程(Practical Python Programming).这个页面包含一些关于课程设置的重要信息. 课程周期和时间要求 该课程最初是作为一个由讲师主导的,持续 3 - ...

  3. Codeforces Round #648 (Div. 2) A. Matrix Game

    题目链接:https://codeforces.com/contest/1365/problem/A 题意 给出一个 $n \times m$ 的网格,两人轮流选择一个所在行列没有 $1$ 的方块置为 ...

  4. python连接mysql数据库,并进行添加、查找数据

    1.删除MySQL数据表中的记录 DELETE FROM table_name WHERE condition; python操作mysql1数据库 import pymysql # 连接mysql数 ...

  5. hdu5501 The Highest Mark

    Problem Description The SDOI in 2045 is far from what it was been 30 years ago. Each competition has ...

  6. git忽略规则以及.gitignore文件不生效解决办法

    正文 Git忽略规则: #此为注释 – 内容被 Git 忽略 .sample # 忽略所有 .sample 结尾的文件 !lib.sample # 但 lib.sample 除外 /TODO # 仅仅 ...

  7. CQRS+Event Sourcing

    using System; using System.Collections.Generic; using System.Linq; namespace CQRS { public class Eve ...

  8. CF1463-D. Pairs

    CF1463-D. Pairs 题意: 有从\(1\)到\(2n\)一共\(2n\)个数字,让你将这\(2n\)个数字分成\(n\)组,每组有两个数字.对于这\(n\)组数字,你可以从中挑选\(x\) ...

  9. 对于kmp求next数组的理解

    首先附上代码 1 void GetNext(char* p,int next[]) 2 { 3 int pLen = strlen(p); 4 next[0] = -1; 5 int k = -1; ...

  10. 钓鱼教程 All In One

    钓鱼教程 All In One youtube https://www.youtube.com/results?search_query=钓鱼教程&sp=CAM%3D 钓鱼证 https:// ...