CC++ & Algorithm

C++按位与 & 和按位或 |

困难26
语言版本:C++Python
概述:用开关和灯泡的比喻,帮你理解按位与和按位或的运算规则。

开关与灯泡:轻松学会C++按位与(&)和按位或(|)

在日常编程中,我们经常需要直接操作二进制位,就像控制一排灯泡的亮灭一样。位运算就是计算机在二进制(0和1)层面上的操作。二进制只有两种状态:0表示“灭”,1表示“亮”。按位与和按位或就像两个开关同时控制一个灯泡,非常直观。掌握它们后,你就能高效地处理权限、标志位、数据压缩等问题。


按位与 &

规则:只有两个对应的位都是1时,结果位才是1;否则为0。
这就像两个串联的开关:必须同时闭合(1),灯泡才亮(1)。任何一个开关断开(0),灯泡就灭(0)。

例子

int a = 6;  // 二进制 110
int b = 3;  // 二进制 011
int c = a & b;  // 110 & 011 = 010 => 2

逐位比较:

  • 第0位:0 & 1 = 0
  • 第1位:1 & 1 = 1
  • 第2位:1 & 0 = 0
    结果是二进制 010,等于十进制 2。

更多例子

  • 检查一个数的某一位是否为1。比如要判断数字 n 的第3位(从右数,第0位开始)是不是1:
    int result = n & (1 << 3); 如果结果不等于0,说明该位为1。
  • 清零某一位:n = n & ~(1 << 2); 将第2位强制变成0。

按位或 |

规则:只要两个对应位中有一个是1,结果位就是1;两个都是0时结果才为0。
这就像两个并联的开关:任意一个闭合(1),灯泡就亮(1)。只有两个都断开(0),灯泡才灭(0)。

例子

int a = 6;  // 110
int b = 3;  // 011
int d = a | b;  // 110 | 011 = 111 => 7

逐位比较:

  • 第0位:0 | 1 = 1
  • 第1位:1 | 1 = 1
  • 第2位:1 | 0 = 1
    结果是 111,等于十进制 7。

更多例子

  • 设置某一位为1:n = n | (1 << 4); 将第4位强制变成1。
  • 合并多个标志位:比如用整数表示多个开关状态,flags = flags | OPTION_A | OPTION_B;

生活小比喻

按位与:想象你的书包有两个扣子。只有两个都扣上,书包才安全(结果=1)。只要有一个没扣,书包就可能掉东西(结果=0)。

按位或:想象你的房门有无钥匙智能锁和机械锁。任意一个锁打开,门就能开(结果=1)。只有两个锁都锁着,门才打不开(结果=0)。

考试例子

  • 按位与:要参加夏令营,既需要语文成绩 ≥ 90(1),又需要数学成绩 ≥ 90(1)。只有两门都达标才算通过(1)。
  • 按位或:周末去游乐园,可以玩过山车或者海盗船。只要至少玩一个项目,今天就算开心(1);两个都不玩才不开心(0)。

常见错误

  1. 混淆 &&&

    • && 是逻辑与,用于布尔值(true/false),结果是 true 或 false。
    • & 是按位与,对整数的每一位独立运算,结果是整数。
    • 错误代码:if (a & b) 本意可能是逻辑判断,但实际按位与后可能得到一个非零整数(被当作true),可能导致逻辑混乱。
    • 正确做法:判断某位存在时用 if ((a & b) != 0),或者用 if ((a & b))(非零即true)但最好显式比较。
  2. 优先级问题
    运算符优先级中,位运算符 &| 比关系运算符(如 ==)低,但比赋值运算符高。容易出错的例子:

    if (x & y == 0)  // 实际被解释为 if (x & (y == 0)),因为 == 优先级高于 &
    

    正确写法:if ((x & y) == 0)

  3. 忘记对负数处理
    负数的二进制表示是补码,按位与/或的结果可能出乎意料。例如 -1 的二进制全是1,-1 & 3 = 3。初学者应先用无符号整数或正整数练习。

  4. & 判断奇偶比取模更快但容易误解
    if (n & 1) 判断奇数(最低位为1),但写成 if (n % 2 == 1) 更直观。位运算适合性能敏感场景,但可读性稍差。


完整示例:判断奇偶、设置标志位

下面的程序展示了按位与和按位或的实用场景:

#include <iostream>
using namespace std;

int main() {
    // 1. 判断奇偶(按位与)
    int number = 9;  // 二进制 1001
    if (number & 1) {   // 检查最低位:1为奇数,0为偶数
        cout << number << " 是奇数" << endl;  // 输出:9 是奇数
    } else {
        cout << number << " 是偶数" << endl;
    }

    // 2. 设置第3位为1(按位或)
    int flags = 0;          // 二进制 0000
    int mask = 1 << 3;      // 二进制 1000 (十进制8)
    flags = flags | mask;   // 现在 flags = 8 (1000)
    cout << "设置第3位后 flags = " << flags << endl;

    // 3. 检查第1位是否被设置(按位与)
    int checkMask = 1 << 1;   // 二进制 0010 (十进制2)
    if (flags & checkMask) {
        cout << "第1位是1" << endl;
    } else {
        cout << "第1位是0" << endl;  // 输出:第1位是0
    }

    // 4. 按位与和或的直观演示
    int a = 6;  // 110
    int b = 3;  // 011
    cout << "a & b = " << (a & b) << endl;   // 2  (010)
    cout << "a | b = " << (a | b) << endl;   // 7  (111)

    return 0;
}

运行结果

9 是奇数
设置第3位后 flags = 8
第1位是0
a & b = 2
a | b = 7

总结与扩展

  • 按位与&,用于清除某些位、检查位状态、乘法快速取模(如 n & 7 相当于 n % 8)。
  • 按位或|,用于设置某些位、合并标志位。

你已经学会了位运算的两个基础操作。接下来可以继续探索:

  • 按位异或 ^:相同为0,不同为1——用于交换变量、加密。
  • 按位取反 ~:0变1,1变0——用于清除位。
  • 左移 << 和右移 >>:快速乘以2或除以2。

把位运算想象成用开关控制灯泡,很快就能得心应手。试着用它们解决实际问题吧!

例题精讲

1单选题

已知 int a = 0b1100, b = 0b1010; 则表达式 a & b 的值是(二进制形式)?

A0b1000
B0b1110
C0b0100
D0b0000
2判断题

按位或运算中,两个操作数的对应位只要有一个为1,结果位就是1。

3填空题
以下代码将变量x的第3位(从0开始)设置为1,请补全运算符号:
int x = 10; // 二进制 00001010
x = x ___ (1 << 3); // 期望x变为 00010010
4单选题

下列哪个表达式可以清空变量y的低4位(即将低4位设为0,其他位不变)?

Ay & 0xF0
By | 0xF0
Cy & 0x0F
Dy | 0x0F
5填空题
请补全以下代码,判断变量num的第5位(从0开始)是否为1:
int num = 42;
if ( (num ___ (1 << 5)) != 0 )
    cout << "第5位为1";