简谈位运算符
- 前言:读者需要对源码,补码有一定了解,在此不过多讲解
一、& 按位与运算符
int foo = 10 & 7;
printf("%d", foo); // 2
规则:将左右两边的表达式每一位进行逻辑与,如果是负数,先转换为正数的补码,进行逻辑与
二、|按位或运算符
int bar = 10 | 15
printf("%d", bar); // 15
将左右两边的表达式每一位进行逻辑或,如果是负数,先转为负数的补码,进行逻辑或
上面这两个运算符,只需单独将它们的二进制位,每一位进行运算即可,考虑负数的问题,先将负数的二进制求出,进行运算,运算结果就是就是该负数的绝对值,再将绝对值取反加1,拿到的就是按位或后的值。
如下代码
int bar = 10 | -7
10的 二进制代码是 -> 1010
-7的 二进制代码是 -> 先求正7 0111 取反加1,就是负数的二进制代码 1001
将这两个值进行相或运算, 1 1011 开头的"1"代表这是一个负数,“”1 1011“”这是一个负数的绝对值! 前面省略28个1,只用1代表是负数,int类型存储是32位
再将该补码取反1加即可得到最后或运算的结果
即:1 0100 + 1 - > 1 0101 == -5
同理 按位与和上面解释原理一样
但下面这两个运算符不那么轻松
三、~ 按位取反
将 “~” 右侧的表达式进行每一位取反
取反规则:
负数:先将该负数正数的二进制代码以补码的形式求出
该负数正数的二进制代码取反加1即是该负数的二进制代码
如:-3的二进制码是 +3 0011 取反加1后的结果 1101
拿到二进制码之后(该码是该负数以补码的形式存储的二进制代码)再取反,得到的二进制代码就是~后的数(负数的最高位是1,但经过取反之后为0,所得结果一定是正数)
正数: 取出表达式的二进制码,将每一位取反(此时最高位是1,代表是一个负数),所以需要取反得到该负数的绝对值(正数的结果一定是一个负数)
如:2的二进制码是 +2 0010 需要每一位取反结果为 1101(最高位是1,所以需要取反拿到这个负数二进制码的绝对值)
0010 + 1 == 0011 该数是一个负数结果为 -3
规则:如果是正数那么得到的结果是负数 + 1的值, 如果是负数那么得到的结果是正数 - 1的值
读者可以不追求底层原理,只求结果
四、^ 异或运算符
int foo = 2 ^ 7; // 双方正数
int bar = -2 ^ 7; // 一方为正一方为负
int mul = -7 ^ -2; // 双方负数
规则:
1、双方正数
当两边都是正数的时候,直接将倆数的二进制代码按位异或即可:
相同为0,不同为1
如:2 ^ 5
"2"的二进制为 0010 "5"的二进制为0101 相异或后为0111 结果为7
2、一方为正一方为负
当有一方为负数时,负数的一方先进行取反加1,求出该负数的二进制码,两方再进行异或,所得结果是一个负数,将这个负二进制码,进行逆运算(次码是源码,需要进行逆运算得到补码形式的二进制码,得到该负数的值)
如:-2 ^ 7
"-2"的二进制码 为 1101 "7"的二进制码 为 0111
所得结果为 1 1001 (前面的一省略28位,用一位表示该二进制码表示一个负数的绝对值)
再逆运算 -> 先减一 再去反,拿到的结果为 1001 - 1 == 1000
取反后 0111 由于是一个负数所以,结果是 -7。
3、双方负数
次用法与第一种类似,先将负数二进制码求出,进行异或,所得结果就是两数异或后的结果。
如:-2 ^ -5;
"-2"的二进制码为1110 "-5"的二进制码为1011 异或所得值为 0101所以所得结果 5;
五、<< 按位左移
规则:左移n位相当于将该数字的二进制乘于左移次方数
正数:直接左移,将二进制码成于左移次方
负数:和左移一样,只是加了负号
如:2 << 2
0010 乘以2的n次方 1000 将1向左移,右边补0
如:-2 << 2
只是加了符号,并不会拿负数的二进制码去左移。
六、>> 按位右移
规则: 右移n位相当于将该数字的二进制除以右移次方数
正数:直接右移,将二进制码除以右移次方,最高位是1补1,最高位是0补0
负数: 和右移一样,只是加了负号
如:2 >> 2
0010 除以2的n次方 1000 将1向右移,左边补0
如:-2 >> 2
只是加了符号,并不会拿负数的二进制码去右移。
七、>>> 按位右移(左边补1)
规则:功能与 >> 一样,只是强制左边补1