一.前言
C语言里有各种各样的操作符,尤其是移位操作符(<<、>>)和位运算符(&、|、^)在底层编程、性能优化等场景下特别实用,玩得溜的话能写出更高效的代码。这篇文章会带你用这些操作符实现几个小技巧:快速数出内存中一个整数的二进制里有多少个1、比较两个数的二进制差异、以及判断一个数是不是2的幂次方。这些技巧看着简单,但在实际开发中能帮大忙。
二.位运算基础回顾
2.1 移位操作符
2.1.1 左移操作符(<<)
在C语言中,左移操作符(<<)是一种位操作符,用于将操作数的所有位向左移动指定的位数。移位规则是:左边抛弃,右边补零。
注意:无论num在这里是正数还是负数,左移相当于给该数乘以2。因此,num<<1的结果是20。
2.1.2 右移操作符(>>)
在C语言中,右移操作符(>>)是一种位操作符,用于将操作数的所有位向右移动指定的位数。右移可以分为两种,其一是逻辑右移、其二是算术右移!
逻辑右移:左边用零填充,右边丢弃!
算术右移:左边用该值的符号位填充,右边丢弃!
逻辑右移演示
算术右移演示
注意:大多数编译器在运行右移操作符的时候都是算术右移!!!
注意:对于右移操作符来说,如果操作数是一个正整数,相当于给该数除以2。负数不满足!!!
int num = 10;
num = num >>1;
num的结果就是5!
int num = 5;
num = num >> 1;
num的结果就是2,相当于num/2。
2.2 位操作符
2.2.1 按位与(&)
在C语言中,按位与操作符 &对两个操作数的每一位进行逻辑与运算。只有当两个对应位都为 1 时,结果位才为 1,否则为 0。
注意:位操作都是对补码进行操作!!!
// 0000 0011 (3的补码)
// 1000 1001 (-9的补码)
// 0000 0001 (结果为:1的补码,正数的补码与原码一致,结果为1。)
2.2.2 按位或(|)
在C语言中,按位或操作符 |
对两个操作数的每一位进行逻辑或运算。只要两个对应位中有一个为 1,结果位就为 1,否则为 0。
注意:位操作都是对补码进行操作!!!
// 0000 0011 (3的补码)
// 1000 1001 (-9的补码)
// 1000 1011 (结果为:-11的补码,需要先取反再+1,变成原码为1111 0101,结果为-117)
三.内存中存储二进制数1的个数
3.1方法一
int main()
{
unsigned int n = 0;
int count = 0;
printf("请输入要求的数:>");
scanf("%d", &n);
while (n)
{
if (n % 2 == 1)
{
count++;
}
n = n / 2;
}
printf("内存中二进制1的个数为:%d", count);
return 0;
}
①对于一个二进制数,先将其%2可以可以判断最后一位是0还是1,再将其/2可以去除最后一位。这个过程类似于一个十进制数,现将其%10可以判断最后一位是0~9中哪一个,再将其/10可以去除最后一位。然后依次循环这个过程即可。
②另一个需要注意点是n被定义为unsigned int无符号整数,这是因为内存中整数是以补码形式存放的,为了消除负数在%2和/2过程中的影响,无论输入的数是正还是负都按无符号整数读入。
3.2 方法二
int main()
{
int n = 0;
int count = 0;
printf("请输入要求的数:>");
scanf("%d", &n);
for (int i = 0;i < 32;i++)
{
if ((n >> i) & 1)
{
count++;
}
}
printf("内存中二进制1的个数为:%d", count);
return 0;
}
①遍历循环int类型的32个二进制位,每次右移1位,并将右移的结果与1按位与。如果是1,说明最右边的1位也是1;如果是0,说明最右边的1位也是0。
比如 int n = 10
// 00000000 00000000 00000000 00001010 (10的补码)
// 00000000 00000000 00000000 00000001 (1的补码)
// 00000000 00000000 00000000 00000000 (结果为0,说明10的二进制表示中最右边1位是0)
3.3 方法三(推荐)
int main()
{
int n = 0;
int count = 0;
printf("请输入要求的数:>");
scanf("%d", &n);
while (n)
{
n &= (n - 1);
count++;
}
printf("内存中二进制1的个数为:%d", count);
return 0;
}
①这个方法的关键在于n &= (n-1),这个算法每执行一次可以去除n的二进制中的一个1。
四.判断两个数的二进制数有几位不同
对于两个整数,其在内存中以二进制补码形式存放。如果想要判断有几位不同,可以考虑先异或,相同为0,不同为1。之后再统计这个结果中1的个数。
int main()
{
int a, b;
printf("请输入两个数:>");
scanf("%d %d", &a, &b);
int c = a ^ b;
int count = 0;
while (c)
{
count++;
c &= (c - 1);
}
printf("不同位有:%d位", count);
return 0;
}
五.快速判断一个数是否为2的幂次方
int main()
{
int n = 0;
printf("请输入这个数:>");
scanf("%d", &n);
if (n & (n - 1))
{
printf("%d不是2的幂次方", n);
}
else
{
printf("%d是2的幂次方", n);
}
}
①这里同样用的n&(n-1),2的幂次方数的特点是其存放在内存中的二进制数只有一个1。
因此,n&(n-1)这个操作只执行一次,对于2的幂次方数来说就可以使其变为0。如果没有变为0,那就意味着二进制数中还有其它1,那必然不是2的幂次方数!!!
通过本文的学习,我们系统地掌握了 C 语言中移位操作符(<<
、>>
)与按位运算符(&
、|
、^
、~
)的几大经典应用:快速统计整数二进制中 “1” 的个数、比较两个数的二进制差异、以及瞬间判断一个数是否为 2 的幂。位运算虽然看似“小技巧”,却能在算法性能和代码简洁度上带来实实在在的提升,也能帮助你更深刻地理解计算机底层的数据表示与运算机制。
如果你在实践中遇到其他有趣的位运算场景,或者对文中方法有不同的思考和改进,欢迎在评论区分享你的心得与疑问。让我们一起交流、讨论,共同进步!