数字电路模拟程序总结
写在前面
OOP课程第二阶段的作业集主要围绕“数字电路模拟程序”展开,从基础逻辑门开始,逐步增加多输入多输出元件、控制引脚、子电路以及异常输入检测。相比第一阶段的多项式题目,这一阶段的题目更强调“对象之间的连接关系”和“系统迭代时的可扩展性”。
第四次作业实现的是数字电路模拟程序 1,主要包含与门、或门、非门、异或门、同或门五种基础逻辑门。第五次作业在此基础上加入了三态门、译码器、数据选择器和数据分配器,开始出现控制引脚、多输出引脚和无效状态。
第六次作业又回到基础逻辑门模型,但增加了子电路和异常输入检测,要求程序不仅能算出电路结果,还要能识别连接信息中的错误。
这三次作业让我明显感受到:面向对象设计并不是简单地把代码拆成几个类,而是要考虑后续题目是否会扩展。如果前一次作业只为了“当前题目能过”而写,到了下一次迭代时就会出现大量修改。尤其是数字电路这类题目,元件类型、引脚类型、输出形式、连接方式都可能变化,所以一开始的数据结构设计非常重要。
本文将从程序结构、类设计、复杂度、Bug、测试和改进方向几个方面对作业 4、5、6 进行总结。
第四次作业
作业要求
第四次作业要求实现一个基础数字电路模拟程序,电路中包含五类基础逻辑门:
- 与门A
- 或门O
- 非门N
- 异或门X
- 同或门Y
输入由两部分组成:一部分是外部输入信号,另一部分是连接信息,例如将某个输入信号连接到某个元件引脚,或者将一个元件的输出连接到另一个元件的输入。
程序最后需要按照与门、或门、非门、异或门、同或门的顺序输出所有能够计算出结果的元件。如果某个元件的输入引脚没有接收到有效输入,则忽略该元件的输出。
实现思路
- 读取所有INPUT:开头的输入信号,将输入名和值保存到inputs列表中。
- 读取所有连接信息,识别连接源和连接目标。
- 如果目标是某个元件的输入引脚,则创建对应的Gate对象。
- 对每个Gate保存每个输入引脚的来源。来源可能是外部输入,也可能是另一个元件的输出。
- 通过循环扫描所有元件,只要某个元件的所有输入值都已知,就计算它的输出。
- 最后按照题目规定的元件类型顺序和编号顺序输出。
代码规模
第四次作业代码规模如下图。
![屏幕截图 2026-06-13 131631]()
类图
第四次作业类图如下,Gate 类承担了最主要的逻辑
![屏幕截图 2026-06-13 132223]()
这种设计的优点是比较直接,所有逻辑门都可以用同一个 Gate 类表示。五种基础门的差别主要体现在输入数量和计算方式上,所以用一个类统一处理是可行的。不过,这种设计也有不足。Gate 类中存在较多 if-else 判断,例如判断当前元件是 A、O、N、X 还是 Y。第四次作业中只有五种门,问题还不明显,但到了第五次作业新增元件后,这种设计会导致 Gate 类越来越长。
复杂度分析
第四次作业的复杂度分析如下。
![屏幕截图 2026-06-13 133904]()
第四次作业中复杂度较高的部分主要有以下几个: - connect方法
该方法需要判断连接源是外部输入还是元件输出,同时还要解析目标引脚。连接信息是整个程序的基础,如果这里处理错误,后面的计算都会出问题。
2.calculateAllGates方法
这个方法通过循环扫描所有元件来推动计算。只要本轮有元件被成功计算,就继续下一轮。这样可以处理多级电路连接,但如果电路中存在无法计算的元件,最终会停留在未计算状态并被忽略。
3.tryCalculateGate方法
该方法判断一个元件是否已经具备所有输入。如果任何一个输入引脚没有连接,或者来源元件尚未计算完成,则返回未知值。
Bug分析
公测
第四次作业在公测中没有出现明显正确性错误,基础逻辑门的计算能够满足题目要求。但是从测试结果看,程序的性能和可扩展性并不算特别好。
互测
互测中比较容易被发现的问题主要集中在以下几类:
1.第一类是引脚没有接满时的处理。题目要求如果某个元件的引脚没有接有效输入,就忽略该元件输出。初学者容易写成默认值为 0 或 1,这样会导致错误输出。我在程序中使用 UNKNOWN 表示未知值,只有所有输入都已知时才计算。
2.第二类是元件输出和外部输入的区分。例如 A 可能是外部输入,而 A(2)1-0 是某个元件的输出。如果只用字符串中是否包含 - 判断,很容易误判。因此我通过引脚号是否为 0 和元件名是否合法共同判断。
3.第三类是排序输出错误。题目要求先输出与门,再输出或门、非门、异或门、同或门,并且同类按编号排序。如果只按照字符串字典序排序,就会出现 X10 排在 X2 前面的问题。所以我专门解析了元件编号进行比较。
测试方法
1.基础门件测试
2.单个或门
3.门级联组合
4.复杂连接
5.边界与特殊组合
6.多层级联电路
第五次作业
作业要求
第五次作业是第四次作业的扩展,新增了四类元件:
- 三态门
- 译码器
- 数据选择器
- 数据分配器
这样一来,电路元件从五种增加到九种。题目难度明显上升,主要体现在以下几个方面:
- 元件不再都只有一个输出引脚。
- 元件开始出现控制引脚。
- 三态门、译码器、数据分配器会产生无效状态。
- 元件输出格式不完全相同。
- 引脚编号规则变复杂。
实现思路
第五次作业中,我仍然保留了Main,InputSignal,Gate三个类,但对Gate类进行了较大扩展。
第四次作业中,所有元件都默认0号引脚为输出,所以输出可以简单地用一个output保存。但第五次作业中,三态门输出是2号引脚,译码器有多个输出,数据分配器也有多个输出。因此我将输出统一设计为数组,通过outputPins记录每个输出值对应的实际引脚编号。
代码规模
第五次作业代码规模如下图。
![屏幕截图 2026-06-13 131750]()
第五次作业代码量比第四次作业明显增加,主要增加在 Gate 类中。原因是新增元件的计算方式、引脚规则和输出规则差别较大。本次作业最复杂的地方不是某一个算法,而是规则多。每种元件的引脚编号、输入数量、输出数量都不一样。如果没有统一的引脚模型,代码很容易变成大量特殊判断。
类图
第五次作业类图如下:
![屏幕截图 2026-06-13 132450]()
复杂度分析
第五次作业的复杂度分析如下。
![屏幕截图 2026-06-13 134124]()
第五次作业中复杂度较高的方法主要有:
1.Gate.isValidInputPin
这个方法需要判断某个引脚是否是当前元件的有效输入端。基础门、三态门、译码器、数据选择器、数据分配器的规则都不同,因此分支较多。
例如三态门的 0 号引脚是控制端,1 号引脚是输入端,2 号引脚是输出端;译码器前 3 个引脚是控制引脚,后面是输入引脚,再后面才是输出引脚。
2.Gate.isOutPutPin
这个方法用于判断某个引脚是否是元件输出。第五次作业中不能再简单认为 0 号引脚就是输出,因为三态门输出是 2,译码器和数据分配器还有多个输出。
3.calculateDecoder
译码器不仅要判断控制引脚是否有效,还要根据输入编码确定哪个输出引脚为 0,其余输出为 1。这里容易出错的是二进制位的顺序,题目中 A0 是低位,A1、A2 依次升高。
Bug分析
公测
第五次作业公测中,基础逻辑门部分相对稳定,主要风险集中在新增元件。尤其是译码器、数据选择器、数据分配器的引脚编号稍有偏差,就会导致输出完全错误。
互测和自测
1.三态门控制端处理错误。三态门控制端为 1 时才导通,输出等于输入;控制端为 0 时输出无效。初学者容易将控制端为 0 时输出写成 0,但题目要求是无效状态,程序输出时应忽略该元件。
2.译码器控制条件写错。题目要求 S1 = 1 且 S2 + S3 = 0,也就是 S1 为 1,S2 和 S3 都为 0。若控制条件不满足,译码器所有输出无效。这里容易把 “S2 + S3 = 0” 理解成普通加法后再判断,或者误以为只要其中一个为 0 即可。
3.译码器输出含义理解错误。译码器正常工作时,不是输出选中编号的电平值,而是输出“哪个编号的输出引脚为 0”。例如 M(3)1:3 表示 Y3 输出 0,其余输出为 1。程序中需要保存被选中的编号,而不是把所有引脚都直接打印出来。
4.数据选择器控制位顺序错误。题目中 S0 是低位,S1 是高位。控制信号 00、01、10、11 分别选择 D0、D1、D2、D3。如果把控制位顺序反过来,部分测试点会出错。
5.数据分配器输出顺序错误。数据分配器需要按照输出引脚编号从小到大输出所有输出值,无效状态用 - 表示。例如 --0- 这种输出不是普通整数,而是一个由 0、1、- 组成的字符串。
测试方法
1.基本元件测试
2.简单电路测试
3.元件组合测试
4.复杂电路
第六次作业
作业要求
第六次作业在数字电路模拟程序 1 的基础上新增两个重要功能:
- 子电路
- 异常输入检测
子电路可以理解为把一部分电路封装起来,在主电路中通过输入输出引脚引用。异常输入检测则要求程序在连接信息错误时,按照题目规定的优先级输出错误信息。
这次作业和前两次不同,它的重点不再是新增更多元件,而是要求程序具有更强的结构化能力和输入检查能力。
实现思路
新增了 Circuit 和 SubInstance 两个类。
Circuit 表示“电路定义”,它不直接计算,而是保存输入列表、输出列表和连接信息。子电路定义读入后,先保存在 subDefs 中。
SubInstance 表示“子电路实例”,它会根据 Circuit 定义建立内部元件,并根据主电路传入的信号进行计算。这样可以将子电路和主电路分开处理。
代码规模
第六次作业代码规模如下图。
![屏幕截图 2026-06-13 131911]()
第六次作业代码规模是三次作业中最大的。虽然元件类型回到了五种基础逻辑门,但由于增加了子电路和异常检测,整体结构复杂度反而更高。这次作业让我体会到,复杂度并不只来自“元件类型多”。即使只有五种门,只要增加层次结构和异常规则,程序复杂度也会快速上升。
类图
第六次作业类图如下,
![屏幕截图 2026-06-13 133127]()
复杂度分析
第六次作业的复杂度分析如下。
![屏幕截图 2026-06-13 134230]()
第六次作业中复杂度较高的部分主要有:
1.checkConnectLine
这是本次作业最关键的方法。它需要按照题目给出的优先级判断异常。如果优先级处理错误,即使检测到了错误,也可能输出错误类型不符合要求。
2.buildMainCircuit
该方法负责根据主电路连接信息建立主电路元件和子电路实例。如果连接目标是子电路输入,需要保存到子电路实例中;如果连接目标是普通元件输入,则创建对应 Gate 并设置来源。
3.SubInstance.step
这个方法用于推动子电路计算。子电路内部元件的输入可能来自子电路输入,也可能来自其他内部元件输出,所以计算顺序仍然需要动态判断。
Bug分析
公测
第六次作业的正确性风险主要集中在异常检测和子电路引用。与前两次作业相比,算法本身并不难,但输入格式和边界条件更多。
易犯错误:
1.把题目中的 input 和 output 理解反了。连接信息中的 input 指的是能发出信号的一端,而不是元件输入引脚。比如 [A A2-1] 中,A 是 input,A2-1 是 output。很多人会因为“元件输入引脚”这几个字,把判断逻辑写反。
2.异常优先级处理不正确。题目明确规定,如果一条连接同时存在多种异常,要按优先级输出。比如一条连接中既有多个输入,又有顺序错误,应该先输出“include more than one input”,而不是顺序错误。
3.只检测主电路异常,没有检测子电路内部异常。子电路也是电路,子电路内部连接同样可能出现错误。如果只在主电路中调用检查方法,子电路部分的错误就会漏掉。
4.输入引脚冲突判断不严谨。一个输入引脚不能接收来自多个不同输出的信号,例如先 [A A2-1],再 [B A2-1],应输出 input signal conflict。但如果同一个源重复连接同一个引脚,是否算冲突要根据题意谨慎判断。我的程序中用 HashMap 记录每个目标引脚已经连接的来源,如果来源不同才报冲突。
测试方法
1.单子电路
2.两个单子电路
3.多子电路
4.异常测试
5.含优先级测试
关于设计模式的思考
1.不要一开始就把所有逻辑写进Main
第四次作业时,把输入、解析、计算、输出都放在 Main 中感觉很方便。但到了第五、六次作业,Main 类越来越长,查找问题也越来越困难。比较合理的做法应该是让 Main 只负责流程调度,把解析器、元件工厂、计算器、异常检查器拆分出来。
2.引脚模型比元件计算更重要
第四次作业中只有五种门,用 if-else 判断还可以接受。第五次作业增加到九种元件后,Gate 类中判断明显变多。更好的设计是定义一个抽象 Gate 类或接口,让 AndGate、OrGate、NotGate、Decoder、Mux、Demux 等类分别实现自己的计算方法。
3.异常检测要先定优先级,在写代码
第六次作业中,如果想到什么错误就检测什么错误,很容易输出顺序不符合题意。正确做法应该是先把异常优先级写在纸上,然后严格按照优先级判断。尤其是“一条连接有多个异常”的情况,一定不能只看最容易检测的那个。










浙公网安备 33010602011771号