程序员入伙书——运算符和表达式
- 2012年12月23日
- 发布在 infotech . on my mind
- 留下评论
上次我们说到了简单数据,现在就要说说怎么烹饪这些数据。因为我们的示例语言是Python,所以这里的内容只能保证对Python完全正确,其它的语言会有相似内容,但不一定完全相同。不用担心,各种编程语言的要素其实是差不多的,学会一门语言的思路,其它的也能很快融会贯通。
适用于数字的运算符
+ - * ** / // % << >> & | ^ ~ < > <= >= == !=
其中,第一行运算符是一般的数学运算,第二行是计算机特色的位运算(不用担心看不懂,后头会讲到),第三行是数值比较。
先说第一行:+和-都很直观,加减是也。*是乘法,因为计算机键盘上没有×号,所以拿星号表示。你会问为什么不用x来代替?我回答:因为x是一个英文字母,会被Python当作一个变量名。
**是乘方的意思,例如2**3等于8,2**-1等于0.5,2**0.5等于1.4142135623730951(即根号2)。你可能会如梦方醒地说,既然可以这么方便地求解一个数的平方根,为啥当初要写那么复杂的一个算法?俺一边躲闪着观众的飞石,一边说:一、那段程序的主要目的,是演示二分查找的算法。二、其实在计算机的芯片级指令里也并没有乘方功能,**运算符的简洁外表的背后,隐藏着泰勒级数展开的算法,不比我那段程序轻松。
/是除法。小时候如果在试卷上这么写,是会被扣分的。老师只允许用÷号,或者以分数形式表示。键盘上没有÷号,就只能用/了。
//是除法取整数商。注意它和/是不同的:3/2的结果是1.5,而3//2会把那个小数部分.5扣除,只留下1。同理,1//2等于0,而不是0.5。拿这个运算符算整数是安全的,而算实数要小心,例如你可以试试0.7//0.1,和《两个世界》一章里的计算结果是一样一样的:6,具体原因在那一章已经讲过了。
%是除法求余数,例如7%4等于3,3%2等于1,8%4等于0。这个运算符很实用,常用来判断一个数字能否被另一个数整除(余数为0),或者把随机数字限定在一个范围内(不需要十分懂下面这个例子):
>>> import random >>> random.random() 0.08662194959873759 >>> int(random.random() * 1E10) % 10 9 >>> int(random.random() * 1E10) % 10 5 >>> int(random.random() * 1E10) % 10 0 >>> int(random.random() * 1E10) % 10 4 >>> int(random.random() * 1E10) % 10 6 >>> int(random.random() * 1E10) % 10 2 >>> int(random.random() * 1E10) % 10 1 >>> int(random.random() * 1E10) % 10 8 >>>
暂且跳过第二行的位运算,这些概念目前有点难,大部分情况下也用不着,先介绍第三行的比较运算吧。
< > <= >= == !=
这六个运算符十分好理解,从左往右,分别是小于、大于、小于等于、大于等于、等于、不等于的意思。怎么样?很简单吧?计算机键盘上没有≤、≥、≠这些字符,所以用<=、>=、!=来代替,就和刚才说过的乘除法似的,比较好懂。对于==,就得多解释一下:为什么不用单个的=呢?因为单个的=被用来表示“赋值”的含义了。看下面和Python Shell的交流过程,来加深理解:
>>> a = 3 # 单个=号,把3放到变量a里 >>> a == 3 # 双==,判断a是否等于3 True >>> a == 4 # 双==,判断a是否等于4 False >>> a = 4 # 单个=号,把4放到变量a里 >>> a == 3 # 看!因为a的值改变,以下两个比较的结果变化了 False >>> a == 4 True >>>
先易后难,再掉过头来说第二行的运算符,这一排叫做位运算:
<< >> & | ^ ~
计算机的任何东西,无论是程序还是数据,归根到底都是二进制0、1的各色组合。咱们看着顺眼的十进制的数字,例如68,在机器里表达是01000100。这里的每个0或者1叫做一个“位”(Bit),每8个位叫做一个“字节”(Byte)。这个词是不是很熟悉?对,你一定听过也说过。有人问你某文件有多大,有多少字节,你说挺大的,有几十兆,这儿说的几十兆后面的单位就是“字节”。计算机里,具有含义的最小信息单位就是每8位一组的字节。
位运算,就是对一到多个字节里的“位”进行操作的过程。举个快速的例子:
>>> 68 << 1 # 01000100的所有位向左平移1位 136 # 10001000即136 >>> 68 >> 1 # 01000100的所有位向右平移1位 34 # 00100010即34 >>> 68 >> 2 # 01000100的所有位向右平移2位 17 # 00010001即17
Python Shell总是用十进制来表达运算结果,所以我把二进制的表达方式放到注释里了(用#开头),这样可以看得直观些。你一定注意到了68、136、34、17这些数之间有个神秘的关系:两倍两倍的!你的观察很正确,事实上,二进制也确实如此,左移一下,整个数字翻倍,右移一下,整个数字减半。就好比十进制的数字,左移一下,整个数字乘10一样。实际的应用中,有很多计算机遇到整数除以2的操作,往往会优化成整数右移一位的机器代码,因为除法是大约耗费三十几个时钟周期的操作,而移位只需要一个时钟周期就搞定了。
&、|、^分别是“与”、“或”、“异或”操作。“与”的意思是:只要有0出现,结果就是0,类似于现实生活中的一票否决。“或”的意思正好相反:只要有1出现,结果就是1,一人得道、鸡犬升天的感觉。“异或”则是看运算双方是否不同,如果不同则结果为1,相同则为0。来看几个例子:
>>> # 01111000 120 >>> 120 & 30 # 00011110 30 24 # 00011000 24 只有第4、5位都是1 >>> # 01111000 120 >>> 120 | 30 # 00011110 30 126 # 01111110 126 从第2到第7位都是1 >>> # 01111000 120 >>> 120 ^ 30 # 00011110 30 102 # 01100110 102 只有一方为0、另一方为1的位运算结果是1
~是对字节的每位求反,把0翻成1,把1翻成0。如果你在Python Shell里做做实验,一定会对~1等于-2,而~0等于-1感到大惑不解。这一点需要涉及很艰深的“补码”的概念,我不准备介绍。只从数字在计算机里的表示法上,来说明这个运算结果确实是对的:1是00000001,-2是11111110,0是00000000,-1是11111111。
在Python语言里,位运算只对整数有效。
适用于字符串的运算符
上面说过的这些运算符,有些对于字符串也是适用的。比较运算全部可以用于字符串,办法就是从第一个字符开始,一个个地逐个比较下去,一直对比到双方不相同了,谁的字符在计算机编码表里数值大些,就算谁大,字符串的长度完全不考虑。例如:
>>> 'b' > 'abcdefghijklmnopqrstuvwxyz' True
这个例子里,左边的字符串仅仅有一个字符b,但它大过了右边字符串的第一个字符a,因此它赢了,右边即使有千军万马都不在话下。
位运算对字符串通通不管用,也没有意义。
+和*可以用于字符串的操作,+是把两个字符串连接起来,*是把字符串重复若干次:
>>> 'air' + 'bag' 'airbag' >>> 'ha' * 5 'hahahahaha' >>>
你一定在琢磨,没准,-和/可以用来从airbus里扣除bus,或者把hahahahaha除以5得到ha。可惜,我已经试过了,没用。因为+和*是肯定能成功的简单拼接操作,而-和/需要被操作的字符串满足很苛刻的条件,这些操作也没有太实际的意义。比如说,hahahahaha除以5可以得ha,而除以4的话,1.25个ha有什么物理含义呢?
%可以放在字符串后面用以格式输出,现在先不讲它,回头说到输入输出的时候再细细聊。
运算顺序
运算的各种合法组合,就是表达式。为了和人类的数学规则相应,Python的四则运算的优先级也是和数学一样的:在没有括号的时候,先算乘方,再算乘除,最后加减。如果需要先算加减,则需要用括号。Python(及简直全部编程语言)都只使用小括号()来调整运算式里的优先级。[]和{}属于语言的其他用途,不是用来表达更外层的那几层括号的。所以看到(a * (b + 3) + c) / ((c – 4) * d)时,不要感到别扭,多看看就好了。当然了,有时括号嵌套很多时会数不清楚,不用担心,我自己也经常数花眼。
同一优先级的运算符,计算的顺序是从左向右,例如3 / 1 / 2的计算过程,是3 / 1,然后用所得到的3除以2,得到1.5。大家看到这个式子,不需要纠结它是不是还意味着3除以1/2,完全没有这个可能。如果是3除以1/2,那么式子应该写成:3 / (1 / 2)。
位操作的优先级大多低于四则运算(求反~运算除外)。例如2 & 1 + 6等价于2 & (1 + 6),结果是2,而非(2 & 1) + 6(结果是6)。在除求反之外的位运算里,首先执行移位操作(<<和>>),其次是异或运算(^),再次是“与”运算(&),最后才轮到“或”运算(|)。
所有的四则运算和位运算优先于比较运算进行,这意思就是说,a + 3 > b | 6这个式子里,大于号两边的表达式是不需要括号的,计算机不会把它误认为a + (3 > b) | 6。这和人们的思维习惯相同。
记不住这些顺序没关系!杀手锏是使用括号!只要用括号一括,什么歧义都没有了,什么规则都不需要背了,此诚利国利民之神器。