上一节,我们剧透了如何用分支条件结构,判断用户可能的输入错误,给出不同输出结果。

  1. x = input('请输入一个整数:')
  2. if x.isdecimal():
  3. x = int(x)
  4. print(type(x)) # 结果int
  5. else:
  6. print('输入错误,请重新执行程序。')

这是Python经典的if-else分支结构语句:为了让程序能在各种可能情况下良好执行,就需要在代码中预先判断。
分支条件结构是预判的一种方式。还有一种叫异常,我们在后面章节会提到。

除了if-else之外,Python主要的语句结构有5种:

  1. 顺序结构:最简单形式,一个接一个执行;
  2. if判断结构:“有条件就上,没条件就过”;
  3. if-else判断结构:“有条件就上,没条件想办法也要上”;
  4. for-in循环:“挨个点名,不遗漏”;
  5. while循环:“有条件就别停,一遍接一遍地做”。

image.png
所有的程序,都可以用这5种结构组合而成。这就是结构化编程。

本节内容:

  1. 分支条件结构:单分支、多分支、嵌套分支
  2. 两种循环结构:for-in循环、while循环
  3. 应用案例:设计实现一个简易计算器

1、分支条件结构

1.1 单分支

如果是单独分支,可以用if语句判断后执行。

  1. x = 2
  2. if x > 0:
  3. print('x大于0') # 会执行分支

注意其中的写法:

  1. 语句后面带冒号(英文的)
  2. 判断后的执行语句,需要缩进,一般是4个空格
  3. x、0和大于号之间不一定需要空格,但添加后更美观

1.2 双分支

如果有两个分支,可以用if-else语句。

  1. x = 2
  2. if x <= 0:
  3. print('x不大于0')
  4. else:
  5. print('x大于0') # 执行这个分支

注意其中else后面也有英文冒号。

1.3 多分支

如果有更多分支,可以用if-elif-else语句。

  1. x = 2
  2. if x < 0:
  3. print('x小于0')
  4. elif x == 0:
  5. print('x等于0')
  6. elif x == 1:
  7. print('x等于1')
  8. elif x == 2:
  9. print('x等于2') # 执行这条分支
  10. else:
  11. print('x大于2')

同样的elif语句末尾也有英文冒号,有多少额外情况,就添加多少elif分支语句。
在写条件判断语句时,要注意程序是从上往下一个个判断的,当条件一旦成立,它就执行对应分支,然后就忽略其他分支,继续往下执行。所以在写的时候一定要确保自己的逻辑正确。

举个错误例子:

  1. # 错误例子
  2. x = 3
  3. if x < 0:
  4. print('x小于0')
  5. elif x > 0:
  6. print('x大于0')
  7. elif x > 2:
  8. print('x大于2')

这段程序中,缺了else分支是可以的。但有两个错误:

  1. 最后一个分支永远不会被执行,因为第二个分支会100%把它“截胡”了:大于2的数一定也大于0。
  2. 没有判断等于0的情况,当x是0时,不会输出任何东西,很多情况下,并不是设计者故意为之,而是遗漏。

所以,在写分支判断结构时,要记住2个原则:

  1. 条件覆盖所有情况,哪怕某些情况下不用做什么,也建议至少思考一遍,比如用pass来代表不作为
  2. 去掉那些已经被其他分支覆盖的,没意义的分支,让自己看得更清
  3. 区分条件时,按序列出组合情况,比如数字从小到大,字母从a到z等

比如,上面的错误案例,可以改成如下:

  1. # 错误修正例子
  2. x = 3
  3. if x < 0:
  4. print('x小于0')
  5. elif x == 0:
  6. pass # 用pass代表不做什么
  7. else:
  8. print('x大于0')

1.4 嵌套分支

当分支的执行语句也需要条件判断时,可以嵌套来使用:

  1. x, y = 3, 4
  2. if x < 0:
  3. print('{}<0'.format(x))
  4. if y >= 0:
  5. print('{}<0<={}'.format(x,y))
  6. elif x < y:
  7. print('{}<{}<0'.format(x,y))
  8. else:
  9. print('{}<={}<0'.format(y,x))
  10. else:
  11. print('{}>=0'.format(x))
  12. if y < 0:
  13. print('{}<0<={}'.format(y,x))
  14. elif x > y:
  15. print('{}>{}>=0'.format(x,y))
  16. else:
  17. print('{}>={}>=0'.format(y,x)) # 这个分支执行

1.5 三元表达式

最后,关于条件判断,Python有一个三元表达式,上一节中也剧透过

  1. x = input('请输入一个整数:')
  2. print(type(int(x))) if x.isdecimal() else print('输入错误,请重新执行程序。')

它的结构是:a if 条件判断 else b,再看几个例子

  1. x = 1 if 2 > 1 else 0 # 1
  2. print('hello' if len('hello') > 5 else 'python') # python

它等价于下面的分支写法:

  1. if 2 > 1:
  2. x = 1
  3. else:
  4. x = 0
  5. if len('hello') > 5:
  6. print('hello')
  7. else:
  8. print('python')

其中,len()是Python的一个内置函数,用来获取集合类数据长度,这里就是获得字符串长度。

三元表达式的意义,是为了让一些很浅显的判断不用写的太复杂,不用占用太多行。
当然,一开始你不习惯,可以放一边,以后遇到分支条件的时候看看,能否写成三元表达式,慢慢训练和寻找感觉。

2、两种循环结构

Python的循环其实不止两种,但这里我们重点介绍99%情况下会用到的for-in和while。

想想看:怎么把一个字符串反过来输出?

2.1 切片器

上一节中,你是否留意字符串处理中的一个技巧?

  1. s = 'hello python'
  2. print(s[::-1])

这是字符串切片器的作用。
切片操作有三个参数 [start: stop: step],表示从start位置到stop位置,输出每个元素,每次前进step步。
当然,每个参数都是省去而使用默认,比如:

  1. s = 'hello python'
  2. s[1:] # 表示从第2个元素(下标为1)开始取所有,'ello python'
  3. s[:2] # 表示从第1个元素(下标为0)开始取到第3个元素(不含),'he'
  4. s[2::2] # 表示从第3个元素(下标为2)开始取所有,每次步进2,'lopto'
  5. s[-1:-5:-1] # 负数代表从尾巴开始数,从最后一个元素,取到倒数第5个(不含),每次倒退1,'noht'

有点迷糊?没关系,它是python实现的一种“工具”,方便我们迭代访问元素。

2.2 for-in迭代

回到问题:字符串反转,我们看看Python的for-in循环怎么写。

  1. s1 = 'hello python'
  2. s2 = '' # 定义一个空字符串
  3. for c in s:
  4. s2 = c + s2
  5. print(s2)

for-in语句注意事项:

  1. 注意在语句后添加英文冒号
  2. 循环内语句需要缩进

这个循环,每次从s1里取一个字符,然后把它添加到s2的前面,最后实现了反转,示意图如下。
image.png

我们再来看另一种写法。
写之前,介绍2个内置函数enumerate()和range()。
enumerate(x)可以为一个“可迭代对象”生成对应的序号。

  1. s1 = 'hello python'
  2. s2 = ''
  3. for i, c in enumerate(s):
  4. s2 = c + s2
  5. print('经过第{:2d}次迭代: s2 = {}'.format(i+1,s2))

注意:生成的序列和下标一致,也是从0作为第1个元素开始的。

再看另一种写法,我们先用range()函数生成一个整数序列(准确叫法叫迭代器),然后用下标来获取元素。

  1. s1 = 'hello python'
  2. s2 = ''
  3. for i in range(len(s1)):
  4. s2 = s1[i] + s2
  5. print('经过第{:2d}次迭代: s2 = {}'.format(i+1,s2))

我们让range()函数生成了从0到s1长度(即12,但不包含)的整数序列,再通过下标获得每个字符。

输出内容和上面写法完全一样,但其实range()写法更灵活。
比如,我们想把从第2个元素开始,所有偶数位的字符取出来。

  1. s = 'hello python'
  2. for i in range(1, len(s1), 2):
  3. print('第{:2d}位字符: {}'.format(i+1,s[i]))

range()函数的完整参数包括3个:

  1. range(start, stop[, step])

看,是不是和切片器参数很像?

好了,for-in循环先掌握这些,足够你应付一大波情况。

2.3 while循环

再看Python的另一个常用循环:while。

我们也设置一个问题:计算从1到100(包含)的数字之和。
可能你很快就心算出来:(1+100)*100/2,不过计算机可没那么聪明,除非你告诉它这个“算法”。
如果不知道这个算法,也可以用循环解决。

  1. n = 1
  2. sum = 0
  3. while n <= 100:
  4. sum += n
  5. print(sum)

当然了,在这么简单的问题下,你也可以用for-in解决。

  1. sum = 0
  2. for i in range(101):
  3. sum += i
  4. print(sum)

如果稍微把问题改一下:把从1开始递增的连续整数累加起来,使其不超过1000,问和是多少?
1,2,3,4……N,把这些连续整数加起来,但你无法提前知道N是多少,这时候while会比for-in更适合。

  1. n, sum = 1, 0
  2. while sum + n <= 1000:
  3. sum += n
  4. n += 1
  5. print(sum, n) # 990,45

于是,你知道从1累加到44,不超过1000。
如果非要用for-in也可以,因为你可以猜大概的数字。

  1. sum = 0
  2. for i in range(1, 40):
  3. if sum + i > 1000: # 判断还能不能往上加
  4. break # 表示提前退出循环
  5. sum += i
  6. else: # 表示完整迭代所有数字没有break情况
  7. for i in range(40, 80):
  8. if sum + i > 1000:
  9. break
  10. sum += i
  11. else:
  12. print('还需要更多数字')
  13. print(sum)

我们第一次估计N=40,但是发现全加完后可能还不够,于是再往后猜40个数字……直到满足靠近但不超过1000。

这里,我们用到了for-in-else循环的完整结构,当循环中没有提前break,循环完毕后就会执行else分支。

到这里你应该可以感受到:for-in更适合已知长度的迭代,while更适合未知长度循环。

3、应用案例:计算器

最后,我们来设计一个自己的计算器,实现以下功能:

  1. 加减乘除,以及取余数和乘方
  2. 输出一张九九乘法表

3.1 分析问题

先来分析下问题,这也是编程第一件要做的事情。

  • 怎样设计输入?
  • 怎样计算?
  • 怎样输出?

我们知道输入的都是字符串,但怎样判断“加减乘除”等运算符呢?
你也许会想到,用字符串的find查找功能,但你如何解决运算优先级呢,比如有括号?
最爽的用户体验,就是输入任何一个表达式,就可以计算出结果。
但目前你可能束手无策,于是,你就只能牺牲用户的交互体验了。
事实上,任何一个好算法,都是一点点从最差的迭代而来。
这是一种通用解决问题的思路:渐进迭代。

3.2 解题思路

所以,我们先按我们能力范围,设计解题思路:把每一种操作定义为一种选择,根据用户的选择来确定采取的计算方式,再让用户输入两个数字,由此计算输出答案。

建议根据今天的内容自己实现一遍。

3.3 参考代码

  1. print('请输入一个选项(整数序号):')
  2. print('1、加法')
  3. print('2、减法')
  4. print('3、乘法')
  5. print('4、除法,如不能整除,输出商和余数')
  6. print('5、乘方')
  7. print('6、打印九九乘法表')
  8. choice = ''
  9. while not choice.isdecimal() \
  10. or int(choice) > 6 or int(choice) < 1:
  11. # 太长的语句,我们用空格加反斜杠来换行
  12. choice = input('请重新输入序号:')
  13. choice = int(choice)
  14. # 除了第6个选择外,其他都需要输入两个数字,我们可以先判断特例
  15. if choice == 6:
  16. for i in range(1,10):
  17. for j in range(1,i+1):
  18. # print函数的end参数控制输出尾巴,'\t'如同你按Tab,默认是换行
  19. print("{} * {} = {}".format(j, i, i*j), end='\t')
  20. print('')
  21. else:
  22. x, y, s = '', '', ''
  23. while not x.isdecimal or not y.isdecimal():
  24. s = input('请输入两个整数,以空格分离:')
  25. if s.count(' ') != 1: # 字符串的count函数统计某个字符出现几次
  26. # 有且只有一个空格,否则不继续本次执行,重新进入下轮循环
  27. continue
  28. x, y = s.split(' ')
  29. x, y = int(x), int(y)
  30. if choice == 1:
  31. print('{} + {} = {}'.format(x, y , x + y))
  32. elif choice == 2:
  33. print('{} - {} = {}'.format(x, y , x - y))
  34. elif choice == 3:
  35. print('{} * {} = {}'.format(x, y , x * y))
  36. elif choice == 4:
  37. if y == 0: # 除数不能是0
  38. print('除数为0') # 这里也可以重新让用户输入
  39. elif x % y == 0: # 能整除
  40. print('{} / {} = {}'.format(x, y , x + y))
  41. else: # 不能整除,输出表达式
  42. print('{} = {} * {} + {}'.format(x, y, x//y, x % y))
  43. elif choice == 5:
  44. print('{} ^ {} = {}'.format(x, y , x ** y))
  45. else: # 这里不会出现,谨慎起见可以输出一行提示
  46. print('Error condition')

注意点:

  1. 循环和条件判断可以互相嵌套
  2. 如果发现很多分支内存在同样代码,说明可以合并,见输入两个整数部分

总结

本节我们重点学习了Python的分支条件结构和循环结构。

顺序执行、分支条件结构、循环结构,构成了程序的执行流程,所有程序流程都可以由这三类组合而成。


作者:程一初
更新时间:2020年8月
image.png