for和while循环:面试官问“死循环怎么排查“,我差点说“重启试试“
前两天跟一个做自动驾驶的朋友聊天他说他们团队最近招新人面试出了道循环题结果把一堆候选人难住了。题目很简单写一个循环遍历激光雷达的360个点找出距离最近的那个点。他问我你猜有多少人写对了我说八成以上吧这又不难。他笑了不到一半。我有点不信。这么基础的东西怎么会翻车他说问题不在循环本身而在边界条件。有人写的循环从0到360有人从1到361还有人直接用而不是。结果要么是数组越界要么是漏掉最后一个点。讲真循环结构看起来是编程里最基础的东西之一但真正写得严谨、写得高效的人其实没那么多。for vs while不是语法选择是语义表达for和while都能实现循环这没错。但在实际开发中选哪个是有讲究的。我的习惯是如果循环次数确定用for如果循环次数不确定用while。比如遍历传感器数据你知道一共有100个点那就用forfor (int i 0; i 100; i) { process(sensor_data[i]); }但如果是等待传感器就绪你不知道要等多久那就用whilewhile (!sensor.isReady()) { std::this_thread::sleep_for(std::chrono::milliseconds(10)); }这种选择不是语法层面的而是语义层面的。for表达的是遍历一个已知范围while表达的是等待某个条件满足。面试的时候如果你能说出这个区别面试官会觉得你对语言的理解不止停留在表面。不过说实话现在很多代码里for的用法越来越灵活了。C11的范围for循环写起来比while还简洁for (const auto point : point_cloud) { process(point); }这种写法不需要知道容器大小也不需要手动管理索引还不容易出错。我个人现在大部分遍历场景都用这种写法只有在需要索引的时候才用传统的for。循环中的性能陷阱循环写起来容易但写高效不容易。我见过一个实习生写的代码处理点云数据for (int i 0; i cloud.size(); i) { double distance sqrt(cloud[i].x * cloud[i].x cloud[i].y * cloud[i].y cloud[i].z * cloud[i].z); if (distance threshold) { // 处理逻辑 } }看起来没问题对吧但有个性能问题。cloud.size()在每次循环的时候都会被调用。如果cloud是个vector这倒无所谓因为size()是O(1)的。但如果是list或者其他容器size()可能是O(n)的整个循环就变成了O(n²)。正确的做法是把size()提到循环外面int size cloud.size(); for (int i 0; i size; i) { // ... }还有一个更隐蔽的问题循环不变量。for (int i 0; i 100; i) { double factor getCalibrationFactor(); // 每次循环都调用 double value sensor_data[i] * factor; }如果getCalibrationFactor()的返回值在循环过程中不会变那你每次循环都调用它就是浪费。应该把它提到循环外面double factor getCalibrationFactor(); for (int i 0; i 100; i) { double value sensor_data[i] * factor; }这种优化在控制循环里特别重要。如果你的控制频率是1000Hz循环里多一次函数调用可能就赶不上实时性要求了。死循环不是bug可能是设计说到循环就不得不提死循环。很多人觉得死循环就是bug其实不一定。在嵌入式机器人开发里主控制循环通常就是个死循环while (true) { readSensors(); updateControl(); sendCommands(); }这个循环永远不会退出直到机器人关机。这是设计不是bug。但有些死循环是真正的bug。比如for (int i 0; i 100; i--) { // 永远不会结束 }或者int i 0; while (i 100) { // 忘记i }这种低级错误在疲劳编码的时候特别容易犯。我以前也写过这种代码debug了半小时才发现是循环变量没更新。面试的时候面试官可能会问你怎么排查死循环标准答案是加日志、用调试器、检查循环条件。但我觉得更实用的答案是预防。怎么写才能避免死循环我的习惯是对于for循环确保循环变量朝着正确的方向变化对于while循环确保循环条件最终会变为false对于复杂的循环加一个最大迭代次数作为安全阀int max_iterations 10000; int iterations 0; while (!converged() iterations max_iterations) { // 迭代逻辑 iterations; } if (iterations max_iterations) { // 报警循环没有正常收敛 }这种写法在优化算法里特别常见。比如ICP配准理论上应该收敛但实际数据可能有噪声导致永远不收敛。加个最大迭代次数程序就不会卡死。嵌套循环复杂度爆炸的起点单层循环还好嵌套循环就容易出问题。for (int i 0; i n; i) { for (int j 0; j m; j) { // O(n*m) } }如果n和m都是100这个循环要执行10000次。如果里面再嵌套一层就是1000000次。在机器人开发里嵌套循环最常见的场景是点云处理、栅格地图操作、多传感器数据关联。这些场景的数据量通常很大嵌套循环很容易成为性能瓶颈。怎么优化一种方法是减少不必要的嵌套。比如你要计算两个点云之间的距离 naive的做法是双重循环for (const auto p1 : cloud1) { for (const auto p2 : cloud2) { double dist distance(p1, p2); } }但如果用空间索引结构比如KD树可以把复杂度从O(nm)降到O(nlog(m))KDTree tree(cloud2); for (const auto p1 : cloud1) { auto nearest tree.findNearest(p1); }这种优化在SLAM和点云配准里特别常见。面试的时候如果你能提到用空间索引优化嵌套循环面试官会眼前一亮。还有一种优化方法是循环展开。现代编译器会自动做这件事但有时候手动展开能带来更好的性能。不过说实话这种优化在大多数场景下没必要代码可读性更重要。给正在准备面试的你一点建议循环结构真的是最基础的语法了。但基础的东西往往最能看出一个人的编程功底。我面试的时候看候选人写循环主要看几点边界条件对不对、循环变量更新对不对、有没有性能陷阱、嵌套循环的复杂度有没有考虑。这些细节做好了说明这个人写过规范的代码有工程素养。怎么提高我的建议是多写多练。不要只看代码要自己动手写。写完之后想想有没有优化的空间有没有更简洁的写法。然后去看看开源的机器人代码比如PCL点云库、Ceres Solver看看人家是怎么写循环的。那些代码都是经过千锤百炼的能学到很多东西。最的是养成好习惯。循环变量用有意义的名字、边界条件写清楚、复杂循环加安全阀。这些习惯一旦养成能帮你避免很多低级bug。如果这篇文章对你有帮助欢迎点赞、在看、转发三连。 你的支持是我持续更新的最大动力。「机器人软件开发面试·从入门到精通」连载系列上一篇第3篇 条件判断if/else——面试中最容易写错的分支逻辑 下一篇预告第5篇 数组与字符串——传感器数据批量处理的第一步有任何问题欢迎评论区留言我会尽量回复。