CC++ & Algorithm

C++位运算的应用

较难15
语言版本:C++Python
概述:用位运算做权限管理、判断奇偶、快速乘除,解决实际问题。

位运算的妙用:用二进制小技巧解决实际问题

位运算是在二进制位(0和1)上直接进行的操作,包括按位与(&)、按位或(|)、按位异或(^)、左移(<<)、右移(>>)等。虽然看起来是底层操作,但它能帮我们快速判断奇偶、管理权限、快速乘除、交换数字……就像一把小巧的瑞士军刀,把复杂的任务变得简单高效。下面我们就来看看几个最常用的位运算小技巧,每一个都会配上生活里的例子和代码,帮你彻底搞懂。

1. 一眼看出奇偶:用按位与判断

原理:任何整数在二进制中,最低位(最后一位)如果是0,就是偶数;如果是1,就是奇数。所以我们只需把这个数和1做按位与——如果结果是1,说明最低位是1,是奇数;结果是0,就是偶数。

生活例子:就像电影院座位号,单号(1,3,5…)最低位是1,双号(2,4,6…)最低位是0。你只要看号最后一位数字是奇数还是偶数,就能知道座位是单号还是双号。

#include <iostream>
using namespace std;

int main() {
    int num = 7;         // 待判断的数字
    if (num & 1) {       // 按位与1:num & 1 相当于检查最低位
        cout << "奇数" << endl;   // 输出:奇数
    } else {
        cout << "偶数" << endl;
    }
    return 0;
}

常见错误:有人会写成 if (num & 1 == 0),但 == 的优先级比 & 高,实际变成 num & (1==0),永远为0,结果永远为假。正确写法是加括号:if ((num & 1) == 0) 或者直接用 if (!(num & 1))

2. 权限管理:用一个整数掌管多个开关

原理:一个整数的不同二进制位可以代表不同的权限(或开关)。比如用最低位表示“读权限”,第二位表示“写权限”,第三位表示“执行权限”。我们给权限分配不同的值(1、2、4、8……这些正好是2的幂,每个位独立)。用按位或(|)可以“授予”权限,用按位与(&)可以“检查”是否拥有某个权限。

生活例子:就像一张校园卡,里面存着多个小标记:能否进图书馆、能否刷食堂、能否借器材。每个标记就是一位。管理员一次性把权限叠加(按位或),你刷卡时机器检查某一位(按位与)决定是否放行。

int READ  = 1;  // 二进制 001,表示读权限
int WRITE = 2;  // 二进制 010,表示写权限
int EXEC  = 4;  // 二进制 100,表示执行权限

int userPerm = READ | WRITE;   // 授予读和写权限,结果:001|010 = 011(3)

// 检查是否有读权限
if (userPerm & READ) {
    cout << "可以读" << endl;   // 输出:可以读
}
// 检查是否有执行权限
if (userPerm & EXEC) {
    cout << "可以执行" << endl; // 不会输出,因为userPerm中没有EXEC位
}

常见错误:混淆了按位或(|)和逻辑或(||)。权限叠加必须用 |,而不能用 ||,因为 || 只返回 true/false,会丢失信息。另外,权限值必须是2的幂(1,2,4,8…),否则不同权限的位会重叠。

3. 快速乘除2的幂:移位比乘法更快

原理:左移一位相当于乘以2,左移n位相当于乘以2^n;右移一位相当于除以2(对正整数),右移n位相当于除以2^n。因为移位是CPU的底层指令,比乘除法快很多,尤其在一些性能敏感的场景(如游戏、嵌入式)很有用。

生活例子:假设你有5颗糖,要分给8个朋友每人一瓶可乐(相当于乘以8),可以直接把糖数左移3位(5×8=40)。或者有40颗糖,平均分给8个小组(除以8),直接右移3位(40÷8=5)。注意:右移只对正整数安全,负数右移会保留符号位(算术右移),结果不是简单的除以2。

int a = 5;           // 初始数字
int b = a << 3;      // 左移3位,等同于 a * 8 = 40
int c = 40 >> 3;     // 右移3位,等同于 40 / 8 = 5
cout << "b = " << b << ", c = " << c << endl;  // 输出:b = 40, c = 5

常见错误:对负数使用右移代替除法。比如 -5 >> 1 在多数C++实现中结果是 -3(因为向负无穷方向取整),而不是 -2。另外,移位位数不能超过类型的位数(比如int通常是32位),否则行为是未定义的。

4. 交换两个数:不用临时变量的魔术

原理:利用异或(^)的三个性质:

  • a ^ a = 0(任何数和自己异或得0)
  • a ^ 0 = a(任何数和0异或不变)
  • 异或满足交换律和结合律

所以 x ^ y ^ y = x。通过三步异或,就能把x和y的值互换,而且不需要额外变量。

生活例子:就像两个同学交换作业本,不给第三个人帮忙。他们做三件事:先把两人的本子异或(变成混合信息),然后用混合信息跟其中一人的原本来回运算,最后各自拿到对方的本子。虽然有点绕,但很巧妙。

int x = 3, y = 5;    // 交换前 x=3, y=5
x = x ^ y;           // 此时 x = 3 ^ 5 = 6
y = x ^ y;           // y = 6 ^ 5 = 3(原来x的值)
x = x ^ y;           // x = 6 ^ 3 = 5(原来y的值)
cout << x << " " << y << endl;   // 输出:5 3

常见错误:如果两个变量指向同一个内存(比如 xy 是同一个变量),那么第一步异或后变成0,最后两个都变成0。所以在实际编程中,除非你确定它们不同,否则建议还是用临时变量更安全。另外,对于int类型异或交换没问题,但浮点数不能直接用异或。

完整示例:把上面技巧串起来

下面的程序会演示判断奇偶、权限管理、快速乘除和交换。你可以直接复制运行,看看结果。

#include <iostream>
using namespace std;

int main() {
    // ---------- 1. 判断奇偶 ----------
    int num = 7;               // 待判断的数字
    if (num & 1) {             // 按位与1
        cout << num << " 是奇数" << endl;
    } else {
        cout << num << " 是偶数" << endl;
    }

    // ---------- 2. 权限管理 ----------
    int READ  = 1;             // 二进制 001
    int WRITE = 2;             // 二进制 010
    int EXEC  = 4;             // 二进制 100
    int userPerm = READ | WRITE;  // 授予读和写
    if (userPerm & READ) {
        cout << "有读权限" << endl;
    }
    if (userPerm & EXEC) {
        cout << "有执行权限" << endl;  // 不会输出
    }

    // ---------- 3. 快速乘除 ----------
    int a = 5;                 // 初始数
    int b = a << 3;            // 左移3位,乘8
    int c = 40 >> 3;           // 右移3位,除8
    cout << "5 * 8 = " << b << ", 40 / 8 = " << c << endl;

    // ---------- 4. 交换两数 ----------
    int x = 3, y = 5;
    x = x ^ y;
    y = x ^ y;
    x = x ^ y;
    cout << "交换后: x = " << x << ", y = " << y << endl;

    return 0;
}

运行结果

7 是奇数
有读权限
5 * 8 = 40, 40 / 8 = 5
交换后: x = 5, y = 3

新手避坑指南

  • 优先级陷阱:位运算符的优先级比比较运算符低,比算术运算符也低。比如 a & b == 0 实际上是 a & (b==0),要习惯加括号。
  • 负数的右移:右移负数在不同编译器下可能结果不同,通常不要用来做除法;最好只用无符号整数或确认正数。
  • 异或交换的“自我交换”:如果待交换的两个变量是同一个(如 swap(a, a)),结果会归零,要避免。
  • 权限值必须是2的幂:不要用1,2,3这样的连续值,否则位会重叠,检查时就会乱。

学完这些,还能怎么玩?

位运算的应用远不止这些,你还可以:

  • 用位运算表示集合(状态压缩):用一个整数的每一位表示一个元素是否存在,比如一个班级的自习座位是否有人,用32位整数就能表示32个座位。
  • 置位、清零、翻转某一位a |= (1<<k) 把第k位置1;a &= ~(1<<k) 清零;a ^= (1<<k) 翻转。
  • 求2的幂的模a & (b-1) 可以快速求 a % b,当b是2的幂时。

如果想深入了解,可以复习“进制转换”和“按位与、或、异或”的基本运算,再试试做一些小题目,比如“用位运算判断一个数是不是2的幂”。位运算就像编程世界里的小魔术,用多了你会越来越喜欢它的简洁和效率。

例题精讲

1单选题

在C++中,使用位运算进行权限管理。假设权限用整数表示:读=1,写=2,执行=4。现在有一个用户权限值为perm,以下哪个表达式可以正确判断该用户是否拥有"写"权限?

A(perm & 2) == 2
B(perm | 2) == 2
C(perm ^ 2) == 0
D(perm & 1) == 1
2判断题

在C++中,若x为整数,表达式 (x & 1) == 0 可用于判断x是否为偶数。

3填空题
以下C++函数使用位运算实现将参数x乘以2的功能,请填写空缺处的表达式。

int doubleValue(int x) {
    return ___;
}
4单选题

阅读以下C++代码: int a = 5, b = 7; a = a ^ b; b = a ^ b; a = a ^ b; 执行结束后,a和b的值分别是多少?

Aa=7, b=5
Ba=5, b=7
Ca=12, b=2
Da=2, b=12
5判断题

在C++中,对无符号整数进行右移1位操作,相当于将该整数除以2并向下取整。