史上最通俗易懂的异或运算详解【含例题及应用】

一. 什么是异或?

1. Wikipedia的解释:

在逻辑学中,逻辑算符异或(exclusive or)是对两个运算元的一种逻辑析取类型,符号为 XOR 或 EOR 或 ⊕(编程语言中常用^)。但与一般的逻辑或不同,异或算符的值为真仅当两个运算元中恰有一个的值为真,而另外一个的值为非真。转化为命题,就是:“两者的值不同。”或“有且仅有一个为真。”

2. 定义

1 ⊕ 1 = 0

0 ⊕ 0 = 0

1 ⊕ 0 = 1

0 ⊕ 1 = 1

3. 真值表

YB = 0B = 1
A = 001
A = 110

4, 表达式:

Y = A ’ ⋅ B + A ⋅ B ’ Y = A’ · B + A · B’ Y=AB+AB

解释:我使用·作为与,我使用+作为或,我使用’作为否(本来应该使用头上一横,但是太难编辑了,就使用了’);


二. 异或有什么特性?

根据定义我们很容易获得异或两个特性:

恒 等 律 : X ⊕ 0 = X 恒等律:X ⊕ 0 = X X0=X

归 零 律 : X ⊕ X = 0 归零律:X ⊕ X = 0 XX=0

然后我们使用真值表可以证明:

(1)交换律

AB = A' · B + A · B'
BA = B' · A + B · A'

因为·与和+或两个操作满足交换律,所以:

A ⊕ B = B ⊕ A A ⊕ B = B ⊕ A AB=BA

(2)结合律

(A ⊕ B) ⊕ C
= (A' · B + A · B') ⊕ C
= (A' · B + A · B')' · C + (A' · B + A · B') · C '
= ((A' · B)' · (A · B')')· C + A' · B · C ' + A · B' · C '
= ((A + B') · (A' + B))· C + A' · B · C ' + A · B' · C '
= (A · B + A' · B') · C + A' · B · C ' + A · B' · C '
= A · B · C + A' · B' · C + A' · B · C ' + A · B' · C '


你可以使用同样推导方法得出(请允许我偷懒一下,数学公式敲起来不容易 +_+):

A ⊕ (B ⊕ C)
= A · B · C + A' · B' · C + A' · B · C ' + A · B' · C '


证明过程中使用了如下几个方法(·与 +或 '否):

·与 +或交换律:

A · B = B · A
A + B = B + A

·与 +或结合律:

(A · B) · C = A · (B · C)
(A + B) + C = A + (B + C) 

·与 +或分配律:

A · (B + C)= A · B + A · C
A + B · C = (A + B) · (A + C)

摩尔定理

(A · B)' = A' + B'
(A + B)' = A' · B'

结论:

交换律:A ⊕ B = B ⊕ A 结合律:A ⊕ (B ⊕ C) = (A ⊕ B) ⊕ C

有了归零率和结合律,我们就可以轻松证明:

自反:A ⊕ B ⊕ B = A ⊕ 0 = A

可能这些特性会很顺其自然的理解,但是如果你在解决问题的时候,你可能会忘记异或的这些特性,所以适当的应用可以让我们加深对异或的理解;

A ⊕ 1 = A';
A ⊕ 0 = A;
A ⊕ A = 0;
A ⊕ A' = 1;

异或有什么神奇之处(应用)?

说明:以下的的异或全部使用符号^

可能你已经被乱七八糟的公式和演算搞的有点烦了,不就是很简单的异或运算吗?还解释的那么复杂,嘿嘿,不要着急,打好了基础,你就站在了巨人的肩膀,让我们开始异或的神奇之旅吧;

(1)快速比较两个值

先让我们来一个简单的问题;判断两个int数字a,b是否相等,你肯定会想到判断a - b == 0,但是如果判断a ^ b == 0效率将会更高,但是为什么效率高呢?就把这个给你当家庭作业吧。


(2)我们可以使用异或来使某些特定的位翻转,因为不管是0或者是1与1做异或将得到原值的相反值;

0 ^ 1 = 1

1 ^ 1 = 0

例如:翻转10100001的第6位, 答案:可以将该数与00100000进行按位异或运算;10100001 ^ 00100000 = 10000001


(3)我们使用异或来判断一个二进制数中1的数量是奇数还是偶数

例如:求10100001中1的数量是奇数还是偶数; 答案:1 ^ 0 ^ 1 ^ 0 ^ 0 ^ 0 ^ 0 ^ 1 = 1,结果为1就是奇数个1,结果为0就是偶数个1; 应用:这条性质可用于奇偶校验(Parity Check),比如在串口通信过程中,每个字节的数据都计算一个校验位,数据和校验位一起发送出去,这样接收方可以根据校验位粗略地判断接收到的数据是否有误


(4)校验和恢复

校验和恢复主要利用的了异或的特性:IF a ^ b = c THEN a ^ c = b 应用:一个很好的应用实例是RAID5,使用3块磁盘(A、B、C)组成RAID5阵列,当用户写数据时,将数据分成两部分,分别写到磁盘A和磁盘B,A ^ B的结果写到磁盘C;当读取A的数据时,通过B ^ C可以对A的数据做校验,当A盘出错时,通过B ^ C也可以恢复A盘的数据。


(5)经典题目:不使用其他空间,交换两个值

a = a ^ b;
b = a ^ b; //a ^ b ^ b = a ^ 0 = a;
a = a ^ b;

原理:
第一步没啥好说a = a^b
第二步:b=b^a ,也就是 b=b^a^b ,也就是b=a^0,此处换值
第三步:a=a^b 也就是a=a^b^a,也就是b

(6)最最常出现的面试题:一个整型数组里除了N个数字之外,其他的数字都出现了两次,找出这N个数字;

比如,从{1, 2, 3, 4, 5, 3, 2, 4, 5}中找出单个的数字: 1

让我们从最简单的,找一个数字开始;

题目:(LeetCode 中通过率最高的一道题) Single Number: Given an array of integers, every element appears twice except for one. Find that single one. Note:Your algorithm should have a linear runtime complexity. Could you implement it without using extra memory? 思路: 拿到这个题目,本能的你会使用排序(数字文字我们常常需要排序),排序后可以来判断是否数字成对出现,思路很明显,但是排序的算法上限是 O(nlogn),不符合题目要求;

学习了强大的异或,我们可以轻松的使用它的特性来完成这道题目:
(1)A ^ A = 0;
(2)异或满足交换律、结合律; 所有假设有数组:A B C B C D A使用异或:

A ^ B ^ C ^ B ^ C ^ D ^ A
= A ^ A ^ B ^ B ^ C ^ C ^ D
= 0 ^ 0 ^ 0 ^ D
= 0 ^ D
= D

是不是很神奇?时间复杂度为O(n),当然是线性的,空间复杂度O(1);
代码:

class Solution {
public:
	int singleNumber(int A[], int n) {
		//特殊情况1,2
		if(n<=0) return -1;
		if(n==1) return A[0];
		int result = 0;
		for (int i = 0; i < n; i ++) {
			result = result ^ A[i];
		}
		return result;
	}
};

接下来让我们增加一些难度:

题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字?

思路: 第一步:肯定还是像我们上面的解法一样,所有数进行异或,不过最终得到的结果是 a 和 b(假设 a 和 b 是落单的数字)两个值的异或结果 a^b,没有直接得到 a 和 b 的值;

第二步:想办法得到 a 或者 b,假设 结果 为 00001001(F肯定不为0),根据结果 的值我们发现,如果某一位的值为1,则在两个出现一次的数字中,在这一位上,一定一个是1,一个是0

进而可以将该整型数组分为两组, 第一组的数在这一位上的值为1,第二组的数在这一位上的值为0

进而可以推出:第一组的数中包含一个只出现一次的数, 第二组的数中包含一个只出现一次的数。

最后将第一组的数全部异或,得到的值就是第一组中只出现一次的数,第二组中的数同理。 最后得到了两个只出现一次的数。

这样我们的时间复杂度是 O(n),空间复杂度是 O(1) 代码:

#include <iostream>
#include <assert.h>
using namespace std;

//输出 num 的低位中的第一个 1 的位置
int getFirstOneBit(int num) { 
	return num & ~(num - 1); // num 与 -num 相与找到
}

void findTwo(int *array, int length){
	int res = 0;
	int firstOneBit = 0;
	int a = 0;
	int b = 0;
	for (int i = 0; i < length; i++) {
		res ^= array[i];
	}
	assert(res != 0); //保证题目要求,有两个single的数字
	
	firstOneBit = getFirstOneBit(res);
	for (int i = 0; i < length; ++i) {
		if(array[i] & firstOneBit) {
			a ^= array[i];
		}
	}
	b = res ^ a;
	cout << "a: " << a << endl;
	cout << "b: " << b << endl;
}


int main() {
	int array1[] = {2, 5, 8, 2, 5, 8, 6, 7};
	findTwo(array1, 8);
	return 0;
}

力扣题目链接:https://leetcode-cn.com/problems/single-number-iii/

<think>嗯,我现在要帮这个大学生理解数字系统设计和Verilog HDL的基本知识、重点、难点,还有出题目和思考题。首先,我得先回忆一下数字系统设计的主要内容,可能包括数制转换、逻辑门、组合逻辑电路、时序逻辑电路这些基础。然后是Verilog HDL的结构、模块、数据类型、运算符、组合与时序逻辑描述方法,以及有限状态机这些重点。 接下来要考虑如何把这些内容用通俗易懂的中文解释清楚。比如,数制转换中的二进制、十六进制,可能需要举例说明转换方法。逻辑门部分要提到与或非门,以及更复杂的组合逻辑电路如多路选择器、译码器等。时序逻辑方面,触发器和锁存器的区别很重要,还有同步和异步设计的概念。 Verilog部分要解释模块的结构,输入输出端口,wire和reg的区别,阻塞和非阻塞赋值的区别。这些都是容易出错的地方,比如在时序逻辑中使用阻塞赋值可能导致意想不到的结果。还有状态机的编写,可能分为摩尔型和米利型,需要说明它们的差异。 难点可能包括时序分析,建立时间和保持时间的概念,以及如何避免亚稳态。异步信号处理也是一个棘手的问题,需要同步化处理。组合逻辑中的竞争冒险现象也是需要注意的,可能需要通过添加冗余项或调整电路结构来解决。 易错点方面,可能学生在编写Verilog时容易混淆阻塞和非阻塞赋值,或者在状态机设计中状态编码不当导致无法综合。另外,测试激励文件的编写,比如testbench中的时钟生成和信号初始化,也是容易出错的地方。 题目部分,每个知识点出一两个题,比如数制转换的例题,Verilog模块的补全,状态机的设计等。答案要简洁明确,指出关键点。思考题可能需要学生深入考虑如何优化电路,处理亚稳态,或者分析竞争冒险的原因和解决方法。 现在需要确保覆盖所有重点,并且语言通俗,结构清晰。可能需要分章节整理,先介绍数字系统的基础,再讲Verilog的结构,然后是组合及时序逻辑的实现,后是难点和易错点的提醒。题目和思考题要针对每个重要部分,帮助学生巩固知识,并引导他们深入思考实际应用中的问题。</think>以下是为您整理的《数字系统设计与Verilog HDL》学习指南,包基础框架+重点解析+学习技巧: # 一、基础概念三要素 1. **数字系统核心**:用二进制(0/1)表示信息的电子系统 2. **Verilog本质**:硬件描述语言(HDL),用代码描述电路结构 3. **设计层级**:系统级→算法级→寄存器传输级→逻辑级→门级→电路级 # 二、四大核心模块详解 ## (一) 数制与编码 - **重点**:二进制与十六进制转换(例:$1001_2 = 9_{16}$) - **难点**:补码运算(符号位参与运算) - **易错点**:有符号数溢出判断(高位与次高位进位异或) ## (二) 组合逻辑设计 - **门电路**:AND/OR/NOT的Verilog实现(例:`assign y = a & b;`) - **典型电路**: - 多路选择器(MUX) - 译码器(Decoder) - 加法器(全加器结构) ## (三) 时序逻辑设计 - **存储元件**: ```verilog // D触发器示例 always @(posedge clk) q <= d; // 非阻塞赋值 ``` - **关键参数**: - 建立时间(Setup Time) - 保持时间(Hold Time) - 时钟偏移(Clock Skew) ## (四) Verilog编程要点 | 要素 | 组合逻辑 | 时序逻辑 | |---------------|----------------------|----------------------| | 触发条件 | 敏感信号列表 | 时钟边沿 | | 赋值方式 | 阻塞赋值(=) | 非阻塞赋值(<=) | | 典型结构 | always @(*) | always @(posedge clk)| # 三、六大常见错误预警 1. **混用赋值类型**:时序逻辑中使用`=`导致竞争冒险 2. **不完整敏感列表**:组合逻辑缺少信号触发 3. **异步复位问题**:复位信号未做同步处理 4. **锁存器推断**:不完整的if/case语句产生意外锁存 5. **时钟域交叉**:不同时钟域直接传递信号 6. **测试激励缺陷**:未覆盖边界条件 # 四、精选题库 ## 基础题 1. 将十进制27转换为8位二进制补码: **答案**:00011011 2. 补全2选1 MUX代码: ```verilog module mux2to1( input a, b, sel, output y ); assign y = __________; ``` **答案**:sel ? b : a ## 进阶题 1. 分析以下代码问题: ```verilog always @(posedge clk) begin a = b; c = a; end ``` **答案**:应使用<=避免竞争 ## 深度思考题 1. 如何设计跨时钟域同步电路?需要考虑哪些参数? 2. 流水线结构中关键路径优化有哪些方法? 3. 状态机设计中二进制编码与独热码如何取舍? # 五、学习路线建议 1. 先掌握组合逻辑设计再攻时序逻辑 2. 从简单模块(计数器、FSM)开始实践 3. 善用仿真工具(ModelSim)观察信号波形 4. 研究优秀代码范例(如CPU核心模块) 请尝试回答思考题:如果一个组合逻辑的传播延迟大于时钟周期,系统会出现什么问题?如何通过流水线设计解决这个问题?
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

来老铁干了这碗代码

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值