新闻详情

新闻详情

首页 / 资讯中心 / 详情

基于Qt的C++拼图游戏开发:从切图到可解打乱与避坑指南

发布时间:2026/10/1 9:09:57来源:尧图网络
基于Qt的C++拼图游戏开发:从切图到可解打乱与避坑指南
简介基于Qt界面库开发的C拼图游戏课程设计源码包面向高校C期末大作业、Qt入门实践及图形界面课设参考覆盖图像切割、随机打乱、拼图交互、移动判定、成功检测与界面切换等典型功能模块结构适合直接运行与讲解答辩。压缩包共181个文件约48.91MB含17个cpp与9个h核心源码、3个ui界面文件、qrc资源文件及图片音频素材同时包含调试生成的obj、lib、dll、tlog等构建产物基于Visual Studio与Qt联合工程可直接打开sln/vcxproj运行。已有636人浏览学习适合作为课程设计选题参考和二次开发基础。包内保留完整工程配置与构建链可从主程序入口理清拼图打乱、移动判定、胜利弹窗等实现逻辑附带说明文档和全套素材省去环境搭建与资源查找时间便于快速完成并提交大作业尤其适合需要快速交付期末大作业的读者。1. 基于Qt的C拼图游戏期末大作业为什么选它课程设计C期末大作业基于Qt开发库开发的拼图游戏源码.zip——这类选题我每年都见到交付效果却天差地别。拼图游戏的本质是用Qt把一张图片切成n×n块打乱到网格里靠点击相邻块与空格交换最终复原原图。它好在难度卡得准C语法、STL容器、类设计、信号槽、绘图和事件处理全都能踩到又有图形界面可以现场演示不会像图书管理系统那样一开口就被判“没技术含量”。适合C基础已经过完、想在期末前交出一份完整界面程序的同学也适合第一次认真接触Qt的人。按可解拼图的逻辑走一天搭框架两天调细节答辩完全来得及。2. Qt开发环境与工程骨架搭一个能跑起来的起点先把环境问题解决再谈代码。很多“拼图游戏源码.zip”下载下来不是不能跑而是本机Qt版本和作者不一致打开就是一片红。课程设计场景下我建议直接按下面这套来选型然后用CMake把工程骨架立起来。2.1 Qt版本选型5.15.2还是6.xMinGW还是MSVC如果机房和笔记本都要跑我推荐Qt 5.15.2 LTS。6.x已经出来好几年但很多教学环境、课件范例还停在5.x你用5.15写的代码在6.x上大概率也能编过反过来却不保证。去Qt官网下载在线安装包选中Qt 5.15.2组件务必展开后把“MinGW 8.1.0 64-bit”或“MSVC 2019 64-bit”勾上这两者是编译器套件不是Qt本身。Windows下只在Qt Creator里写MinGW对新手最省心不用额外装Visual Studio如果老师要求用VS就选MSVC套件还要补装对应的“Microsoft Visual C Redistributable”否则运行时会报缺dll。Qt下载安装时不要贪多课程设计用Widgets模块就够。QML、Android交叉编译组件好几个G装完只会拖慢系统。装完打开Qt Creator在“工具→选项→Kits”里确认能看到“Desktop Qt 5.15.2 MinGW 64-bit”这个套件看不到就点“重新扫描”。这里有个隐患电脑上如果装过多个Qt版本Kits自动检测可能配错编译器导致后续头文件、库版本混用第5章会专门讲那个经典报错。另外拿到zip先解压到一个纯英文无空格的路径比如D:\Puzzle目录带中文或空格会引发一些莫名其妙的构建问题。2.2 CMakeLists.txt配置版本号、自动moc和资源建工程用CMake而不是qmake。qmake虽然只有几行但工程复杂后不好扩展而且现在很多答辩提问默认CMake你顺手用CMake写一遍能多讲两句。Qt Creator新建项目选“Application → Qt Widgets Application”构建系统选CMake让它生成最小工程再把CMakeLists.txt改成这样cmake_minimum_required(VERSION 3.16) project(PhotoPuzzle) set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # AUTOMOC让带有Q_OBJECT的头文件自动经过moc处理 set(CMAKE_AUTOMOC ON) # AUTORCC让.qrc资源文件自动经过rcc编译 set(CMAKE_AUTORCC ON) find_package(Qt5 5.15 REQUIRED COMPONENTS Widgets) add_executable(PhotoPuzzle main.cpp MainWindow.cpp MainWindow.h PuzzleBoard.cpp PuzzleBoard.h resources.qrc ) target_link_libraries(PhotoPuzzle PRIVATE Qt5::Widgets)set(CMAKE_AUTOMOC ON)和set(CMAKE_AUTORCC ON)这两行不能省否则信号槽编译报错、qrc资源不生效。resources.qrc要实实在在列在add_executable里AUTORCC才会处理它。工程里每新增一个窗口类记得同步把它加进这一节漏掉的文件不会参与编译但不会报错只在运行时出现“未定义符号”之类的怪现象。main.cpp的写法也定型一下。窗口不能放在栈上构造后直接return那样窗口没显示程序就退出标准入口是进入事件循环#include MainWindow.h #include QApplication int main(int argc, char *argv[]) { QApplication app(argc, argv); MainWindow w; w.show(); return app.exec(); }这段没有业务逻辑它只负责创建事件循环。Qt程序里所有点击、计时、重绘都靠app.exec()驱动你要是把它换成while死循环界面会卡死。图片加载、初始化这些操作放在MainWindow构造函数里而不是塞到main里。2.3 qrc资源文件图片打包进exe的正确姿势拼图游戏没有素材就跑不起来。不要在代码里写绝对路径比如C:/User/Desktop/cat.jpg课程设计要交源码和演示换一台电脑路径就废。把图片放进qrc资源文件编译时会内嵌进可执行文件别人拿到zip解压后运行只要exe在图就在。先在工程里新建resources.qrc把一张原图、一张空格占位图加进去它的结构长这样RCC qresource prefix/ file aliaspuzzle.jpgimages/puzzle.jpg/file file aliasblank.bmpimages/blank.bmp/file /qresource /RCC在C里加载资源统一用冒号前缀QPixmap origin(:/images/puzzle.jpg);两点要提醒。第一开头这个冒号是Qt资源协议的一部分别写成/images/puzzle.jpg那就变成去读本地文件系统了。第二qrc编辑界面在Qt Creator里可以直接拖图但建议理解手写XML的结构出现路径问题时能更快定位。资源文件的位置和qrc中声明的相对路径必须一致图片素材别超过1MB否则exe会膨胀。网上流传的拼图素材图拿来练习没问题课程设计原创性弱了点建议自己拍一张横向风景照或者选正方形构图的原图下一章会讲为什么要尽量取正方形。3. 拼图核心逻辑图片切分、打乱与交换判定界面能弹出来之后先写PuzzleBoard这个不依赖界面的核心类。我习惯把游戏逻辑全部放在一个纯C类里不碰Qt界面组件这样便于测试答辩时也能说“我做了逻辑与界面分离”。类的核心成员就两个棋盘数组vector 和边长m_size值0代表空格其余1到n*n-1代表图片块编号。3.1 切分图片先等比缩放再居中裁剪把原图统一处理成正方形再按n×n切成小方格。切图用QPixmap::copy(QRect)按区域抠不要用QPainter逐个画像素后者容易产生锯齿且代码啰嗦。// src 是已处理的正方形原图gridSize 是行列数id 从0到gridSize*gridSize-1 QPixmap PuzzleBoard::cutPiece(const QPixmap src, int id, int gridSize) { int w src.width() / gridSize; int h src.height() / gridSize; int col id % gridSize; int row id / gridSize; return src.copy(col * w, row * h, w, h); }这里依赖的src要先统一处理不要在切每一块时才缩放。统一处理我放在界面类里// 把加载的原图等比缩放铺满480x480再从中间裁出正方形 QPixmap squareRaw origin.scaled( 480, 480, Qt::KeepAspectRatioByExpanding, // 等比放大到最短边超过480 Qt::SmoothTransformation ); int x (squareRaw.width() - 480) / 2; int y (squareRaw.height() - 480) / 2; QPixmap square squareRaw.copy(x, y, 480, 480);参数要理解到位。KeepAspectRatioByExpanding是等比放大到图片能铺满480×480可能出现图片超出目标尺寸的部分再用copy从正中间挖出480×480效果等价于“居中裁剪”。如果你用IgnoreAspectRatio强制拉伸图会变形用KeepAspectRatio不带Expanding又会有黑边。SmoothTransformation是平滑缩放默认的FastTransformation会出锯齿。480这个数字不是随便定的4×4时每块正好120px要支持5×5可以改成500或者根据界面按钮尺寸动态算。切完的小图还有一个细节copy出来的子图是120×120但按钮控件自身会有边距和边框相邻两块之间可能露出1像素背景色深色背景下特别明显。我给每块画一个深灰细边框QPainter p(piece); p.setPen(QPen(QColor(40, 40, 40), 1)); p.drawRect(piece.rect().adjusted(0, 0, -1, -1));QPainter在函数内部作为局部对象使用没问题drawRect画完即析构。这样视觉上拼图块之间有细线分隔而不是露出窗口背景。3.2 可解的打乱别用std::shuffle这是拼图游戏最容易翻车的地方也是C随机数在这个项目里的关键应用。如果用std::shuffle把0到n*n-1随机排一遍大约一半的初始棋盘永远拼不回去表现是拼到最后两块反着前功尽弃。原因是15-puzzle这类滑板拼图存在排列奇偶性约束空格每次移动交换两个块会改变排列的奇偶状态但并非所有奇偶状态都可达。课程设计最稳的方案不是做数学判定而是从完成态出发模拟空格往四个方向随机移动几百步。因为每一步都是合法移动最终棋盘一定可解。代码如下#include random #include numeric void PuzzleBoard::shuffleBoard() { // 0 表示空格其余为 1..size*size-1 std::vectorint tmp(m_size * m_size); std::iota(tmp.begin(), tmp.end(), 0); // 完成态0在末尾 int empty m_size * m_size - 1; // 空格初始在右下角 // C随机数mt19937配合random_device做种子别再rand() std::mt19937 rng(std::random_device{}()); for (int step 0; step 300; step) { int r empty / m_size; int c empty % m_size; static const int dr[] {-1, 1, 0, 0}; static const int dc[] {0, 0, -1, 1}; int dir rng() % 4; // 0上 1下 2左 3右 int nr r dr[dir]; int nc c dc[dir]; if (nr 0 || nr m_size || nc 0 || nc m_size) { continue; // 越界方向跳过不计步数 } std::swap(tmp[empty], tmp[nr * m_size nc]); empty nr * m_size nc; } m_board.swap(tmp); }三个参数说明。第一std::iota生成完成态保证tmp里的排列是0,1,2,...的顺序0作为空格排在最后而不是最前这个约定要和胜利判定保持一致。第二为什么300步4×4棋盘大约100次移动就足够混乱300次让顺序更自然5×5建议调到500太小残局一眼看出原图太大看不出算法稳定。第三越界时用continue而不是break只放弃当前方向、不减少总步数避免边界区域打乱不充分。这段代码直接绕开了无解棋盘你甚至可以暂时不写可解性判定第6章我会补一个自测函数。注意全工程要约定统一的值语义。我这里0是空格1是原图左上角第一块最后编号是右下角那块。如果你把0当成第一块图胜利判定、切图映射、界面显示全部要跟着改最容易错位。3.3 点击移动与胜利判定边界检查比看起来重要交互规则就一条点击的块必须与空格相邻才允许移动点其他位置静默忽略不弹错也不乱跳。实现如下bool PuzzleBoard::tryMove(int row, int col) { if (row 0 || row m_size || col 0 || col m_size) { return false; // 入口先做越界保护 } auto blankIt std::find(m_board.begin(), m_board.end(), 0); int blank static_castint(blankIt - m_board.begin()); int br blank / m_size; int bc blank % m_size; if (std::abs(row - br) std::abs(col - bc) ! 1) { return false; // 曼哈顿距离不为1不是相邻块 } int idx row * m_size col; std::swap(m_board[idx], m_board[blank]); m_moves; return true; }越界保护必须写在最前面。虽然UI层传来的row、col来自按钮下标理论合法但一旦你在这个函数里复用、传入异常值abs处理负数会得到不合理的正数最终交换到错误的位置。把保护放在底层入口是写通用组件的习惯。胜利判定用顺序比较法bool PuzzleBoard::isSolved() const { for (int i 0; i m_board.size() - 1; i) { if (m_board[i] ! i 1) { return false; } } return m_board.back() 0; // 空格在右下角才算赢 }isSolved在每次点击后都会调用4×4只有16个元素性能无所谓但只读方法记得加const修饰。到这里PuzzleBoard的核心接口齐了reset、shuffleBoard、tryMove、isSolved再加上一个读取棋盘的访问器board()。界面层只负责把状态画到按钮上、把点击传给tryMove业务错误不会污染UI答辩时这个设计能加分。4. 界面与交互从点击到计时的完整实现核心逻辑跑通后界面层要做的是“把棋盘状态画到按钮上把鼠标点击翻译成tryMove”。不少课程设计在这一步开始糊按钮和数字全写死换不了图、改不了难度后面就没法继续。这一章把界面搭法、动态刷新和附加功能一次讲完。4.1 Qt Designer还是手写布局期末作业的性价比选择Qt官方推荐用Qt Designer拖界面热词里也常见“Qt Designer界面设计”。但我的建议是本作业手写布局一个.ui文件也别加。理由有两点。第一拖出来的窗口会生成ui_mainwindow.h答辩时老师问“这部分代码是你写的吗”很难解释清楚。第二棋盘按钮数量随难度动态变化你没法在设计器里摆好固定16个或25个按钮设计器更适合做参数设置面板不适合做动态网格。所以界面结构这样做中央widget放一个QVBoxLayout从上到下叠三样东西——一个QLabel显示完整原图作为提示、一个QGridLayout放棋盘按钮、一个QHBoxLayout放控制按钮组新游戏、步数、计时、换图。整体只有三层布局代码一目了然。4.2 用QPushButton网格承载拼图块选QPushButton而不是QLabel是因为按钮自带clicked信号不需要重写mousePressEvent。棋盘按钮的创建放在initBoard函数里void MainWindow::initBoard() { // 清理旧布局里的按钮避免切难度或换图时控件重复累积 while (QLayoutItem* item m_gridLayout-takeAt(0)) { if (QWidget* widget item-widget()) { widget-deleteLater(); } } int n m_board.size(); m_buttons.clear(); for (int i 0; i n * n; i) { auto* btn new QPushButton(this); btn-setFixedSize(m_cellPx, m_cellPx); btn-setIconSize(QSize(m_cellPx, m_cellPx)); m_gridLayout-addWidget(btn, i / n, i % n); int row i / n; int col i % n; connect(btn, QPushButton::clicked, this, [] { onTileClicked(row, col); }); m_buttons.append(btn); } updateBoard(); }lambda按值捕获row和col这一点很关键。如果写引用捕获或循环结束后再取所有按钮拿到的都是最后一次循环的i。QPushButton::clicked信号自带一个bool参数但connect时省略形参的lambda也能匹配这是Qt信号槽的参数兼容规则不用额外写(bool)。网格布局的间距建议设为0相邻按钮贴合才像拼图碎片如果想有一点缝隙设1像素即可。updateBoard负责把棋盘数据刷新到按钮图标void MainWindow::updateBoard() { const auto board m_board.board(); // 返回 const vectorint for (int i 0; i board.size(); i) { int id board[i]; QPixmap icon (id 0) ? m_blankPixmap // 空格显示空白图 : m_pieces[id - 1]; // 编号1对应pieces[0] m_buttons[i]-setIcon(QIcon(icon)); m_buttons[i]-setToolTip(QString::number(id)); } }注意编号偏移m_pieces容器按下标0开始存图第一块图在pieces[0]但棋盘编号是1所以取图要用id-1。这种“编号和数组下标差一”的问题是拼图项目高频翻车点写的时候瞪大眼睛。开局时m_board是完成态updateBoard会显示完整拼图点击“新游戏”后shuffle再updateBoard就变成了乱序棋盘。4.3 计步计时、新游戏与换图步数最直接。tryMove返回true就在界面更新一个QLabel显示m_board.moves()。计时用QTimer成员变量每秒触发一次void MainWindow::startTimer() { m_elapsedSeconds 0; m_timer.start(1000); // 每1000毫秒触发一次 } void MainWindow::onTimerTick() { m_elapsedSeconds; m_timeLabel-setText(QString(用时 %1 秒).arg(m_elapsedSeconds)); }connect写在MainWindow构造函数里只连接一次不要在每局游戏里重复连否则onTimerTick会被多次调用。QTimer触发精度对秒级计时足够如果你想显示毫秒可以改成100但界面闪烁会比较明显。“新游戏”按钮的逻辑是reset棋盘、shuffleBoard、清零步数和时间、updateBoard。先reset再shuffle确保起始状态是完成态再模拟移动如果直接对当前乱序棋盘调用shuffle状态会变得不可预期。“换图”按钮打开QFileDialogQString path QFileDialog::getOpenFileName( this, 选择图片, QString(), 图片文件 (*.png *.jpg *.bmp) ); if (!path.isEmpty()) { QPixmap origin(path); if (origin.isNull()) { return; // 加载失败静默返回 } loadPuzzleFromPixmap(origin); }loadPuzzleFromPixmap内部重新执行“等比缩放→居中裁剪→切块”流程再调用initBoard重建按钮。origin.isNull()的判断不能省否则后续scaled一个空pixmap会得到空图界面没有任何反馈。窗口尺寸我一般这样处理按钮尺寸不是写死120而是用480除以棋盘边长保证不同难度下总宽度一致。按钮放进布局后用户拖动窗口时布局会自动居中按钮不会拉伸变形。想做跟随窗口缩放也可以但需要重写resizeEvent重新切图课程设计不建议做锁定一个合理尺寸更省心。到这里一个4×4滑动拼图已经能玩选图、切分、打乱、点击交换、计时计步胜利后弹一个QMessageBox提示完成。下一步是把最容易翻车的环境问题和细节错误一次排干净。5. 拼图游戏避坑清单5个会让程序崩溃的细节很多同学逻辑写对了却卡在环境和细节上看起来像代码有bug其实是另一个层面的问题。整理5条高频踩坑记录按“现象→原因→解决”展开。5.1 CMake报“cannot mix incompatible Qt library”怎么办现象Qt Creator里点运行编译报fatal: cannot mix incompatible Qt library (version ex50601) with this library后面跟一串版本号。原因编译用的头文件来自一个Qt版本链接或运行时却用了另一个版本。最常见的是CMake缓存的CMAKE_PREFIX_PATH指到了旧Qt根目录或者PATH环境变量里同时存在多个Qt的bin目录顺序靠前的被优先找到。版本号里的ex50601一般是Qt 5.6.1或5.15相关变体遇到“version exxxxxx”字样基本可以断定是双版本混用。解决在Qt Creator里打开CMakeLists后进入“工具→选项→Kits”确认当前套件绑定的Qt版本是且仅是5.15.2然后删掉构建目录下的CMakeCache.txt重新Configure。如果依旧报错打开系统环境变量PATH把旧版本Qt的bin路径删除或移到末尾再重启Qt Creator。注意别同时设置Qt5_DIR和CMAKE_PREFIX_PATH指向不同版本这是最常见的隐性冲突源。5.2 qrc资源不生效运行时一片空白现象程序能编译能运行但所有按钮要么空白要么只显示背景色控制台输出类似“Could not load”或“QPixmap::scaled: Pixmap is a null pixmap”。原因qrc路径错误或资源文件根本没参与编译。常见写法是把图片放到工程目录外的绝对路径或者在别名为中文的路径下另一类是CMakeLists.txt的add_executable里漏写了resources.qrcAUTORCC没机会处理资源。解决代码里统一用“:/别名”方式加载“:/”后面必须和qrc文件里写的完全一致。如果肉眼看不出来直接在Qt Creator项目树里双击qrc文件逐个对照资源项。改过qrc后没重新构建也会出现资源不更新CMake理论上会自动检测qrc变更但遇到老构建目录手动Clean后重新构建更可靠。5.3 打乱后拼图无解最后两块死活反着现象4×4拼图玩到最后末尾两块位置互换棋盘卡死怎么点都回不到完成态。原因打乱使用了std::shuffle或手写全排列随机生成了不可达的排列。滑板拼图的合法移动只覆盖全部排列中满足特定奇偶条件的那一半无解棋盘对玩家没有任何提示就是干瞪眼。解决把打乱算法换成第3章的“从完成态模拟空格移动”这是从根源上消除无解。如果接手的是别人源码里面用了shuffle可以在shuffle后加一个isSolvable循环重洗直到可解为止第6章会给这个校验函数。但演示时这种方案可能反复重试造成卡顿不如直接改成模拟移动一步到位。5.4 快速连点边缘按钮程序闪退现象鼠标快速连续点击四周按钮程序瞬间退出调试器定位在std::vector相关代码报“out of range”或“access violation c0000005”。原因点击事件的row/col来自按钮数组下标正常情况下合法。翻车点一般在两处一是在处理点击的槽函数里重建了按钮旧按钮被deleteLater清理而事件循环还在派发clicked事件造成悬垂指针二是某段代码越过了tryMove的边界保护直接对board[row*ncol]读写边缘格子差一位就踩飞。解决PuzzleBoard::tryMove入口的row/col边界保护必须有这是第一道防线。UI层点击后不要立刻销毁重建按钮而是依靠updateBoard刷新状态deleteLater语义是“下一次事件循环再删除”不要在同一个槽函数里继续访问旧按钮。如果依旧闪退在tryMove里加一行qDebug() row col复现点击顺序看打印就能定位是哪一路非法坐标。5.5 高分屏和教室投影下图片模糊现象自己电脑上运行清晰接到教室投影或外接2K/4K屏上按钮里的图片变得模糊边缘还有色带。原因Windows下Qt默认的高分屏缩放策略与QPixmap尺寸不匹配。120px的图标在2倍缩放的屏幕上被拉伸到240px自然糊。另一个变体是main()里设置高分屏属性后投影仪驱动不支持缩放画面依旧拉伸。解决在main函数创建QApplication之前设置两条Qt属性QApplication::setAttribute(Qt::AA_EnableHighDpiScaling); QApplication::setAttribute(Qt::AA_UseHighDpiPixmaps); QApplication app(argc, argv);注意两条语句必须在app构造之前执行放在后面不生效。课程设计做到这一步就够了如果投影环境还是糊可以把原图分辨率提高切块前缩放到更大尺寸再平滑降采样会好一些。这类问题在答辩现场非常尴尬提前在投影上试一次比答辩台上临时调快捷得多。6. 验证与进阶让作业在答辩时不心虚主体功能跑通后花半天时间做两件事写一个不依赖界面的自测函数加两个性价比高的功能点。这两件事能让你在答辩时从“能跑”变成“有底气”。6.1 写一个不依赖界面的自测函数PuzzleBoard是纯逻辑类不依赖界面很适合在main里临时调用验证。一个小套路shuffleBoard后用15-puzzle的可解性公式检查结果这是防止“无解棋盘”再次混进代码的保险丝。bool isSolvable(const std::vectorint b, int n) { int inv 0; for (int i 0; i (int)b.size(); i) { for (int j i 1; j (int)b.size(); j) { if (b[i] b[j] b[i] b[j]) { inv; // 跳过空格0参与计数 } } } if (n % 2 1) { return inv % 2 0; // 奇数宽度逆序数必须为偶数 } int blank std::find(b.begin(), b.end(), 0) - b.begin(); int rowFromBottom n - blank / n; // 空格从底部数第几行 return (inv rowFromBottom) % 2 0; // 偶数宽度逆序数行号需同偶 }自测代码可以临时放在main里PuzzleBoard board(4); for (int i 0; i 100; i) { board.shuffleBoard(); Q_ASSERT(!board.isSolved()); Q_ASSERT(isSolvable(board.board(), 4)); }程序能跑完这100轮说明打乱逻辑既没有原地不动也没有产出无解状态。Q_ASSERT只在Debug构建下生效Release下会被编译器优化掉所以它不是运行时校验而是开发期自检工具。答辩时你可以现场说“这里用奇偶性公式校验了100次打乱结果全部可解”这句话比贴十行代码更有说服力。6.2 值得加的三个功能与工作量排序如果还有时间按性价比排序加功能。第一是难度选择QComboBox里放3/4/5三个选项切换后重新initBoard半小时以内。第二是自定义图片已经实现了QFileDialog换图那是展示原图外的加分项。第三是最佳步数记录用QSettings存到注册表或配置文件里代码量很小QSettings settings(MySchool, PhotoPuzzle); int best settings.value(best, INT_MAX).toInt(); if (m_moves best) { settings.setValue(best, m_moves); statusBar()-showMessage(QString(新纪录 %1 步).arg(m_moves)); }存储的路径参数“MySchool”“PhotoPuzzle”是组织名和应用名不需要真实存在Qt自动管理。答辩时演示“新纪录”弹出来效果比“程序不崩溃”直观得多。不建议再加“提示正确位置高亮”这类功能边界情况多代码量大期末阶段容易把自己拖进新坑。我的习惯是课程设计代码一定留一个Debug后能跑完的自测入口这样答辩前连夜改代码至少能自动验证核心逻辑没被改坏。希望这些切图细节、打乱算法和踩坑经验能让你少熬两个晚上。祝答辩顺利。本文还有配套的精品资源点击获取
网站建设高端定制企业官网
RELATED

相关资讯

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

较早相关资讯

最新相关资讯

Modern JavaScript Tutorial 实战:范围外判断的两种等价写法——NOT 变体与德摩根定律 2026/10/1 9:57:18

Modern JavaScript Tutorial 实战:范围外判断的两种等价写法——NOT 变体与德摩根定律

文档/教程前端 【免费下载链接】en.javascript.info Modern JavaScript Tutorial 项目地址: https://gitcode.com/gh_mirrors/en/en.javascript.info 点击查看 免费下载 导读 本文围绕 en.javascript.info(Modern JavaScript Tutorial)中&…

阅读更多 →
DeepSeek Harness 插件实战:dshmarket 与 modlens 安装配置及故障排查指南 2026/10/1 9:57:11

DeepSeek Harness 插件实战:dshmarket 与 modlens 安装配置及故障排查指南

1. 为什么我要花时间折腾 DeepSeek Harness 插件第一次接触 DeepSeek Harness 是在一个做智能体工作流的朋友那里。他当时给我演示了一段自动化流程:从本地知识库拉取资料,经过模型推理,再自动生成结构化的项目文档,整个过程行云流…

阅读更多 →
多租户Odoo SaaS部署实战:Docker架构、实例管理与避坑指南 2026/10/1 9:57:11

多租户Odoo SaaS部署实战:Docker架构、实例管理与避坑指南

简介:整套基于Docker的多租户Odoo实例管理方案,适合具备服务器管理经验、负责企业级Odoo部署与运维的技术人员。该资源详细讲解SaaS Kit工具包的安装配置流程,包括Python依赖库的安装、目录结构搭建、Nginx与PostgreSQL配置、Odoo用户权限调整…

阅读更多 →
技术选型的经历,比「我用了什么」值钱得多 2026/10/1 9:57:05

技术选型的经历,比「我用了什么」值钱得多

技术简历上写「使用 Kafka 实现异步解耦」,和写「在 Kafka 和 RabbitMQ 之间选了前者,因为……」,是两个层级。 前者说明你会用,后者说明你会判断。而工作年限越长,后者的权重越高。 为什么选型经历值钱 因为它暴露的是…

阅读更多 →
OFDM低复杂度无边带SLM改进:分组选择映射与幅值标记法 2026/10/1 9:57:05

OFDM低复杂度无边带SLM改进:分组选择映射与幅值标记法

简介:面向无线通信与信号处理领域,这份资源针对OFDM系统峰均功率比(PAPR)过高的问题,提出基于选择映射(SLM)的低复杂度改进方案。传统SLM需多次IFFT计算候选信号,还要传输边带信息&a…

阅读更多 →
内存又炸了!我用C# IAsyncEnumerator给国产库做“流式Left Join“,2亿行数据OOM从此说拜拜 [特殊字符] 2026/10/1 9:57:05

内存又炸了!我用C# IAsyncEnumerator给国产库做“流式Left Join“,2亿行数据OOM从此说拜拜 [特殊字符]

🩸 一、 翻车剖析:传统 JOIN 的"内存黑洞"是怎么形成的? 很多新手老铁写 LEFT JOIN,脑子里想的是这样的: // ❌ 反面教材:内存黑洞写法 var orders await conn.QueryAsync(“SELECT * FROM t_or…

阅读更多 →

今日资讯

本周资讯

本月资讯

看完文章仍有疑问?

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

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