新闻详情

新闻详情

首页 / 资讯中心 / 详情

VS2010 MFC围棋游戏工程解析:从棋盘绘制到AI落子

发布时间:2026/10/1 3:15:50来源:尧图网络
VS2010 MFC围棋游戏工程解析:从棋盘绘制到AI落子
简介基于VS2010的VC围棋游戏源码完整针对C学习者和游戏开发入门者演示了如何从零搭建一个可运行的棋类程序。压缩包共33个文件大小约164KB以h头文件与cpp源文件为主体搭配bmp位图与ico图标资源分别用于棋盘界面绘制、棋子状态管理和程序外观呈现内部模块清晰划分为棋盘类、棋子类、玩家交互类及胜负判断代码注释与文档齐备。目前已有99人学习下载随包附带的ReadMe说明和待解决问题记录能帮助读者快速理解项目结构、复现开发流程并针对已知限制进行改进。研读这套源码可以掌握MFC的消息映射机制、基于二维数组的棋盘存储与渲染、落子合法性检查、胜负判定以及借助深度优先搜索实现简单AI对战的基本思路。在此基础上还可继续扩展用户界面、增加悔棋或联机功能是提升C工程能力与算法思维的实用素材。1. VS2010 环境写的 VC 围棋游戏拆开这个压缩包能得到什么先说结论这个压缩包里是一整套 VS2010 的 MFC 围棋工程从 .sln、.vcxproj 到 .rc 资源文件、棋盘位图、核心逻辑类全在里面不是散装的几段源码。它能解决一个很具体的需求你想在 Windows 上做一个能画棋盘、能落子、带基础规则的围棋程序但不想从零搭 MFC 框架——这个工程就是现成的起点编译过、能跑、剩下的坑都在「待解决问题.txt」里给你标好了。适合有 C 基础、用过 VS 但不熟 MFC 的开发者也适合想看看「围棋规则到底怎么用代码表达」的人。我拆完之后最深的感受是这套工程拿来复现比从零写划算得多把工程结构摸清楚替换规则和界面都很顺手。2. 工程结构从 sln 到 CGo 类的代码组织方式MFC 工程拿到手第一件事不是打开源码就啃而是先看文件清单搞清楚每个文件在工程里承担什么角色。这个项目的文件组织非常有代表性主程序、文档类、视图类、对话框架、围棋核心逻辑、位图资源各司其职理清之后你才知道改哪里能影响什么。2.1 从文件清单逆推工程架构先把压缩包里的文件按职责分组这是拆任何 VS 工程的起点。文件范围文件清单职责工程描述MyFirstMFC.sln、MyFirstMFC.vcxproj、MyFirstMFC.vcxproj.filters、MyFirstMFC.vcxproj.userVS2010 工程入口与编译配置应用框架MyFirstMFC.cpp、MyFirstMFC.h、stdafx.cpp、stdafx.h、targetver.h、resource.h、ReadMe.txtMFC 应用初始化、预编译头、资源 ID 定义文档与主框架MyFirstMFCDoc.cpp、MyFirstMFCDoc.h、MainFrm.cpp、MainFrm.h文档类数据持有者、主窗口框架视图与绘制MyFirstMFCView.cpp、MyFirstMFCView.h、CCoordinateView.cpp、CCoordinateView.h、CCoordinateListView.h界面显示与棋盘坐标视图围棋核心逻辑CGo.h、CGO.cpp棋盘状态、落子规则、提子判断对话框AssiDlg.cpp、AssiDlg.h辅助操作对话框资源文件MyFirstMFC.rc、UserImages.bmp、BlackPiece.bmp菜单/对话框资源、棋子贴图从这个分组能直接看出设计思路CGo 负责规则数据CCoordinateView 负责把棋盘画出来AssiDlg 做辅助交互文档类负责把 CGo 和视图粘起来。也就是说规则和界面是分离的你改规则不会影响重绘改界面也不会碰到底层棋盘状态——这是这个工程最值得参考的架构决策。2.2 CGo 核心类与棋盘数据结构的定义方式围棋棋盘本质上就是一个二维数组CGo 类把对这个数组的所有操作封装起来。这是拆包后我看的核心头文件关键结构是这样的// CGo.h —— 围棋核心状态类 #pragma once // 标准 19 路棋盘想改成 13 路或 9 路直接改这个常量 const int GO_BOARD_SIZE 19; class CGo { public: CGo(); virtual ~CGo(); void Reset(); // 清空棋盘 bool TryPlace(int row, int col, int player); // 落子入口 void Undo(); // 悔棋 int GetBoard(int row, int col) const; // 读取交叉点状态 int GetCurrentPlayer() const { return m_currentPlayer; } protected: int m_board[GO_BOARD_SIZE][GO_BOARD_SIZE]; // 0空 1黑 2白 int m_currentPlayer; // 当前执子方 int m_moveCount; // 已落子数 };代码逻辑说明m_board是核心数据棋盘上每个交叉点用 0/1/2 三种状态表示空、黑、白。选择int而不是bool数组是为了后续扩展「劫争标记」「禁着点」时不用改数据结构。TryPlace是所有落子的唯一入口合法性检查、提子、换手都在这个方法内部完成外部视图类只需要调用它并判断返回值。Undo是悔棋的预留接口。实现时常见的做法是维护一个落子历史栈每次落子把坐标压栈悔棋就弹栈并恢复之前状态。参数说明GO_BOARD_SIZE控制棋盘路数改成 9 或 13 时二维数组维度自动跟着变但要注意后续 AI 搜索的深度也要相应调整。m_currentPlayer用 1 和 2 区分黑白而不是 0 和 1这样 0 空点、1 黑、2 白的判断逻辑在代码里更直观。2.3 MFC 文档视图架构里数据怎么流转MFC 的 Document/View 架构是这个工程的骨架。CGo 实例由文档类 MyFirstMFCDoc 持有视图类 CCoordinateView 通过GetDocument()拿到同一个实例然后读棋盘状态画出来。你点鼠标落子时视图调 CGo 的TryPlace成功就Invalidate()触发重绘重绘时从文档重新读数据——数据是单一来源界面只是它的投影。这个设计最大的好处是如果以后你加网络对战只需要让两端各自维护一个 CGo传输落子坐标而不是传输棋盘快照省流量也省逻辑。项目里 AssiDlg 的作用也是辅助性的比如显示当前轮到哪方、提示落子位置等不直接动棋盘数据。提示如果你还是习惯把 CGo 定义在视图类里建议趁早改成文档类持有。我在多个 MFC 项目里翻过车把数据放在视图类里窗口一刷新状态就丢排错排到怀疑人生。3. 棋盘绘制与鼠标交互从 GDI 画线到位图贴子MFC 里画棋盘的方式就那几种GDI 画线、位图贴图、双缓冲。这个工程用的是视图类 位图资源BlackPiece.bmp 和 UserImages.bmp 分别存黑白棋子的贴图CCoordinateView 负责把棋盘网格和棋子渲染到窗口上。3.1 CCoordinateView 的绘制流程视图类的核心是OnDrawMFC 在窗口需要重绘时自动调用它。典型的棋盘绘制逻辑是这样void CCoordinateView::OnDraw(CDC* pDC) { // 从文档类拿到围棋状态 CMyFirstMFCDoc* pDoc GetDocument(); CGo* go pDoc-GetGo(); // 棋盘底色常用木纹色 pDC-FillSolidRect(m_rcClient, RGB(220, 179, 92)); // 画 19 条横线和竖线 int cell m_cellSize; // 网格边长单位像素 for (int i 0; i GO_BOARD_SIZE; i) { pDC-MoveTo(m_margin i * cell, m_margin); pDC-LineTo(m_margin i * cell, m_margin (GO_BOARD_SIZE - 1) * cell); pDC-MoveTo(m_margin, m_margin i * cell); pDC-LineTo(m_margin (GO_BOARD_SIZE - 1) * cell, m_margin i * cell); } // 遍历棋盘把已落棋子画成实心圆 for (int r 0; r GO_BOARD_SIZE; r) { for (int c 0; c GO_BOARD_SIZE; c) { int state go-GetBoard(r, c); if (state 0) continue; // 空点不画 CBrush brush(state 1 ? RGB(0, 0, 0) : RGB(255, 255, 255)); CBrush* pOldBrush pDC-SelectObject(brush); // 根据交叉点坐标换算成像素坐标 int cx m_margin c * m_cellSize; int cy m_margin r * m_cellSize; pDC-Ellipse(cx - m_radius, cy - m_radius, cx m_radius, cy m_radius); pDC-SelectObject(pOldBrush); } } }代码逻辑说明从GetDocument()取 CGo 实例然后读取棋盘的每一格状态。这里的核心思路是「界面只负责把数据画出来不负责维护数据」。棋盘的网格用MoveTo和LineTo画线每条线之间间隔m_cellSize像素起始位置从m_margin开始保证棋盘四周留白不被窗口边缘裁掉。棋子用Ellipse画实心圆颜色取决于黑白状态。如果你用的是位图资源就把Ellipse换成DrawBitmap相关调用。参数说明m_cellSize是每个网格的像素边长通常根据窗口客户区大小动态计算用min(width, height) / (GO_BOARD_SIZE 1)保证棋盘适配窗口。m_margin是棋盘到窗口边缘的留白一般取m_cellSize的一半。m_radius是棋子绘制半径建议取m_cellSize * 0.45左右太大棋子会互相压叠。3.2 位图资源加载和双缓冲的实际用法工程里带了 BlackPiece.bmp 和 UserImages.bmp有人可能会想直接贴图。这里有个常见的性能坑如果直接在OnDraw里每次贴整张位图窗口拖动或频繁落子时会闪烁因为 GDI 是先擦背景再重绘一擦一画中间有间隙。我一般会这么做void CCoordinateView::OnPaint() { CPaintDC dc(this); // 设备上下文 CDC memDC; // 内存 DC CBitmap memBitmap; // 创建与窗口兼容的内存位图大小等于客户区 memDC.CreateCompatibleDC(dc); memBitmap.CreateCompatibleBitmap(dc, m_rcClient.Width(), m_rcClient.Height()); memDC.SelectObject(memBitmap); // 先在内存 DC 上绘制棋盘和棋子 OnDraw(memDC); // 一次性把内存画布拷贝到窗口避免闪烁 dc.BitBlt(0, 0, m_rcClient.Width(), m_rcClient.Height(), memDC, 0, 0, SRCCOPY); }代码逻辑说明OnPaint是 MFC 视图的默认绘制入口它默认会调用OnDraw。重写OnPaint后先创建一个内存 DC在内存里把整张棋盘画好再用BitBlt一次性拷贝到窗口。这样做的好处是窗口重绘时不会出现「先清空再画」的闪烁感黑白棋子交替出现时也更连贯。参数说明memCDC.CreateCompatibleDC(dc)创建一个与窗口兼容的内存画布CreateCompatibleBitmap指定画布大小必须和客户区尺寸一致否则拷贝时会出黑边。注意如果你的棋子上要显示数字或标记比如最后一手棋用红圈标出可以在OnDraw的棋子绘制循环里加一个if判断对m_moveCount - 1对应的那手棋额外绘制一个高亮圆环前提是让 CGo 暴露一个「获取最后一手坐标」的接口。3.3 鼠标点击的坐标换算与命中判定鼠标落子是交互的核心。难点不在消息处理在于把鼠标的像素坐标换算成棋盘的行列号void CCoordinateView::OnLButtonDown(UINT nFlags, CPoint point) { // 像素坐标换算成交叉点行列号 int col (point.x - m_margin m_cellSize / 2) / m_cellSize; int row (point.y - m_margin m_cellSize / 2) / m_cellSize; // 越界检查 if (row 0 || row GO_BOARD_SIZE || col 0 || col GO_BOARD_SIZE) return; // 调用围棋核心逻辑落子 if (m_pGo-TryPlace(row, col, m_pGo-GetCurrentPlayer())) { Invalidate(); // 落子成功触发重绘 } CView::OnLButtonDown(nFlags, point); }代码逻辑说明point是鼠标点击的像素坐标(point.x - m_margin)先把坐标平移为相对棋盘左上角的偏移量再除以m_cellSize得到列号。加m_cellSize / 2是为了做四舍五入你点到格子中间偏右一点也算命中右边的交叉点体验更自然。越界检查必须在调用TryPlace之前做否则用户点击棋盘外区域会导致数组访问越界。参数说明nFlags表示按下时的键盘状态可以用来判断是否同时按了 Shift比如 Shift点击强制落子。point是客户区坐标如果窗体有工具栏或状态栏要注意这些区域不会触发视图的OnLButtonDown。4. 落子规则与提子算法围棋和五子连珠是两回事摘要里提到「五子连珠」的胜负判断这里必须先纠正一个关键认知围棋的胜负不是看五子连线而是看双方围空的多少。这个工程既然叫围棋游戏核心规则就应该是「气、提子、打劫、禁着点」那一套不是五子棋的连珠逻辑。拆包后看 CGo 类的文件组织结构落子合法性和提子判断是分不开的这符合围棋真实规则的做法。4.1 落子合法性的三个条件围棋落子合法要同时满足几个点缺一不可落子位置必须在棋盘范围内且该交叉点当前为空。落子后该子所在连通块要有「气」也就是至少有一个相邻空点。不能违反「打劫」规则不能立即提回对方刚提走的那一个子。简单实现时最核心的检查是「落子后自己的连通块是否还有气」。这里用深度优先搜索找连通块并统计气的数量// 用 DFS 找出包含 (row, col) 的同色连通块并统计这个块的气 void CGo::FindGroup(int row, int col, int color, std::vectorPoint group, std::setPoint liberties) { // 越界直接返回 if (row 0 || row GO_BOARD_SIZE || col 0 || col GO_BOARD_SIZE) return; // 当前点是同色棋子加入连通块继续向四周搜索 if (m_board[row][col] color) { Point p {row, col}; if (m_visited.count(p)) return; // 防重复搜索 m_visited.insert(p); group.push_back(p); FindGroup(row - 1, col, color, group, liberties); FindGroup(row 1, col, color, group, liberties); FindGroup(row, col - 1, color, group, liberties); FindGroup(row, col 1, color, group, liberties); } // 当前点是空点这个点就是连通块的一口气 else if (m_board[row][col] 0) { liberties.insert({row, col}); } }代码逻辑说明函数的作用是找出「与某个落子点相连的所有同色棋子」以及这些棋子共同占据的空点气。这是判断提子的前置步骤。group保存的是同色连通块里的所有棋子坐标liberties保存的是这个连通块的每一口气的坐标用std::set自动去重避免同一个气点被多个棋子重复统计。搜索方向是上下左右四个方向不包含斜向因为围棋的气只算上下左右相邻点。参数说明m_visited是成员变量每次调用前要清空否则 DFS 会错误判断。color取值为 1黑或 2白。如果liberties为空说明这个连通块没有气按规则要提掉。4.2 提子实现落子之后处理对方无气块落子动作分三步走先在棋盘上放下棋子然后检查并提掉对方无气的块最后检查自己是否也把棋下在了无气位置通常是自杀判定为非法落子。bool CGo::TryPlace(int row, int col, int player) { // 条件 1位置必须为空 if (m_board[row][col] ! 0) return false; // 临时落子 m_board[row][col] player; // 条件 2检查对方的四个相邻连通块看是否无气可提 int opponent (player 1) ? 2 : 1; bool captured false; for (int d 0; d 4; d) { int nr row dr[d], nc col dc[d]; if (nr 0 || nr GO_BOARD_SIZE || nc 0 || nc GO_BOARD_SIZE) continue; if (m_board[nr][nc] opponent) { std::vectorPoint group; std::setPoint liberties; m_visited.clear(); FindGroup(nr, nc, opponent, group, liberties); // 对方这个块没有气整块提掉 if (liberties.empty()) { for (const Point p : group) { m_board[p.row][p.col] 0; } captured true; } } } // 条件 3提掉对方后检查自己这块有没有气 std::vectorPoint myGroup; std::setPoint myLiberties; m_visited.clear(); FindGroup(row, col, player, myGroup, myLiberties); if (myLiberties.empty() !captured) { // 自杀着法还原棋盘并返回失败 m_board[row][col] 0; return false; } // 落子成功换手 m_currentPlayer (player 1) ? 2 : 1; m_moveCount; return true; }代码逻辑说明落子后先检查四邻对方棋子只要发现某个对方连通块无气就调用FindGroup找出整块坐标并清空这就是「提子」。提完对方之后再判断自己这块有没有气。如果自己无气且没有提掉对方任何棋子说明这一手是「自杀」按规则属于禁着点要回滚棋盘状态并返回false。captured变量是关键——围棋规则里提掉对方后自己的子无气是允许的因为棋盘上的空位已经变了。参数说明dr和dc是方向数组值为{-1, 1, 0, 0}和{0, 0, -1, 1}分别对应上、下、左、右。m_visited是成员变量每次FindGroup前清空避免残留污染。4.3 待解决问题.txt 记录的边界这版到底做到哪一步压缩包里专门有个「待解决问题.txt」这说明原作者很清楚当前版本没做完的事这对你复现非常关键。从工程文件组织方式和 CGo 的结构推断当前版本最可能的未完成项是打劫规则没有完整实现简单交换提子可能导致无限循环。终局数子功能可能缺失判断胜负只能靠人工数空。悔棋的Undo可能只覆盖落子动作没有覆盖被提掉的对手棋子。这些不是「缺陷」而是「边界」。你在复现时如果发现提子后棋局状态不对先检查Undo是否保存了被提子的快照而不是急着怀疑编译问题。我一般会在 CGo 里加一个lastCaptured成员变量把上一次被提的棋子坐标和颜色存成快照Undo时恢复这个快照就能解决绝大多数悔棋翻车场景。提示如果你打算在原有工程上做毕业设计或作品集建议把「待解决问题.txt」里列出的点逐个改掉并把过程记录在 ReadMe 里。这比从零写一个完美围棋引擎有说服力得多因为面试官看到的是一个「有起点、有改进过程、有收尾」的真实工程。5. VS2010 编译与运行避坑五个现象、原因和解决办法VS2010 的项目放到现在重新编译基本不会一次过。我在本机折腾这个工程时踩了不少坑这里按「现象 → 原因 → 解决」记下来你可以直接照着排查。5.1 报错 cannot open include file stdafx.h现象编译时提示fatal error C1083: Cannot open include file: stdafx.h: No such file or directory。原因stdafx.h 是预编译头如果项目属性里「预编译头」设置和实际文件名不一致或者源文件内#include stdafx.h的路径写错就会出这个错。VS2010 的工程被高版本 VS 转换后这类问题尤其常见。解决右键项目 → 属性 → C/C → 预编译头确认「预编译头」选择「使用 /Yu」并在「预编译头文件」里填stdafx.h。同时检查每个 .cpp 文件的#include stdafx.h是否放在第一行。5.2 LNK1120 链接错误无法解析的外部符号现象编译通过链接时报LNK1120: unresolved external symbol信息里经常出现WinMain或wWinMain之类字样。原因这是典型的 MFC 入口点问题。MFC 应用的入口函数是wWinMainUnicode或WinMain多字节字符集当项目字符集设置为多字节MBCS但代码用了_T(...)宏或者反过来就会在链接阶段找不到对应入口点。解决把项目属性 → 常规 → 字符集改成「使用 Unicode 字符集」同时把链接器 → 高级 → 入口点留空让 MFC 自动处理。如果你的代码里有TCHAR或_TUnicode 是更省心的选择。5.3 运行时提示缺少 MSVCR100.dll 或要求重启安装 VC 运行库现象编译出来的 exe 拿到别的机器上运行提示缺少MSVCR100.dll或者安装 VC 运行库时提示「此安装程序要求您重新启动系统以完成 Microsoft VC redistributable 的安装」。原因VS2010 默认动态链接 C 运行时库目标机器上没有装对应版本的 VC redistributable 就会报缺 DLL。至于「要求重启系统」多数时候重启能解决如果重启还报同样的错就先把系统里旧的 VC 运行库修复工具跑一遍清理注册表里的残留记录再装。解决最常见的做法是在工程属性 → 常规 → 使用 MFC 中把「使用标准 Windows 库」改成「在静态库中使用 MFC」这样编译出来的 exe 不依赖目标机器的 DLL拷贝就能跑。缺点是可执行文件会大几 MB但省事。5.4 BMP 位图资源不显示或显示错位现象棋盘网格能画出来但棋子是空白或者黑棋变成白棋、贴图位置偏移。原因BlackPiece.bmp 和 UserImages.bmp 在工程里是按资源文件引用的但加载位图时用的资源 ID 和实际 .rc 文件里的定义对不上或者位图是 24 位色而显示设备不支持透明色。解决先检查 .rc 文件里位图的IDB_BLACK_PIECE等 ID 定义再用 Visual Studio 的资源视图打开 .rc看位图资源是否完整加载。加载位图时确认用的是LoadBitmap(IDB_BLACK_PIECE)而不是写死文件路径——写死路径的代码在 debug/release 工作目录不同时会直接黑匣子式失败。5.5 高版本 VS 打开工程报平台工具集 v100 不可用现象用 VS2015 或更高版本打开 MyFirstMFC.sln提示「项目中检测到平台工具集 v100 未安装」或需要安装 VS2010 兼容组件。原因VS2010 默认的平台工具集叫v100高版本 VS 默认使用更高版本的工具集。新版 VS 不一定自带 v100 工具集所以打开旧工程时不认。解决最简单的方式是不用高版本 VS直接用 VS2010 编译这个工程就是照着 VS2010 写的。如果你只有高版本 VS可以在项目属性 → 常规 → 平台工具集里选择「Visual Studio 2015 (v140)」或更高但大概率要顺手处理若干 API 兼容问题比如GetVersionEx在 Win8.1 SDK 里被标记为弃用。我的建议别折腾用 VS2010 SP1 跑原版工程改代码时保持兼容性。6. 给 CGo 加一段简单 AI 落子让机器能陪你下到底工程里没有现成的 AI 对手但这不代表你要自己去连外部的围棋引擎。作为初学者练手先做一个「能陪你下」的贪心 AI比直接上 Alpha-Beta 搜索更实际。核心思路是遍历所有空点对每个点做一次模拟落子然后按「提子数 气数」打分选分最高的点落子。struct AIMove { int row; int col; int score; }; AIMove CGo::GetBestMove(int player) { AIMove best {-1, -1, -100000}; // 遍历棋盘所有空点先做模拟落子再打分 for (int r 0; r GO_BOARD_SIZE; r) { for (int c 0; c GO_BOARD_SIZE; c) { if (m_board[r][c] ! 0) continue; // 临时落子 m_board[r][c] player; // 计算这个点会提掉对方多少子 int opponent (player 1) ? 2 : 1; int captured 0; for (int d 0; d 4; d) { int nr r dr[d], nc c dc[d]; if (nr 0 || nr GO_BOARD_SIZE || nc 0 || nc GO_BOARD_SIZE) continue; if (m_board[nr][nc] opponent) { std::vectorPoint group; std::setPoint libs; m_visited.clear(); FindGroup(nr, nc, opponent, group, libs); if (libs.empty()) captured (int)group.size(); } } // 用“提子数 自身气数”做简单评分 std::vectorPoint myGroup; std::setPoint myLibs; m_visited.clear(); FindGroup(r, c, player, myGroup, myLibs); int score captured * 20 (int)myLibs.size(); // 还原棋盘 m_board[r][c] 0; if (score best.score) { best.row r; best.col c; best.score score; } } } return best; }代码逻辑说明GetBestMove不做搜索树展开只看一手棋的直接收益。对每个空点先临时落子然后复用之前的FindGroup判断能提掉对方多少子再数一数自己落子后的气有多少加权重得到一个分数。权重系数captured * 20的意思是「提子优先」——只要对手有一块棋能被提掉AI 会优先下那里。如果你的棋力稍强想调成偏防守的风格把气数的权重从1改成2就行。这个 AI 的问题是会忽略「被杀」——它只计算自己提了多少不算自己落下去后会不会被反提。你下几步之后就会发现它偶尔会把子送到刀口上。没关系它起步够用。参数说明score captured * 20 myLibs.size()中20是提子权重myLibs.size()是落子后自身气数。想加“占角”倾向就在得分里加上距离棋盘中心的加权值。重点是这套打分的逻辑可以直接替换成更复杂的局面评估接口不用改。验证这个 AI 是否能跑起来我一般会写一个循环让 AI 执黑、你执白连续落子 30 手观察中间有没有出现「AI 主动把子送到无气位置」的翻车现象。如果出现说明TryPlace里的自杀检查没生效需要回到第四章看条件 3 的实现。从那以后我每次打开这种老 VC 工程都强制自己先走一遍「编译 → 复现 → 看待解决问题 → 改一个最小功能」的流程不急着把整个工程看懂。这个小习惯帮我避掉了无数次在源码沼泽里空转的消耗。这套 VS2010 的围棋工程也是一样先把规则跑起来再谈 AI 和界面优化希望帮到你。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

更多精彩内容,欢迎继续阅读

较早相关资讯

最新相关资讯

Apache SeaTunnel 2.3.3 与 Web 控制台部署全记录 2026/10/1 4:23:25

Apache SeaTunnel 2.3.3 与 Web 控制台部署全记录

如果你在大数据或者数据平台领域待过一阵子,肯定听说过Apache SeaTunnel。它是一款使用门槛很低的分布式数据集成工具,能帮你把各种数据源之间搬数据,比如MySQL到Hive、Kafka到ClickHouse等等。相比其他同步工具,SeaTunnel最吸引我…

阅读更多 →
YOLOv8+PaddleOCR车牌识别完整工程实践 2026/10/1 4:23:25

YOLOv8+PaddleOCR车牌识别完整工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

阅读更多 →
Wine + FEX-Emu + DXMT:在iOS上运行Windows应用的兼容层技术栈解析 2026/10/1 4:23:19

Wine + FEX-Emu + DXMT:在iOS上运行Windows应用的兼容层技术栈解析

1. 从“Madeira”这个名字说起:它到底指什么第一次看到“Madeira”这个词,绝大多数人脑子里蹦出来的可能是那座葡萄牙的度假海岛,或者是那种叫马德拉的加强葡萄酒。但在我这个常年折腾跨平台兼容层的人眼里,这个词出现在技术语境里…

阅读更多 →
Radon-Fourier算法:破解雷达高速目标距离徙动与相参积累难题 2026/10/1 4:23:12

Radon-Fourier算法:破解雷达高速目标距离徙动与相参积累难题

1. 为什么你的雷达总是“看丢”运动目标做雷达信号处理的兄弟应该都有过这种体验:静止目标用MTD做相参积累,信噪比蹭蹭涨,检测轻轻松松;一旦目标运动起来,尤其是高速、高机动目标,积累增益就开始打折&#…

阅读更多 →
深度解析中国乘用车T-Box市场:技术、产业链与选型避坑指南 2026/10/1 4:23:12

深度解析中国乘用车T-Box市场:技术、产业链与选型避坑指南

先问一个问题:你手上那台车的远程控车、远程空调、忘锁车提醒,这些看起来“很智能”的功能,背后到底是什么硬件在干活?答案就是T-Box。这个词在车联网圈子里几乎天天被提到,但真正能把小盒子的市场格局、技术架构和量产…

阅读更多 →
深入PyTorch内部机制:Tensor、Autograd与算子调度实战 2026/10/1 4:23:12

深入PyTorch内部机制:Tensor、Autograd与算子调度实战

1. 为什么值得花时间啃PyTorch内部机制很多人用PyTorch的路径都差不多:跟着教程搭个CNN,跑通MNIST,然后开始调包训练自己的模型。能跑就行,谁管它里面怎么转的?我一开始也是这个心态,直到有次训练loss突然变…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

联系尧图顾问,获取一对一建站咨询

立即免费咨询 📞 400-888-8888
📞 ✉