当前位置: 首页 > news >正文

C语言——位操作运算

        位操作是 C 语言中直接对二进制位进行运算的底层操作,在系统编程、嵌入式开发、算法优化等领域有重要应用。C语言提供了 6 个位操作运算符,这些运算符只能作用于整型操作数,也就是只能作用于带符号或无符号 char、short、int、long 类型。以下是六种操作符及其表示方法:

含义表示符号
按位与(AND)&
按位或(OR)|
按位异或(XOR)^
按位取反(一元运算符)~
左移<<
右移>>

           由于 C 语言中 printf 没有输出二进制形式数据的格式,所以首先自定义一个打印二进制形式数据的函数print_binary。其示例如下:

#include <stdio.h>
#include <stdint.h>

/**
 * @brief		二进制输出函数
 * @param num:	需要转换的数据
 * @param bits:转换后二进制的位数
 */
void print_binary(uint32_t num, int bits)
{
	for (int i = bits - 1; i >= 0; i--) {
		printf("%d", (num >> i) & 1);	/* 移位 并和 1相与 得到对应 位 */
		if (i && (i % 4 == 0)) {        /* 每 4 位加一个空格 */
			printf(" ");
		}
	}
	printf("\n");
}

int main()
{
    /* 正数在计算机中是以二进制补码形式存储,正数的原码、反码和补码一致 */
	print_binary(18, 8);	/* 输出 8 位: 0001 0010 */
    /* 负数在计算机中是以二进制补码的形式存储的 */
    print_binary(-18, 8);	/* 1110 1110 -> -18的补码形式,原码为 1001 0010 */

	return 0;
}

        还有一点,要了解有符号数在计算机中存储形式, 在计算机中数值一律以二进制补码的形式储存。

  • 正数的原码、反码及补码的形式是一样的。
  • 但是负数的原码、反码及补码的形式不一样,负数的二进制原码的最高位是符号位,假设现在令 X = -13,则其二进制原码形式为 1000 1101。
  • 负数的二进制反码是由二进制原码的符号位不变,其余各位取反所得。则 X 的二进制反码形式为 1111 0010。
  • 负数的二进制补码则为二进制反码符号位不变再在最末位加上 1 所得。则 X 的二进制补码形式为 1111 0011。

1、基础位操作符

1.1、按位与 &

  • 特性:对应位同为 1 时结果为 1,否则为 0。
  • 示例:
uint8_t a = 0b00011010, b = 0b01001100, c = 0b000000000;
c = a & b;
print_binary(c, 8);		/* 输出:0000 1000 */
  • 应用: 

         1、掩码操作:提取特定位。

uint8_t port = 0b11001010;
uint8_t masked = port & 0x0F;	/* 获取低四位 -> 0b00001010 */
print_binary(masked, 8);		/* 输出:0000 1010 */

        2、清零位:x &= ~(1 << n)

uint8_t x = 0b11100100;
x = x & ~(1 << 6);		/* 将 x 的bit6清零 -> 0b10100100 */
print_binary(x, 8);		/* 输出:1010 0100 */

1.2、按位或 |

  • 特性:对应位的任意一位为 1 则结果为 1。

  • 示例:

uint8_t a = 0b01000010, b = 0b10001010, c;
c = a | b;
print_binary(c, 8);		/* 输出:1100 1010 */
  •  应用:

        1、设置位:x |= (1 << n)

    uint8_t x = 0b00001010;
    x = x | (1 << 5);		/* 设置 x 的bit5 -> 0b00011010 */
    print_binary(x, 8);		/* 输出:0010 1010 */

             2、合并标志位

    #define READ	0x01        /* 0000 0001 */
    #define WRITE	0x02        /* 0000 0010 */
    
    int flags = READ | WRITE;
    print_binary(flags, 8);		/* 0000 0011 */
    printf("0x%02x\n", flags);	/* 0x03 */

    1.3、按位异或 ^

    • 特性:对应位相同为 0,不同为 1。
    • 示例:
    uint8_t a = 0b01000010, b = 0b10001010, c;
    c = a ^ b;
    print_binary(c, 8);		/* 输出:1100 1000 */
    • 应用:

            1、交换变量(不需要使用临时变量)。

    int m = 5, n = 3;
    m ^= n;
    n ^= m;
    m ^= n;
    printf("m = %d, n = %d\n", m, n);	/* m = 3, n = 5 */

            2、数据加密(使用密钥异或)。

            3、切换位状态:x ^= (1 << n)

    uint8_t x = 0b11001010;
    x ^= (1 << 6);			/* 切换 x 的bit6 -> 0b10001010 */
    print_binary(x, 8);		/* 输出:1000 1010 */

     1.4、按位取反 ~

    • 特性:将数据二进制形式的各位反转,0 变为 1, 1 变为 0。
    • 注意:结果与数据类型的宽度有关。
    • 示例: 
    uint8_t q = 0b11000100;
    print_binary(~q, 8);	/* 0011 1011 */
    •  应用:
    uint8_t mask = ~0x07;
    print_binary(mask, 8);

    1.5、移位操作

    1.5.1、左移 <<

    • 特性:低位补 0,相当于乘 2^n。
    int d = 5;
    print_binary(d, 8);			/* 0000 0101 */
    print_binary(d << 3, 8);	/* 0010 1000 */
    printf("d = %d\n", d << 3);	/* 40 */

    1.5.2、右移 >>

    • 特性:无符号数,高位补 0(逻辑移位);有符号数,补符号位(算术移位)。

             在计算机内存当中,负数一律按照补码的形式进行存储,例如现在有一个负数 -8,其二进制原码形式为[1000 1000]_{2},这个地方需要注意的是高位的 1 为符号位,即当这个数字是负数的时候高位为 1,正数时高位为 0,且符号为不计入数值当中,只能表示正负数的概念。

            当 -8 存入计算机当中的时候,内存中需要对负数的原码符号位不变再进行按位取反加一的操作,即进行求补码的操作;特别注意,符号位不参与变化,补码为[1111 1000]_{2}。负数右移操作时需要补符号位 1 ,则右移 2 位后为[11111110]_{2}

            当需要将移位后的负数从内存当中取出的时候,首先需要将补码转化成原码,转变规则为对当前的补码取反加一(其中符号位不参与变化),转变后的原码为:[10000010]_{2}

    1.5.3、应用

    • 快速计算:x * 8  -->  x << 3

    •  提取位字段:(x >> 4) & 0x0F

    2、高级应用技巧

    2.1、位掩码技术

    • 检查位:
      if (x & (1 << n))
    • 设置多个位:
      x |= (BIT0 | BIT2)
    • 清除多个位:
      x &= ~(BIT1 | BIT3)

    2.2、位字段结构

    struct {
        unsigned int enable : 1;
        unsigned int mode   : 3;
        unsigned int status : 4;
    } device_reg;

     2.3、高效位计数

    int count_bits(uint32_t x)
    {
        int count = 0;
        while (x) {
            x &= x - 1;    /* 清除最低位的 1 */
            count++;
        }
        return count;
    }

    2.4、奇偶校验

    bool is_odd(uint8_t x)
    {
        return x & 1;    /* 比 x % 2 更快 */
    }

     3、典型应用场景

    3.1、硬件寄存器操作

    /* 设置 GPIO 引脚为输出模式 */
    #define GPIO_MODE_OUT    (1 << 0)
    volatile uint32_t *reg = (uint32_t*)0x40020000;
    *reg |= GPIO_MODE_OUT;        /* 设置位 */
    *reg &= ~(0X0F << 4);         /* 清除高 4 位 */

    3.2、数据压缩存储

    /* 存储 8 个 bool 值到 1 字节 */
    uint8_t flags = 0;
    flags |= (condition1 << 0);
    flags |= (condition2 << 1);
    /* 读取第 3 位 */
    bool val = (flags >> 2) & 1;

    3.3、图像处理

    /* 快速计算 RGB 平均值 */
    uint32_t pixel = 0xRRGGBB;    /* 此处RR、GG、BB仅是代表RGB通道的值 */
    uint8_t avg = ((pixel >> 16) + ((pixel >> 8) & 0xFF) + (pixel & 0xFF)) / 3;

    4、性能优化示例

            传统方法:

    bool is_odd(uint8_t x)
    {
        if (x % 2 == 0)
            return false;
        else
            return true;
    }

            位操作优化:

    bool is_odd(uint8_t x)
    {
        return x & 1;
    }

            掌握位操作可以显著提升底层系统编程能力,但在应用时需权衡效率与代码可读性。建议:关键位置使用位操作优化,复杂逻辑优先保证代码可维护性。

    5、注意事项

    5.1、移位安全

    • 避免超出类型宽度,假设 uint32_t x,这时 x 是32位数据,如果 x << 33 则是未定义行为。

    #include <stdio.h>
    #include <stdint.h>
    
    int main()
    {
    	uint32_t x = 0xFFFF;	/* 数据只有 32 位 */
    	uint32_t y = x << 33;	/* 左移	33 位导致算术溢出 */
        printf("%d\n", y);		/* 131070 */
    
    	return 0;
    }
    • 有符号数右移结果与编译器的实现相关。
    #include <stdio.h>
    
    int main()
    {
    	int x = -15;
    	printf("(-15 >> 3) = %d\n", x >> 3);	/* -2 */
        print_binary(x, 8);			/* 1111 0001 -> -15的补码形式,原码为 1000 1111 */
        print_binary(x >> 3, 8);	/* 1111 1110 -> 15右移2位的补码,原码为 1000 0010 */
    
    	return 0;
    }

     5.2、运算符优先级

            位操作符优先级低于比较运算符,建议多使用括号。

    if (x & 0x0F == 0x08)    /* 错误:实际是 x & (0x0F == 0x08)
    /* 建议写法 */
    if ((x & 0x0F) == 0x08)

    5.3、可移植性

    • 字节序(大小端)问题影响位字段的内存布局。
    • 使用固定宽度类型(如uint_t)增强可移植性。

    相关文章:

  1. electron + vue3 + vite 主进程到渲染进程的单向通信
  2. Gravitino源码分析-SparkConnector 实现原理
  3. HTML5的新特性有哪些?
  4. 网络安全配置截图 网络安全i
  5. 【AI赋能】AI工具图文创造指南:从主题到一键发布的完整指南
  6. 动态ip和静态ip适用于哪个场景?有何区别
  7. CODEGEN:一种基于多轮对话的大型语言模型编程合成方法
  8. 永洪科技深度分析实战,零售企业的销量预测
  9. 隐私保护在 Facebook 用户身份验证中的应用
  10. 从连接到交互:SDN 架构下 OpenFlow 协议的流程与报文剖析
  11. LLM论文笔记 19: On Limitations of the Transformer Architecture
  12. 【江协科技STM32】TIM编码器接口测速(学习笔记)
  13. 【虚拟化】Docker Desktop 架构简介
  14. SyntaxError: Illegal return statement
  15. 【git】ssh配置提交 gitcode-ssh提交
  16. 消防行业如何借助 TDengine 打造高效的数据监控与分析系统
  17. DeepSeek-进阶版部署(Linux+GPU)
  18. 随机过程的核心概念与Matlab实现
  19. 乐鑫打造全球首款 PSA Certified Level 2 RISC-V 芯片
  20. Python基于Django的医用耗材网上申领系统【附源码、文档说明】
  21. 万网做网站花多少钱/搜索引擎优化的缺点包括
  22. wordpress旧版本哪个好些/seo免费培训
  23. 云南免费网站建设/有产品怎么找销售渠道
  24. 政府网站建设 安徽/百度ai人工智能平台
  25. 品牌建设 凝心/360优化大师历史版本
  26. 专业网站建设制作价格低/网站排名推广软件