[c语言日寄](bit)位检查——初探字节之下

embedded/2025/1/18 11:55:09/

哈喽大家好啊,在今天的快乐刷题中,我遇到了一个很有意思的题目:

题目

统计二进制中1的个数

基本思路

没错……这道题的对象比较特殊。
不同于过去常见的题目,之前的题目的对象都是基本数据类型,而这道题的对象却是基本数据类型之下——bit,也就是位。

位(bit)

“bit”是“binary digit”的缩写,中文意思是位。它是计算机中表示数据的最小单位,每个0或1就是一个位,而8个bit组成一个字节(Byte)。
在C语言中,没有直接的bit型变量类型,但可以通过位域(bit fields)或位运算来模拟bit型变量的定义(通过结构体)。

想要进行对位的操作,就需要以位为对象的专用运算符。

常见位运算符

  • 按位与(&)
    功能:两个相应的二进制位都为1时,结果才为1,否则为0。

  • 按位或(|)
    功能:两个相应的二进制位中只要有一个为1,结果就为1,否则为0。

  • 按位异或(^)
    功能:两个相应的二进制位相异时,结果为1,相同则为0。
    可以用于交换两个变量的值。
    点击此处查看往期内容:整数交换的三种方式。

  • 按位取反(~)
    功能:对二进制位取反,即0变1,1变0。

  • 左移(<<)
    功能:将二进制位向左移动指定的位数,左边移出的位被丢弃,右边空出的位用0填充。
    可以用于快速实现乘以2的幂次方的操作, x << n 相当于 x * 2^n。

  • 右移(>>)
    功能:将二进制位向右移动指定的位数,对于无符号数,左边空出的位用0填充;对于有符号数,左边空出的位用符号位填充(即符号扩展)。
    可以用于快速实现除以2的幂次方的操作,例如 x >> n 相当于 x / 2^n(对于无符号数)。

位运算在处理二进制数据时非常高效,能够直接对内存中的数据进行操作。

判断一个bit是否为1

要怎么样读取二进制的位数呢?
事实上,刚刚我们介绍的位运算符虽然能够对位进行操作,但是他们的对象依旧是基本数据类型。(A & B中,A、B都是基本数据类型)那到底要怎样实现遍历全部的bit呢?
答案是将bit的值用基本数据类型表示。

让我们看看下面这张图:
在这里插入图片描述
假如这张图代表一个二进制数据n,我们要做的事情是:

  • 如果最后一个bit的值为1,那么这个数据用十进制的int表示为1。
  • 反之,如果最后一个bit的值为0,那么这个数据用十进制的int表示为0.

十进制1和n的内存对比如下:
在这里插入图片描述
这时候,如果我们使用按位与(&)会怎么样?

没错!在这里插入图片描述
n&1的值为1!

那如果最后一位为0呢?这时候我们再使用一次按位与(&):
在这里插入图片描述
没错!是0!

使用按位与,我们可以实现判断最后一位是否为1

结合if,我们可以轻松实现这个判断:

if( n & 1 )

此时,我们实现了判断一个bit是否为1的程序。

遍历所有的bit

我们判断bit的条件是:这个bit是最后一位。
那么,我们只要每判断一次bit,我们把数据”往后移动一位“,就可以实现下一次判断的条件。

担当起这个任务的位运算符为: >>。

所谓右移运算符

右移运算符(>>)是一种位运算符,用于将一个数的二进制表示向右移动指定的位数。

  • 基本概念
    功能:将一个数的二进制位向右移动指定的位数,右边移出的位被丢弃,左边空出的位根据数的类型进行填充。
    语法:number >> n,其中number是要进行右移操作的数,n是要移动的位数。
  • 移动规则
    • 对于无符号数:左边空出的位用0填充。例如,对于无符号整数10(二进制为 1010),10 >> 1的结果是5(二进制为101),左边空出的一位用0填充。

    • 对于有符号数:左边空出的位用符号位(即最高位)填充,这种填充方式称为符号扩展。

      例如,对于有符号整数-10(假设其二进制补码表示为11110110),-10 >> 1的结果是-5(二进制补码为11111011),左边空出的一位用符号位1填充。

具体实现

此时,我们只需建立这样一个语句,就可以轻松实现“往后移动一位”。

a = (unsigned int)a >> 1;

其中,(unsigned int)是强制类型转换,是确保左边空出的一位用0填充。

循环跳出方式

跳出循环?当全部的1都被“舍弃”,那么不就变成0了吗?使用while即可。

while (a != 0){}

全代码

我们将上面取得的成果全部结合起来,便得到了以下内容:

#include<stdio.h>int sta_1(int a);int main() {int a = 0;scanf_s("%d", &a);printf("%d", sta_1(a));return 0;
}int sta_1(int a) {int num = 0;while (a != 0) {if (a & 1) num++;a = (unsigned int)a >> 1;}return num;
}

以上,是该题的一般解法。
但是,不要小看我和题目的羁绊啊喂!

Brian Kernighan算法

Brian Kernighan算法专门计算二进制中1的个数的算法

内容

int countBits(int n) {int count = 0;while (n != 0) {n &= n - 1;  // 将n中最右边的1及其右边的所有位都置为0count++;     // 计数器加1}return count;
}

基本思想

利用n & (n-1)操作来消除一个整数n的二进制表示中最右边的1。

具体步骤

初始化一个计数器count为0。

当n不等于0时,执行以下操作:
执行n &= n-1,将n中最右边的1及其右边的所有位都置为0。
将计数器count加1。

返回计数器count的值,即二进制表示中1的个数。

辅助理解

  • 对于循环退出条件:如果一个整数不为0,那么这个整数至少有一位是1。

  • 对于n-1:如果我们把这个整数减1,那么原来处在整数最右边的1就会变为0,原来在1后面的所有的0都会变成1(如果最右边的1后面还有0的话)。其余所有位将不会受到影响。

例子

对于整数n=7,其二进制表示为111:

  • 第一次循环:n=7 & 6=6,count=1,此时n的二进制表示为110;
  • 第二次循环:n=6 & 5=4,count=2,此时n的二进制表示为100;
  • 第三次循环:n=4 & 3=0,count=3,此时n变为0,循环结束。
    最终,count的值为3,即n的二进制表示中有3个1。

算法的优势在于,它只需要执行与1的个数相等的循环次数,即执行次数等于二进制表示中1的个数,这使得该算法在时间复杂度上更加高效。

嘿嘿

今天的刷题分享就到这里~
通过这道题,我们深入探索了位运算的奥秘,从基础操作到高效算法
希望这篇文章能让你在编程路上更进一步,在你下次遇到类似问题时,能轻松搞定。喜欢的话,别忘了点赞关注哦,我们下次再见!


http://www.ppmy.cn/embedded/154935.html

相关文章

GPT-5 传言:一场正在幕后发生的 AI 变革

新的一年&#xff0c;让我们从一个引人入胜的话题开始&#xff1a;如果我告诉你&#xff0c;GPT-5 并非虚构&#xff0c;而是真实存在呢&#xff1f;它不仅真实存在&#xff0c;而且正在你看不见的地方悄然塑造着世界。我的基本假设是&#xff1a;OpenAI 已经秘密开发出 GPT-5&…

Vue.js组件开发-实现后端返回二进制文件在浏览器自动下载

在Vue.js组件开发中&#xff0c;若需实现从后端获取二进制文件并触发浏览器自动下载&#xff0c;可以利用axios&#xff08;或其他HTTP客户端库&#xff09;来向后端发送请求&#xff0c;随后利用Blob对象及URL.createObjectURL方法生成一个可供下载的链接&#xff0c;最后通过…

Excel文件按部门切分成多个文件

一、需求 1、文件夹按部门创建 2、文件名按原始文件名命名 二、代码实现 import org.apache.poi.ss.usermodel.*; import org.apache.poi.xssf.usermodel.XSSFWorkbook;import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import jav…

jenkins-Job构建

一. 简述&#xff1a; 通过 Jenkins Job&#xff08;也称为 Jenkins 项目或任务&#xff09;&#xff0c;可以定义和执行各种构建、测试和部署操作。以下是关于如何创建和配置 Jenkins Job 的详细指南&#xff0c;以及一些常见的任务类型和最佳实践。 二. 关于jenkins job&…

网络协议基础--IP协议

一.IP基本认识 1.IP的作用 位置&#xff1a;IP 在 TCP/IP 参考模型中处于第三层&#xff0c;即网络层。 功能&#xff1a;网络层主要实现主机与主机之间的通信&#xff0c;也称为点对点&#xff08;end to end&#xff09;通信。这种通信方式确保了在复杂的网络环境中&#x…

【Uniapp-Vue3】vite.config中安装插件unplugin-auto-import自动导入vue和uniapp

对着项目右键-->使用命令行窗口打开所在目录&#xff0c;就会弹出终端 在终端中输入如下命令&#xff0c;后回车。 npm install unplugin-auto-import 在项目目录下创建vite.config.js 在vite.config.js文件中输入如下代码&#xff1a; import { defineConfig } from vi…

飞牛 使用docker部署Watchtower 自动更新 Docker 容器

Watchtower是一款开源的Docker容器管理工具&#xff0c;其主要功能在于自动更新运行中的Docker容器 Watchtower 支持以下功能&#xff1a; 自动拉取镜像并更新容器。 配置邮件通知。 定时执行容器更新任务。 compose搭建Watchtower 1、新建文件夹 先在任意位置创建一个 w…

【Linux】常用指令详解二

前言 介绍一些Linux常用命令&#xff0c;本文为文章【Linux】常用指令详解一的续作 1.绝对路径与相对路径 绝对路径&#xff1a;从系统根目录开始&#xff0c;可以完整描述文件或目录的路径。使用绝对路径可以准确定位到系统中的某个文件或目录。 相对路径&#xff1a;相对…