《代码大全2》读书笔记五
第十七章 不常见的控制结构
17.1 子程序中的多次返回
- 用来增加代码的可读性。假如在函数中途就计算出了结果,那么直接返回比最后一起返回更容易理解。
- 在检查到错误的条件(如错误的输入、未满足的前条件)后,直接清理现场、立刻退出,可以增强可读性,减少条件判断的层次。否则每个条件判断都用一个if-else处理,会导致非常深的缩进层次,非常难懂。
17.2 递归
不过公正来讲,递归确实不算一个常用的模式。
之前用到递归的情景中,我印象最深刻的就是dfs、树(尤其二叉树)。书中又提到了快排算法。
递归方法将问题拆解成若干个该问题本身,从而调用自己求解。递归非常技巧化、优雅,适用于小问题,但对于大问题容易降低可读性,推荐使用普通迭代方法。
使用递归需要注意:
- 一定要确保递归有结束条件。这也是我想到递归的第一反应。(想起了算法的特性之一即是可结束性。)
- 使用安全计数器,防止无穷递归。私以为也可以防止爆栈(这次结对项目我还爆过栈……)。可以与上一条结合使用,不过个人感觉这个方法像是保底的措施,而且对于__规模确确实实有可能增长到很大__的问题,设定计数器似乎不合适。
- 把递归限定在一个子程序内。循环递归可读性很低,容易出错。
- 注意栈。
- 不要用递归完成完全可以用普通循环、迭代实现的功能,如计算阶乘、斐波拉契数列。这样可读性更差。这启发我,也许递归使用的是分治法,A问题可以分割成若干个小的A问题,问题规模指数型增长;而阶乘、斐波拉契数列这样的问题,实质上是A问题经过计算后转化成了另一个A问题,这用循环解决就好了。
17.3 goto
goto的反对观点
goto破坏了代码缩进格式,破坏了代码的分块,破坏了自上而下的结构,降低了代码可读性,也难以跟踪、不利于编译器优化。
对goto的征讨最先由Dijkstra发起,随后便被观察于实验证实。提这一句是因为突然看到Dijkstra大佬被震撼了!
goto的支持观点
goto应该被谨慎地使用,在一些情况下,goto非常有用。goto可以减少重复代码,可以用于过程最后清理资源,可以加快运行速度。
在实践中,我也确实遇到了这样的问题。如函数结束前需要清理资源,分散在各个地方违背DRY原则,写在一处又需要多层条件判断来保证最后在一个地方退出函数。大胆设想一下这是语言设计不够完善,对函数或者代码段可以像对异常处理一样,提供一个finally方法。
错误处理与goto
- 用goto在过程最后清理资源,就是刚才说的finally问题。
- 用goto简化错误条件判断,在条件错误时通过goto跳过一段代码,类似函数中途退出。
用非goto的方式如何完成这些工作:
- 用深层次的if-else嵌套,然而这样非常复杂,可读性、可维护性不强。
- 用状态变量代替goto。
- 用try-finally方法代替goto。不过个人认为try-finally作为异常处理,应该用于处理真正的异常,这其实也是这本书之前也强调过的一点。
- (所以我没有什么好方法……
if语句中的某一层想要调用else语句
非常奇怪的逻辑,如
if( a ){
if( b ){
Do sth1;
goto STH3;
}
} else{
Do sth2;
STH3:
Do sth3;
}
这种逻辑非常奇怪,很难想到什么时候需要用……而且很容易重构错。可以选择的方法:
- 假如Do sth3比较复杂,可以提取为一个单独的子程序。
- 如若不然,可以重新排列if-else关系。
goto使用原则
在现代高级语言中,大部分问题不需要goto,小部分需要goto的问题中,也有一大部分可以转换为其他结构。goto太危险,因此要少用。一些原则如下:
- 不要用goto完成完全可以用已有的规范控制结构完成的工作。
- 如果语言内置了等价的控制结构,不要用goto完成。
- 如果试图用goto提高性能,要审慎衡量得失。
- 每个程序内尽量只使用一个标号
- 尽量让goto向前跳转而非向后跳转
- 确认所有goto标号都被用到了,删除无用的标号
- 确认goto不会产生不可达的代码。
第十八章 表驱动法
18.1 表驱动法使用总则
表驱动法:为了简化逻辑,直接将输入-输出关系对应成一张表格,通过查表法获取结果,快捷又高可读性。
两个的问题:
- 用什么方法查表。可用方法包括直接访问、索引访问、阶梯访问
- 表中存放什么数据。
18.2 直接访问表
通过数组这样的直接访问方式访问表项。如:用数组获取12个月各有几天,或用多重数组获取各种情况下医保的保费。
用于表示太过复杂以至于无法用代码计算(如保费),或者完全没什么内在逻辑的数据(如各个月各有几天(反正我觉得没逻辑))。
书中给出了一个非常强大的例子。有一个信息流,有20种可能的信息,各自有对应的id;如果通过if-else判断代码会非常恐怖,就算用类封装起来内部也还是有恐怖的if-else语句。然而如果通过查表可以轻松解决,而且将各种信息体现为数据而非硬编码,更加易于维护。
对于没有数据-结果一一对应关系的数据,如0 ~ 18对应一个表项、19 ~ 25对应一个表项等等,如何构造查询表的键值:
- 复制数据,从而有一一对应关系。太暴力,而且不容易修改,只适用于超简单的情况。
- 用表达式将数据转换为键值。
- 建立单独的函数将数据转换为键值,可读性、可维护性更强。
18.3 索引访问表
就不浪费笔墨解释什么是索引访问表了。
优点:
- 表查询方法的普遍优点。
- 对于子表可以较满填充、但主表填充不满的情况,索引表能节约空间。
- 可以用多种方法进行索引。
18.4 阶梯访问表
这是一个没怎么使用的技巧。
假如对于某个值,处在不同的区间对应不同的表项,可以将不同区间端点列为一张表,通过循环与各个端点比较便可以得到所在区间。
这个方法有表驱动方法的普遍优点,它与其他表驱动方法区别在于,其他方法试图解决键值与表项数据的无规律对应,而这个方法试图解决原始数据与键值的无规律对应。
需要注意:
- 注意每一种可能的上端点,不要允许某些数据跑掉。
- 注意 < 与 <= 之间的区别,> 与 >= 同理。
- 考虑用二分查找取代顺序查找。
- 阶梯方法比较耗时(而且个人觉得容易错),在数据项不多的情况下可以考虑用索引表替代。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· winform 绘制太阳,地球,月球 运作规律
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 上周热点回顾(3.3-3.9)
· 超详细:普通电脑也行Windows部署deepseek R1训练数据并当服务器共享给他人