数字电路模拟程序总结

写在前面
OOP课程第二阶段的作业集主要围绕“数字电路模拟程序”展开,从基础逻辑门开始,逐步增加多输入多输出元件、控制引脚、子电路以及异常输入检测。相比第一阶段的多项式题目,这一阶段的题目更强调“对象之间的连接关系”和“系统迭代时的可扩展性”。
第四次作业实现的是数字电路模拟程序 1,主要包含与门、或门、非门、异或门、同或门五种基础逻辑门。第五次作业在此基础上加入了三态门、译码器、数据选择器和数据分配器,开始出现控制引脚、多输出引脚和无效状态。
第六次作业又回到基础逻辑门模型,但增加了子电路和异常输入检测,要求程序不仅能算出电路结果,还要能识别连接信息中的错误。
这三次作业让我明显感受到:面向对象设计并不是简单地把代码拆成几个类,而是要考虑后续题目是否会扩展。如果前一次作业只为了“当前题目能过”而写,到了下一次迭代时就会出现大量修改。尤其是数字电路这类题目,元件类型、引脚类型、输出形式、连接方式都可能变化,所以一开始的数据结构设计非常重要。
本文将从程序结构、类设计、复杂度、Bug、测试和改进方向几个方面对作业 4、5、6 进行总结。
第四次作业
作业要求
第四次作业要求实现一个基础数字电路模拟程序,电路中包含五类基础逻辑门:

  • 与门A
  • 或门O
  • 非门N
  • 异或门X
  • 同或门Y
    输入由两部分组成:一部分是外部输入信号,另一部分是连接信息,例如将某个输入信号连接到某个元件引脚,或者将一个元件的输出连接到另一个元件的输入。
    程序最后需要按照与门、或门、非门、异或门、同或门的顺序输出所有能够计算出结果的元件。如果某个元件的输入引脚没有接收到有效输入,则忽略该元件的输出。
    实现思路
  1. 读取所有INPUT:开头的输入信号,将输入名和值保存到inputs列表中。
  2. 读取所有连接信息,识别连接源和连接目标。
  3. 如果目标是某个元件的输入引脚,则创建对应的Gate对象。
  4. 对每个Gate保存每个输入引脚的来源。来源可能是外部输入,也可能是另一个元件的输出。
  5. 通过循环扫描所有元件,只要某个元件的所有输入值都已知,就计算它的输出。
  6. 最后按照题目规定的元件类型顺序和编号顺序输出。
    代码规模
    第四次作业代码规模如下图。
    屏幕截图 2026-06-13 131631
    类图
    第四次作业类图如下,Gate 类承担了最主要的逻辑
    屏幕截图 2026-06-13 132223
    这种设计的优点是比较直接,所有逻辑门都可以用同一个 Gate 类表示。五种基础门的差别主要体现在输入数量和计算方式上,所以用一个类统一处理是可行的。不过,这种设计也有不足。Gate 类中存在较多 if-else 判断,例如判断当前元件是 A、O、N、X 还是 Y。第四次作业中只有五种门,问题还不明显,但到了第五次作业新增元件后,这种设计会导致 Gate 类越来越长。
    复杂度分析
    第四次作业的复杂度分析如下。
    屏幕截图 2026-06-13 133904
    第四次作业中复杂度较高的部分主要有以下几个:
  7. 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.多层级联电路

第五次作业
作业要求
第五次作业是第四次作业的扩展,新增了四类元件:

  • 三态门
  • 译码器
  • 数据选择器
  • 数据分配器
    这样一来,电路元件从五种增加到九种。题目难度明显上升,主要体现在以下几个方面:
  1. 元件不再都只有一个输出引脚。
  2. 元件开始出现控制引脚。
  3. 三态门、译码器、数据分配器会产生无效状态。
  4. 元件输出格式不完全相同。
  5. 引脚编号规则变复杂。
    实现思路
    第五次作业中,我仍然保留了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.异常检测要先定优先级,在写代码
    第六次作业中,如果想到什么错误就检测什么错误,很容易输出顺序不符合题意。正确做法应该是先把异常优先级写在纸上,然后严格按照优先级判断。尤其是“一条连接有多个异常”的情况,一定不能只看最容易检测的那个。
posted @ 2026-06-13 14:21  惨比打工人  阅读(3)  评论(0)    收藏  举报