文章目录
前言:
本篇主要针对C语言中对二进制进行处理的一些操作符,又由于在内存中存储的是整数的补码,因此是对补码进行操作。
一、计算机中的二进制
我们知道一个整数有原码,反码和补码,在计算机内存中整数的存储形式是其二进制补码,那我们就讨论一下不同的整数是怎么存储在计算机中的,且是在32位计算机的环境下。
1.整数的分类

计算机默认整数位有符号整数,所以在创建无符号整数时需要用unsigned int
对于有符号整数在32位计算机中
| 符号位 | 数值位 | ||||||||||||||||||||||||||||||
最高位为符号位,其余位位数值位,且正数符号位记为0,负数记为1。
2.原码,反码和补码
(1)对于正数:原码,反码和补码都一样。
举个例子,正整数15在32位计算机中
| 原码 | 0 | 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||
| 反码 | 0 | 1 | 1 | 1 | 1 | |||||||||||||||||||||||||||
| 补码 | 0 | 1 | 1 | 1 | 1 |
由于表格大小限制,只展示了符号位与有意义的数值位,中间的0省略了。
(2)对于负整数:原码,反码和补码都不一样,反码就是原码取反,而补码就是对反码加1,且满二进一,在整个过程中,符号位保持不变。
举个例子,对于负整数15
| 原码 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 1 | 1 |
| 反码 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 0 |
| 补码 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 0 | 1 |
(3)无符号整数:原码,反码和补码都一样。
这里不再举例说明。
*再说明一点:比如在电脑上打印一个整数,则要将补码转化为原码来计算整数的值,而补码转原码,只需要将补码先取反,再加1。
二、操作符
1.移位操作符
1.1 左移操作符:<<
(1)移位规则:左边抛弃,右边补零
(2)举个例子:对正整数6,进行左移1的操作
代码示例
#include<stdio.h>
int main()
{
int a = 6;
int b = a << 1;
printf("%d\n", b);
return 0;
}
运行结果:12
(3)结合前面所讲分析
对于正整数a=6:a<<1
| 符 号 位 | 数值位 | ||||||||||||||||||
| 补码 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | ||
| 进行左移1位 | |||||||||||||||||||
| 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | |||
| 左边抛弃,右边补零 | |||||||||||||||||||
| 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | 0 | |||
所以b的补码为:0000 0000 0000 0000 0000 0000 0000 1100
再转化为原码为:0000 0000 0000 0000 0000 0000 0000 1100
即 打印出b=8+4=12
1.2 右移操作符:>>
原理同左移操作符相似,因此下面简单说明
1.2.1 逻辑右移
移位规则:左边补零,右边抛弃
1.2.2 算术右移
移位规则:左边补原来的符号位,右边抛弃
我所用的vs2022编译环境下,采用算术右移,因此下面展示的结果为算术右移的结果。
(1)首先拿正整数6来看:
代码展示
#include<stdio.h>
int main()
{
int a = 6;
int b = a >> 1;
printf("%d\n", b);
return 0;
}
运行结果:3
(2)再拿负数6来看:
代码展示
#include<stdio.h>
int main()
{
int a =-6;
int b = a >> 1;
printf("%d\n", b);
return 0;
}
运行结果:-3
对负数6计算逐过程分析:
| 符 号 位 | 数值位 | ||||||||||||||||||
| 原码 | 1 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | ||||
| 反码 | 1 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 0 | 0 | 1 | ||||
| 补码 | 1 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | ||||
| 补码右移1位 | |||||||||||||||||||
| 1 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 | |||||
| 左边补原来的符号位,右边抛弃 | |||||||||||||||||||
| 补码 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 0 | 1 | ||||
| 先取反 | |||||||||||||||||||
| 反码 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ||||
| 再加1 | |||||||||||||||||||
| 原码 | 1 | 0 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 1 | ||||
所以得到的原码为1000 0000 0000 0000 0000 0000 0000 0011
即为整数:-3
2. 位操作符
2.1 按位与操作符:&
(1)操作规则:将两个数的补码的相对的位进行计算,对应位同时为1则记为1,有0则记为0。
(2)举例:
直接看代码
#include<stdio.h>
int main()
{
int a =3;
int b = -5;
int z = a & b;
printf("%d\n", z);
return 0;
}
运行结果:3
(3)分析:
| 符 号 位 | 数值位 | |||||||||||||||
| a的补码 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
| b的补码 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
| 按位或操作 | ||||||||||||||||
| z的补码 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
| z的原码 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
所以z=3。
2.2 按位或操作符:|
(1)操作规则:补码的对应位只要有1则记为1,同时为0记为0。
(2)举例:
直接看代码
#include<stdio.h>
int main()
{
int a =3;
int b = -5;
int z = a | b;
printf("%d\n", z);
return 0;
}
运行结果:-5
(3)分析:
| 符 号 位 | 数值位 | |||||||||||||
| a的补码 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
| b的补码 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
| 按位或操作 | ||||||||||||||
| z的补码 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
| z的原码 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
所以z= -5
2.3 按位异或操作符:^
(1)操作规则:补码的对应位相同则记为0,相异则记为1。
(2)举例:
直接看代码
#include<stdio.h>
int main()
{
int a =6;
int b = -5;
int z = a ^ b;
printf("%d\n", z);
return 0;
}
运行结果:-3
(3)分析:
| 符 号 位 | 数值位 | |||||||||||||
| a的补码 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
| b的补码 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 1 |
| 按位异或操作 | ||||||||||||||
| z的补码 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 1 | 0 | 1 |
| z的原码 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 |
所以z= -3
2.4 按位取反操作符: ~
(1)操作规则:补码对应位原来为1则记为0,原来为0则记为1。
(2)举例:
直接看代码
#include<stdio.h>
int main()
{
int a =5;
int z = ~ a;
printf("%d\n", z);
return 0;
}
运行结果:-6
(3)分析:
| 符 号 位 | 数值位 | |||||||||||||
| a的补码 | 0 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 1 |
| 按位取反操作 | ||||||||||||||
| z的补码 | 1 | 1 | 1 | 1 | 1 | ... | 1 | 1 | 1 | 1 | 1 | 0 | 1 | 0 |
| z的原码 | 1 | 0 | 0 | 0 | 0 | ... | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 |
所以z=-6
三、拓展与应用
1. 按位异或:^
上面我们已经知道,按位异或:相同为0,相异为1。
所以有:a^a=0
a^0=a
且满足交换律:a^a^b=a^b^a=b
练习:不引入第三个变量,交换两个数的值。
代码展示
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int main()
{
int a, b;
scanf("%d %d", &a, &b);
printf("交换前a=%d b=%d\n", a, b);
a = a ^ b;
b = a ^ b;
a = a ^ b;
printf("交换后a=%d b=%d\n", a, b);
return 0;
}
运行结果:

2.移位操作符与按位与
练习:编写代码实现:求⼀个整数存储在内存中的⼆进制中1的个数。
代码展示
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int count_1_of_n(int n)
{
int i = 0;
int count = 0;//用来统计1的个数
for (i = 0; i <32 ; i++)//需要n的二进制位右移31次
{
if (((n >> i) & 1) == 1)//只有n最右边的一位为1时,按位与1才等于1。
{
count++;
}
}
return count;
}
int main()
{
int n;
scanf("%d", &n);
int k = count_1_of_n(n);
printf("%d\n", k);
return 0;
}
运行结果

7的二进制补码为:0000 0000 0000 0000 0000 0000 0000 0111
其中有3个1
更优的算法:
原理:
设n=15(为了方便,只写出为1的二进制位),然后n=n&(n-1)
补码的变化:
n 1 1 1 1 n-1 1 1 1 0 第一次 : n&(n-1) ; count++ n 1 1 1 0 n-1 1 1 0 1 第二次 : n&(n-1) ; count++ n 1 1 0 0 n-1 1 0 1 1 第三次 : n&(n-1) ; count++ n 1 0 0 0 n-1 0 1 1 1 第四次 : n&(n-1) ; count++ n 0 0 0 0
代码展示
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
int count_1_of_n(int n);//函数声明
int main()
{
int n;
scanf("%d", &n);
int k = count_1_of_n(n);
printf("%d\n", k);
return 0;
}
int count_1_of_n(int n)
{
int count = 0;
while (n)
{
n = n & (n - 1);
count++;
}
return count;
}
运行结果


2331

被折叠的 条评论
为什么被折叠?



