Chapter 17 Unusual Control Structures
不常见的控制结构
17.1 Multiple Returns from a Routine 子程序中的多处返回
1. 如果能增强可读性,那么就使用return。
2. 用防卫子句(guard clause)(早返回或早退出)来简化复杂的错误处理
3. 减少每一个程序中return的数量
如果在读子程序的后部时,你没有意识到它从前面某个地方返回的可能性,想理解这个子程序就很困难。由此可见,使
用return要十分谨慎——只当它们能增强可读性的时候才去使用。
17.2 Recursion 递归
递归并不常用,但如果使用的谨慎,还是可以得到非常优雅的解。
递归式是对付复杂事物很有价值的工具——在用于对付适当问题的时候
·Example of Recursion 递归的例子
假设你有一个表示迷宫的数据类型。本质上迷宫是一个网格,在网格上的每一个点,你都可能向上下左右四个方向移动。
如果用了递归,找到出路会显而易见。你从入口处开始,然后尝试所有可能的路径,直到找到走出去的路来。当你第一次走到某一
个点上的时候,你试着向左走。如果不能向左走,那么试着向上或者向下,如果还不行,那就向右走了。你不用担心迷路,因为你
经过某个点的时候,都会在那里留下一些面包屑,所以同一个点是不会走两次的。
C++示例:用递归穿越迷宫
bool FindPathThroughMaze( Maze maze, Point position ) {
//if the position has already been tried, don't try it again.
if( AlreadyTried( maze, position) ) {
return false;
}
//if the position is the exit, declare success
if( ThisIsTheExit( maze, position) ) {
return true;
}
//remember that this position has been tried
RememberPosition( maze, position );
//Check the paths to the left, up, down, and to the right; if
//any path is successful, stop looking
if( MoveLeft( maze, position, &newPosition ) ) {
if(FindPathThroughMaze( maze, newPosition) ) {
return true;
}
}
if( MoveUp( maze, position, &newPosition ) ) {
if(FindPathThroughMaze( maze, newPosition) ) {
return true;
}
}
if( MoveDown( maze, position, &newPosition ) ) {
if(FindPathThroughMaze( maze, newPosition) ) {
return true;
}
}
if( MoveRight( maze, position, &newPosition ) ) {
if(FindPathThroughMaze( maze, newPosition) ) {
return true;
}
}
return false;
}
1. 第一行代码负责检查这个点有没有尝试过。编写Recursion子程序的关键目标之一就是要防止产生无穷递归。在本例中,如果不
对某一点是否尝试过进行检查,那么就可能无限地尝试下去。
2. 第三行语句记得这点已经尝试过了。这将消除因为产生了回环路径而出现无穷递归的可能。
·Tips for Using Recursion 使用递归的技巧
1. 确认递归能够停止
检查子程序以确认其中含有一条非递归的路径。通常意味着该子程序中含有一项判断,无须进一步递归,就能停下来。
2. 使用安全计数器防止出现无穷递归
如果你在一种不允许使用上述简单测试的环境中是用递归,那么就用安全计数器来防止产生无穷递归。
安全计数器必须是一个不随每次递归调用而重新创建的变量。可以用一个类成员变量,或者把该安全计数器作为参数加
以传递。
例如,有时在递归中,假如判断,如果对子程序的调用次数超过了安全上限,递归就会停止。
3. 把递归限制在一个子程序内
循环递归(A调用B,B调用C,C调用A)非常危险,因为它很难检查。依靠脑力来管理位于一个子程序内的递归已经够
困难了;理解跨越多个子程序的递归是在是勉为其难。
4. 留心栈空间。
使用递归以后,无法保证你的程序会使用多少栈空间,也很难预测程序在运行期间会表现的怎样。不过,你还是可以按
下述步骤来控制程序在运行期间的表现。
首先,如果你使用了安全计数器,那么再给它设置上限的时候,需要考虑的事项之一就是,你愿意给该递归子程序分配
多少栈空间。把它设置足够低,以防止栈溢出。
其次,应该注意观察递归函数中局部变量的分配情况,特别要留意那些内存消耗大的对象。
5. 不要用递归去计算阶乘或者斐波那契数列
在计算机科学教科书中存在这样的愚蠢的例子来讲解递归。就是计算阶乘或者斐波那契数列。
用循环去计算阶乘,这很合适。
总结,在用递归之前你应该考虑它的替换方案。你用递归能做到的,同样也可以使用栈和循环来做到。有时用这种方法好,有时用
另外一种好。你在定下来使用哪种方法之前,请把两者都考虑一下。
Key Points 要点
·多个return可以增强子程序的可读性和可维护性,同时可以避免产生很深的嵌套逻辑。但是使用它的时候要多加小心。
·递归能够优雅地解决一小部分问题。对他的使用也要加倍小心。
·在少数情况下,goto是编写可读性和可维护性代码的最佳方法。但是这种情况非常罕见。除非万不得已,不要使用goto。