python-操作符相关介绍(二)

    上一章我们说了部分的操作符,今天继续讲操作符。

1,一元减法(-), 识别(+)

    这两个符号和二元的加减法一样的符号一样,但是只有一个操作数,例如-1,+1。其实负数的话,我们用的就是一元减法,整数的话这个+号可以不带,但是带上也没啥问题,记住这个+号是一元的,看一下下面的代码。

a = -1 + +1
print("a={}".format(a))
#结果a=0

上面的a的表达式中,第一个加号是二元运算符,第二个加号是一元运算符,表示负一加正一,结果就是0。 第二个加号实际上可以省略。

2,按位求补(~)

这个操作符就是将内存中表示数字比特位每一位取反,就是将0变成1,1变成0,举个例子:我们如果用四bit表示一个数,那么5在内存中的表示就是0101,-5在内存中表示就是1011,这是~5就是1010,值就变成了-6,同样,~(-5),就是0100,值就变成了4。看过我之前文章的同学,可能会对补码的定义还有印象,但是现在这个概念和我们之前谈的补码又有不同。重点是前两个字,按位,说明这个是对每一位进行操作的。如果你对数字在内存中的表示不明白,建议看看我之前的文章pyhon-整数以及内存表示方式。有时候温故而知新,所以我又写了转换位内存表示的代码,大家可以再温习一下内存中bit的转化。

"""
获取内存bit值
a: 整数
b: 内存存储的bit数
返回一个bit列表
"""


def get_memory_bit(a, bits_num):
    minus = False
    # 如果a是负数,则取a的绝对值
    if a < 0:
        a = abs(a)
        minus = True
    #用于存放比特
    bits = []
    #如果a大于0,则不停的将a的余数倒序存入列表,将a更新位商
    while a > 0:
        #取余数
        tmp = a % 2
        #将余数倒序插入列表,也就是始终在0的位置插入余数
        bits.insert(0, tmp)
        # Floor除法,值是取整数部分
        a = a // 2

    #如果整个列表的长度小于用于存放bit数,则在0的位置不断的插入0
    while len(bits) < bits_num:
        bits.insert(0, 0)

    #如果是负数,则开始求负数的补码,内存中我们都用补码表示数字
    if minus:
        i = 0
        #先求出反码,就是每一位取反,也就是如果是0则变成1,如果是1则变成0
        while i < len(bits):
            if bits[i] == 0:
                bits[i] = 1
            else:
                bits[i] = 0
            i += 1
        i = len(bits) - 1
        
        #将最后一位加1,如果等2,说明有进位,将当前位置0,继续循环,
        # 如果不等于2,将当前位置1,循环结束
        while i >= 0:

            if bits[i] + 1 == 2:
                bits[i] = 0
            else:
                bits[i] = 1
                break
            i = i - 1

    return bits


a = 5
b = -5

print(get_memory_bit(a, 4), get_memory_bit(b, 4))
print("~a={},~b={}".format(get_memory_bit(~a, 4), get_memory_bit(~b, 4)))

#结果:
#[0, 1, 0, 1] [1, 0, 1, 1]
#~a=[1, 0, 1, 0],~b=[0, 1, 0, 0]

为了表明内存中是如何存储数字的,我写了get_memory_bit这样的函数,他可以模拟出内存的表示,这个函数需要你给出用多少位来表示数字,因为最高位是代表符号位,所以需要知道有多少位。

从输出的结果来看~确实是将内存中每一个位取反了。

3,冥运算(**):

    这个运算符表示多少次方,例如x**y就是x的y次方,这是个纯数学运算,我们可能用到的不多,简单下一下代码:

a = 2**3
b = (-2)**4
print("a={},b={}".format(a, b))
#结果:a=8,b=16

需要注意的是,运算符的优先级,其实我们一直没有谈运算符的优先级,因为运算符排优先级是很复杂的事情,要记住所有是很困难的,所以在你不确定的时候,只需要加括号就行,就像b表达式中的-2加了括号,因为一元运算符-的优先级是比冥运算的优先级低,所以如果不加括号,就会先进行冥运算,这样结果就是-16了。

4,比较运算符,集合子集,超集,相等,不等 < ,<=,  >,>=,==,!=

上一篇我们讲逻辑与或非的时候,其实就已经用了比较运算符,比较运算符会返回一个布尔值,看一下下面的代码:

a = 1 < 3
b = 1 > 3
c = 2 >= 2
d = 2 <= 2
e = 2 != 2
f = 2 == 1.0+1.0
g = 2 == 2.000000000000001
g1 = 2 == 2.0000000000000001
h = 0.1+0.1+0.1 - 0.1 - 0.1 - 0.1 == 0
print("a={},b={},c={},d={}\n\
e={},f={},g={},g1={},h={}".format(a, b, c, d, e, f, g, g1, h))
#结果:a=True,b=False,c=True,d=True
#     e=False,f=True,g=False,g1=True,h=False

注意格式化字符串的第二个\,这个是python的代码换行符。我这样做的原因是因为不想让你看代码的时候往左滑动。这里面注意>=和<=,c和d的表达式都为真,因为>=的含义是大于或者等于,只要满足一个条件就为真,同样<= 的含义就是小于或者等于,满足一个条件为真。另外注意g和g1,这两个结果是不一样的,之前我们说过,如果两个操作数,如果一个是整数,一个是浮点数,那么就会将整数转化为浮点数再计算。浮点数是不精确的,所以只要小数位足够多,就会产生为真的情况,这种情况一般是比较极端的,因为实际的操作中一般不会出现这么大精度的比较。但是h的表表达式发生的概率还是相对较大的,这个结果是假,因为左边的0.1+0.1+0.1 - 0.1 - 0.1 - 0.1结果是一个非0的很小的数,所以如果有浮点数表达式和0的比较,一定要非常小心,尽量避免出现这种情况。

这些比较运算符同样适用于集合,当然我们说集合的关系不是大小,而是谁是谁的超集,谁是谁的子集,或者两者相等,看下面的代码:

a = {1} < {3}
b = {1, 3} > {3}
c = {1, 2, 3} >= {1, 2, 3}
d = {2} <= {2}
e = {2} != {2}
f = {1, 2} == {2, 1}

print("a={},b={},c={},d={}\n\
e={},f={}".format(a, b, c, d, e, f))

#结果:a=False,b=True,c=True,d=True
#     e=False,f=True

 集合的比较需要注意一点,就是这两个集合必须有包含和被包含的才能比较,如果这两个集合都有对方没有的元素,那么不论是任何类型的比较都是False(除了不等于),就比如a的表达式,这两个集合都存在对方都没有的元素,所以不论是大于,小于,等于,结果都是False。

5,右移>>和左移<<

这和按位求补(~),以及位运算 (& | ^)一样,都是涉及到了数字在内存的中表示方式。因为python只要内存允许,可以表示无穷大的数,所以理论上python的左移不会溢出。当右移的时候,最高位往右移动,最低位舍去。因为最高位已经右移,当前最高位的值和之前的最高位的值一样。这样做是为了保证右移之后,这个数的正负不会变。左移的时候,因为整体向左移动,因为python内存足够的情况下可以表示无穷大的数,所以python左移后位数会越来越多,最低位补0。左移可以看作是将这个数乘以2,而右移则是floor除法。看一下下面的代码:

a = -1 << 2
a1 = -1 * (2**2)
b = -1 >> 8
b1 = -1 // (2**8)
c = 1 << 2
c1 = 1 * (2**2)
d = 1 >> 8
d1 = 1 // (2**8)

print("a={},b={},c={},d={},\n\
a1={},b1={},c1={},d1={}".format(a, b, c, d, a1, b1, c1, d1))
#结果a=-4,b=-1,c=4,d=0,
#   a1=-4,b1=-1,c1=4,d1=0

a,b,c,d都是左移或者右移,a1,b1,c3,c4对应的乘法或者floor除法,可以看到结果是一样的。值的注意的是-1这个值,右移的时候值是不变的,因为负1的补码表示全是1,所以无论右移多少次,还是-1。

6,索引[](序列以及映射)

序列可以认为是有序排列的容器,包含了列表,字符串,元组,映射就是字典,通过键值对关联。看一下代码:

#列表
a = [1, 2, 3, 4,5,6]
#字符串
b = "abcdef"
#元组
c = (1, 2, 3, 4, 5, 6)
#字典
d = {"a": 1, "b": 2, "c": 3}
print("a[1]={},b[2]={},c[3]={},d['a']={}".format(a[0], b[5], c[3], d["a"]))
#结果 a[1]=1,b[2]=f,c[3]=4,d['a']=1

从结果来看,对于序列,都是先从下标0开始的第一个元素,最后一个元素的下标是序列的长度减一。对于字典输入键的名字可以得到值。

7,分片[::]

分片用于序列操作,用两个冒号分成了三部分,例如[x:y:z],x代表起始位置,y代表结束位置(不包含),z代表步长,如果z不填,则默认步长是1。分片的操作有点tricky,我们看一下代码:

x = [1, 2, 3, 4, 5, 6]
a = x[0:2]
b = x[0:2:1]
c = x[1:6:2]
d = x[-5:-1]
e = x[-1:-7:-1]
f = x[-1:-7]
print("a={},b={},c={},d={},e={},f={}".format(a, b, c, d, e, f))
#结果:a=[1, 2],b=[1, 2],c=[2, 4, 6],d=[2, 3, 4, 5],e=[6, 5, 4, 3, 2, 1],f=[]

上面的代码我是以列表为例子来讲的,对于字符串和元组,也是一样的。a的表达式结果可以看出,取了第一个元素和第二个元素,因为序列的下标是默认从0开始,但是没有取下标是2的元素,所以分片不包含结束位置。b和a的效果是一样的,指定了步长1,因为默认的步长就是1。c的指定的步长是2,所以我们会取到下标1,3,5,7但是因为7已经超过结束的位置6了,所以取了三个元素:2,4,6。对于d的下标就觉得有点奇怪了,因为有负数,python默认最后一个元素的位置是-1,然后往前依次是-2,-3,-4。。。。,所以下标-5对应的值是2,所以从-5到-1对应的值2,3,4,5。e是列表逆序的一种方法,开始的位置的下标是-1,这对应最后一个元素,然后步长是-1,所以下一个元素的下标是-2,这是倒数第二个元素,所以通过这种方式,实现了列表的逆序。f的值是个空的,那是因为步长出了问题,-1是开始的位置,-7是结束的位置,但是步长是1,所以-1递增1永远不会得到-7,所以这个是非法的输入,所以结果是空。我们记住一点,开始的位置加上步长最后会到达或者超过结束的位置才是合法的输入,要不然就不会取的任何的值。

    可能有的思考仔细的同学在想,为啥逆序用负数,我用正数可以么?答案是不可以,假如我们要用正数x[5:0:-1],这样操作后,第一个元素是取不到的,因为这个结束的下标是不包含的,那是不是可以写成x[5:-1:-1]呢,这样也是不行的,这起来很混乱,5和-1是同一个位置,因为结束位置是不包含的,所以取到的结果是空。那么x[5:-7:-1]是不是可以呢?答案是肯定的。因为-7已经超过第一个元素的范围了,所以可以包含第一个元素。但是不管怎样,至少用到了一个负数。不过还是建议不要这种正负数混用,确实有点不和谐,说到这里,希望大家明白了切片操作的真谛。

操作符的介绍我们就讲到这里,当然有一些更高级的操作符的用法,这个到时候和具体的类型结合起来说。


 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白快快跑哦

您的鼓励是我最大的动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值