BLOG2-NCHU-数字电路模拟程序+课堂测验
题目集1-2数字电路模拟程序+课堂测验
一、前言
随着前三周的单部电梯调度程序问题已被解决,现在又有着新的问题需要我们解决-数字电路模拟程序。该类题目也是呈现一种逐步变难,呈现迭代递进的特点。从第一次的数字电路模拟程序处理五种元件,与门、或门、非门、异或门、同或门,到第二次则新增三态门、译码器、数据选择器、数据分配器,总共涵盖九种元件。程序上一环套一环,元件特性和引脚规则越来越复杂,挑战性也随之升级。
吸取前三周的大题目程序编写的设计思路,现在的我会先思考题目中会涉及几种类,并且每个类中的方法如何设计,实现题目要求的功能。在这两次题目集中,核心涉及到的知识点可总结为四点:1.单一职责原则(SRP)的逐步优化,从题目集一的类职责相对集中,到题目集二的精细化拆分;2.类的封装与职责划分不断深化,降低模块间耦合;3.正则表达式的应用:通过 Pattern 和 Matcher 实现灵活的字符串解析,适配复杂的元件命名规则。4.算法的优化:从简单 BFS 到分层传播 + 循环检测,确保结果准确。除此之外,除了有两次数字电路模拟程序的大作业外还有一次课堂测验,总共150道题目,多以选择题填空题和判断题为主。在这些题目中大部分都是些基础题,考的都是大家最基础的知识关于java。虽然说这次课堂测试考的大部分都是基础题,但是我也依旧还是做错了许多,150道题错了29道题被扣了45分。这实属不应该,因为在测试完之后我仔细检查我错的题目发现都是一些不该错的题目,应当好好反省问题所在。
二、设计与分析
1、课堂检测
错误类型与原因分析:
这次课堂测验的150到基础题的错题,集中暴露了我在Java中的薄弱点,具体可分为四个板块:判断题(8道):错误集中在基础知识上,有关一些名词的定义记得并非牢固导致出现错误,单选题(8道):在这八道题目中,有的题目确实自己没见过但有的确实见了很多遍错了实属是自己的问题,多选题(9道):多选题是最能考验对知识点是否熟悉的一个题型,正是因为多选题最考验Java语言的基本功因此错的也是最多的,需要在后期学习过程中多加注意,填空题(4道):有关代码补充的地方自己掌握的并非太好,错了好几个。
分析与改进:
总的来说,通过这次测验知道自己的短板之处确实能够让自己在之后的学习中懂得取长补短的道理,在基础方面任然存在许多薄弱的地方,依旧还有很多知识点没有真正的掌握。好在通过老师的这个课堂小测验真正让我意识到自己的诸多不足之处,也让我明白了想要真正学好Java语言还有很长一段路要走,其中的小知识点更是不能忘记,应该反复学习直到真正的掌握。
2、数字电路模拟程序-1
题目如下:
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序,1.电路元件:电路中包含与门、或门、非门、异或门、同或门五种元件。元件特征如下:a.与门:包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是高电平,输出引脚才是高电平,只要有一个输入引脚为低电平,输出引脚输出低电平。b.或门:包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是低电平,输出引脚才是低电平,只要有一个输入引脚为高电平,输出引脚输出高电平。c.非门:包含一个输入引脚和一个输出引脚。输出引脚的电平与输入引脚的电平相反,如输入为低电平,输出则为高电平。d.异或门:包含两个输入引脚和一个输出引脚。当两个输入引脚电平不一致时输出引脚输出高电平,否则输出低电平。e.同或门:包含两个输入引脚和一个输出引脚。当两个输入引脚电平一致时输出引脚输出高电平,否则输出低电平。2.程序输入:用A、O、N、X、Y 分别用作与门、或门、非门、异或门、同或门五种元件的元件标识符。3.程序输出:按照与门、或门、非门、异或门、同或门的顺序依次输出所有元件的输出引脚电平。同类元件按编号从小到大的顺序排序。
SourceMonitor分析


类图分析

分析心得:
在完成这次编译时,并没有什么太大的难点,核心在于理清“电路信号逻辑”和“类的职责边界”。以下我将从三个方面来对本次题目进行分析。
一、设计思路分析
在该题目中,我采用了面向对象的设计思路,其核心是将 “引脚” 和 “元件” 抽象为独立的类,避免数据和逻辑混乱:
- Pin 类:专门封装引脚的核心属性 —— 组件名、引脚号、信号值,同时提供 isComponentInput ()、isComponentOutput () 方法,用于判断引脚类型,这样能清晰区分输入和输出引脚,避免后续信号传播时混淆。例如,非门的输入引脚可通过isComponentInput()标记,避免信号传播时混淆输入 / 输出方向;
- Component 类:适配基础逻辑门的特性,每个元件实例包含多个输入引脚和一个固定编号为 0 的输出引脚,通过 addInputPin () 方法管理输入引脚,calculateComponentOutput () 方法计算输出,把元件的 “属性” 和 “行为” 封装在一起。
- CircuitProcessor 类:作为整个程序的调度核心,负责解析输入数据、建立元件间的连接关系、执行信号传播和生成输出结果,相当于 “电路控制器”,隔离了数据解析和业务逻辑。
之所以把 Pin 单独设为类,是因为不同元件的引脚数量、类型不同,单独封装后能灵活管理,比如与门可以添加多个输入引脚,非门只能添加一个,通过类的方法约束就能避免引脚配置错误。
将 Pin 单独封装的关键价值在于:不同元件的引脚数量 / 类型差异可通过类方法约束(如与门支持多输入引脚,非门仅支持 1 个输入引脚),避免配置错误。
二、算法分析
本题设置的核心算法在于输入源到基础逻辑门的单向信号传播和输出的计算两个部分:
- 信号传播算法:采用简单的广度优先搜索(BFS),将输入源节点加入队列,单次遍历连接的引脚并赋值。将电信号赋值给对应的元件输入引脚,触发元件计算输出引脚电信号:
- 输出传播算法:在计算输出的规则中,仅考虑了1.有效输入是否存在;2.输入是否接满这两个条件并无存在太复杂的算法设计,个人认为不算太难。
三、代码质量分析
通过 SourceMonitor 生成的代码分析报告,可以清晰地看出我提交的代码虽然通过了测试点,但在质量上存在不少问题,代码整体质量处于中等偏下低水平,还有许多地方需要整改:
- 优势:文件规模精简、方法粒度控制较好;
- 短板:平均复杂度偏高、注释覆盖率严重不足、部分方法嵌套过深。由报告数据知,最大复杂度达到了8,平均复杂度达到了6.50,多数方法处于 “可接受但需优化” 区间,长期迭代易逐步突破高风险阈值。除此之外,注释行占比在8.8%严重不足,导致维护成本较高。嵌套过深(最大6层):深层嵌套导致逻辑绕,修改时易遗漏边界条件。
3、数字电路模拟程序-2
题目如下:
请编程实现数字电路模拟程序。以下内容中,首行用#号标注的为本次新增的题目要求,其余内容与“数字电路模拟程序-1相同。
1.电路元件:电路中包含与门、或门、非门、异或门、同或门、三态门、译码器、数据选择器、数据分配器九种元件。元件特征如下:a.与门:包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是高电平,输出引脚才是高电平,只要有一个输入引脚为低电平,输出引脚输出低电平。b.或门:包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是低电平,输出引脚才是低电平,只要有一个输入引脚为高电平,输出引脚输出高电平。c.非门:包含一个输入引脚和一个输出引脚。输出引脚的电平与输入引脚的电平相反,如输入为低电平,输出则为高电平。d.异或门:包含两个输入引脚和一个输出引脚。当两个输入引脚电平不一致时输出引脚输出高电平,否则输出低电平。e.同或门:包含两个输入引脚和一个输出引脚。当两个输入引脚电平一致时输出引脚输出高电平,否则输出低电平。f.三态门:三态门的作用类似于电路中的开关。包含一个输入引脚、一个输入控制引脚、一个输出引脚。当控制引脚为高电平时,三态门输入输出之间导通,输出电平等于输入电平;当控制引脚为低电平时,三态门输入输出之间呈现高阻态(类似开关断开),输出为无效状态。g.译码器:译码器的作用是讲输入的编码转换为一路有效信号。一个译码器包含两个或多个输入引脚(如图中的A2\A1\A0)、三个控制引脚(如图中的S3\S2\S1)、4个或多个输出引脚(如图中的Y7~Y0)。根据输入输出的数量有2-4线译码器、3-8线译码器等。当控制引脚当S1 =1,S2 +S3 =0时,译码器正常工作,输出引脚只有一个输出信号0,其余输出为1;哪个引脚输出0由输入引脚的编码决定,例如:图中的3-8线译码器三个输入引脚信号的编码与输出引脚的编码对应,A2\A1\A0输入000时,Y0输出0,其余输出1;A2\A1\A0输入001时,Y1输出0,其余输出1;依次类推。控制引脚不满足S1 =1,S2 +S3 =0时,译码器处于无效状态,所有输出为无效值。h.数据选择器:数据选择器的作用是从多路输入信号中选择一个,并将其信号直接送往唯一的输出端,选择哪一路输入信号由控制端决定。如图所示控制端有两个则输入端有4个,S1\S0是两个控制端,D3D0是输入端,S1\S0的4种信号组合00、01、10、11分别选择D3D0其中一路输入。如S1S0=00,则Y=D0;S1S0=01,则Y=D1;S1S0=10,则Y=D2;S1S0=11,则Y=D3,根据输入引脚数量的不同有二选一数据选择器(1个控制端)、四选一数据选择器(2个控制端)、八选一数据选择器(3个控制端)等。i.数据分配器:数据分配器的作用与数据选择器正好相反,是将唯一的一路输入信号输出到多路输出引脚的其中之一,选择哪一路输出引脚输出由控制端决定。如图所示控制端有两个AB,输出端有4个W0\W1\W2\W3,D是输入端,AB的4种信号组合00、01、10、11分别选择W3~W0其中一路输出,其他三路输出为无效状态。如AB=00,则W0=D;AB=01,则W1=D;AB=10,则W2=D;AB=11,则W3=D。根据输出引脚数量的不同有二路数据分配器(1个控制端)、四路数据分配器(2个控制端)、八路数据分配器(3个控制端)等。2.程序输入:a.元件信息:用A、O、N、X、Y、S 、M、Z、F分别用作与门、或门、非门、异或门、同或门、三态门、译码器、数据选择器、数据分配器九种元件的元件标识符。3.程序输出:按照与门、或门、非门、异或门、同或门、三态门、译码器、数据选择器、数据分配器的顺序依次输出所有元件的输出引脚电平。同类元件按编号从小到大的顺序排序。
SourceMonitor分析


类图分析

通过类图分析可知对以下方面有所优化:
1.新增 ValidityChecker 类,与 Component 类呈依赖关系,专门处理 “元件计算前置条件校验”,解耦 Component 的 “逻辑计算” 与 “有效性判断” 职责;
2.Pin 类扩展isControlPin()方法,标记控制引脚(如三态门 0 号引脚、译码器 0-2 号引脚),丰富引脚分类维度;
3.Component 类通过重载构造方法适配不同元件类型,保持与 Pin 类、ValidityChecker 类的低耦合关联。
分析心得:
此次题目,我个人认为跟第一次比的难度提升了一个档次,因为要考虑的东西太多,新增的四种元件涉及控制引脚、编码转换、多路选择 / 分配等复杂逻辑,这些导致花了我很长的时间去设置测试点并且找到漏洞去修改程序代码,通过测试点,以下是测量测试点的代码提交表,测试了不下20次才将所有测试点一一通过:

以下我也将从三个方面进行分析。
一、设计方面:
题目二是从题目一的基础下进行迭代扩展,整体架构保留了 Pin、Component、CircuitProcessor 的核心结构,但针对新增元件做了进一步的优化以及增添新类来计算。
- 新增 ValidityChecker 类:把 “元件计算前置条件校验” 从 Component 类中抽离,比如三态门控制引脚是否为高电平、译码器 S1/S2/S3 是否满足有效条件,都交给这个类处理。这样 Component 类就能专注于 “引脚配置 + 输出计算”,不用混着校验逻辑,符合单一职责原则。
- 扩展 Pin 类功能:新增 isControlPin () 方法,专门识别控制引脚(比如三态门的 0 号引脚、译码器的 0-2 号引脚),解决了之前 Pin 类只能区分输入 / 输出引脚的局限。
- 优化 Component 类结构:按元件类型拆分初始化逻辑,比如三态门、译码器、选择器 / 分配器分别在构造方法里初始化对应的控制引脚、输入引脚、输出引脚,避免所有元件挤在一套逻辑里。但这一点在后续的大作业中会进行修改,因为实在是太臃肿了将所有元件的初始化都堆在一个Component里。
二、算法方面:
该题目算法相较于上一题的算法复杂度高了不少,在上一题的算法上进行修改:
对于信号传播算法:
1.分层传播:先调用了propagateControlPins方法传播控制引脚信号,再调用propagateNormalPins方法传播普通引脚信号。因为控制引脚的状态直接决定元件是否能工作,必须先让控制信号稳定且正确,否则元件计算出来的结果都是错的(比如三态门控制引脚没传播完就计算,会误判为高阻态)。
2.对于以上代码检测引脚信号功能做了修改,通过hasChange标记判断是否有信号更新,循环执行传播和计算,直到无信号变化为止,确保链式依赖场景下所有引脚电平稳定,以此来解决只执行一次传播为对元件新的引脚信号进行更新。
对于输出计算算法也有所改变
1.对于三态门:先通过 ValidityChecker.isTriStateValid () 校验控制引脚是否为高电平,有效则直接把输入引脚信号传给输出,无效则输出 null(高阻态);
2.对于译码器:分三步计算 —— 第一步校验控制引脚(S1=1、S2=0、S3=0),无效则所有输出 null;第二步把输入引脚的二进制编码转成十进制(比如 A2A1A0=001 转成 1);第三步根据十进制数映射到对应输出引脚(3-8 译码器的 1 对应 Y1 引脚),让该引脚输出 0,其余输出 1;
例如:检验译码器控制引脚是否有效:if (!ValidityChecker.isDecoderValid(this)) {outputPins.values().forEach(pin -> pin.setSignal(null));。在有效的情况下再输出对应输出的结果,若无效那么就不输出关于译码器的任何信号。
3.对于数据选择器和数据分配器,核心是 “控制引脚组合→选择码→引脚映射”。比如四选一选择器(Z (2) 1)有 2 个控制引脚,把控制引脚的信号按位组合成二进制数(S1=0、S0=1→01→十进制 1),再用这个数找到对应的输入引脚(控制引脚数 2,输入引脚从 2 开始,所以 1+2=3 号引脚);数据分配器则反过来,用选择码找到对应的输出引脚,把输入信号传过去,其余输出 null。这里用了 Math.pow (2, i) 计算位权,比如控制引脚 0 对应 2^0=1,控制引脚 1 对应 2^1=2,加起来就是选择码,刚好匹配引脚的编号规律。
三、代码质量分析
通过 SourceMonitor 生成的代码分析报告,可以清晰地看出我提交的代码虽然通过了测试点,但在质量上存在不少问题,相对应题目集一的代码质量问题主要集中在高复杂度方法、过深的代码块嵌套、注释覆盖率不足等方面。
在代码中Component.Component()最大复杂度达到了12最大块深度达到了7,这是典型的“超大方法”,构造方法本应仅做初始化,却包含大量业务逻辑,违反了单一职责原则,因此在后续代码编译阶段必须修改该方法中的代码内容和作用。不仅如此,除了在Component.Component()中高复杂度方法分布以下三个方法也是高复杂度方法分布CircuitProcessor.parseInputLine(),ValidityChecker.isDecoderValid()ValidityChecker.areAllInputPinsValid(),十分容易造成程序的损坏
注释行占比也才14.9%远低于标准的20%~30%,同样也需要修改。
重复代码多:比如数据选择器和分配器的控制引脚组合计算逻辑很像,但我没提取成通用方法,而是写了两套几乎一样的代码,后期改 bug 时要改两处,容易漏改。
三、踩坑心得
1.代码优化方面:
在这两次的大题目中,我发现一旦出现了很多种情况下,我编译的代码量就会非常多有的代码就十分的冗杂但又不得不要否则会出现错误的情况。在这种情况下,我应当多去考虑老师讲的一些模式去解决以上问题,比如工厂模式,桥接模式。除此之外,我还应该在需要添加注释的代码行中添加应有的注释,不应该偷懒否则在之后若代码需要修改,或者查找代码功能会非常麻烦。对有的类中的方法该分类就分类,不能全部留在一个类中,否则容易出现错误,不符合单一职责原则。
2.算法设计方面:
- 信号传播方面:一开始并没有考虑分层传播,直接通过第一次输入引脚信号的形式将所接入的引脚赋值信号,结果一直导致测试点过不去,后来还是通过自己设置了测试点才知道因为控制信号没传到位,一直输出无效值。
- 信号输出方面:对于信号的输出有几点需要及其注意的,1.对于三态门和译码器是有着控制引脚控制的所有应当先判断控制引脚是否接入有效信号后再去计算信号的输出;2.对于译码器要尤为注意,计算输出的是引脚的电平是低电平,并且其他输出引脚的电平是高电平;3.与门和或门的输出情况是,当与门接入了一个低电平,不管其他引脚直接输出低电平即可而对于或门只有有一个引脚接入的是高电平,那么不管其引脚是否接满或者接入其他引脚输出的只会是高电平。
- 输入算法检测:题目中也提到了:
![image]()
因此需要注意的是:A的输出连接B的输入,B的输入端连C的输入 这等于 A的输出连B、C的输入,这也是题目中的一个测试点。 - 循环算法的检测:在测试链式依赖场景(A→B→C)时,发现 C 的输出一直是 null,调试才看到 B 的输出在第一次传播后更新了,但 C 没收到信号。加了 hasChange 循环后,第二次传播时 B 的新信号会触发 C 的计算,问题才解决。
3.设计方面:
- 类的设计:在类这方面的设计,题集一类的分类我感觉并没有什么不妥或者说太大问题,但是到了题集二基于题集一的基础上,就将类设计的十分臃肿。比如Component 类就这样,因此在设计类方面需要有待提高。
- 引脚设计的唯一性:第一次没考虑引脚重复创建的问题,比如解析 “A-1” 时创建了两次 Pin 对象,导致两个对象的信号不一致。后来加了 pinCache 缓存,每次创建引脚前先查缓存,保证同一个引脚只有一个实例,信号同步问题就解决了。
四、改进建议
1.类设计的进一步解耦:
在题目集二代码中,类的内容就变得非常的冗杂,比如在Component方法的构造类中复杂深度达到了12,不仅将Component的构造方法写入其中还将许多实现其他功能的方法也加入里面导致看起来过于臃肿。而且对类的设计十分的不友好,因此打算在后期的大作业中,将不同元件的初始化拆成独立方法,在构造方法里只调用对应的初始化方法,降低复杂度。
2.算法的优化:
- 在题集2的代码中,有的元件输出信号实现代码大致相同,比如数据选择器 / 分配器的控制引脚组合计算、译码器的编码转换提取成工具方法,这样避免了重复代码。
- 优化信号传播效率:现在的循环检测是只要有信号变化就全量传播,后期可以改成 “增量传播”—— 只传播发生变化的引脚对应的连接引脚,减少不必要的计算。
3.代码质量的改进: - 注释规范:给核心方法、复杂逻辑、特殊规则加注释,比如给 ValidityChecker.isDecoderValid () 加注释说明 “译码器有效条件:S1=1 且 S2=0 且 S3=0”,给选择码计算加注释说明位权规则。
- 拆分超大方法:将Component构造方法、CircuitProcessor的parseInputLine()等超大方法拆分为多个小方法(每个方法不超过50行),例如将parseInputLine()拆分为parseComponentInfo()、parseConnectionInfo()、parseInputSignalInfo(),降低复杂度和嵌套深度。每个方法的代码行度不超过 50 行,降低复杂度和嵌套深度。
五、总结
两次数字电路模拟程序的编写外加一次的课堂基础测验,让我从 “能实现功能” 向 “能设计合理的结构” 迈了一小步,也暴露了自己在基础知识、设计算法,类方面上的不足。在后续的大作业中,我会按照以上问题重构我的代码结构,尤其是拆分超大方法,补充注释,同时系统复习Java基础知识点,整理易混知识点对照表,通过小型测试代码强化记忆。虽然每次的大作业题目都比较难,但好在正是通过了这些难题锻炼了我们的设计思维以及解决问题的更好方法,在实践中不断提升自己的编程实力。


浙公网安备 33010602011771号