zl程序教程

您现在的位置是:首页 >  后端

当前栏目

C语言之按位取反~(七十一)

C语言 按位 取反
2023-09-14 09:09:58 时间
  1. 计算机存储数据基本知识

  • 计算机中二进制数包括(正数和负数)是以补码形式存储。

  • 符号位:补码的最左侧首位是符号位,0表示正数,1表示负数。

二进制有三种形式:原码、反码、补码。

  • 正数的补码和反码:是正数本身(原码)。

  • 负数的反码:符号位不变(是1),其余位按位取反,不用加一。

  • 负数的补码:符号位不变(是1),先按位取反,再加一。(即是反码加一)

在计算机内存中,负数以补码形式存在(即取反,再加一)。

  • 计算机并不直接存储二进制原码,而是存储二进制的补码。正数的补码就是原码。比如1,原码0000 0001,补码也为0000 0001。就存储的是原码0000 00001.

  • 而计算机不直接存储负数,而是存储负数的补码,负数的补码计算规则是"符号位不变,原码取反再加一"。

例如:-1怎么存储的?

原码1000 0001

取反1111 1110,

加一1111 1111

1111 1111就是-1的二进制补码。

当负数要输出给别的程序用的时候怎么办呢?总不能把存储的补码给别人的吧?给人用之前需要将补码逆向再转换成原码,否则数据就不对了。

负数转换补码的规则:取反,再加一,符号不变。

那么:

补码转换原码的规则:符号不变,先减一,再取反。

这样就得到原码,可以输出了。

  1. 将正数和负数按位取反(~)

特别注意:

"~"符号取反的用法,场景也不一样,同样是"~"符号,有的是求反码,有的是求补码,所以要搞清楚。

举两个例子说明:

<1>.代码示例一
#include <stdio.h>
 
int main(){
  int a = 0;
  printf("~0 = %d\n",~a);
 
  a = 1;
  printf("~1 = %d\n",~a);
 
  a = 2;
  printf("~2 = %d\n",~a);
 
  a = -1;
  printf("~(-1) = %d\n",~a);
 
  a = -2;
  printf("~(-2) = %d\n",~a);
}

打印:

~0 = -1
~1 = -2
~2 = -3
~(-1) = 0
~(-2) = 1
  • ~0步骤

步骤1:首先计算出0的补码,即它本身

步骤2:因为0的补码是负数,所以要还原其原码在输出;

0的补码和反码是他本身,再对0按位取反,就变成1111 1111,因为最高位为1,所以为负数,负数是以补码的形式存储,所以输出时要转换成正确的原码。

因为原码转补码的规则是:先取反,再加一

所以补码转原码的规则是:先减一,再取反。

步骤1:首先计算出0的补码,即它本身
0的原码的补码是他本身:
0000 0000

步骤2:因为0的补码是负数,所以要还原其原码在输出
~0:即所有位数,按位取反
1111 1111
  
  当你要输出的时候,编译器发现最高位符号位是1,这个数是个负数,而负数在计算机里面是用补码存储的,所以此时计算机认为这个1111 1111是补码,它要转换成原码输出,于是先减去1,除了符号位不变,其他位全部取反。

因为原码转补码的规则是:先取反,再加一
所以补码转原码的规则是:先减一,再取反。
减一操作:
1111 1110

取反操作
1000 0001 = -1

  • ~1步骤

步骤1:首先计算出1的补码,即它本身

步骤2:因为1的补码是负数,所以要还原其原码在输出;

因为原码转补码的规则是:先取反,再加一

所以补码转原码的规则是:先减一,再取反。

步骤1:首先计算出1的补码,即它本身
1的原码的补码是他本身:
0000 0001

步骤2:因为1的补码是负数,所以要还原其原码在输出;
~1的反码:即所有位数,按位取反
1111 1110

 如果要输出打印,此时编译器发现其为最高位为1,是个负数,负数在计算机里面是用补码存储的,所以此时计算机认为这个1111 1110是补码,它要转换成原码输出,于是先减去1,除了符号位不变,其他位全部取反。

减一操作:
1111 1101

取反操作
1000 0010 = -2
  • ~(-1)步骤

步骤1:首先计算出-1的补码(因为负数在计算机中以补码形式存在)

步骤2:再对-1的补码按位取反

步骤1:首先计算出-1的补码
-1的原码:
1000 0001

-1的反码:即按位取反(负号不变)
1111 1110

加一
1111 1111

步骤2:再对-1的补码按位取反,包括符号位
~(1111 1111) =0000 0000 = 0 

  • ~(-2)步骤

步骤1:首先计算出-2的补码因为负数在计算机中以补码形式存在)

步骤2:再对-2的补码按位取反

步骤1:首先计算出-2的补码因为负数在计算机中以补码形式存在)
-2的原码:
1000 0010

-2的反码:即按位取反(负号不变)
1111 1101

加一
1111 1110

步骤2:再对-2的补码按位取反
0000 0001 = 1

<2>.代码示例二:按位取反参与多项运算
#include <stdio.h>  
  int flag = 0x104; //0000 0001 0000 0100  
  int flag2 = 0x04;

  printf("flag2 = %#x\n",flag2);
  printf("~flag2 = %#x\n",~flag2);
  
  int b = flag & ~flag2;
  printf("b = %#x\n",b);
  return 0;
}

打印:

flag2 = 0x4
~flag2 = 0xfffffffb
b = 0x100

  • 求flag & ~flag2
1.flag = 0x104
二进制原码:
0000 0001 0000 0100  

2.flag2 = 0x04
二进制原码:
0000 0000 0000 0100

~flag2(按位取反):
1111 1111 1111 1011

flag与上flag2的反码
     0000 0001 0000 0100  
     1111 1111 1111 1011
=    0000 0001 0000 0000 = 0x100

所以b = 0x100,和计算机打印的一样。