C#教程(7)———— 位运算符
前言
在之前我们一共介绍了算术运算符、赋值运算符、比较运算符和逻辑运算符四种运算符,而在运算符大家庭中还有一个较为独特的运算符类型,称为位运算符,那么什么是位运算符,又有哪些位运算符呢?
1 位运算符
位运算符是一种较为特殊的运算符,和之前介绍的四种运算符不同的是,位运算符直接作用于位(bit),并依次按位进行操作,而我们也知道,在计算机中所有的数据都是以二进制的形式保存的,而非十进制,因此,位运算符的使用不能再像以往一样在十进制的角度进行考虑了,它的操作都是以二进制位来进行处理的,所以在理解上还会有一定的难度。
在C#中位运算符一共有6个:
- 按位求补运算符或称为按位取反运算符,符号为~
- 左移运算符,符号为<<
- 右移运算符,符号为>>
- 按位与运算符,符号为&
- 按位或运算符,符号为|
- 按位异或运算符,符号为^
1.1 按位取反运算符
按位取反运算符是一个一元运算符,也可以将它理解为按位否运算符,它会将每一位上的二进制数由1变为0或由0变为1,不过在之前我们也有提到过,计算机存储负数或正数的时候,是占用了每一个数据的首位作为符号位的,第一位是1表示负数,是0表示正数,而按位取反运算符会连带着第一位的符号位一起改变,也就是说经过这个运算,正数会变成负数,负数会变为正数,那么我们可以看一下以下的代码。
int num1 = 14;
int num2 = -15;
Console.WriteLine(~num1);//输出结果为-15
Console.WriteLine(~num2);//输出结果为14
神奇的事情发生了,数据经过了按位取反运算,符号发生了变化没错,但为什么14取反后不是-14,而是-15呢?这个数据又是怎么来的?
1.1.1 原码,反码和补码
这里就涉及到计算机到底是如何存储负数这个问题了,那么我们来想一下,根据之前所说的,用第一位表示符号,后面表示数字,最简单的正负数存储方法是什么样的。
比如用1来举例,就以sbyte类型来说,一共有八位,第一位为符号位,那么+1的二进制存储形式就是这样的:0000 0001,那-1呢,只是符号变了数值没变,那是不是就可以用:1000 0001呢?
事实上并不是,这种思路明明更加清晰,为什么没被采用呢?原因很简单,就是因为数字中有一个很特殊的存在,那就是0,二进制的0保存下来是什么样的:0000 0000,但采用刚才的保存方式,会出现一个特殊的二进制值:1000 0000,也就是-0的概念,我们都知道在数学中,0的正负是不需要考虑的,那也就是说这种方式会在计算机中造出一个-0,这就有一定的问题了,那么我们是怎么解决-0这个问题的呢?
就是通过补码,那么这里就需要解释三个新的概念了,原码、反码和补码。
-原码:数据的符号位加上数据本身的绝对值构成原码,简单来说就是按照刚才我们所说的那种形式保存的数据本身
+1的原码: 0000 0001
-1的原码: 1000 0001
- 反码:正数的反码是其本身,负数的反码是在原码的基础上,符号位不变,其它位全部取反
[+1] = [0000 0001]原 = [0000 0001]反
[-1] = [1000 0001]原 = [1111 1110]反
- 补码:正数的补码是其本身,负数的补码是在反码的基础上加1
[+1] = [0000 0001]原 = [0000 0001]反 = [0000 0001]补
[-1] = [1000 0001]原 = [1111 1110]反 = [1111 1111]补
根据上面的描述,我们也能看到,反码和补码主要影响的就是负数,而计算机在存储数据时存储的实际上就是补码,我们可以再来想想-0的问题,如果是-0的话,补码会变成什么呢?
[-0] = [1000 0000]原 = [1111 1111]反 = [0000 0000]补
经过补码的运算,-0也变成了和+0一样的数字了,借助这个方法,就可以避免正负0的问题了。
那么除了正负0外,补码还解决了什么问题呢?
那就是减法的问题,计算机和人脑不同,人脑可以轻易理解减法,但计算机直接理解减法会是个很复杂的情况,也因此,计算机在计算减法时实际上是把减号后的数字当做负数运算的,也就是说 [1 - 1]这个式子,计算机实际上的运算是 [1 + (-1)],那么这样的运算在使用原码时会出什么问题呢?
1 - 1 = 1 + (-1) = [0000 0001]原 + [1000 0001]原 = [1000 0010]原 = - 2
可以看到使用原码运算时,[1 - 1]的运算结果变成了-2,显然是不正确的,但如果使用补码呢?
1 - 1 = 1 + (-1) = [0000 0001]补 + [1111 1111]补 = [0000 0000]补 = 0
这下就没有问题了,感兴趣的朋友也可以自己用别的数字试验一下哦。
1.1.2 按位取反运算结果
那么我们再回到按位取反运算符,来看一下14和-15两个数在经过取反时经历了什么。
~14 = ~[0000 1110]补 = [1111 0001]补 = [1111 0000]反 = [1000 1111]原 = -15
~(-15) = ~[1111 0001]补 = [0000 1110]补 = [0000 1110]反 = [0000 1110]原 = 14
这就是按位取反在运算中的经过了。
1.2 移位运算符
移位运算符是一个二元运算符,我们先来看运行结果:
Console.WriteLine(14 << 1);//输出结果为28
Console.WriteLine(14 >> 1);//输出结果为7
看起来是不是很奇怪,这个运算符到底做了什么呢?我们直接来看原理
14 = 0000 1110
14 <<1 = 0001 1100 = 28
14>>1 = 0000 0111 = 7
看明白了么?其实也可以通过移位运算符这个名字来看出来,实际上这两个运算符就是将数据的二进制向左或向右移动了,比如14<<1就是将14的二进制数向左移动一位,将左边移出去的数据抛弃,在右边多出来的位置上补0。
能理解左移的话右移也就很好理解了,就是反过来将数据向右移位,右侧移出去的数据抛弃左侧多出的空位补0。
1.3 按位与、或、异或运算符
这三个运算符中前两个大家看起来应该不陌生,就是上期我们提到的逻辑运算符,只是只保留了一半,那么在这里又会有什么样的效果呢?
1.3.1 按位与运算符(&)
我们先来看示例:
Console.WriteLine(13 & 14);//输出结果为12
结果是不是很莫名其妙,那我们还是放到二进制中来看
13 = 0000 1101
14 = 0000 1110
12 = 0000 1100
发现规律了么,和逻辑与运算符(&&)类似,按位与运算符(&)是根据二进制位,如果两个数这一位上的数据都是1的话,那么会返回1,如果有一个0或者全是0的话,就会返回0,所以13和14经过运算后,就变成了12这个数字。
1.3.2 按位或运算符(|)
一样先看演示
Console.WriteLine(13 | 14);//输出结果为15
我们再来看二进制数据找一下规律。
13 = 0000 1101
14 = 0000 1110
15 = 0000 1111
没错,和逻辑或运算符(||)类似,按位或运算符(|)也是两个数在这一位上,如果有一个为1就返回1,只有全是0的时候才返回0。
较为特殊的是,和之前的按位取反,移位等位运算符只能应用于整数或者能转换为整数的char类型不同,按位与和按位或两个运算符还可以应用于bool类型,进行逻辑判断,也就是说这两个运算符既可以作为位运算符,又可以 作为逻辑运算符。
true | false = true
true & false = false
以上两种形式也是合法的
1.3.3 按位异或运算符(^)
异或是一个较为新奇的概念了,同样我们还是先来看示例。
Console.WriteLine(13 ^ 14);//输出结果为3
这次的数据更加奇怪了,放到二进制中来看一下呢?
13 = 0000 1101
14 = 0000 1110
3 = 0000 0011
找到规律了么?感觉和或有点类似但又有点不一样,什么是异或呢?
- 异或:判断两个数相同时返回0,不同时返回1
和或不同,当两个数据相同的时候,无论都是1还是都是0,异或都会返回0,而或在两个数值都为1的时候返回的是1,这也就是异或和或的区别了,所以它叫做异或。
1.4 复合运算符
今天提到的所有运算符都可以和赋值运算符形成复合运算符。
a^=b 等价于 a = a^b
a&=b 等价于 a = a&b
总结
以上就是今天的所有内容了,今天主要介绍的是c#中的六种位运算符,而因为位运算符的运算涉及到二进制数据,所以在理解中还是有一定难度的,也希望大家能够认真理解。