C语言中& | ^ ~ >>

时间:2022-08-06 20:55:15

一直头疼的符号,现在不头疼了。

我们都知道,数据在计算机里是以二进制形式表示的。在实际问题中,常常 也有一些数据对象的情况比较简单,只需要一个或几个二进制位就能够编码表示。如果在一个软件系统中这种数据对象非常多,用一个基本数据类型表示,对计算机 资源是一种浪费。另一方面,许多系统程序需要对二进制位表示的数据直接操作,例如许多计算机硬件设备的状态信息通常是用二进制位串形式表示的,如果要对硬 件设备进行操作,也要送出一个二进制位串的方式发出命令。由于C语言的主要设计目的是面向复杂的系统程序设计,所以它特别提供了对二进制位的操作功能,称 为位运算。

  位运算应用于整型数据,即把整型数据看成是固定的二进制序列,然后对这些二进制序列进行按位运算。与其它 高级语言相比,位运算是C语言的特点之一。但是由于位运算的应用涉及更深入和更广泛的内容,初学者不必细究,在实际应用中可逐步体会其优越性。这部分内容 相对比较独立,读者可根据实际需要选择学习。本书仅对位运算及其应用作简要的介绍。

3.5.1 位运算符

  因为一个二进制位只能取值为0或者1,所以位运算就是从具有0或者1值的运算对象出发,计算出具有0或者1 值的结果。C语言提供了6种基本位运算功能:位否定、位与、位或、位异或、位左移和位右移。其中除位否定是单目运算外,其余5种均为双目运算,6个位运算 符分为4个优先级别,参见表3-9。

表3-9 逻辑运算符

运算符 含义 运算对象个数 结合方向 优先级

~ 按位求反 单目运算符 自右向左 1

<< 按位左移 双目运算符 自左向右 2

>> 按位右移 双目运算符 自左向右 2

& 按位与 双目运算符 自左向右 3

| 按位或 双目运算符 自左向右 4

^ 按位异或 双目运算符 自左向右 5

说明:

① 位运算的优先级是:~→<<、>>→&→|→^。

② 位运算的运算对象只能是整型(int)或字符型(char)的数据。

③ 位运算是对运算量的每一个二进制位分别进行操作。

3.5.2 按位逻辑运算

  按位逻辑运算包括:位与、位或、位异或和位否定等四种运算。为了帮助读者理解,我们设a和b都是16位二进制整数,它们的值分别是:

        a: 1010,1001,0101,0111

        b: 0110,0000,1111,1011

    为了便于阅读,a和b中每4位用一个逗号分开。以下介绍对于a和b的位与、位或、位异或和位否定等按位逻辑运算。

  1.按位与运算 (&)

    按位与是对两个运算量相应的位进行逻辑与,"&"的运算规则与逻辑与"&&"相同。

    &于运算符 :相同位置同为1则为1,否则为0。

    按位与表达式:c=a&b

         a: 1010,1001,0101,0111

      & b: 0110,0000,1111,1011

          c: 0010,0000,0101,0011

  2.按位或运算(|)

    按位或是对两个运算量相应的位进行逻辑或操作,其运算规则与逻辑或"||"相同。

    | 或运算符:相同位置有一个是1则为1,同为0才为0

    按位或表达式:c=a|b

        a: 1010,1001,0101,0111

      | b: 0110,0000,1111,1011

        c: 1110,1001,1111,1111

  3.按位异或运算(^)

    按位异或运算的规则是:两个运算量的相应位相同,则结果为0,相异则结果为1。

    ^异或运算符:相同位置数字相同为0,不同为1。

    即: 0^0=0 0^1=1 1^0=1 1^1=0

    按位异或表达式:c=a^b

        a: 1010,1001,0101,0111

      ^ b: 0110,0000,1111,1011

         c: 1100,1001,1010,1100

    可见,异或运算的含义是:两个相应位的值相异,则结果为1,相同则为0。

  4.按位求反运算符(~)

    按位求反运算运算规则是将二进制表示的运算对象按位取反,即将1变为0,将0变为1。

    按位异或表达式:c=~a

       ~ a: 1010,1001,0101,0111

         c: 0101,0110,1010,1000

  5.按位逻辑运算的应用

例3-8:设 int x=7,求y=~x

        y=~x=~7=~(0000,0000,0000,0111)=1111,1111,1111,1000=-8

    可见,对x的值(7)按位求反结果恰为-8的补码表示,其原因是计算机中有:

    整数求负=整数求补=按位求反+1 

    所以:按位求反=整数求负-1。

    请注意求反运算与单目减和逻辑非运算的区别:

     y=-x;  结果为:y=-7,

        y=!x; 结果为:y=0。

  例3-9:用按位与运算屏蔽特定位(将指定位清为0)。

    设 n=051652(八进制数),计算m=n&0177,则:m=052。

              n: 0,101,001,110,101,010

    & 0177: 0,000,000,001,111,111

            m: 0,000,000,000,101,010

    经过位与运算,将n前9位屏蔽掉,即截取n的后7位。

  例3-10:用按位与运算保留特定位。

    要想将一个变量n的特定位保留下来,只要设一个数,使该数的某些位为1,这些位是与要保留的n的特定位相对应的位,再将n与该数按位与。

    设 n=011050(为八进制数。对应的二进制为:0,001,001,000,101,000),要将n的右起第2、4、6、8、10位保留下来,只要 n=n&01252,则有:

                n: 0,001,001,000,101,000

     & 01252: 0,000,001,010,101,010

                n: 0,000,001,000,101,000 (n=01050)

    注意,按位与的"&"功能与取地址运算的"&"不同,尽管两者采用了相同的符号。

  例3-11:用按位或运算将指定的位置为1。

    设:x=061,y=016,则z=a|b为:

          x: 0000,0000,0011,0001

        | y: 0000,0000,0000,1110

          z: 0000,0000,0011,1111

    即将x或y中为1的位的相应位置成1,其结果是z中的后6位为1。

  例3-12:用按位异或运算将某个量的特定位翻转。

    要将变量n的特定位翻转,即原来为1的变0,为0的变1,只要设一个数,使该数的某些位为1,这些位是与n中要翻转的相对应的位,然后将n与该数进行按位异或运算。

    设:a=015,要将后四位翻转,只要a=a^017,则:

            a: 0000,0000,0011,1101

      ^ 017: 0000,0000,0011,1111

             a: 0000,0000,0000,0010

3.5.3 移位运算

  

C语言提供了两个移位运算:左移和右移,它们是把整数作为二进制位序列,求出把这个序列左移若干位或者右移 若干位所得到的序列。左移和右移都是双目运算,运算符左边的运算对象是被左移或右移的数据,而运算符右边的运算对象是指明移动的位数。数据左移或右移后空 出来的位置补0。

左移、右移运算表达式的一般形式为:

x << n 或 x >> n

其中x为移位运算对象,是要被移位的量;n是要移动的位数。

左移运算的规则是将x的二进制位全部向左移动n位,将左边移出的高位舍弃,右边空出的位补0。右移是将x的各二进制位全部向右移动n位,将右边移出的低 位舍弃,左边高位空出要根据原来量符号位的情况进行补充,对无符号数则补0;对有符号数,若为正数则补0,若为负数则补1。

  例如,设a=7,则:

b=a<<2  即:b=0000,0111<<2=0001,1100=28

c=a>>2  即:c=0000,0111>>2=0000,0001=1

左移的一个特殊用途是将整数值乘以2的幂,例如:左移运算表达式1<<4的计算结果是16,右移可以用于将整数值除乘2的幂。

3.5.4 位运算赋值运算符

  位运算符与赋值运算符可以组成以下5种位运算赋值运算符:

&=、 |=、 >>=、 <<=、 ^=

由这些位运算赋值运算符可以构成位运算赋值表达式。例如:

x&=y 相当于:x=x&y

x<<=2 相当于:x=x<<2

x>>=3 相当于:x=x>>3

x^=5 相当于:x=x^5

题目:写一个函数,求两个整数的之和,要求在函数体内不得使用+、-、×、÷。

分析:这又是一道考察发散思维的很有意思的题目。当我们习以为常的东西被限制使用的时候,如何突破常规去思考,就是解决这个问题的关键所在。

看到的这个题目,我的第一反应是傻眼了,四则运算都不能用,那还能用什么啊?可是问题总是要解决的,只能打开思路去思考各种可能性。首先我们可以分析人们是如何做十进制的加法的,比如是如何得出5+17=22这个结果的。实际上,我们可以分成三步的:第一步只做各位相加不进位,此时相加的结果是12(个位数57相加不要进位是2,十位数01相加结果是1);第二步做进位,5+7中有进位,进位的值是10;第三步把前面两个结果加起来,12+10的结果是22,刚好5+17=22

前面我们就在想,求两数之和四则运算都不能用,那还能用什么啊?对呀,还能用什么呢?对数字做运算,除了四则运算之外,也就只剩下位运算了。位运算是针对二进制的,我们也就以二进制再来分析一下前面的三步走策略对二进制是不是也管用。

5的二进制是10117的二进制10001。还是试着把计算分成三步:第一步各位相加但不计进位,得到的结果是10100(最后一位两个数都是1,相加的结果是二进制的10。这一步不计进位,因此结果仍然是0);第二步记下进位。在这个例子中只在最后一位相加时产生一个进位,结果是二进制的10;第三步把前两步的结果相加,得到的结果是10110,正好是22。由此可见三步走的策略对二进制也是管用的。

接下来我们试着把二进制上的加法用位运算来替代。第一步不考虑进位,对每一位相加。00 11的结果都00110的结果都是1。我们可以注意到,这和异或的结果是一样的。对异或而言,0011异或的结果是0,而0110的异或结果是1。接着考虑第二步进位,对000110而言,都不会产生进位,只有11时,会向前产生一个进位。此时我们可以想象成是两个数先做位与运算,然后再向左移动一位。只有两个数都是1的时候,位与得到的结果是1,其余都是0。第三步把前两个步骤的结果相加。如果我们定义一个函数AddWithoutArithmetic,第三步就相当于输入前两步骤的结果来递归调用自己。

有了这些分析之后,就不难写出如下的代码了:

int AddWithoutArithmetic(int num1, int num2)

{

        if(num2 == 0)

                return num1;

 

        int sum = num1 ^ num2;

        int carry = (num1 & num2) << 1;

 

        return AddWithoutArithmetic(sum, carry);

}