CC++ & Algorithm

C++按位异或 ^ 和按位取反 ~

困难25
语言版本:C++Python
概述:用“找不同”游戏和镜子反转来理解异或和取反。

找不同与反转镜:C++ 按位异或 ^ 和按位取反 ~

在编程中,我们有时需要直接操作数字的二进制位——每一个“0”和“1”就像电路开关一样。今天我们要学习两个好玩的位运算操作符:异或(^)取反(~)
异或可以帮你“找出两个数字的不同之处”,而取反就像是把数字的每一个位都“翻个面”。它们虽然简单,但在加密、交换变量、设置标记位时特别有用。下面我们边玩边学。


1. 找不同游戏:按位异或 ^

异或的规则只有一句话:相同为0,不同为1
想象你在玩“找不同”游戏:对比两幅图,如果某个位置图案一样,就在格子里打“×”(0);如果不一样,就打“√”(1)。异或也是这么干的——它把两个整数的二进制位逐位对比。

举例说明

假设有两个数字:

  • a = 6,二进制是 110
  • b = 3,二进制是 011

把它们对齐后逐位比较:

位位置(从右往左)第0位第1位第2位
a011
b110
a ^ b101
  • 第0位:0和1不同 → 结果1
  • 第1位:1和1相同 → 结果0
  • 第2位:1和0不同 → 结果1

所以结果是二进制 101,也就是十进制 5。
代码实现很简单:

#include <iostream>
using namespace std;
int main() {
    int a = 6;  // 二进制 110
    int b = 3;  // 二进制 011
    int c = a ^ b;  // 异或运算,结果 101(十进制5)
    cout << "a ^ b = " << c << endl;  // 输出 5
    return 0;
}
生活中的比喻
  • 羽毛球比赛:两位选手动作相同(都正手或都反手)时不得分(0),动作不同(一个正手一个反手)时才得分(1)。
  • 双胞胎找不同:你和双胞胎同学穿同款校服,如果今天都穿白袜子,那就是“相同”;如果一个人穿白袜子、一个人穿黑袜子,那就是“不同”。

2. 异或的魔法性质:两次异或回原数

异或有一个非常神奇的性质:连续两次异或同一个数,会变回原来的数

  • 公式:a ^ b ^ b == a
  • 为什么呢?因为第一次异或得到“不同”的标记,第二次再和b异或时,原先不同的位会被再次翻转,正好回到原来的样子。

这个性质有什么用?

  • 简单加密:假设你想把数字 x 变成“密文”,可以用一个密钥 key 做异或:y = x ^ key。别人看到 y 不知道原数,但你知道再用 key 异或一次就能恢复:x = y ^ key
  • 交换两个变量的值(不用临时变量)
    int x = 5;   // 第一个数
    int y = 10;  // 第二个数
    x = x ^ y;   // 步骤1:x 变成 5^10
    y = x ^ y;   // 步骤2:y 变成 (5^10)^10 = 5(恢复了原 x)
    x = x ^ y;   // 步骤3:x 变成 (5^10)^5 = 10(恢复了原 y)
    // 现在 x=10,y=5,完美交换!
    
    不过这种方法在实际编程中不常用(因为可读性差),但作为智力题很有趣。
生活比喻:静音键

电视遥控器上的“静音”键原理和异或一样:按一下,声音从有变无(第一次异或);再按一下,声音从无变有(第二次异或)。你连续按两次静音,电视音量会回到原来的状态。


3. 反转镜:按位取反 ~

取反是一个“一元运算符”(只对单个数字起作用)。它的效果是把数字的每一位都翻转:0变成1,1变成0。就像照镜子,黑变成白,白变成黑。

举例说明

假设 a = 6,在32位系统中,二进制表示为:
00000000 00000000 00000000 00000110(前面全是0)

取反后变成:
11111111 11111111 11111111 11111001 —— 这是补码形式,对应的十进制是 -7

#include <iostream>
using namespace std;
int main() {
    int a = 6;  // 二进制 ...0110
    int d = ~a; // 取反,结果 ...1001(补码)
    cout << "~a = " << d << endl;  // 输出 -7
    return 0;
}
为什么结果是负数?

因为 int 类型最高的那一位(第31位)是符号位,0表示正数,1表示负数。取反后,原来正数的符号位变成1,所以结果必然是负数。这个负数的值可以用公式计算:~x = -x - 1

  • 验证:~6 = -7,因为 -6 - 1 = -7
  • 再试:~0 = -1(全1的补码)。
生活中的比喻
  • 黑白反转:把一张黑白照片的底片查看,白色区域变成黑色,黑色变成白色。
  • 密室开关:假设一扇门有开关(1=开,0=关),按一下取反按钮,门的状态就会完全反转。

4. 新手容易犯的常见错误

  1. 搞反异或和逻辑或

    • 逻辑或 || 是判断“两个条件至少一个成立”,返回 true/false。
    • 按位异或 ^ 是对二进制位逐位运算,返回整数。
      错误示例:if (a ^ b) 本意可能是想判断a和b是否不相等?但a ^ b会产生一个数值,不等于0则条件为真。如果想判断不相等,应该用 if (a != b) 更直观。
  2. 忘记取反后数字是负数
    新手可能以为 ~6 的结果是 9(因为乍一看只看到最后几位),但实际上前面所有高位都变成了1,结果是一个很大的负数。计算时要注意 int 的位数(通常32位)。

  3. 异或的优先级
    异或 ^ 的优先级比关系运算符(如 ==!=)低!
    例如:if (a ^ b == 0) 其实是先算 b == 0,再算 a ^ (b==0),结果可能和你想的不一样。
    安全写法:if ((a ^ b) == 0)if (a == b)

  4. 用异或交换变量时遇到自己
    如果 xy 指向同一个变量(比如用引用或指针时),三次异或会把值变成0。所以现实中更推荐使用临时变量交换。


5. 完整可运行示例代码

下面是一个完整的程序,演示了异或和取反的基本用法,包括加密/解密小实验和取反计算。

#include <iostream>
using namespace std;
int main() {
    // ========== 基本异或 ==========
    int a = 6;      // 二进制 110
    int b = 3;      // 二进制 011
    int xorResult = a ^ b;   // 异或,结果为 101 (5)
    cout << "a ^ b = " << xorResult << endl;  // 输出 5

    // ========== 异或加密/解密 ==========
    int secret = 42;       // 要加密的秘密数字
    int key = 99;          // 加密密钥(随便选)
    int cipher = secret ^ key;   // 加密:密文 = 秘密 ^ 密钥
    cout << "明文: " << secret << ", 密文: " << cipher << endl;
    
    int recovered = cipher ^ key; // 解密:再异或一次密钥
    cout << "解密后: " << recovered << endl;  // 恢复为42

    // ========== 按位取反 ==========
    int num = 6;   // 二进制 ...0110
    int flipped = ~num;   // 取反,结果是 -7
    cout << "~num = " << flipped << endl;  // 输出 -7
    
    // 也可以用公式验证:~x = -x - 1
    cout << "验证公式:-" << num << " - 1 = " << (-num - 1) << endl;  // 输出 -7

    // ========== 异或交换变量(避免自赋值) ==========
    int x = 50;    // 第一个数
    int y = 100;   // 第二个数
    cout << "交换前: x = " << x << ", y = " << y << endl;
    x = x ^ y;     // 第一步
    y = x ^ y;     // 第二步
    x = x ^ y;     // 第三步
    cout << "交换后: x = " << x << ", y = " << y << endl;  // x=100, y=50

    return 0;
}

输出预期

a ^ b = 5
明文: 42, 密文: 77
解密后: 42
~num = -7
验证公式:-6 - 1 = -7
交换前: x = 50, y = 100
交换后: x = 100, y = 50

6. 相关知识点指引

学完异或和取反,下一步可以了解:

  • 按位与 &按位或 |:它们和异或类似,但规则不同,常用于清除/设置特定位。
  • 移位运算 <<>>:可以把二进制位向左或向右移动,快速实现乘2、除2。
  • 位掩码(bitmask):用多个位表示开关状态,比如用 1<<3 表示第3个开关。
  • 补码:理解计算机如何存储负数(取反+1),对理解 ~ 的结果非常有帮助。

如果你想挑战更复杂的应用,可以尝试用异或实现“只出现一次的数字”算法(LeetCode 136题),或者用取反实现“二进制反转”函数。

记住:位运算虽然看起来有点“硬核”,但一旦熟悉它们,你就能用极少的代码完成很多巧妙的操作。下次写程序时,不妨想想能不能用 ^~ 来简化逻辑!

例题精讲

1单选题

已知 int a = 5, b = 3,则表达式 (a ^ b) ^ a 的值是?

A5
B3
C0
D8
2判断题

在C++中,若 unsigned char x = 0,则 ~x 的结果是 255。

3填空题
下列代码使用异或交换两个整型变量 a 和 b 的值,请补全空缺部分。\nint a = 3, b = 7;\na = a ^ b;\nb = ___;\na = a ^ b;
4单选题

关于按位取反运算符 ~,当 int a = -1(补码表示)时,~a 的结果是?

A0
B-2
C1
D一个很大的正数
5填空题
以下代码将整数 x 的第 k 位(从0开始)取反,即若该位为0则变为1,为1则变为0。请补全空缺。\nint x = 10;   // 二进制 1010\nint k = 2;    // 操作第2位\nx = x ^ (___); \n// 预期结果:1010 的第2位是0,取反后为1,得到1110即14