#C++ Day8 October 14 2025

//5 – 6 – 3 XOR Exclusive OR operator

#include <iostream>

using namespace std;

/*

^ XOR :XOR from high to low,if two values are unequal, there will be 1

*/

int main() {

//1.the XOR’s definition

int a = 0b1010;

int b = 0b0110;

//  ^ = 0b1100; //Dec = 12

cout << (a ^ b) << endl;

cout << “———-” << endl;

//2.Reverse the flag bit

int c = 0b1000101;

// 0b0001000;

cout << c  << endl;

cout << (c ^ 0b1000) << endl; // the first xor

cout << ((c ^ 0b1000) ^ 0b1000) << endl;//xor again,xor is adaptive to the exchange and combine rules

//two equal values,then xor will be full of the 0

cout << “———-” << endl;

//3.exchange the values

int d = 17;

int e = 19;

d = d ^ e;

e = d ^ e; // = d’ ^ e = d ^ e ^ e = d ^ 0 = d

d = d ^ e; // = d’ ^ d = d ^ e ^ d = d ^ d ^ e =0 ^ e = e, xor is adapt to the exchange rule

cout << d << ‘ ‘ << e << endl;

//3.1 any values xor with 0, it’s the same value ifself;

//3.2 two same values xor, the output will be 0;

//3.3 xor is adaptive to the exchange and the combine rule;

//exclusive OR(XOR):not promote bit’s  binary add rule(不带进位的二进制加法)

cout << “———-” << endl;

//4.occur the odd’s number(出现奇数次的数)

int f = 3;//0b011

int g = 4;//0b100

int res = f ^ g ^ f; //=f^f^g=0^g=g

cout << res << endl;

//even’s number will finally become 0, and the odds will be left finally

cout << “———-” << endl;

//5.encrypt

int x = 1314;

cout << “520” << x << endl;

int y = (x ^ 3135); // 3135 is the key

cout << “520” << y << endl; //5202333

cout << “520” << (y ^ 3135) << endl; //decrypt

return 0;

}

AI:

好问题👍

这个其实是理解 XOR(异或)运算 的核心所在。我们慢慢来,不急着看公式。先一步步想清楚概念。

先回忆一下普通的 二进制加法:

A B A+B(带进位)

0 0 0

0 1 1

1 0 1

1 1 10(进位1,结果0)

看到最后一行了吗?

只有当 A 和 B 都是 1 时,才会产生 进位。

现在我们看 XOR 的真值表:

A B A⊕B

0 0 0

0 1 1

1 0 1

1 1 0

是不是发现——

XOR 跟普通加法几乎一模一样,只是当 1+1 时,它不产生进位,而是直接输出 0。

也就是说:

XOR 就像是在做「不考虑进位」的二进制加法。

我们换个角度想:

在二进制加法里,其实结果有两个部分:

1. 和(Sum):就是不带进位的部分 → A ⊕ B

2. 进位(Carry):是产生进位的部分 → A & B

所以:

A + B = (A ⊕ B) + ((A & B) << 1)

这就是计算机做加法的原理(位运算实现加法)。

我想确认你理解到这里:

你能解释一下「为什么当 A=1 且 B=1 时 XOR 的结果是 0 吗」?

(提示:想想这时“1+1”的结果在二进制下是什么样的)

AI2:

对!你说对了 👏

不过我们可以再把这句话拆开一点,让逻辑更清晰。

A=1、B=1 时,普通加法得到 10(也就是结果位 0,进位 1)。

而 XOR 只保留“结果位”,不管进位,所以它只取 0。

换句话说:

• 普通加法 = 结果位 + 进位

• XOR = 只要结果位,不要进位

所以我们说它是 “不带进位的二进制加法”

来,我们检验一下你的理解:

我给你两个二进制数:

A = 1011  

B = 1101

你能试着算出 A ⊕ B 的结果吗?

(一步一步来,不用急——我们只做 XOR,不带进位的)

AI3:

太棒了 🎯 完全正确!

我们来一起验证一下思路:

   1011

⊕  1101

=  0110

逐位解释:

A B A⊕B

1 1 0

0 1 1

1 0 1

1 1 0

✅ 结果:0110

小结一下:

• XOR(异或)相当于“不带进位的二进制加法”;

• 如果要做完整的加法,就要加上 进位 = A & B 的那一部分。

想让我带你一起推一推,怎么用 “XOR + AND + 移位” 来在 C 语言里实现一个不用 + 的加法器吗?

AI4:

很好~那我们一起来“拆解”加法器的思路吧。

我们先从逻辑讲起,不直接写代码。

🧩 第一步:回忆一下我们刚才说的公式

普通二进制加法可以写成:

Sum = A ⊕ B          // 不带进位的部分

Carry = (A & B) << 1  // 进位,往左移一位

但这个 Carry 可能又会跟 Sum 相加,还会继续产生新的进位。

所以我们要 重复这个过程,直到进位变成 0。

💡 第二步:我们可以用循环实现这个过程

每一轮做的事:

1. sum = a ⊕ b

2. carry = (a & b) << 1

3. 把 a = sum, b = carry

4. 继续直到 carry == 0

🔧 第三步:用 C 语言写出来

int add(int a, int b) {

    while (b != 0) {

        int sum = a ^ b;       // 不带进位的和

        int carry = (a & b) << 1; // 计算进位

        a = sum;               // 更新a

        b = carry;             // 更新b

    }

    return a;

}

这个算法完全没用 + 或 -,却能实现加法。

现在我想考考你 😏

假如我们输入 add(5, 3),

你能试着写出每一轮循环里 a、b、sum、carry 的变化吗?(我们可以先看前两轮)

AI5:

没关系~这个地方第一次接触确实有点绕,我们慢慢拆开。

我们先换个思路:别看代码,先把**“加法的过程”**想象成两步循环发生的事。

🧠 想象一下你要算 5 + 3

先写成二进制:

5 = 0101  

3 = 0011

第一步:XOR,不带进位加法

我们先做 “不带进位”的加法(XOR):

0101

⊕ 0011

= 0110   ← 这就是 6

但因为有些地方 “1+1” 了(会产生进位),我们还得算出进位

第二步:AND,然后左移一位

计算哪些位置产生进位:

0101

& 0011

= 0001

进位要往左移动一位(因为二进制加法中进位加到下一列):

0001 << 1 = 0010

这代表进位是 2。

第三步:再加一次

现在我们要把

不带进位的结果 0110 (6)

再加上进位 0010 (2)

所以我们又回到第一步——再做一次 XOR + AND。

看到这个循环了吗?

每次做:

1. 不带进位加法(XOR)

2. 算出新的进位(AND << 1)

3. 如果进位不为 0,就再来一轮。

最后进位变成 0,结果就是最终的和。

这样解释有点清楚了吗?

要不要我帮你把上面的例子(5 + 3)一步步继续算完,让你看到循环怎么结束的?

AI6:

很好,我们就把刚才那个例子(5 + 3)一步一步完整推到底 💪

🧩 起始值:

a = 0101 (5)

b = 0011 (3)

🌀 第 1 轮循环

1️⃣ 不带进位加法(XOR)

0101

⊕ 0011

= 0110   → sum = 6

2️⃣ 计算进位(AND,然后左移)

0101

& 0011

= 0001

0001 << 1 = 0010   → carry = 2

更新 a、b:

a = 0110 (6)

b = 0010 (2)

🌀 第 2 轮循环

1️⃣ XOR

0110

⊕ 0010

= 0100   → sum = 4

2️⃣ AND + 左移

0110

& 0010 = 0010

0010 << 1 = 0100   → carry = 4

更新 a、b:

a = 0100 (4)

b = 0100 (4)

🌀 第 3 轮循环

1️⃣ XOR

0100

⊕ 0100

= 0000   → sum = 0

2️⃣ AND + 左移

0100

& 0100 = 0100

0100 << 1 = 1000   → carry = 8

更新 a、b:

a = 0000

b = 1000

🌀 第 4 轮循环

1️⃣ XOR

0000

⊕ 1000

= 1000   → sum = 8

2️⃣ AND + 左移

0000

& 1000 = 0000

0000 << 1 = 0000   → carry = 0

这时 b = 0,循环结束 ✅

🎯 最终结果

a = 1000(二进制)

= 8(十进制)

所以 add(5, 3) 的结果就是 8

你能看到:

• 每一轮 “不带进位加法” 用 XOR;

• 每一轮 “进位” 用 AND 然后左移;

• 不断重复,直到进位没了。

要不要我帮你画一张小图,把这个「XOR + AND + 循环」的逻辑画成流程图?那样会更容易记。

AI7:

你的结论非常准确!**异或(XOR)运算确实是找到“数组中出现奇数次数字”的高效方法**,尤其适用于“只有1个或2个数字出现奇数次,其余数字均出现偶数次”的场景。其核心优势在于**时间复杂度O(n)、空间复杂度O(1)**(无需额外存储大量数据),远优于哈希表等需要额外空间的方法。

要理解为什么XOR能做到这一点,首先需要掌握XOR运算的4条核心性质,这是整个逻辑的基础:

### 一、XOR运算的核心性质

异或运算用符号“^”表示,运算规则是“**相同为0,不同为1**”(二进制层面),但从实用角度,更关键的是其推导出来的4条规律:

1. **交换律**:`a ^ b = b ^ a`(顺序不影响结果)  

2. **结合律**:`(a ^ b) ^ c = a ^ (b ^ c)`(分组不影响结果)  

3. **自身异或为0**:`a ^ a = 0`(一个数和自己异或,结果为0)  

4. **与0异或为自身**:`a ^ 0 = a`(一个数和0异或,结果还是它本身)  

### 二、场景1:只有1个数字出现奇数次,其余均为偶数次

这是最基础的场景,也是XOR应用的核心。

#### 原理推导:

假设数组为`[a, b, c, a, b]`,其中`c`出现1次(奇数次),`a`和`b`各出现2次(偶数次)。  

根据交换律和结合律,我们可以将“相同数字”优先分组异或:  

`(a ^ a) ^ (b ^ b) ^ c`  

根据“自身异或为0”,`a^a=0`,`b^b=0`,代入后得到:  

`0 ^ 0 ^ c`  

再根据“与0异或为自身”,`0^0=0`,最终结果为:  

`0 ^ c = c`  

可见,**所有出现偶数次的数字会通过异或“抵消”为0,最终剩下的就是出现奇数次的数字**。

#### 实例演示:

数组`[2, 3, 2, 4, 4, 3, 3]`,其中`3`出现3次(奇数次),其余数字各出现2次(偶数次)。  

计算过程:  

`2 ^ 3 ^ 2 ^ 4 ^ 4 ^ 3 ^ 3`  

= `(2^2) ^ (3^3^3) ^ (4^4)`  

= `0 ^ (0 ^ 3) ^ 0`  

= `0 ^ 3 ^ 0`  

= `3`  

最终结果恰好是出现奇数次的`3`。

### 三、场景2:有2个数字出现奇数次,其余均为偶数次

如果数组中有2个数字(设为`x`和`y`)出现奇数次,其余均为偶数次,XOR同样能解决,但需要多一步“分组拆分”。

#### 原理推导:

1. **第一步:得到x^y**  

   对整个数组异或,所有出现偶数次的数字会抵消为0,最终结果为:`0 ^ x ^ y = x ^ y`(记为`res = x ^ y`)。  

   由于`x ≠ y`(若x=y则出现次数为偶数,矛盾),`res`的二进制中**至少有1位是1**(这是关键:说明x和y在这一位上的二进制值不同)。

2. **第二步:找到“区分位”,拆分数组**  

   找到`res`中任意一个为1的二进制位(比如最低位的1,记为`mask`)。  

   用`mask`对数组中所有数字进行“与运算”:  

   – 数字与`mask`的结果为1:放入组A(这类数字在“区分位”上为1)  

   – 数字与`mask`的结果为0:放入组B(这类数字在“区分位”上为0)  

   由于`mask`是`x^y`的为1的位,`x`和`y`会被分到不同的组(一个在A,一个在B),且**每组内只有1个数字出现奇数次**(其余均为偶数次)。

3. **第三步:分别求x和y**  

   对组A所有数字异或,得到其中出现奇数次的数字(比如`x`);  

   对组B所有数字异或,得到另一个出现奇数次的数字(比如`y`)。

#### 实例演示:

数组`[1, 2, 3, 2, 3, 4, 4, 5]`,其中`1`和`5`各出现1次(奇数次),其余数字各出现2次(偶数次)。  

1. 全数组异或:`1^2^3^2^3^4^4^5 = 1^5 = 4`(二进制`100`,即`res=4`)。  

2. 找区分位:`res=4`的最低为1的位是第3位(二进制`100`),`mask=4`。  

3. 分组:  

   – 组A(数字&4=1):`4`(4&4=4≠1?重新算:数组中数字是1(001)、2(010)、3(011)、4(100)、5(101)。mask=4(100),则:  

     – 1&4=0 → 组B  

     – 2&4=0 → 组B  

     – 3&4=0 → 组B  

     – 4&4=4 → 组A  

     – 5&4=4 → 组A  

   最终组A:`[4,4,5]`(5出现1次奇数次),组B:`[1,2,3,2,3]`(1出现1次奇数次)。  

4. 分组异或:  

   – 组A异或:`4^4^5=5`  

   – 组B异或:`1^2^3^2^3=1`  

   得到两个出现奇数次的数字`1`和`5`。

### 四、注意事项

XOR的这个用法有一个严格前提:**除了目标数字(1个或2个),其余所有数字必须出现偶数次**。如果有3个及以上数字出现奇数次,单纯用XOR无法直接找到所有目标(需要结合更复杂的逻辑,如哈希表统计次数)。

综上,XOR利用其独特的运算性质,实现了“零额外空间找奇数次数字”的高效解法,是算法面试中非常经典的考点。

AI8-1:

很好 👍 那我们就从「为什么要有补码」讲起。

在计算机里,加法和减法最好都能用“加法器”来完成,因为电路实现加法比减法简单。

但问题是:

如果我们用“原码”表示负数,比如:

十进制 原码(二进制 8 位)

+5 0000 0101

-5 1000 0101

你会发现:

(+5) + (-5) = 0000 0101 + 1000 0101 = 1000 1010 → 这并不是 0!

所以原码不能直接用于运算。

于是就有了一个聪明的办法——补码(Two’s Complement)

它的思路是:

让“负数”变成一种特殊的正数形式,这样加法电路就能处理它。

计算规则(以 8 位为例):

1️⃣ 正数的补码 = 原码(不变)

2️⃣ 负数的补码 = 先取反(把 1→0,0→1),再 +1

例如:

-5 的原码是 1000 0101

→ 取反:0111 1010

→ 加 1:0111 1011

所以 -5 的补码是 1111 1011(注意最高位 1 表示负号)。

当你用补码运算时:

(+5) + (-5)

= 0000 0101 + 1111 1011 = 1 0000 0000

去掉溢出的最高位 → 0000 0000 ✅(结果正确!)

我想让你试一下:

👉 请你帮我算算,在 8 位系统下,-3 的补码是什么?

(记得:先取反,再加 1)

AI8-2:

非常好 👏,你算得完全正确!

我们来验证一下你的过程:

• 原码(-3):1000 0011

• 取反:0111 1100

• 加 1:0111 1101

→ 所以补码是 1111 1101 ✅

现在你已经理解了补码的生成方式。

我们再往下一步走一点:

你觉得用补码来表示负数,有什么好处呢?

(提示:它跟加法、减法、电路设计的关系很大~)

AI8-3:

完全正确!👏👏

你已经抓到补码最关键的两个优点了:

1️⃣ 简化电路设计

因为加法和减法都可以用同一个“加法器”完成。

举个例子:

想算 5 – 3,其实就是 5 + (-3)。

如果 -3 用补码表示,就能直接丢进加法器里计算。

2️⃣ 避免算错数(特别是符号位的问题)

在原码或反码下,符号位要单独处理,容易出错;

但补码让符号位“融入”了数值部分,整体运算更自然。

来个小练习巩固一下?

👉 如果是 8 位系统,1111 0110 这个补码代表的是十进制多少?

(提示:你可以先判断它是正数还是负数,再反推出原码。)

//5 – 6 – 4 reverse relied on each bit(bitwise NOT)

#include <iostream>

using namespace std;

/*

~  reverse relied on each bit’s symbol(bitwise NOT)

this is an unary operator(单目运算符)

*/

int main() {

//1.the unary operator’s (reverse relied on each bit’s symbol) definition

int a = 0b00000000000000000000000000000001;

int b = 0b11111111111111111111111111111110; //four 8 bits number

cout << (~a) << endl; //-2

cout << b << endl; //-2

cout << “—————” << endl; 

//2.Two’s Complement(补码)

//if 0b00000000000000000000000000000000 =  0

//and if 0b11111111111111111111111111111111 = -1

//then minus 1  0b11111111111111111111111111111110 = -2

int c = 0b0;

cout << (~c) << endl; // 32 bits’ 1 will output -1

cout << “—————” << endl;

//3.get the reverse number

int d = 18;

cout << (~d + 1) << endl;//-18

//原码、反码、补码、移码

//1’s ~ (bitwise NOT) is -2

//0’s ~ (bitwise NOT) is -1

return 0;

}

AI8-3:

在计算机中,0b0(二进制0)按位取反后结果为-1,这是由**按位取反的运算规则**和**补码表示法**共同决定的,核心原因在于计算机用补码表示有符号整数。

### 具体分析步骤:

#### 1. 明确“0b0”的二进制完整形式

在计算机中,整数通常以固定位数(如32位、64位)的二进制存储。例如,32位系统中,`0b0`(十进制0)的二进制是**32个0**:  

`00000000 00000000 00000000 00000000`  

#### 2. 按位取反(~)的运算规则

按位取反会将二进制的每一位都翻转(0变1,1变0)。因此,32位的0取反后,所有0都会变成1,结果是**32个1**:  

`11111111 11111111 11111111 11111111`  

#### 3. 补码中“全1”表示-1

计算机中,有符号整数用**补码**表示,规则是:  

– 正数的补码 = 原码(符号位为0,数值位为本身);  

– 负数的补码 = 反码 + 1(反码是原码除符号位外取反)。  

而“全1”的二进制(如32位的`11111111…1111`)恰好是-1的补码:  

– 推导:假设求-1的32位补码  

  – 1的原码:`00000000…0001`  

  – 1的反码(除符号位外取反):`11111111…1110`(符号位为1,表示负数)  

  – 反码加1得补码:`11111111…1110 + 1 = 11111111…1111`  

#### 4. 验证逻辑合理性

从运算逻辑上看,-1的补码“全1”满足补码的核心特性:**负数加其绝对值等于0**。  

例如:`-1 + 1 = 0`  

– -1的补码:`11111111…1111`  

– 1的补码:`00000000…0001`  

– 相加结果:`11111111…1111 + 00000000…0001 = 100000000…0000`(溢出后截断为32位,结果为`00000000…0000`,即0),完全符合逻辑。  

### 结论

0b0按位取反后得到“全1”的二进制,而在补码表示法中,“全1”恰好是-1的编码,因此结果为-1。这是计算机为了统一加减法运算(将减法转化为加法)而设计的补码规则导致的必然结果。

AI9-1:

为什么 这样 //3.get the reverse number

int d = 18;

cout << (~d + 1) << endl;//-18

这段代码的输出结果为`-18`,核心原因是:**`~d + 1` 是计算机中求一个整数相反数(负数)的标准操作**,这是由补码的运算规则决定的。

### 具体分析步骤:

#### 1. 明确 `d = 18` 的二进制补码

在C++中,`int` 通常为32位有符号整数,用补码表示。  

18的二进制原码(正数的补码与原码相同)为:  

`00000000 00000000 00000000 00010010`  

#### 2. 按位取反 `~d` 的结果

`~` 是按位取反运算符,将二进制每一位翻转(0变1,1变0)。  

对18的补码取反后得到:  

`11111111 11111111 11111111 11101101`  

#### 3. 加1操作 `~d + 1` 的结果

取反后加1,得到:  

`11111111 11111111 11111111 11101101 + 1 = 11111111 11111111 11111111 11101110`  

#### 4. 该结果是 `-18` 的补码

在补码规则中,负数的补码定义为“其绝对值的反码 + 1”。  

– 18的绝对值是18,其原码为 `00000000…00010010`  

– 反码为 `11111111…11101101`(除符号位外取反)  

– 反码加1后正是 `11111111…11101110`,即 `-18` 的补码。  

### 结论

`~d + 1` 本质是**通过补码运算求 `d` 的相反数**。对于任意整数 `d`,`~d + 1` 的结果恒等于 `-d`,因此当 `d = 18` 时,输出自然为 `-18`。这是计算机中利用补码统一加减法运算的典型应用。

AI9-2:

哦,你这里用的这一行代码,其实就是在用 补码的原理 来求负数。我们慢慢拆开来看:

int d = 18;

cout << (~d + 1) << endl;  // 输出 -18

1️⃣ ~d 是什么?

• ~ 是 按位取反(bitwise NOT)

• 18 的二进制(32 位 int 为例):

00000000 00000000 00000000 00010010

• 取反后:

11111111 11111111 11111111 11101101

这就是 18 的反码

2️⃣ +1 是为什么?

• 在补码表示中,负数 = 取反 +1

• 所以:

~18 + 1

= 11111111 11111111 11111111 11101101 + 1

= 11111111 11111111 11111111 11101110

• 这就是 -18 的补码,所以输出就是 -18。

🔑 核心理解

• ~d + 1 就是 求 d 的补码负数

• 换句话说,这种方式可以把一个正数变成负数

如果你愿意,我可以画一张 正数 → 取反 → +1 → 负数补码 的流程图,让你一眼就看懂为什么会是 -18。

AI10:

好呀,我们把这些概念理一理,它们都是计算机表示有符号数的方法,但用法和原理不同。我们用 8 位为例子来讲。

1️⃣原码(Sign-Magnitude

规则:最高位是符号位,0 表示正,1 表示负,其余位表示数值大小。

例子

• +5 → 0000 0101

• -5 → 1000 0101

问题:加减法电路麻烦,需要特殊处理符号位。

2️⃣反码(Ones’ Complement

规则

• 正数:与原码相同

• 负数:原码符号位不变,其余位取反

例子

• +5 → 0000 0101

• -5 → 1111 1010

特点:减法可以用加法器做,但有“0 的两种表示”(0000 0000 和 1111 1111)。

3️⃣补码(Two’s Complement

规则

• 正数:原码不变

• 负数:取反 +1

例子

• +5 → 0000 0101

• -5 → 1111 1011

特点

• 加减法都可以直接用加法器

• 只有一种 0 表示

• 最高位仍表示符号(1 负,0 正)

4️⃣移码(Biased / Excess-K

规则:把原码加上一个偏移值(bias),通常 bias = 2^(n-1),使所有数都是正数

用途:浮点数指数部分常用移码

例子(8 位,bias = 128):

• 真值 0 → 1000 0000

• 真值 -5 → 1000 0000 – 5 = 0111 1011

• 真值 +5 → 1000 0000 + 5 = 1000 0101

如果你愿意,我可以画一张图表把原码、反码、补码、移码对比起来,一眼就能看懂它们的关系。

你想要我画吗?