通用GUI编程技术——Win32 原生编程实战(二十三)——GDI 双缓冲技术:消除闪烁完全指南

news2026/4/8 22:53:09
通用GUI编程技术——Win32 原生编程实战二十三——GDI 双缓冲技术消除闪烁完全指南前言为什么我的界面在闪烁说实话这个闪烁问题困扰了我很久。当你刚接触 Win32 GDI 编程写出一个可以响应窗口大小变化、可以绘制一些简单图形的程序时一切看起来都很美好。直到某天你尝试在窗口中持续绘制动画或者窗口需要频繁重绘时 —— 突然间你的界面开始疯狂闪烁看起来像是快要坏掉的老式显像管显示器。这个问题不只是新手会遇到很多有经验的开发者在一开始没处理好绘制逻辑时同样会踩这个坑。闪烁问题的本质不是你代码写错了而是你还没有理解 Windows 的绘制机制是如何工作的。今天我们要深入探讨的是如何使用双缓冲技术来彻底解决这个闪烁问题。这不仅仅是一个技术技巧更是理解 Windows 图形绘制机制的必经之路。环境说明在开始之前先说明一下我的开发环境操作系统: Windows 11 Pro 10.0.26200编译器: MSVC (Visual Studio 2022)目标平台: Win32 API 原生开发图形库: GDI (Graphics Device Interface)闪烁的根源Windows 绘制机制解析要解决闪烁问题首先得搞清楚它为什么会产生。Windows 的绘制机制设计上遵循先擦除、后绘制的原则这个设计本身没错但在某些场景下会变成灾难的源头。WM_ERASEBKGND 的默认行为当 Windows 需要重绘一个窗口时它会先发送WM_ERASEBKGND消息。这个消息的目的很明确给应用程序一个机会来擦除窗口的背景。如果你没有处理这个消息DefWindowProc会使用窗口类中注册的背景刷子来填充背景区域。根据 Microsoft Learn 的官方文档WM_ERASEBKGND的返回值含义如下返回 TRUE非零: 表示应用程序已经擦除了背景系统不需要再做任何操作返回 FALSE零: 表示窗口仍然被标记为需要擦除这里有个关键点如果你在WM_ERASEBKGND中返回 TRUE那么在后续处理WM_PAINT消息时PAINTSTRUCT结构的fErase成员会是 FALSE表示系统已经完成了背景擦除。BeginPaint 返回的背景刷子当你调用BeginPaint函数时事情会变得更加有趣。根据 Microsoft Learn 的文档如果窗口类有一个背景刷子BeginPaint会自动使用这个刷子来擦除更新区域的背景。这意味着即使你没有处理WM_ERASEBKGNDBeginPaint也会帮你完成背景擦除。具体来说如果你在注册窗口类时设置了hbrBackground成员比如设置为(HBRUSH)(COLOR_WINDOW1)那么BeginPaint会用这个刷子填充背景。两次绘制造成闪烁现在你应该能看出问题所在了。每次重绘时实际上发生了两次绘制操作第一次: 系统用背景刷子擦除背景通常是白色或系统颜色第二次: 你的WM_PAINT处理函数绘制实际内容如果这两次操作之间有一定的时间间隔或者绘制过程需要较长时间人眼就能看到这个擦除-重绘的过程表现为恼人的闪烁。更糟糕的是如果你的绘制内容很复杂或者窗口大小变化频繁这个闪烁会变得更加明显。这就是为什么直接在屏幕 DC 上绘制复杂图形时闪烁问题会更加严重。消除闪烁的基本方法在深入双缓冲之前我们先来看看一些简单但有效的方法来减轻闪烁问题。这些方法不需要实现完整的双缓冲但在很多场景下已经足够了。处理 WM_ERASEBKGND 返回 TRUE最简单的方法就是阻止默认的背景擦除行为。你可以在窗口过程中这样处理caseWM_ERASEBKGND:return1;// 告诉系统我们已经处理了背景擦除这样做的好处是你的WM_PAINT处理函数会完全控制绘制过程不会有先擦除背景的步骤。但前提是你必须在绘制之前自己填充背景否则你可能会看到之前绘制的内容残留。⚠️ 注意如果你返回 TRUE 表示已经处理了背景擦除但实际上没有擦除背景你可能会看到视觉伪影。所以要确保在绘制之前正确填充背景。使用 NULL 类背景刷另一个方法是在注册窗口类时不设置背景刷子WNDCLASS wc{0};wc.lpfnWndProcWindowProc;wc.hbrBackgroundNULL;// 不设置背景刷子// ... 其他成员RegisterClass(wc);当hbrBackground为 NULL 时BeginPaint不会自动擦除背景WM_ERASEBKGND也不会被发送。这把所有绘制控制权都交给了你的WM_PAINT处理函数。InvalidateRect 的 bErase 参数InvalidateRect函数的第三个参数bErase控制是否在重绘时擦除背景InvalidateRect(hwnd,NULL,FALSE);// bErase FALSE当你传递 FALSE 时系统不会在发送WM_PAINT之前擦除背景。这在需要频繁更新窗口内容时很有用因为你可以在上一帧的内容基础上绘制新的内容。InvalidateRect和InvalidateRgn的主要区别在于前者处理矩形区域后者可以处理任意形状的区域通过 HRGN 句柄。对于大多数情况InvalidateRect就足够了而且使用起来更简单。双缓冲技术原理现在我们进入正题。双缓冲技术是解决闪烁问题的终极方案它的核心思想是所有的绘制操作先在内存中完成然后再一次性将结果复制到屏幕上。你可以把它理解为画画家的工作方式画家不会直接在画布上作画而是在草稿纸上先完成所有细节确认无误后再一次性复制到正式画布上。在图形编程中这个草稿纸就是一个内存 DCDevice Context。内存 DC 作为后端缓冲内存 DC 是一个与屏幕 DC 兼容的内存设备上下文。它不像屏幕 DC 那样直接连接到显示器而是关联到一个位图对象。你可以在内存 DC 上进行任何绘制操作这些操作不会立即显示在屏幕上。一次性绘制完成后拷贝当你在内存 DC 上完成所有绘制后可以使用BitBlt函数将整个位图内容一次性复制到屏幕 DC 上。因为BitBlt是一个高度优化的操作通常在硬件层面完成所以这个拷贝过程非常快人眼无法察觉中间状态。为什么这样能消除闪烁双缓冲消除闪烁的关键在于用户永远看不到绘制过程只能看到最终结果。无论你在内存 DC 上绘制了多长时间绘制过程有多么复杂屏幕上只会发生一次更新那就是BitBlt操作。这就好比看电影电影实际上是由一帧帧静止画面组成的但因为播放速度足够快我们感知到的是流畅的动画。同样双缓冲技术通过确保只显示最终画面避免了中间绘制状态带来的视觉干扰。完整双缓冲实现理论讲完了现在我们来看看如何在代码中实现双缓冲。我们从一个基本的WM_PAINT处理函数开始逐步改进。创建兼容 DC 和位图首先我们需要创建一个与屏幕 DC 兼容的内存 DCcaseWM_PAINT:{PAINTSTRUCT ps;HDC hdcBeginPaint(hwnd,ps);// 获取客户区域尺寸RECT rcClient;GetClientRect(hwnd,rcClient);intcxClientrcClient.right-rcClient.left;intcyClientrcClient.bottom-rcClient.top;// 创建兼容 DCHDC hdcMemCreateCompatibleDC(hdc);if(hdcMemNULL){EndPaint(hwnd,ps);return0;}// 创建兼容位图HBITMAP hbmMemCreateCompatibleBitmap(hdc,cxClient,cyClient);if(hbmMemNULL){DeleteDC(hdcMem);EndPaint(hwnd,ps);return0;}// ... 后续代码}这里我们使用CreateCompatibleDC创建一个与屏幕 DC 兼容的内存 DC。然后使用CreateCompatibleBitmap创建一个与屏幕 DC 兼容的位图。这个位图的尺寸与客户区域相同确保能容纳整个窗口的内容。在内存 DC 上绘制所有内容接下来我们需要将位图选入内存 DC然后在其上进行绘制// 将位图选入内存 DCHBITMAP hbmOld(HBITMAP)SelectObject(hdcMem,hbmMem);// 填充背景因为我们禁用了默认背景擦除HBRUSH hbrBackgroundCreateSolidBrush(RGB(255,255,255));FillRect(hdcMem,rcClient,hbrBackground);DeleteObject(hbrBackground);// 在内存 DC 上绘制实际内容// 例如绘制一些图形HPEN hPenCreatePen(PS_SOLID,2,RGB(255,0,0));HPEN hPenOld(HPEN)SelectObject(hdcMem,hPen);Ellipse(hdcMem,50,50,200,200);Rectangle(hdcMem,150,150,300,300);SelectObject(hdcMem,hPenOld);DeleteObject(hPen);注意这里我们显式填充了背景。因为我们通常会处理WM_ERASEBKGND来阻止默认背景擦除所以需要自己填充背景。BitBlt 一次性拷贝到屏幕现在内存 DC 上已经有了完整的绘制内容我们将其拷贝到屏幕// 将内存 DC 的内容拷贝到屏幕BitBlt(hdc,rcClient.left,rcClient.top,cxClient,cyClient,hdcMem,0,0,SRCCOPY);BitBlt的参数依次是目标 DC、目标位置、宽度和高度、源 DC、源位置、光栅操作码。SRCCOPY表示直接复制像素这是最常用的操作。资源清理的正确顺序最后我们需要正确清理所有 GDI 对象。这里有一个重要的顺序问题// 恢复原始位图SelectObject(hdcMem,hbmOld);// 删除我们创建的位图DeleteObject(hbmMem);// 删除内存 DCDeleteDC(hdcMem);// 结束绘制EndPaint(hwnd,ps);return0;}⚠️ 注意在删除位图之前必须先将其从 DC 中选出来。这是因为当一个位图被选入 DC 时你不能删除它。这里我们通过选入原始位图hbmOld来实现这一点。资源管理的顺序很重要如果搞反了可能会导致内存泄漏或者程序崩溃。复杂场景下的双缓冲基本的双缓冲实现已经能解决大部分闪烁问题但在实际应用中我们还需要考虑一些复杂场景。处理窗口大小变化当窗口大小改变时我们需要重新创建缓冲位图以适应新的尺寸。一个常见的做法是将缓冲位图作为窗口类的一部分存储在WM_SIZE消息中更新// 全局或窗口类成员变量HBITMAP g_hbmBufferNULL;intg_cxBuffer0;intg_cyBuffer0;caseWM_SIZE:{intcxClientLOWORD(lParam);intcyClientHIWORD(lParam);// 如果尺寸变化重新创建缓冲位图if(cxClientg_cxBuffer||cyClientg_cyBuffer){if(g_hbmBuffer!NULL){DeleteObject(g_hbmBuffer);}HDC hdcGetDC(hwnd);g_hbmBufferCreateCompatibleBitmap(hdc,cxClient,cyClient);ReleaseDC(hwnd,hdc);g_cxBuffercxClient;g_cyBuffercyClient;}return0;}这样做的好处是避免在每次WM_PAINT时都重新创建位图提高了性能。只在尺寸确实变化时才重新创建。缓冲位图的重新创建有时候你需要在某些条件下强制重新创建缓冲位图比如当绘制内容发生重大变化时。你可以通过将缓冲位图句柄设置为 NULL 来触发重新创建voidInvalidateBuffer(HWND hwnd){if(g_hbmBuffer!NULL){DeleteObject(g_hbmBuffer);g_hbmBufferNULL;}InvalidateRect(hwnd,NULL,TRUE);}部分重绘优化PAINTSTRUCT.rcPaintWindows 只会重绘被标记为无效的区域。这个区域信息存储在PAINTSTRUCT结构的rcPaint成员中。我们可以利用这个信息来优化双缓冲caseWM_PAINT:{PAINTSTRUCT ps;HDC hdcBeginPaint(hwnd,ps);// 只重绘需要更新的区域if(!IsRectEmpty(ps.rcPaint)){intcxPaintps.rcPaint.right-ps.rcPaint.left;intcyPaintps.rcPaint.bottom-ps.rcPaint.top;HDC hdcMemCreateCompatibleDC(hdc);HBITMAP hbmMemCreateCompatibleBitmap(hdc,cxPaint,cyPaint);HBITMAP hbmOld(HBITMAP)SelectObject(hdcMem,hbmMem);// 在内存 DC 上绘制// ... 绘制代码// 只拷贝需要更新的区域BitBlt(hdc,ps.rcPaint.left,ps.rcPaint.top,cxPaint,cyPaint,hdcMem,0,0,SRCCOPY);SelectObject(hdcMem,hbmOld);DeleteObject(hbmMem);DeleteDC(hdcMem);}EndPaint(hwnd,ps);return0;}这种优化对于大型窗口特别有用因为它减少了需要复制的数据量。不过要注意如果你的绘制逻辑依赖于整个窗口的状态比如有一些全局布局计算部分重绘可能会增加复杂度。性能考量双缓冲虽然能有效消除闪烁但也带来了额外的内存和 CPU 开销。我们需要权衡这些因素。何时需要双缓冲不是所有情况都需要双缓冲。以下场景建议使用窗口内容频繁更新如动画绘制操作复杂耗时较长用户明显感知到闪烁需要平滑的视觉体验对于简单的静态内容或者偶尔重绘的窗口可能不需要完整的双缓冲实现。位图尺寸与内存占用缓冲位图的内存占用与分辨率成正比。一个 1920x1080 的 32 位位图大约需要 8MB 内存。对于大多数现代计算机来说这个开销可以接受但在极端情况下如超高分辨率或多窗口可能需要注意。DWM 时代的现代方案从 Windows Vista 开始微软引入了桌面窗口管理器DWM它使用合成技术来渲染窗口。DWM 会为每个窗口创建一个离屏表面然后由 DWM 负责最终的屏幕合成。在 DWM 时代传统的双缓冲技术的重要性有所下降因为 DWM 本身就提供了一定程度的双缓冲效果。但这并不意味着双缓冲没有用 —— 对于频繁更新的内容双缓冲仍然能显著改善用户体验。现代 Windows 开发中微软推荐使用硬件加速的 API 如 Direct2D 来替代 GDI。根据 Microsoft Learn 的文档Direct2D 提供了更好的性能和更现代化的特性。实战示例平滑动画演示让我们通过一个实际的动画例子来验证双缓冲的效果。这个例子会绘制一个在窗口中移动的圆形使用双缓冲来确保动画流畅。#includewindows.h#includecmath// 全局变量HWND g_hwndNULL;intg_xPos0;intg_yPos0;intg_xDirection1;intg_yDirection1;constintg_radius30;LRESULT CALLBACKWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);intWINAPIwWinMain(HINSTANCE hInstance,HINSTANCE hPrevInstance,PWSTR pCmdLine,intnCmdShow){constwchar_tCLASS_NAME[]LAnimationWindow;WNDCLASS wc{};wc.lpfnWndProcWindowProc;wc.hInstancehInstance;wc.lpszClassNameCLASS_NAME;wc.hbrBackgroundNULL;// 禁用默认背景刷wc.hCursorLoadCursor(NULL,IDC_ARROW);RegisterClass(wc);g_hwndCreateWindowEx(0,CLASS_NAME,LDouble Buffer Animation,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,800,600,NULL,NULL,hInstance,NULL);if(g_hwndNULL)return0;ShowWindow(g_hwnd,nCmdShow);// 启动动画定时器SetTimer(g_hwnd,1,16,NULL);// 约 60 FPSMSG msg{};while(GetMessage(msg,NULL,0,0)){TranslateMessage(msg);DispatchMessage(msg);}return0;}LRESULT CALLBACKWindowProc(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam){switch(uMsg){caseWM_ERASEBKGND:return1;// 阻止默认背景擦除caseWM_TIMER:{// 更新位置RECT rcClient;GetClientRect(hwnd,rcClient);intcxClientrcClient.right-rcClient.left;intcyClientrcClient.bottom-rcClient.top;g_xPosg_xDirection*5;g_yPosg_yDirection*5;// 边界检测if(g_xPosg_radiuscxClient||g_xPos-g_radius0){g_xDirection*-1;}if(g_yPosg_radiuscyClient||g_yPos-g_radius0){g_yDirection*-1;}// 触发重绘InvalidateRect(hwnd,NULL,FALSE);return0;}caseWM_PAINT:{PAINTSTRUCT ps;HDC hdcBeginPaint(hwnd,ps);RECT rcClient;GetClientRect(hwnd,rcClient);intcxClientrcClient.right-rcClient.left;intcyClientrcClient.bottom-rcClient.top;// 创建双缓冲HDC hdcMemCreateCompatibleDC(hdc);HBITMAP hbmMemCreateCompatibleBitmap(hdc,cxClient,cyClient);HBITMAP hbmOld(HBITMAP)SelectObject(hdcMem,hbmMem);// 填充背景HBRUSH hbrBgCreateSolidBrush(RGB(240,240,240));FillRect(hdcMem,rcClient,hbrBg);DeleteObject(hbrBg);// 绘制移动的圆形HBRUSH hbrCircleCreateSolidBrush(RGB(255,100,100));HPEN hPenCreatePen(PS_SOLID,2,RGB(200,50,50));HGDIOBJ hbrOldSelectObject(hdcMem,hbrCircle);HGDIOBJ hPenOldSelectObject(hdcMem,hPen);Ellipse(hdcMem,g_xPos-g_radius,g_yPos-g_radius,g_xPosg_radius,g_yPosg_radius);// 恢复和清理SelectObject(hdcMem,hbrOld);SelectObject(hdcMem,hPenOld);DeleteObject(hbrCircle);DeleteObject(hPen);// 一次性拷贝到屏幕BitBlt(hdc,0,0,cxClient,cyClient,hdcMem,0,0,SRCCOPY);SelectObject(hdcMem,hbmOld);DeleteObject(hbmMem);DeleteDC(hdcMem);EndPaint(hwnd,ps);return0;}caseWM_SIZE:// 初始化位置到中心if(g_xPos0g_yPos0){g_xPosLOWORD(lParam)/2;g_yPosHIWORD(lParam)/2;}return0;caseWM_DESTROY:PostQuitMessage(0);return0;}returnDefWindowProc(hwnd,uMsg,wParam,lParam);}这个例子展示了完整的双缓冲动画实现。你可以编译运行它观察动画是否流畅。如果你移除双缓冲代码直接在屏幕 DC 上绘制你会明显看到闪烁现象。常见问题与调试技巧在实现双缓冲时你可能会遇到一些常见问题。这里我来总结几个坑点和相应的解决方案。问题1双缓冲后仍然闪烁如果你实现了双缓冲但仍然看到闪烁可能的原因包括没有正确处理 WM_ERASEBKGND: 即使使用双缓冲如果系统还在擦除背景你仍然会看到闪烁。确保你的WM_ERASEBKGND处理返回 TRUE。缓冲位图尺寸不匹配: 如果缓冲位图比实际客户区域小BitBlt可能无法完全覆盖窗口。确保使用GetClientRect获取准确的尺寸。部分重绘时的问题: 如果你只重绘部分区域确保缓冲位图包含完整的内容否则可能会看到残影。问题2内存泄漏GDI 对象的内存泄漏是一个常见问题。你可以通过任务管理器查看 GDI 对象数量来检测泄漏。正常情况下GDI 对象数量应该保持稳定如果持续增长说明有泄漏。确保每个CreateCompatibleDC都有对应的DeleteDC每个CreateCompatibleBitmap都有对应的DeleteObject。问题3性能不如预期如果双缓冲后性能反而下降可能的原因包括频繁创建/销毁缓冲位图: 如前面所述应该在WM_SIZE时创建缓冲位图并缓存而不是每次WM_PAINT都重新创建。不必要的背景填充: 如果你的绘制内容会完全覆盖背景可以跳过背景填充步骤。过大或过小的缓冲位图: 缓冲位图应该与客户区域大小匹配。调试技巧为了验证双缓冲是否正常工作你可以在内存 DC 绘制时使用不同的背景色然后观察屏幕上是否能看到这个颜色。如果在BitBlt之前就能看到颜色变化说明你的双缓冲没有正常工作。另一个技巧是使用GetTickCount或高精度计时器来测量绘制时间找出性能瓶颈。总结到这里我们已经完整地介绍了 Win32 GDI 双缓冲技术。从闪烁问题的根源到双缓冲的原理再到完整的实现和优化我们覆盖了所有关键知识点。双缓冲技术虽然是一个老技术但在理解图形绘制原理方面仍然很有价值。即使现代开发可能使用更高级的 API但这些底层概念是通用的。希望这篇文章能帮助你彻底解决 Win32 GDI 中的闪烁问题。如果你在实际应用中遇到其他问题欢迎继续探索和实验 —— 毕竟最好的学习方式就是动手实践。参考资料:WM_ERASEBKGND message - Microsoft LearnBeginPaint function - Microsoft LearnComparing Direct2D and GDI - Microsoft LearnDouble buffering in Direct2D - Stack Overflow

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SpringBoot-17-MyBatis动态SQL标签之常用标签

文章目录 1 代码1.1 实体User.java1.2 接口UserMapper.java1.3 映射UserMapper.xml1.3.1 标签if1.3.2 标签if和where1.3.3 标签choose和when和otherwise1.4 UserController.java2 常用动态SQL标签2.1 标签set2.1.1 UserMapper.java2.1.2 UserMapper.xml2.1.3 UserController.ja…

wordpress后台更新后 前端没变化的解决方法

使用siteground主机的wordpress网站,会出现更新了网站内容和修改了php模板文件、js文件、css文件、图片文件后,网站没有变化的情况。 不熟悉siteground主机的新手,遇到这个问题,就很抓狂,明明是哪都没操作错误&#x…

网络编程(Modbus进阶)

思维导图 Modbus RTU(先学一点理论) 概念 Modbus RTU 是工业自动化领域 最广泛应用的串行通信协议,由 Modicon 公司(现施耐德电气)于 1979 年推出。它以 高效率、强健性、易实现的特点成为工业控制系统的通信标准。 包…

UE5 学习系列(二)用户操作界面及介绍

这篇博客是 UE5 学习系列博客的第二篇,在第一篇的基础上展开这篇内容。博客参考的 B 站视频资料和第一篇的链接如下: 【Note】:如果你已经完成安装等操作,可以只执行第一篇博客中 2. 新建一个空白游戏项目 章节操作,重…

IDEA运行Tomcat出现乱码问题解决汇总

最近正值期末周,有很多同学在写期末Java web作业时,运行tomcat出现乱码问题,经过多次解决与研究,我做了如下整理: 原因: IDEA本身编码与tomcat的编码与Windows编码不同导致,Windows 系统控制台…

利用最小二乘法找圆心和半径

#include <iostream> #include <vector> #include <cmath> #include <Eigen/Dense> // 需安装Eigen库用于矩阵运算 // 定义点结构 struct Point { double x, y; Point(double x_, double y_) : x(x_), y(y_) {} }; // 最小二乘法求圆心和半径 …

使用docker在3台服务器上搭建基于redis 6.x的一主两从三台均是哨兵模式

一、环境及版本说明 如果服务器已经安装了docker,则忽略此步骤,如果没有安装,则可以按照一下方式安装: 1. 在线安装(有互联网环境): 请看我这篇文章 传送阵>> 点我查看 2. 离线安装(内网环境):请看我这篇文章 传送阵>> 点我查看 说明&#xff1a;假设每台服务器已…

XML Group端口详解

在XML数据映射过程中&#xff0c;经常需要对数据进行分组聚合操作。例如&#xff0c;当处理包含多个物料明细的XML文件时&#xff0c;可能需要将相同物料号的明细归为一组&#xff0c;或对相同物料号的数量进行求和计算。传统实现方式通常需要编写脚本代码&#xff0c;增加了开…

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器的上位机配置操作说明

LBE-LEX系列工业语音播放器|预警播报器|喇叭蜂鸣器专为工业环境精心打造&#xff0c;完美适配AGV和无人叉车。同时&#xff0c;集成以太网与语音合成技术&#xff0c;为各类高级系统&#xff08;如MES、调度系统、库位管理、立库等&#xff09;提供高效便捷的语音交互体验。 L…

(LeetCode 每日一题) 3442. 奇偶频次间的最大差值 I (哈希、字符串)

题目&#xff1a;3442. 奇偶频次间的最大差值 I 思路 &#xff1a;哈希&#xff0c;时间复杂度0(n)。 用哈希表来记录每个字符串中字符的分布情况&#xff0c;哈希表这里用数组即可实现。 C版本&#xff1a; class Solution { public:int maxDifference(string s) {int a[26]…

【大模型RAG】拍照搜题技术架构速览:三层管道、两级检索、兜底大模型

摘要 拍照搜题系统采用“三层管道&#xff08;多模态 OCR → 语义检索 → 答案渲染&#xff09;、两级检索&#xff08;倒排 BM25 向量 HNSW&#xff09;并以大语言模型兜底”的整体框架&#xff1a; 多模态 OCR 层 将题目图片经过超分、去噪、倾斜校正后&#xff0c;分别用…

【Axure高保真原型】引导弹窗

今天和大家中分享引导弹窗的原型模板&#xff0c;载入页面后&#xff0c;会显示引导弹窗&#xff0c;适用于引导用户使用页面&#xff0c;点击完成后&#xff0c;会显示下一个引导弹窗&#xff0c;直至最后一个引导弹窗完成后进入首页。具体效果可以点击下方视频观看或打开下方…

接口测试中缓存处理策略

在接口测试中&#xff0c;缓存处理策略是一个关键环节&#xff0c;直接影响测试结果的准确性和可靠性。合理的缓存处理策略能够确保测试环境的一致性&#xff0c;避免因缓存数据导致的测试偏差。以下是接口测试中常见的缓存处理策略及其详细说明&#xff1a; 一、缓存处理的核…

龙虎榜——20250610

上证指数放量收阴线&#xff0c;个股多数下跌&#xff0c;盘中受消息影响大幅波动。 深证指数放量收阴线形成顶分型&#xff0c;指数短线有调整的需求&#xff0c;大概需要一两天。 2025年6月10日龙虎榜行业方向分析 1. 金融科技 代表标的&#xff1a;御银股份、雄帝科技 驱动…

观成科技:隐蔽隧道工具Ligolo-ng加密流量分析

1.工具介绍 Ligolo-ng是一款由go编写的高效隧道工具&#xff0c;该工具基于TUN接口实现其功能&#xff0c;利用反向TCP/TLS连接建立一条隐蔽的通信信道&#xff0c;支持使用Let’s Encrypt自动生成证书。Ligolo-ng的通信隐蔽性体现在其支持多种连接方式&#xff0c;适应复杂网…

铭豹扩展坞 USB转网口 突然无法识别解决方法

当 USB 转网口扩展坞在一台笔记本上无法识别,但在其他电脑上正常工作时,问题通常出在笔记本自身或其与扩展坞的兼容性上。以下是系统化的定位思路和排查步骤,帮助你快速找到故障原因: 背景: 一个M-pard(铭豹)扩展坞的网卡突然无法识别了,扩展出来的三个USB接口正常。…

未来机器人的大脑:如何用神经网络模拟器实现更智能的决策?

编辑&#xff1a;陈萍萍的公主一点人工一点智能 未来机器人的大脑&#xff1a;如何用神经网络模拟器实现更智能的决策&#xff1f;RWM通过双自回归机制有效解决了复合误差、部分可观测性和随机动力学等关键挑战&#xff0c;在不依赖领域特定归纳偏见的条件下实现了卓越的预测准…

Linux应用开发之网络套接字编程(实例篇)

服务端与客户端单连接 服务端代码 #include <sys/socket.h> #include <sys/types.h> #include <netinet/in.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <arpa/inet.h> #include <pthread.h> …

华为云AI开发平台ModelArts

华为云ModelArts&#xff1a;重塑AI开发流程的“智能引擎”与“创新加速器”&#xff01; 在人工智能浪潮席卷全球的2025年&#xff0c;企业拥抱AI的意愿空前高涨&#xff0c;但技术门槛高、流程复杂、资源投入巨大的现实&#xff0c;却让许多创新构想止步于实验室。数据科学家…

深度学习在微纳光子学中的应用

深度学习在微纳光子学中的主要应用方向 深度学习与微纳光子学的结合主要集中在以下几个方向&#xff1a; 逆向设计 通过神经网络快速预测微纳结构的光学响应&#xff0c;替代传统耗时的数值模拟方法。例如设计超表面、光子晶体等结构。 特征提取与优化 从复杂的光学数据中自…