位运算符及其相关操作详解

简介: 位运算符及其相关操作详解

位运算符详解

前言:由于位运算符是直接对二进制数操作,因此对二进制、八进制、十六进制不甚了解的小伙伴建议先看这篇二进制、八进制、十六进制与十进制的相互关系,这样阅读本篇时将事半功倍

总览

  • 位运算是对计算机存储的二进制序列的相应位进行操作
  • 位运算的操作数必须是整数型或字符型
运算符 示例
按位与 & a & b
按位或 | a | b
按位异或 ^ a ^ b
按位取反 ~ ~ a
左移 << a << b
右移 >> a >> b

按位与 &

  • 按位与是指:参加运算的两个操作数,按二进制对应的位进行“与”运算。如果两个相应的二进制位都为1,则得到的结果为1,否则位0。
  • 0 & 0 = 0,0 & 1 = 0,1 & 1 = 1,1 & 0 = 0,即 全一为1,有零则零
int a = 15;
  //15的二进制序列为 0000 0000 0000 0000 0000 0000 0000 1111
  int b = 17;
  //16的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
  int c = a & b;
  //a和b按位与,那么得到的二进制序列应该是 0000 0000 0000 0000 0000 0000 0000 0001
  //即十进制的1
  printf("c = %d\n", c);

按位或 |

  • 两个相应的二进制位中只要有一个为1,则得到的结果为1。
  • 0 | 0 = 0,0 | 1 = 1,1 | 0 = 1,1 | 1 = 1,即 有一为1,全零为0
int a = 15;
//15的二进制序列为 0000 0000 0000 0000 0000 0000 0000 1111
int b = 17;
//16的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
int c = a | b;
//a和b按位与,那么得到的二进制序列应该是 0000 0000 0000 0000 0000 0000 0001 1111
//即十进制的31
printf("c = %d\n", c);

按位异或 ^

  • 参加运算的两个二进制位,如果相同则为0,不同则为1
  • 1 ^ 1 = 0,1 ^ 0 = 1,0 ^ 1 = 1,0 ^ 0 = 0,即 不同为1,相同为0
int a = 15;
//15的二进制序列为 0000 0000 0000 0000 0000 0000 0000 1111
int b = 17;
//16的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
int c = a ^ b;
//a和b按位与,那么得到的二进制序列应该是 0000 0000 0000 0000 0000 0000 0001 1110
//即十进制的30
printf("c = %d\n", c);

按位取反 ~

  • 将二进制中的每个位的 0变成1,1变成0
  • 如 ~025就是对八进制数25(即二进制数000 010 101)进行按位取反,得到752(111 101 010)
int a = 15;
//15的二进制序列为 0000 0000 0000 0000 0000 0000 0000 1111
int b = ~ a;
//a按位取反,二进制序列为 1111 1111 1111 1111 1111 1111 1111 0000
// 由于在计算机内部是按补码储存,那么转换为反码 1111 1111 1111 1111 1111 1111 1110 1111
// 转化为原码 1000 0000 0000 0000 0000 0000 0001 0000
//即十进制的-16
printf("b = %d\n", b);
int c = -1;
//-1的二进制序列为 1111 1111 1111 1111 1111 1111 1111 1111
int d = ~c;
//c按位取反,二进制序列为 0000 0000 0000 0000 0000 0000 0000 0000
//即十进制的0
printf("d = %d\n", d);

左移 <<

  • 左移运算符是用来将一个数的各二进制位全部向左移动若干位,右边的空位补零
  • 例如 a = 3(10) = 011(2),那么 a << 1的结果就是110(2)=6(10)
int a = 17;
//a的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
int b = a << 2;
//b为所有二进制为左移两位得到 0000 0000 0000 0000 0000 0000 0100 0100
//即十进制数 68
printf("b = %d\n", b);

右移 >>

  • 右移运算符是用来将一个数的各二进制位全部向右移动若干位
  • 逻辑右移:右移之后的空位无论原符号位是什么,统一补零
  • 算术右移:如果原符号位位1,那么空位全部补1,反之如果符号位为0,那么空位补0
  • 一般来说,编译器采用的是算术右移
int a = 17;
  //a的二进制序列为 0000 0000 0000 0000 0000 0000 0001 0001
  int b = a >> 2;
  //b为所有二进制为左移两位得到 0000 0000 0000 0000 0000 0000 0000 0100
  //即十进制数4
  int c = -15;
  //c的二进制原码序列为 1000 0000 0000 0000 0000 0000 0000 1111
  //反码 1111 1111 1111 1111 1111 1111 1111 0000
  //补码 1111 1111 1111 1111 1111 1111 1111 0001
  int d = c >> 2;
  //如果是逻辑右移
  //d的二进制序列为 0011 1111 1111 1111 11111 1111 1111 1100
  //如果是算术右移
  //d的二进制序列为 1111 1111 1111 1111 1111 1111 1111 1100
  //反码 1111 1111 1111 1111 1111 1111 1111 1011
  //原码 1000 0000 0000 0000 0000 0000 0000 0100
  //即十进制数-4
  printf("b = %d\n", b);
  printf("d = %d\n", d);

位运算的运用

判断一个数是否为偶数

  • Tips:利用位运算对一个数进行奇偶判断的速度要快于if(num % 2 == 0){……}这一操作
  • 我们知道二进制数每一位的权重为2,因此要判断一个数是否为偶数,只需要判断它二进制位的第一位是1还是0
  • 如果第一位是1,那么十进制值就会加上20 = 1这个奇数,而偶数加奇数一定为奇数,因此该数一定是奇数
  • 如果第一位是0,那么这个1就不会加上,该数就一定是偶数
  • 那我们如何得到二进制数的第一位呢?将这个数和1进行按位与操作就可以了,因为1的二进制位只有第一位为1,这样我们就可以单独对二进制的第一位进行判断,从而判断该数的奇偶性
#include<stdio.h>
int main()
{
  int a;
  while (scanf_s("%d", &a) != EOF)
  {
        //注意:关系运算符的优先级高于&,|,^操作符
    if ((a & 1) == 0)
      printf("%d是偶数\n",a);
    else
      printf("%d是奇数\n", a);
  }
  return 0;
}

统计二进制数中1的个数

方法一

  • 我们知道如果我们要获得十进制数的每一位,我们可以对这个数不断进行模10除以10操作
  • 例如要获得十进制数139的每一位,139 % 10 = 9,得到各位9,139 / 10 = 13,13 % 10 = 3,得到十位3,13 / 10 = 1,1 % 10 = 1,得到百位1
  • 那么对二进制数也可以这样操作,只是将模10除以10改为模2除以2
int One_Count(unsigned int num) //参数定义为无符号型是为了确保对负数运算的正确
{
  int count = 0;
  while (num)
  {
    if (num % 2 == 1)
      count++;
    num /= 2;
  }
  return count;
}

方法二

  • 可以利用 & 获得二进制的每一位
int One_Count(int num)
{
  int count = 0;
  for (int i = 0; i < 32; i++)
  {
    if (((num >> i) & 1) == 1)
      count++;
  }
  return count;
}

方法三(巧解)

  • 可能有小伙伴会认为上面的方法二中的循环固定要循环32次,当计算小数字二进制中1的个数时会浪费时间,那么还有没有更加极致的方法呢?
  • 有!我们来看一个表达式:n = n & (n - 1)
  • 举个例子,n = 15(10) = 1111(2), n - 1 = 14(10) = 1110(2), n = n & (n-1) = 1110(2)
    n = 1110(2), n - 1 = 1101(2), n = n & (n-1) = 1100(2)
    n = 1100(2), n - 1 = 1011(2), n = n & (n-1) = 1000(2)
    n = 1000(2), n - 1 = 0111(2), n = n & (n-1) = 0000(2)
  • 我们可以发现,这个表达式每一次计算,都可以将数n二进制位中最右边的1变成0,因此我们可以创建一个循环,n = n & (n - 1)这个表达式可以运行的次数就是数n二进制位中含1的个数
int One_Count(int num)
{
  int count = 0;
  while (num)
  {
    num = num & (num - 1);
    count++;
  }
  return count;
}

统计两个二进制数中不同位的个数

  • 根据按位异或 ^ 的规律,两个二进制位如果不同,则结果为1,相同结果为0,因此我们可以将这两个数先按位异或,在统计结果二进制位中1的个数
#include<stdio.h>
int One_Count(int num)
{
  int count = 0;
  while (num)
  {
    num = num & (num - 1);
    count++;
  }
  return count;
}
int main()
{
  int a, b, c;
  scanf_s("%d %d", &a, &b);
  c = a ^ b;
  printf("%d和%d二进制不同位有%d个\n", a,b,One_Count(c));
  return 0;
}

将一个数用二进制形式打印(左边为高位,右边为地位)

#include<stdio.h>
void Print(int num)
{
  unsigned int temp = 0x80000000;
   //即二进制数 1000 0000 0000 0000 0000 0000 0000 0000
   //第一位不看成符号位
  while (temp)
  {
    if ((num & temp))
      printf("1 ");
    else
      printf("0 ");
    temp >>= 1;
  }
}
int main()
{
  int a;
  scanf_s("%d", &a);
  Print(a);
  printf("\n");
  return 0;
}

不创建临时变量交换两个数

  • 这里我们要用到按位异或 ^
  • 这种方法的运行效率不如创建临时变量
void exchange(int *a, int *b)
{
    int temp = *a;
    *a = *b;
    *b = temp;
}
  • 这种方法仅作了解即可
#include<stdio.h>
int main()
{
  int a = 2, b = 3;
  a = a ^ b;
  b = a ^ b;
  a = a ^ b;
  return 0;
}


相关文章
|
Docker 容器
卸载Docker
当Docker卸载残留组件时,需手动清理。执行`yum remove`移除多个Docker相关包,包括client、common和selinux版本。接着删除`/etc/systemd/system/docker.service.d`、`/var/lib/docker`和`/var/run/docker`目录。先用`yum list installed | grep docker`列出已安装包,再用`yum -y remove`卸载`docker-ce`和`docker-ce-selinux`。
559 0
|
2月前
|
数据采集 存储 Web App开发
基于 Selenium 的美团外卖动态数据爬虫实现方案
基于 Selenium 的美团外卖动态数据爬虫实现方案
|
3月前
|
人工智能 数据挖掘 API
普通人OpenClaw副业指南|能力放大+阿里云/本地部署+免费API配置+3类低风险变现路径及避坑要点
2026年,OpenClaw(Clawdbot)已成为普通人撬动副业的实用工具——它并非需要专业编程技能才能驾驭的复杂系统,而是能放大已有能力的“效率杠杆”。其核心价值在于将重复劳动、流程化工作自动化,让用户从繁琐操作中解放出来,聚焦于能创造价值的核心环节。本文结合真实副业案例,拆解“能力+AI”的副业方法论,提供2026年阿里云及本地多系统部署流程、免费大模型API配置方法,梳理3类低门槛副业方向与避坑要点,全程不含营销词汇,让普通人无需技术基础也能快速启动副业。
671 0
|
8月前
|
算法 大数据 数据挖掘
Python循环语句:从基础到进阶的实用指南
本文深入浅出地讲解Python循环语句,涵盖for和while循环的基础与进阶用法,结合实例解析遍历、嵌套、控制流及性能优化技巧,助你掌握自动化处理数据的核心技能。
715 0
|
5月前
|
XML 弹性计算 数据库
2026阿里云服务器发票开具全流程指南与常见问题解答
在阿里云平台购买服务器后,开具发票是财务报销与成本核算的关键环节。阿里云支持电子发票与纸质发票两种形式,开票流程需遵循 “实名认证与抬头一致” 原则,不同认证类型(个人 / 企业)在开票权限、流程上存在差异。本文结合官方最新规则,详细拆解开票全流程,并解答税率、抬头修改、报销有效性等常见问题,帮助用户高效完成开票操作。
|
Prometheus 监控 Kubernetes
Prometheus 在微服务架构中的应用
【8月更文第29天】随着微服务架构的普及,监控和跟踪各个服务的状态变得尤为重要。Prometheus 是一个开源的监控系统和时间序列数据库,非常适合用于微服务架构中的监控。本文将详细介绍 Prometheus 如何支持微服务架构下的监控需求,包括服务发现、服务间的监控指标收集以及如何配置 Prometheus 来适应这些需求。
662 3
|
Web App开发 缓存 测试技术
体育赛事直播系统怎么开发(足球篮球电竞)
一、通过购买现成源码(如“熊猫比分”)并进行二次开发,是一种快速启动体育直播项目的方式,适合需求变动不大、预算有限的情况。但需注意,大规模改动可能增加工作量和潜在错误。 二、全新定制开发体育直播系统,虽成本高、周期长(约2周),但能完全根据客户需求设计,提供更高的灵活性和定制化程度。开发前需详细规划功能与界面布局,确保最终产品符合预期。
体育赛事直播系统怎么开发(足球篮球电竞)
|
前端开发 JavaScript
在 Vue3 + ElementPlus 项目中使用 computed 实现前端静态分页
本文介绍了在Vue3 + ElementPlus项目中使用`computed`属性实现前端静态分页的方法,并提供了详细的示例代码和运行效果。
746 1
在 Vue3 + ElementPlus 项目中使用 computed 实现前端静态分页