程序员入伙书——如果、那么、否则

在《初涉算法》一章,我说过程序没什么神秘的,无非是做三件事:

  1. 计算
  2. 流程控制
  3. 输入输出

关于基本的计算,前头几章已经讲过了。现在开始说说流程控制(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 and b  0:
	action 3
elif a > 0 and b <= 0 and c <= 0:
	action 4
elif a  0 and c > 0:
	action 5
elif a  0 and c <= 0:
	action 6
elif a <= 0 and b  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”和它的弟兄们的更多故事,下回分解

评论关闭