在计算机底层,所有数据都以二进制形式存储和运算。位操作作为直接操作二进制位的技术,具有高效、简洁的特点,广泛应用于底层开发、算法优化、嵌入式编程等领域。本文将从基础原理出发,结合实战代码,详细讲解位操作在乘法、除法、加法、平均值计算、绝对值获取及二进制 1 的个数统计等场景中的应用,帮助你掌握这一核心技术。

一、位操作基础:核心运算与原理

位操作基于二进制位(0 或 1)进行,核心运算包括与(&)、或(|)、异或(^)、左移(<<)、右移(>>),它们的规则如下:

运算符号 名称 规则说明
& 按位与 两位均为 1 时,结果为 1;否则为 0(可用于 “保留指定位”“判断奇偶”)
^ 按位异或 两位不同时,结果为 1;否则为 0(可用于 “无进位求和”“交换变量”“取反指定位”)
<< 左移 所有位向左移动 n 位,右侧补 0(等价于乘以 2ⁿ,效率远高于乘法运算符)
>> 右移 正数左补 0,负数左补 1(等价于除以 2ⁿ,向下取整)

位操作的核心优势在于直接操作硬件层面的数据,无需经过高级语言的运算优化,因此执行速度极快。例如,x << 3(乘以 8)的执行效率远高于x * 8,这也是底层开发中优先使用位操作的原因。

二、实战场景 1:快速实现乘法运算

乘法的本质是 “重复加法”,但直接循环加法效率较低。利用位操作的左移等价于乘 2特性,可大幅优化乘法实现,甚至支持正负数运算。

1. 快速求一个数的 7 倍

7 可以拆分为8 - 1,而 8 是 2³,因此 “乘以 7” 可转化为 “左移 3 位(乘 8)后减原数”,代码如下:

// 计算 x * 7
int multiply_by_7(int x) {
    return (x << 3) - x; // 等价于 x*8 - x = x*7
}

该方法仅需 1 次左移和 1 次减法,执行效率远超x * 7

2. 通用乘法实现(仅用逻辑运算)

若需实现任意两个整数的乘法(含负数),需先通过异或判断符号,再用 “左移 + 加法” 计算绝对值的乘积,最后调整符号。其中加法需用位操作实现(无+运算符)。

步骤拆解:
  1. 位操作实现加法:用异或(^)求 “无进位和”,用与(&)+ 左移求 “进位”,循环处理进位直至为 0;

  2. 处理符号:通过 “一正一负” 判断结果是否为负,用 “取反加 1”(补码特性)获取绝对值;

  3. 优化乘法:循环判断乘数的最低位,若为 1 则累加被乘数(左移后),同时乘数右移(等价于除以 2)。

完整代码:
// 仅用位操作实现加法(无+运算符)
int add(int a, int b) {
    do {
        int sum = a ^ b;      // 异或:无进位求和
        int carry = (a & b) << 1; // 与+左移:计算进位
        a = sum;
        b = carry;
    } while (b != 0); // 进位为0时结束
    return a;
}
​
// 仅用位操作实现乘法(支持正负数)
int multiply(int a, int b) {
    int result = 0;
    // 1. 判断结果符号(一正一负则为负)
    int is_negative = ((a < 0) ^ (b < 0)) ? 1 : 0;
    
    // 2. 取绝对值(补码特性:负数取反加1得绝对值)
    a = (a < 0) ? add(~a, 1) : a; 
    b = (b < 0) ? add(~b, 1) : b;
    
    // 3. 优化乘法:左移累加
    while (b > 0) {
        if (b & 1) { // 若b最低位为1,累加当前a(已左移)
            result = add(result, a);
        }
        a <<= 1; // a左移:等价于a*2
        b >>= 1; // b右移:等价于b/2,缩小循环范围
    }
    
    // 4. 调整符号(负数则取反加1)
    return is_negative ? add(~result, 1) : result;
}
测试案例:
printf("3 * 5 = %d\n", multiply(3, 5));      // 输出15
printf("(-4) * 6 = %d\n", multiply(-4, 6));  // 输出-24
printf("(-2) * (-3) = %d\n", multiply(-2, -3)); // 输出6

三、实战场景 2:不用除法运算符实现除法

除法的本质是 “重复减法”,但直接循环减法效率极低(如 1000/1 需循环 1000 次)。利用位操作的右移等价于除 2特性,可通过 “二分法” 快速逼近商,大幅减少循环次数。

核心思路:

  1. 记录被除数的剩余部分(初始为被除数)和商(初始为 0);

  2. 每次找到最大的 2 的幂次倍数(multi),使得 “除数 ×multi” 不超过剩余部分的一半;

  3. multi加到商中,同时从剩余部分中减去 “除数 ×multi”;

  4. 重复步骤 2-3,直至剩余部分小于除数。

完整代码(仅处理正整数):

// 不用/运算符实现正整数除法(返回商的整数部分)
int div(const int x, const int y) {
    int left_num = x; // 被除数剩余部分
    int result = 0;   // 商的结果
    
    // 剩余部分 >= 除数时,继续计算
    while (left_num >= y) {
        int multi = 1; // 除数的倍数(初始为1,即2⁰)
        
        // 找到最大的multi:y*multi <= 剩余部分的一半(left_num>>1等价于left_num/2)
        while (add(y, y) <= left_num) { // 避免y*multi溢出,用add(y,y)替代y*2
            multi <<= 1; // multi×2
            y <<= 1;     // 除数×2(减少乘法运算)
        }
        
        result = add(result, multi); // 商累加倍数
        left_num -= y;               // 剩余部分减去“除数×multi”
        y >>= multi;                 // 恢复除数原值(因之前左移了multi次)
    }
    
    return result;
}

优化说明:

  • 原思路中 “y * multi” 可能导致溢出,此处用 “add(y, y)”(等价于y*2)替代,同时左移除数,避免溢出;

  • 循环次数从 “被除数大小” 降至 “二进制位数”(如 32 位整数最多循环 32 次),效率极大提升。

四、实战场景 3:其他高频位操作技巧

除了四则运算,位操作在 “平均值计算”“绝对值获取”“二进制 1 的个数统计” 等场景中也有巧妙应用,以下为具体实现。

1. 位操作求两个数的平均值

常规方法(a + b) / 2可能导致溢出(如a=2¹⁵-1b=2¹⁵-1,相加后超出 int 范围),而位操作可避免溢出,核心原理基于二进制位的 “相同贡献” 和 “不同贡献”:

  • 相同位(a&b):若两位均为 1,平均值为 1((1+1)/2=1),直接保留;

  • 不同位(a^b):若两位不同,和为 1,平均值为 0.5,右移 1 位(等价于除以 2)取整。

代码实现:
// 位操作求a和b的平均值(无溢出,向下取整)
int average(int a, int b) {
    return (a & b) + ((a ^ b) >> 1);
}
示例验证:
  • a=3(011)b=5(101)a&b=001a^b=110(110>>1)=11(3),结果1+3=4(正确);

  • a=4(100)b=5(101)a&b=100a^b=001(001>>1)=0,结果4+0=4(正确,(4+5)/2=4.5 向下取整为 4)。

2. 位操作计算数的绝对值

利用补码特性:负数的补码是 “原码除符号位取反加 1”,而绝对值的本质是 “符号位变为 0”。通过右移获取符号位,再用异或和加法实现通用绝对值计算:

  • 正数:符号位为 0,右移 31 位(32 位 int)后为 0,异或后不变,最终结果为自身;

  • 负数:符号位为 1,右移 31 位后为0xFFFFFFFF(-1 的补码),异或后取反,加 1 后得到绝对值。

代码实现:
// 位操作计算绝对值(支持32位int)
int abs(int x) {
    int sign = x >> 31; // 获取符号位(正数0,负数0xFFFFFFFF)
    return (x ^ sign) - sign; // 正数:(x^0)-0=x;负数:(x取反)-(-1)=x取反+1
}
示例验证:
  • x=5(0x00000005)sign=0(5^0)-0=5(正确);

  • x=-5(0xFFFFFFFB)sign=0xFFFFFFFF(FB ^ FF)=0404 - FF=05(正确)。

3. 统计二进制中 1 的个数

核心技巧:x & (x-1) 会 “消除 x 的最后一个 1”(如x=6(110)x-1=5(101)x&(x-1)=100,消除了最后一个 1)。循环执行该操作,直至 x 为 0,循环次数即为 1 的个数。

代码实现:
// 统计x的二进制中1的个数
int count_one(int x) {
    int count = 0;
    while (x) {
        count++;
        x = x & (x - 1); // 消除最后一个1
    }
    return count;
}
示例验证:
  • x=7(111):循环 3 次(111→110→100→0),count=3(正确);

  • x=8(1000):循环 1 次(1000→0),count=1(正确)。

五、总结:位操作的核心价值与应用场景

位操作的本质是 “直接操作二进制位”,其核心价值在于高效性(避免高级运算的开销)和安全性(避免溢出)。常见应用场景包括:

  1. 底层开发:驱动程序、嵌入式系统中操作硬件寄存器(如控制 GPIO 引脚、配置定时器);

  2. 算法优化:位排序、布隆过滤器、状态压缩(如 DP 中的状态用二进制表示);

  3. 数据处理:哈希算法、加密算法(如 AES、RSA 中大量使用位操作);

  4. 性能敏感场景:游戏引擎、实时系统中需要快速运算的模块。

掌握位操作不仅能提升代码效率,更能帮助你理解计算机底层的运算逻辑,是进阶高级开发工程师的必备技能。建议结合本文代码反复实践,逐步积累实战经验。

Logo

openvela 操作系统专为 AIoT 领域量身定制,以轻量化、标准兼容、安全性和高度可扩展性为核心特点。openvela 以其卓越的技术优势,已成为众多物联网设备和 AI 硬件的技术首选,涵盖了智能手表、运动手环、智能音箱、耳机、智能家居设备以及机器人等多个领域。

更多推荐