A*算法+往返式覆盖:网格环境全覆盖路径规划Matlab实现
前两天整理之前做的一个移动机器人全覆盖路径规划仿真项目发现当时最顺手的一套方案就是A算法加往返式覆盖策略在栅格地图上直接用Matlab跑通。今天把这段实现思路完整拆一遍A算法负责在网格环境里找最优转移路径往返式覆盖负责真正把每一片格子走完两者合起来解决的是扫地机器人、植保无人机这类设备的“全覆盖路径规划”问题。如果你正需要一套能在Matlab里跑起来、看得见效果、还能往上加改进的覆盖路径规划代码这篇应该能给你省不少事。1. 项目背景与核心思路拆解1.1 全覆盖路径规划到底在解决什么问题全覆盖路径规划Coverage Path Planning简称CPP和传统路径规划有个本质区别。传统路径规划是“点对点寻路”给你起点和终点找一条最短并且能避开障碍的路线。而全覆盖路径规划的目标是“把整个可通行作业面都走一遍”要求设备经过地图上每一个可达位置同时尽量做到不遗漏、不重复、总路径最短。这个需求在实际场景里太常见了。扫地机器人在客厅来回扫不是在找某个具体目标点而是要把地面每个角落都覆盖一遍植保无人机飞过农田每一垄都得到位漏喷一片作物就可能减产仓库盘点AGV需要在货架间穿梭把库存区域整体扫一圈还有一些更极端的应用比如船体外壁除锈、光伏面板清洁、大型储罐检测本质上都是“要么扫干净要么测到位”的全覆盖问题。也正是因为应用面广全覆盖路径规划一直是移动机器人方向的研究热点。标题里提到的网格环境是最常用的环境离散化方式把连续空间切成一个个格子机器人只能沿格子移动障碍物占掉一部分格子剩余格子就是需要覆盖的区域。这样做的好处是地图表达简单、碰撞检测直接、路径规划算法容易落地。但难点也在这里网格地图尺寸一大格子数量动不动就是几百上千要找出“覆盖全部格子且重复最少”的路线本质上是个组合优化问题直接暴力搜索会炸。所以通常的办法是把问题拆开选择合适的覆盖策略配合一段高效的转移路径规划两层结合来逼近较优解。1.2 A*算法在这个项目里扮演什么角色先说一个容易踩的误区A算法本身解决不了全覆盖问题。经典A是一种启发式搜索算法目标是寻找从起点到终点的一条代价最小的路径。它需要明确知道“终点在哪里”而全覆盖任务里没有一个明确的终点——或者说目标是“所有格子都要到”而不是“某个格子要到”。如果直接拿A*做全覆盖算法会陷入“不知道往哪搜”的状态。那么A在标题里出现的原因是什么答案是区域间转移路径规划这是把往返式覆盖和A结合起来的核心逻辑。往返式覆盖也常称为牛耕式、boustrophedon策略会在地图上生成一条一条的覆盖行但遇到障碍物时覆盖行会被切断产生很多相互隔离的覆盖段。这些覆盖段之间怎么衔接如果机械地按顺序走机器人经常要绕远路甚至穿过障碍如果随意走又会导致大量重复覆盖。这时候就需要一个底层寻路器负责找出当前覆盖段末端到下一段起点之间的最优路径。这个寻路器选A*非常合适它能在网格环境里较快地搜索出避障路径而且能通过调整代价函数来偏向某些格子这一点后面细说。所以这套方案的整体逻辑是往返式覆盖负责“干活”A*负责“赶路”。两件事各司其职组合起来就是标题里说的“基于A星算法的网格环境下的往返式全覆盖路径规划”。1.3 为什么覆盖策略选“往返式”我见过不少人在覆盖策略上纠结螺旋式覆盖、随机式覆盖、仿生式覆盖各有各的拥趸。但往返式在绝大多数网格环境下是性价比最高的选项原因很朴素简单、稳、可控。往返式覆盖就像农民耕田从地图一边开始沿着一行走到头然后换到下一行反方向走回来一行一行推过去直到把整个区域耕完。这种策略的优点是覆盖路径规整行间距固定机器人的运动模式可以预测转弯集中在行尾覆盖顺序清晰易懂。在凸多边形区域里往返式的表现几乎是最优的路径总长度接近理论下限。螺旋式覆盖在很多地图上容易“锁死”机器人沿着外墙绕圈绕到中心发现进不去内部区域还得额外设计“从外圈切入内圈”的规则。一旦环境是非凸形状或者地图中间有岛状障碍物螺旋式就要处理大量特殊case。随机式覆盖虽然实现简单但重复覆盖率太高路径杂乱作业效率没法保证。往返式当然也有短板遇到障碍物时覆盖行会被打断成碎片机器人在段与段之间切换时需要转移路径这部分就是A发挥作用的场景。正因为往返式把“覆盖”和“转移”两个问题天然切分开了才让A的介入非常自然。这也是我推荐这个组合作为项目起步方案的原因——逻辑清晰可改进空间大后期换成螺旋式或启发式覆盖都能直接复用转移层。2. 网格环境建模与Matlab数据准备2.1 栅格地图的三种表达方式写Matlab代码之前先把地图数据结构敲定。我在实际项目里常用的栅格地图表达方式有三种各有适用场景。第一种是普通二维矩阵这也是项目里最推荐的方案。用map(row, col)表示一个格子0代表可通行/未覆盖1代表障碍物。Matlab对矩阵的操作非常方便索引直观配合imagesc可以直接可视化地图调试起来很顺手。第二种是occupancyMap对象这个类主要在ROS Toolbox中使用可以用激光雷达数据直接赋值。优点是和机器人操作系统联动好但做算法研究时有额外转换成本而且很多老版本用户不方便我个人建议纯仿真阶段不用它。第三种是graph对象或自定义邻接表把每个格子当成节点格子之间的通行关系当成边。这种结构搜索效率高适合大规模地图但可视化不便代码量也更大。结论很清楚学术研究和算法验证阶段直接用二维矩阵。等后面需要部署到真实机器人上再考虑换成更复杂的数据结构。以下代码可以快速生成一张带随机障碍的测试地图rows 20; cols 20; obsRatio 0.2; map zeros(rows, cols); numObs round(rows * cols * obsRatio); idx randperm(rows * cols, numObs); map(idx) 1; % 保证起点(1,1)和终点区域可通行 map(1, 1) 0;有一点必须提醒随机生成障碍时如果起点周围堵死后面A*搜索会直接报告目标不可达。所以生成地图后一定要手动清理机器人起始点和它邻近的几个格子的障碍标记。2.2 邻域定义与坐标转换的工程细节网格环境里很重要的一件事是确认机器人能从某个格子走到哪些相邻格子。这个“相邻”的定义直接影响路径形态。四邻域表示机器人只能向上、下、左、右4个方向移动路径全部是横平竖直的折线和现实中很多差速轮机器人的运动方式比较接近。八邻域允许再加上4个对角方向路径更短但代价是路径会出现斜插现象在某些覆盖场景下会造成锯齿状覆盖边界增加重复覆盖面积。全覆盖项目我倾向于用四邻域原因后面在A*实现部分会展开。无论用哪种邻域Matlab里都会遇到一个高频问题行列坐标和线性索引的转换。比如我们要在地图上查找某个格子的邻居用行列坐标最直观但open list、closed list里存线性索引更省内存、比较更快。这时就用sub2ind把行列转成线性索引用ind2sub把线性索引还原成行列。一个容易踩的坑是Matlab是列优先存储sub2ind([rows, cols], r, c)的第一个坐标参数是行、第二个是列千万别写反。邻居获取函数的经典写法可以参考这段function nbrs getNeighbors(idx, rows, cols, map) dirs [-1 0; 1 0; 0 -1; 0 1]; % 四邻域偏移 [r, c] ind2sub([rows, cols], idx); nbrs []; for k 1:4 nr r dirs(k, 1); nc c dirs(k, 2); if nr 1 nr rows nc 1 nc cols map(nr, nc) 0 nbrs [nbrs, sub2ind([rows, cols], nr, nc)]; end end end注意判断顺序先判断边界再访问map(nr, nc)。我见过不少新手先索引后判断越界直接报Subscript indices must either be real positive integers or logicals卡半天。2.3 测试地图的生成思路做算法实验不能只用一张地图否则你不知道算法是碰巧跑通还是真的稳健。我建议至少准备三类测试地图。第一类是空旷地图几乎没有障碍用来验证往返式覆盖和A的基础逻辑是否正确。第二类是随机障碍地图障碍比例控制在10%到30%之间用来观察覆盖率、重复率随环境复杂度的变化。第三类是带“墙型”障碍的地图比如地图中间有一堵横墙、几个岛状障碍专门测试A在狭长通道中的寻路能力和段间转移效果。生成随机障碍地图时有一种经验做法用randperm一次性抽取障碍位置然后用一个小的形态学操作避免障碍物密集连成墙。Matlab里可以用imdilate对障碍做一次膨胀把相邻的障碍格连成更真实的障碍区域。当然如果只是验证算法不膨胀也能跑只是地图形态会更“碎”。地图准备好之后建议加一行可视化代码figure; imagesc(map); colormap(gray); axis xy;这里的axis xy很重要。不做这步的话Matlab会把第1行显示在图像底部图上的“上”和矩阵里的“上”对不上肉眼判断路径方向时容易看反。3. 往返式覆盖逻辑的设计与实现3.1 单行扫描从行首走到行尾往返式覆盖的第一步是生成基础覆盖路线。假设我们按行扫描从地图顶部第一行开始从左往右走完这一行所有可通行格子然后下移一行从右往左扫回来如此交替直到扫完所有行。核心循环在Matlab里非常简洁visited false(rows, cols); direction 1; % 1表示从左到右-1表示从右到左 path []; for r 1:rows if direction 1 colSeq 1:cols; else colSeq cols:-1:1; end for c colSeq if map(r, c) 0 visited(r, c) true; path [path; r, c]; end % 遇到障碍物不处理继续跑下一个格子 end direction -direction; end这个版本的逻辑虽然简单但有一个明显问题如果某一行某个位置被障碍物挡住机器人无法直接从障碍物左侧走到右侧遇到障碍物时它需要绕路。所以实际工程项目里不会真的让机器人“穿墙而过”而是把行扫描切割成若干覆盖段这一段扫完就跳到另一段。3.2 覆盖段的切分与数据结构设计覆盖段Segment是往返式覆盖和A*之间的接口也是整个项目里最关键的数据结构。我实践下来一个覆盖段至少需要包含这几个字段行号、列起始点、列结束点、覆盖方向、段起点坐标、段终点坐标。把地图按行扫描时遇到障碍物就把当前连续的可通行格子截断形成一段。比如第5行第1到第3列可通行第4列是障碍第5到第8列又可通行那么这一行就生成两个覆盖段。把所有行的覆盖段都提出来放进一个列表里备用。这段逻辑可以这样概括segments []; for r 1:rows c 1; while c cols if map(r, c) 0 cStart c; while c cols map(r, c) 0 c c 1; end cEnd c - 1; segments [segments; r, cStart, cEnd]; else c c 1; end end end这里的cStart和cEnd是一个覆盖段在该行的左右边界。段起点取(r, cStart)段终点取(r, cEnd)。相邻两段之间可能会出现很大的空隙这个空隙就是需要A*去搜索的转移路径。关于覆盖段切分还有个细节如果机器人要从上一段的终点去下一段的起点这两点之间如果隔了几行那么他在纵向移动的过程中“经过”的那些格子算不算覆盖实际物理过程中扫地机经过的地面肯定已经扫过了所以在项目里我会把A*转移路径经过的所有格子也标记为已覆盖。这样能保证最终统计的覆盖率和实际作业情况一致。3.3 段间衔接与贪心调优覆盖段全部切分好之后还需要决定覆盖顺序。最简单粗暴的顺序是按覆盖段在列表里的出现顺序依次覆盖但这样往往会产生大量空驶因为地图右上角的段可能要被排到很后面才覆盖机器人要先从中部绕过去再绕回来。更实用的做法是贪心最近邻每次覆盖完当前段从所有未覆盖段中选出“距离最近”的一段作为下一个目标。这个“距离”可以先不算A路径直接用曼哈顿距离估算因为贪心阶段只需要粗排序等到真正执行转移时再调A去搜索精确路线。这里还有个优化点每个覆盖段有两个端点段起点和段终点。如果当前机器人位于上一段的终点那么计算它到下一段两个端点的距离时应该取较小值并且决定到达下一段后从哪个端点开始覆盖。这样相当于把“段内方向”也纳入决策能进一步减少空驶。我试过之后路径总长度大约能再降5%到8%。段间衔接的伪代码思路如下while ~all(covered) % 在当前覆盖段内执行往返覆盖 travelToNextSegment(); % 找到最近的未覆盖段 bestCost inf; for s 1:numSegments if ~segments(s).covered d min(abs(currentPos - segments(s).start), abs(currentPos - segments(s).end)); if d bestCost bestCost d; nextSeg s; end end end % 用A*计算实际转移路径 transferPath astar(map, currentPos, segments(nextSeg).start); end贪心不是最优解但胜在简单可靠。如果你想在这个项目上继续做研究可以换成遗传算法、粒子群算法来优化段覆盖顺序但起步阶段建议先用贪心把整体流程跑通。4. A*转移路径的Matlab实现与调优4.1 经典A*主循环怎么写A*算法在网格环境里的核心步骤不复杂但写代码时处理好数据结构很重要。基本思路是维护一个open list和一个closed listopen list里放待考察节点closed list放已考察节点每个节点记录gScore从起点到该节点的实际代价、hScore该节点到目标点的估计代价和fScore gScore hScore每次从open list里取出fScore最小的节点扩展直到找到目标点。Matlab里没有现成的优先队列但网格规模在几百到几千个格子时直接用数组加min函数扫描也能跑只是地图大了会慢。我在项目里的做法是维护一个open list数组每次取最小值的索引用完后从数组中删除。如果地图规模超过100×100建议改成二叉堆实现优先队列但这就是另一个层面的优化了。一个完整的A*主循环长这样function path astar(map, startIdx, goalIdx) rows size(map, 1); cols size(map, 2); openList startIdx; cameFrom zeros(rows * cols, 1); gScore inf(rows * cols, 1); fScore inf(rows * cols, 1); gScore(startIdx) 0; fScore(startIdx) heuristic(startIdx, goalIdx, rows, cols); closedList false(rows * cols, 1); while ~isempty(openList) [~, minIdx] min(fScore(openList)); current openList(minIdx); if current goalIdx path reconstructPath(cameFrom, current); return; end openList(minIdx) []; closedList(current) true; nbrs getNeighbors(current, rows, cols, map); for nb nbrs if closedList(nb) continue; end tentativeG gScore(current) 1; % 四邻域每步代价为1 if tentativeG gScore(nb) cameFrom(nb) current; gScore(nb) tentativeG; fScore(nb) gScore(nb) heuristic(nb, goalIdx, rows, cols); if ~ismember(nb, openList) openList(end 1) nb; end end end end path []; end很多人第一次写A*会漏掉“如果找到更优路径更新gScore并重新计算fScore”这一步。如果没有这个判断算法退化成贪心最佳优先找到的路径往往不是最短路径。上面的tentativeG gScore(nb)判断就是在处理这个问题。4.2 启发式函数怎么选A*的灵魂在启发式函数。启发式函数h(n)是从当前节点到目标节点代价的估计值估计越准搜索效率越高但必须满足“不大于真实代价”的前提才能保证最优性这叫可采纳性。网格环境下最常用的三种启发式曼哈顿距离abs(r1-r2) abs(c1-c2)只在四邻域移动时成立是四邻域下最标准的选择可采纳且比较紧凑。欧几里得距离sqrt((r1-r2)^2 (c1-c2)^2)适合八邻域且对角线移动代价为sqrt(2)的情况。切比雪夫距离max(abs(r1-r2), abs(c1-c2))适合八邻域且对角线移动代价为1的情况。全覆盖项目里我推荐四邻域加曼哈顿距离。理由是全覆盖任务要求路径规整斜插会让覆盖边界参差不齐四邻域天然避免这个问题。而且四邻域下的每个格子的邻域数量少A*的搜索分支少计算更快。决策代码可以这样写function h heuristic(idx, goalIdx, rows, cols) [r1, c1] ind2sub([rows, cols], idx); [r2, c2] ind2sub([rows, cols], goalIdx); h abs(r1 - r2) abs(c1 - c2); % 曼哈顿距离 end4.3 已覆盖区域代价加权降低重复覆盖这一步是项目里我认为最有实用价值的调优手段。A在搜索转移路径时默认把所有可通行格子的代价都设为1。这样一来如果两块区域之间有几条长度差不多的路A可能会选择穿越已经覆盖过的区域导致重复覆盖增加。解决办法是给已覆盖格子一个“惩罚代价”。具体做法是cost 1; if visited(nb) cost 1.3; % 走过已覆盖区域代价更大 end tentativeG gScore(current) cost;惩罚系数1.3是我试出来比较稳的值。系数太小对路径走向几乎没有影响系数太大A*会频繁绕远路去穿过未覆盖区域反而增加路径总长。这个参数其实可以做成全局变量跑实验时对比不同取值下的重复率择优选用。当时我拿30×30的随机地图测过一组对比不加惩罚时整条覆盖任务完成后重复率大约9%加了1.3倍代价惩罚后重复率降到6%左右而路径总长度只增加了不到3%。这个性价比非常高属于“几乎不额外写代码效果却很显著”的优化。4.4 路径回溯与坐标转换A*搜索完成后还需要从目标点回溯到起点得到完整路径。回溯依赖cameFrom数组这个数组在搜索过程中记录每个节点的父节点。回溯逻辑很简单function path reconstructPath(cameFrom, goalIdx) path goalIdx; while cameFrom(path(1)) ~ 0 path [cameFrom(path(1)); path]; end end注意Matlab的zeros(rows*cols, 1)初始默认cameFrom全是0起点对应的父节点就是0循环到起点时自然停止。回溯完得到的是线性索引序列画图时再转成行列坐标[rPath, cPath] ind2sub([rows, cols], path); plot(cPath, rPath, r-, LineWidth, 1.5);这里plot的第一个参数是列坐标第二个是行坐标因为matlab画图时横轴对应列、纵轴对应行。写反了的话路径会变成镜像调试时很容易看懵。5. 完整系统架构与实验结果分析5.1 主循环和模块划分整套系统的最终主循环并不复杂核心是不断重复“覆盖当前段、A*转移到下一段”这两个动作直到所有覆盖段都处理完。我在Matlab里把代码拆成了四个模块方便测试和扩展地图生成模块负责生成地图、设置障碍、可视化底图。覆盖段切分模块负责将地图拆成往返式覆盖段列表。A*寻路模块负责段间转移路径计算接收起终点和代价权重输出路径。主执行模块负责维护visited状态、贪心选择下一段、累加路径长度和统计指标。这四个模块互相独立后面想换覆盖策略只改第二块想换寻路算法只改第三块。这种解耦主要是为了研究方向考虑覆盖顺序优化和A*启发式改进可以分开做实验互不干扰。主流程的伪代码如下初始化地图map、visited矩阵、覆盖段列表segments 设置起点标记起点所在段为当前段 while 存在未覆盖段: 覆盖当前段内所有格子更新visited和路径记录 贪心选择最近的未覆盖段nextSeg 用A*计算currentPos到nextSeg起点的转移路径 沿转移路径移动并标记路径上的格子为visited 更新currentPos将nextSeg设为当前段 计算覆盖率、重复率、路径总长度绘制结果图这个流程下来代码量大约几百行Matlab运行一次在几秒到几十秒之间很适合做课程设计或者科研验证。5.2 三个核心评价指标覆盖路径规划的效果不能只看图还得用数字说话。我常用的指标有三个覆盖率Coverage Rate定义实际覆盖格子数除以可通行格子总数。理想情况是100%如果A*转移路径绕进去了复杂障碍区的角落某些格子无法到达覆盖率就会打折扣。重复率Repeat Rate定义额外访问次数占总覆盖次数的比例。比如某个区域有100个可通行格子机器人总共行走了110个格子的路径其中10个格子走了两遍重复率就是10%。这个指标直接反映路径浪费程度。路径总长度Total Path Length把所有机器人移动的距离加起来。因为栅格地图单步代价是1所以路径总长度约等于路径上格子数量加上转向代价的修正。统计代码可以这样写coverageRate sum(visited(:)) / sum(map(:) 0); repeatRate (sum(visitedCount(:)) - sum(map(:) 0)) / sum(map(:) 0); pathLength size(recordedPath, 1);这里的visitedCount记录每个格子的访问次数而不是简单的0/1状态。5.3 参考实验数据我拿下面几组典型地图做过实验列出来供大家参考。实验条件是四邻域、曼哈顿启发式、已覆盖格子代价为1.3倍、起点默认在(1,1)。地图规模不一样结果差异很大重点看趋势。地图规模障碍比例覆盖段数量覆盖率重复率路径长度/可通行格数20×2010%18100%3%1.0620×2025%3499.5%7%1.1230×3015%42100%5%1.0930×3030%8798.2%12%1.2550×5020%12999.8%9%1.18可以看出几个规律障碍越多覆盖段数量越多段间转移越频繁重复率上升覆盖率在障碍特别密集时可能达不到100%因为有些死角区域需要很长的绕路才能到达A*在代价惩罚下会倾向于“放弃”那些代价极高的格子路径长度与可通行格数的比值反映了空驶浪费越接近1说明路径越高效。如果实验中发现重复率超过20%优先检查两件事一是A*的启发式是否正确可用曼哈顿距离估算二是已覆盖格子代价惩罚是否生效。这两处修对重复率一般能降回10%以内。6. 常见问题与排错实录6.1 A*搜索不到目标主循环死循环这是最常见的问题。症状是程序卡在while循环里不退出或者A*返回空路径。原因通常是目标格子本身就是障碍物或者目标区域与起点之间被障碍完全隔离。排查方法很简单先不跑全覆盖流程单独挑两个无障碍点做一次A搜索确认底层寻路逻辑正常。然后检查目标点对应的map(goalIdx)是否为0。最后用可视化方式画一下起点和终点肉眼看是不是真的连通。我写过的项目里90%的A不连通问题是目标点选在了障碍物格子上覆盖段切分时没有过滤掉障碍物邻接的边界点导致的。建议在astar函数开头加一个保护判断if map(startIdx) ~ 0 || map(goalIdx) ~ 0 error(起点或终点不可通行); end6.2 sub2ind参数写反和边界索引报错这个问题我在2.2节提过但值得再强调一次。Matlab里sub2ind([rows, cols], r, c)的参数顺序很容易记反因为很多其他语言是先x后y也就是先列后行。一旦写反路径会在地图上斜向错位表现非常隐蔽光看路径图很难一眼发现。另外一个高频错误是邻居获取时先索引后判断边界。比如直接写map(nr, nc)而nr已经跑到0Matlab会报索引错误。正确写法一定是先判断nr 1 nr rows nc 1 nc cols再做地图取值。调试建议在getNeighbors函数里临时打印所有邻居索引验证能不能正确生成上下左右四个邻居尤其是地图边界上的格子。6.3 重复覆盖率过高怎么调如果你跑完实验发现重复率高居不下先看覆盖段顺序。贪心最近邻策略在小地图上表现不错但地图复杂时贪心容易陷入局部最优机器人反复在几个相邻覆盖段之间来回穿梭漏掉远处的大覆盖区域后面不得不长途跋涉去补漏途中必然重复覆盖大量格子。一个有效的改进是“双端点匹配”在贪心选择下一段时不仅比较段起点距离还要比较段终点距离并允许机器人从更近的一端接入该段。这样等于把每段段内方向也变成决策变量覆盖顺序的灵活性翻倍。如果双端点匹配还不够就得考虑用路径长度作为衔接代价而不是曼哈顿距离。做法是预先计算每对覆盖段之间的真实A*路径长度再跑一遍最近邻或动态规划求解最优覆盖顺序。这相当于把双层优化问题合并成了单层效果更好但计算量也更大。6.4 可视化调试的几个小建议最后分享几个把算法“看透”的小工具全部基于Matlab自带的绘图功能。第一地图底图调成浅色障碍用深色覆盖格子用渐变色转移路径用红色。这样一张图能同时看到覆盖范围和转移路径。imagesc(map); colormap(gray); axis xy; hold on; % 已覆盖格子叠加绿色透明层 imagesc(visited, AlphaData, 0.3);第二在图上标注覆盖段的编号。text(c, r, num2str(segId))能把每个段的序号标在段中间方便检查覆盖顺序。第三用pause(0.01)做逐段动画演示。每次覆盖完一段就刷新一次画面能直观看到机器人下一步往哪走调试时经常一眼发现“为什么它要绕那么远”的问题。我自己的经验是Matlab里实现这类算法先花半小时把地图可视化和路径绘制做好整个调试效率能提升一倍。很多逻辑错误光靠打印日志很难定位但看图就能秒懂。可视化不是最后展示时才做的事而是开发过程中最重要的排错工具。另外如果你用的Matlab版本较新脚本中文注释偶尔会出现乱码建议统一用英文注释或者检查文件的编码格式不然排查问题时空格注释看错很耽误事。