程序员入伙书——如果、那么、否则
- 2012年12月26日
- 发布在 infotech . on my mind
- 留下评论
在《初涉算法》一章,我说过程序没什么神秘的,无非是做三件事:
- 计算
- 流程控制
- 输入输出
关于基本的计算,前头几章已经讲过了。现在开始说说流程控制(Flow Control)。
流程控制是程序的核心价值。如果一段程序从头到尾平铺直叙,一条大路走到黑,就不好意思说自己是程序了,顶多是在打算盘。试想,如果没有流程控制,“从1累加到100”的任务就只能这样完成,老没劲了:
total = 0 total += 1 total += 2 total += 3 : : total += 99 total += 100
一个有趣的程序,一定有种种的岔路和转圈。读者可能会问:你不是说计算机不会思考,所有的行为都是已知的么?岔路的变数从何而来?俺回答:道路是已知的,走路的人是未知的。种种变数,来自于人类或其它机器的输入。例如你正在看这个页面,计算机不知道你下一步是往上翻屏、往下翻,还是关掉窗口。你的下一步动作是机器无法预知的,但计算机做好了应对你的任何操作的准备——只要你不掐电源。
下面我就开始介绍“岔路”了:如果这样,如果那样。
举个例子,Python有个功能叫max,如果你在Python Shell里写max(1, 3)按回车,max一定会回答“3”。我们看看,如果自己来写这个功能,程序是什么样的。尝试一下这个例子——在Python Shell下按Ctrl-N打开一个窗口,输入这段代码:
a = input('Please input a number: ') b = input('Please input another number: ') if a > b: larger = float(a) else: larger = float(b) print('%g is larger.' % larger)
按Ctrl-S把文件存为mymax.py(随意起个其它名字,也没关系的),然后按F5键运行它:
我的运行结果是:
>>> ======================= RESTART ======================== >>> Please input a number: 3 Please input another number: 4.65 4.65 is larger. >>>
好像确实是我们预想的,对吧。
当然这个程序并不结实,如果你输入的不是数字,而是个乱七八糟的东东,比如“i love you”啥的,当场就可以围观程序口吐白沫瘫倒在地。在现实生活里,程序员们对输入值的防范比这个严格得多,回头介绍到输入输出时,我会更详细地介绍。现在不需要特别关心防黑问题。
在这个程序里,我们看到了关键字if和else。现在我来详细介绍它们。
if的意思是“如果”,else的意思是“否则”。顾名思义,这两个词的用法就是:如果某条件成立,则执行其对应的动作,否则,执行别的。可能被执行的程序块以缩进至少一个空格来表示,当缩进结束时,代表本条if-else条件选择的结束。
if 逻辑判断表达式: 做点啥事 再做点别的 想想我还能做点啥事 else: 做点另外的啥事 再做点别的
如果if后面紧跟的逻辑判断表达式成立——我们称“为真”——程序就会进入它后面的这块代码,并在执行完这块之后,跳过else。如果if的逻辑判断不成立——我们称“为假”——程序就会跳过它,选择进入else。
如果可选择的可能性不止if和else两种怎么办?举例来说,学生考试的各分数段可以分为A、B、C、D四个等级。Python语言还有一个关键字elif(即else if的缩写)来满足更多选择的可能:
score = float(input('Please input a number: ')) if score > 80: print('A') elif score > 60: print('B') elif score > 40: print('C') else: print('D') >>> ======================== RESTART ======================== >>> Please input a number: 40 D >>> ======================== RESTART ======================== >>> Please input a number: 65 B >>>
注意,if-elif-else结构虽然可以列出很多可能,但它是单选题,程序最多只能选择进入其中一种。选择的方法很直观,它从上到下一路扫过来,首先遇到哪个条件为真,就立即进入它对应的那个程序块,并忽略其他所有的条件判断。拿刚才的这个程序为例,如果你输入65,那是肯定满足score > 60和score > 40的,但程序先遇到了score > 60判断为真,于是它毅然进入print(‘B’)并忽略了所有其它的选择可能。
为了更进一步说明这一点,我们可以做个实验,把刚才这个程序的各种条件调换一下顺序:
score = float(input('Please input a number: ')) if score > 40: print('C') elif score > 60: print('B') elif score > 80: print('A') else: print('D')
你会发现,大于40的任何分数,例如65、80、100,通通被判为C。这是因为,程序遇到的第一个真命题就是score > 40,它立刻掉了进去,忽略了在人类世界中更正确的选择。计算机是很笨的,它不知道一种正确和另一种正确之间,那个更正确。如果真给了它独立思考的能力,它没准还会想:100比40大得多,只比80大一点点,100 > 40当然比100 > 80更加正确,我选“C”天经地义,你们这些愚蠢的人类!
其实,第一段程序(正确的那个)等价于执行了四个独立的if判断,而且,这四个逻辑判断是可以随意调换顺序而不出错的:
if score > 80: print('A') if 60 < score <= 80: print('B') if 40 < score <= 60: print('C') if score <= 40: print('D')
因为我们了解elif中悄悄蕴含了一个“else”、即“前头所有条件都不成立”的假设,所以我们利用这个假设,让elif来替我们筛掉其它可能,而不是自己殚精竭虑地,在后续的程序里,一再复述这些可能的对立面。当然,前提是正确的筛选顺序。
在if-elif-else结构里,只有if是必须的,elif和else都可以没有。如果只有一个if,且这个if也不成立,那么程序就什么也不做,直接跳到if块后面的语句。
在实际应用里,有一些好习惯。这些好习惯之间可能是打架的,使用时需要权衡利弊:
- 把发生概率较高的条件放到前头,在执行时可以省去很多判断的过程。如果要猜姓名,先猜“李王张刘”比先猜“欧阳东方”效率要高。
- 把程序块较大较复杂的条件放到前头,可以提高程序的易读性,给将来读程序的人一种这样的心理暗示:这段代码看完,任务就完成80%了,心情愉悦。
- 如果影响条件判断的因素比较多,把它们先按一个因素分成少数几块,再各自逐级分层判断,这样可以减少判断次数,也可以让程序条理更清晰。脑补一下这种场景:一个巨大的路口,无数分支一字排开,每个分支通往全国各地的市县,和另一种情景的区别:先是个二选一的岔口,分别通北方南方,开下去渐渐看到各省,然后是各市县。举例来说,如果一个程序需要判断a, b, c三个值,当它们各自为正为负的时候如何如何。那么,与其写八种组合,依次if-elif-elif,不如先按a的值分为一层if-else,再用b的值判断下一层,最后用c的值判断第三层。这样,原本可能会执行1-8次、平均4次的判断,就会缩减到3次。当变数增多的时候,这个优化效果就更明显。分层时,最好按照“影响最大的因素”到“最琐碎的因素”逐级展开,这样的程序更容易看明白。例如一个动物物种分类的程序,判断动物有几条腿、是否长羽毛,比判断它是不是双眼皮更加重要。
if a > 0 and b > 0 and c > 0: action 1 elif a > 0 and b > 0 and c <= 0: action 2 elif a > 0 and b <= 0 and c > 0: action 3 elif a > 0 and b <= 0 and c <= 0: action 4 elif a <= 0 and b > 0 and c > 0: action 5 elif a <= 0 and b > 0 and c <= 0: action 6 elif a <= 0 and b <= 0 and c > 0: action 7 else: action 8 优化为: if a > 0: if b > 0: if c > 0: action 1 else: action 2 else: if c > 0: action 3 else: action 4 else: if b > 0: if c > 0: action 5 else: action 6 else: if c > 0: action 7 else: action 8
在最后的这段程序对比里,你看到了一个新词:and,这个关键字很直白,“并且”的意思。关于“and”和它的弟兄们的更多故事,下回分解。