xuzhengfu / pilot

进入编程世界的第一课
1 stars 0 forks source link

p1-3-structure-2 第三章 程序的基本结构(二):操作符与函数 #21

Open xuzhengfu opened 4 years ago

xuzhengfu commented 4 years ago

1. 了解 “操作符与函数” 的本质

如果源代码是我们写出来的故事,那么 “操作符与函数” 就是故事中的事件,是电影中的一幕幕场景。

上一章里我们说过,数据就是程序里的角色,同类型的数据之间可以对话和互动,而操作符与函数就是他们之间互动的方式。

2. 了解 操作符

2.1 了解 常用操作符 的种类

Python(其他编程语言也差不多)有好多类操作符,我们这里介绍最常用的四类:算术操作符、大小比较操作符、赋值操作符和逻辑运算操作符。

2.2 了解 算术操作符

算术操作符就是加减乘除之类的,专门针对整数和小数。

a = 6
b = 30
f = 3.14
g = 0.7

a + b
a - b
a * b
b / a
a * g
g / f

结果如下:

36
-24
180
5.0
4.199999999999999
0.22292993630573246

在学习用书中依次运行如下命令:

b ** 2
b ** a
f ** a
g ** f

结果如下:

900
729000000
958.4685972127362
0.3262930666604556

在学习用书中依次运行如下命令:

b // a # 计算 30 除以 6 的商
b % a # 计算 30 除以 6 的余数
b // 4 # 计算 30 除以 4 的商
b % 4 # 计算 30 除以 4 的余数
f // 2 # 浮点数也可以的
f % 2
g // f # 浮点数除浮点数也没问题
g % f

结果如下:

5
0
7
2
1.0
1.1400000000000001
0.0
0.7

2.3 插播:了解 代码注释

上面的代码中井号 # 开始的部分叫做 “注释”(井号这个名字是我们中国人才懂的,在英语世界里这个符号有好些个名字,比如 number、pound、hash),注释是源代码里被编译器和解释器忽略的部分,也就是说这部分写了机器也不看,是写给我们人类的。

程序复杂起来之后,不管编程语言本身多么易懂或者多么接近自然语言,还是会有看不明白的可能,所以作为一种好习惯我们应该在需要时写下一些重要的注释,方便别人阅读和理解我们写的代码,也方便自己,因为难保过些时自己都忘了为啥这么写!

有不少编程语言提供这种配套的工具,可以自动分析源代码里的注释来生成程序文档,比如 Python 的 pydoc。

注释并非越多越好。注释一定是 “光看源代码难以知晓的重要信息”,我个人的风格是程序尽量写得不需要注释,然后再写少量必须注释的内容。

  • 比如写了一个经典算法的实现,参考的算法说明可以作为一个链接附上,方便不了解这个算法原理的人参考;
  • 再比如某处代码用到了一个临时假定,是未来有可能变化的,也很适合记录下来,并且标记一个 TODO 之类的标签,这样以后你只要全文检索 TODO 就能找出所有这类假定,检查是不是情况已经变化或者有了更好的解决方案,而做出相应修改。

# 既可以用在行首也可以在中间,总之 # 之后直到行尾的部分就是注释。

因为对编译器和解释器来说,注释无非就是 “人类自己看而我不需要理” 的意思,所以有时候我们想暂时移除某行代码又不想删掉(因为等下可能还要用),就可以临时在这行代码前加个 #,想加回来的时候去掉 # 就好了,这也是个常用的手法。

2.4 了解 大小比较操作符

比较大小的一组操作符,> < >= <= == != 分别是 大于、小于、大于或等于、小于或等于、等于 和 不等于 关系。这几个操作符比较它左右的数据的大小关系,如果符合操作符的含义就返回布尔值真(True),否则返回布尔值(False)。在 Python 里这些操作符不仅可以比较数字,还可以比较其他很多类型的数据。

a < b
a >= a
a <= f
a + b == 36
a + b != 36
'aaa' < 'aab'
a * g == 4.2

结果如下:

True
True
False
True
False
True
False

2.5 插播:了解 浮点误差

目前我们用的计算机叫 “电子计算机”,用电平的高低表示数,电平高的是 1,低的是 0,只有这两种状态,也就是说计算机只认识 0 和 1。要用 0 和 1 表示我们要用到的各种数字,就要采用二进制计数法。

二进制计数法对于整数来说没啥问题,每一个十进制整数都对应唯一的二进制表示,但对于小数来说问题就来了,绝大部分十进制小数是没法用有限位数表示为二进制小数的,如果想知道为什么,可以看看这篇文章,如果看不明白也没关系,反正记住这个结论就行了。这个结论决定了在计算机里小数是以一种“近似值”的方式表示的,0.7 在计算机里是个近似等于 0.7 的二进制小数,这个小数乘上 6 之后的结果也是有微小的误差的,转换回十进制小数给我们看结果时就会出现上面那样的奇怪现象。

  1. 如果需要绝对精确的结果,那么不要用小数,只用整数进行运算,然后自己计算小数点的位置,绝大部分计算器就是这么做的;
  2. 如果不需要绝对精确,那么只要结果的误差足够小就可以了,这时候可以用浮点数进行运算,但比较时不要直接比较结果是不是相等(==),而用误差范围来比较:

2.6 了解 赋值操作符

赋值操作符就是等号 =,正是为了避免和这个操作符冲突,大小比较中的 “等于” 才会采用双等号 == 表示。

赋值操作符本身很简单,就是把右边的值赋给左边的变量(无论右边的东西多复杂,都先算出它的值)。

比如:

赋值操作符有一些变体,比如下面这个:

c += 2
c

结果如下:

38

上面的 c += 2 等价于 c = c + 2,是一种简化写法。

类似的还有 -= *= /= %= **= //= 这一组赋值操作符的变体,与 += 同理。

2.7 了解 逻辑运算操作符

逻辑运算操作符是布尔值之间的运算,结果也是布尔值。我们知道布尔值是逻辑上的真和假,而逻辑运算操作符就对应了逻辑代数(也叫布尔代数)里的各种运算。

逻辑运算 是 "程序中的逻辑判断和分支" 的基础,是非常重要的部分。

基本的逻辑运算就三个:andornot,即

  • 两个布尔值如果都是 “真”,他们的 运算结果也为 “真”,其他情况下结果都是 “假”;
  • 两个布尔值中只要有一个是 “真”,他们的 运算结果就为 “真”,如果两个都是 “假”,那么 运算结果为 “假”;
  • 布尔值 “真” 的 运算结果为 “假”,布尔值 “假” 的 运算结果为 “真”。
t = True
f = False

t and f
t or f
not t
t and (not f)
(not t) or f

结果如下:

False
True
False
True
False

2.8 操作符小结

  • 操作符是对数据进行操作的一些符号,最常用的有算术运算、大小比较、赋值、逻辑运算等;
  • 通过例子学习和掌握常用的这几类操作符;
  • 了解代码注释的语法和意义;
  • 了解浮点表示误差和规避方法。

3. 了解 函数

程序里的函数概念完全来自数学里的函数。

3.1 了解 函数的基本概念

在数学里,我们可以定义一个函数,函数有一些自变量(也就是一些输入),“函数定义” 就是 定义如何通过这些输入计算出函数的值(也就是输出)。函数定义好之后,随时可以用具体的值送进去算出一个具体的函数值。

程序里的函数也是这样: 我们先要定义函数,也就是定义它的名字(叫做 函数名 function name)、若干输入变量(叫做 参数 argument)以及如何通过输入变量算出函数的值(叫做 返回值 return value);一旦定义好,就可以在程序的其他地方随时使用这个函数,给它具体的参数值,它就给出具体的返回值。

我们先看看怎么用函数,术语叫做函数调用(call),因为 Python 本身自带了很多已经定义好的函数,我们可以直接拿来用。其中有一些我们之前已经用过了:

  • print(a, b,...):在命令行输出界面打印输入的参数(可以是各种类型的数据);
  • type(x):返回参数 x 的数据类型;
  • abs(x):返回参数 x(整数或者浮点数)的绝对值。
  • 这几个都是 Python 的内置函数,可以直接用。

这是一个 Python 内置函数,叫做 isinstance(x, c),它有两个输入参数,第一个是一个数据,第二个是一个数据类型,如果第一个数据是第二个数据类型,isinstance 函数返回 True,否则就返回 False。所以 isinstance 可以用来判断某个值或者变量是不是某种类型。

这是一个 Python 的平方根函数 math.sqrt(x),这个函数前面有个 math,这表示函数 sqrt(x) 不在 Python 的内置环境中,而是放在一个叫 math 的函数包(学名叫 模块 module)里。

要使用这种函数,第一种方法是:

  • 先 引入(import)对应的函数包;
  • 使用时函数名前面要带上模块的名字,中间加个 .

就像下面这样:

import math
math.sqrt(16)

另外一种方法是,从对应的模块里指明引入这一个函数,使用时就不用带上模块名了:

from math import sqrt
sqrt(40)

sqrt(x) 函数可以接受整数或者浮点数输入,返回值是浮点数。

3.2 了解 如何定义函数

在 Python 里 "函数的定义" 以关键字 def 开始,后面依次是 函数名、小括号括起来的参数列表 和 一个冒号,之后的代码就是函数的算法定义,直到关键字 return 开始的 返回语句,这个返回语句会终止函数的运行,并把 return 后面的值作为函数的返回值,返回到调用函数的地方。

3.3 插播:了解 代码缩进

  • 所谓代码缩进就是一行代码不顶格写,而是在行首空若干格再写。
  • 缩进可以很多层,比如 Python 中第一层缩进空四格,第二层缩进空八格,第三层缩进空十二格,依此类推。
  • 程序员都喜欢用 “等宽字体(fix-width font)”,就是因为等宽字体在缩进时非常整齐美观。

缩进是为了让代码层次更加鲜明地呈现出来。比如上面函数定义的代码,函数体的部分是 “隶属于” 函数定义 def 语句的,是它的组成部分,所以缩进一层表示这个从属关系;而后面的函数调用则不是,所以最后两行不缩进,和 def 语句平齐,代表是同一层级的代码。

在 Python 中缩进是强制的,如果不进行正确的缩进,比如如果函数体没有正确的缩进,解释器会因为找不到必须的部分而报错。

目前主流的程序编辑器都提供了 自动缩进、自动排版 的功能,在 VSCode 和 Jupyter Notebook 中书写代码时大部分时候都会自动正确的缩进,所以很方便。

3.4 了解 函数的意义

  • 函数是编程中最重要的概念之一。实现它有助于我们实现程序的 “模块化(modulization)”,我们可以把特定的工作用一个函数来实现,然后在需要的地方反复地调用它。这是一种 “代码重用” 的方法,一个函数就好像乐高积木里的一个组件,我们可以通过组合各种不同功能的函数来实现复杂的目标。

  • 函数还是协作编程的基础。如果我们实现了一个很有用的函数,我们可以写一个文档说明函数的输入参数和返回值是什么,这样的文档叫做 “调用接口(interface)”,别人都可以照此使用我们写的函数,甚至不需要了解它是怎么实现的(比如我们就直接拿了 math.sqrt() 来用而不需要自己写一个)。函数天生就把自己的接口和实现分离开,让协作和复用成果更容易。

"模块化" 和 "接口独立" 是编程时最重要的两个思维模型(没有之一),我们在后面还会深入介绍。

3.5 函数小结

  • 我们可以通过 声明函数名、函数参数列表 以及 计算函数返回值的算法 来定义一个函数;在 Python 中通过 defreturn 两个关键字来做这件事;
  • 定义好的函数可以随时被调用,送入具体的参数值,得到函数返回值,返回值可以用于赋值也可以用作参数值来调用其他函数;
  • 函数是编程中重要的概念,我们在第四部分还会深入讲解;
  • 了解 Python 的缩进规则。

Logging

2020-02-15 16:27:13 initialize