面向对象程序设计——数字电路模拟程序
前言
两次题目集围绕“数字电路模拟”主题逐步深化,从基础逻辑门扩展到常用组合逻辑器件。
| 题目集 | 元件 | 核心知识点 |
| 数字电路模拟程序-1 | 与门(A)、或门(O)、非门(N)、异或门(X)、同或门(Y) | 1. 基本逻辑门功能与真值表 2. 多输入引脚处理(与门、或门) 3. 电路连接关系解析 4. 信号传播与电平计算 5. 无效输入处理(引脚未连接) |
| 数字电路模拟程序-2 | 新增:三态门(S)、译码器(M)、数据选择器(Z)、数据分配器(F) | 1. 控制引脚概念(三态门、译码器等) 2. 多输出元件(译码器、数据分配器) 3. 编码/解码逻辑(译码器、数据选择器) 4. 高阻态与无效状态处理 5. 复杂引脚编号规则(控制-输入-输出顺序) |
两个题目均为综合编程题,程序-1:中等偏上,适合考察基本电路模拟和字符串解析能力;程序-2:困难,在程序-1基础上大幅增加元件类型和输出逻辑,对细节处理要求更高。
设计与分析
程序一
题目:
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序,
1、电路元件
电路中包含与门、或门、非门、异或门、同或门五种元件。元件特征如下:
与门:
包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是高电平,输出引脚才是高电平,只要有一个输入引脚为低电平,输出引脚输出低电平。
或门:
包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是低电平,输出引脚才是低电平,只要有一个输入引脚为高电平,输出引脚输出高电平。
非门:
包含一个输入引脚和一个输出引脚。输出引脚的电平与输入引脚的电平相反,如输入为低电平,输出则为高电平。
异或门:
包含两个输入引脚和一个输出引脚。当两个输入引脚电平不一致时输出引脚输出高电平,否则输出低电平。
同或门:
包含两个输入引脚和一个输出引脚。当两个输入引脚电平一致时输出引脚输出高电平,否则输出低电平。
2、程序输入
1)元件信息:
用A、O、N、X、Y 分别用作与门、或门、非门、异或门、同或门五种元件的元件标识符。
电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。
例如:A(8)1表示一个8输入引脚的与门,O(4)2代表一个4输入引脚的或门。
电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。
例如:X8表示一个异或门,Y4代表一个同或门,N1代表一个非门。
约束条件:
不同元件的编号可以相同,如X4、Y4。
同一电路中同种元件的编号不可重复,可以不连续
2)引脚信息:
引脚信息由“元件名-引脚号”构成,。
例如:A(8)1-2代表与门A(8)1的2号引脚。
3)电路的输入信息:
电路的输入格式:
INPUT:英文空格+输入1+”-”+输入信号1+英文空格+输入2+....+输入n+”-”+输入信号n
例如:
“INPUT: A-0 B-1 C-0”代表整个电路包括3个输入:A、B、C 分别输入0,1,0信号。
4)连接信息
引脚的连接信息格式:
[+输出引脚+英文空格+输入引脚1+。。。。+英文空格+输入引脚+]
例如:
[A A(8)1-1 A(8)1-3 X5-2]
代表信号从引脚A发送给与门A(8)1的1、3两个引脚,以及异或门X5的2号引脚。
[Y8-0 N1-1 O(4)2-3 Y2-1]
代表信号从引脚Y8-0发送给非门N1的1号引脚、或门O(4)2的3号引脚、同或门Y2的1号引脚。
约束条件:
一个输出引脚可以连接多个输入引脚,即将输出引脚的信号传给每一个输入引脚。但一个输入引脚不能连接多个输出引脚。
输出引脚不能短接在一起。
5)输入结束信息
所有输入以end为结束标志,end之后出现的内容忽略不计
3、程序输出
按照与门、或门、非门、异或门、同或门的顺序依次输出所有元件的输出引脚电平。同类元件按编号从小到大的顺序排序。
如果某个元件的引脚没有接有效输入,元件输出无法计算,程序输出结果忽略该元件
4、测试输入默认满足以下条件:
1)每个元件的输入引脚连续编号。假设元件有n个输入引脚,则其编号取值范围为[1,n],且引脚号不重复。
2)本题涉及的五种元件都只有一个输出引脚,输出引脚号默认为0。
5、后续迭代设计
数字电路模拟程序1:
基础逻辑门元件构成电路
数字电路模拟程序2(本次作业):
1、包含多输入输出的组合电路元件如数据选择器;
2、元件引脚类型除输入、输出之外,增加控制引脚,如三态门。
数字电路模拟程序3:
增加带反馈的电路、时序电路元件,如D触发器、JK触发器。
数字电路模拟程序4:
1、增加子电路;
2、增加程序异常输入的检测。
解题思路:
1:输入解析
-
读取输入:逐行读取直到"end" -
解析INPUT行: -
格式: INPUT: 输入名-信号值 ... -
解析后存入 Map<String, Integer> inputSignals
-
-
解析连接信息: -
格式: [源引脚 目标引脚1 ... 目标引脚n] -
每个连接创建 Connection对象 -
从引脚名中提取元件信息
-
2:建立电路模型
-
提取所有元件: -
从所有连接的目标引脚中解析元件名 -
通过元件名解析类型、引脚数、编号 -
创建 Component对象,存入Map<String, Component>
-
-
建立连接关系: -
对每个连接,建立 源→[目标列表]的映射 -
同时建立 目标引脚→源引脚的反向映射(用于检查冲突)
-
3:信号传播模拟
Queue<String> signalQueue = new LinkedList<>();
// 1. 初始化队列:将所有输入信号加入队列
for (每个输入信号) {
计算该信号影响的所有元件引脚
如果元件的某个引脚得到信号:
更新元件inputs
如果元件所有输入都有信号了:
将元件加入计算队列
}
// 2. BFS传播
while (!计算队列为空) {
取出一个元件
根据类型计算输出:
AND: 所有输入为1 → 1,否则0
OR: 任意输入为1 → 1,否则0
NOT: 输入取反
XOR: 输入不同 → 1,否则0
XNOR: 输入相同 → 1,否则0
输出结果记录到output字段
将输出引脚加入signalQueue
while (!signalQueue为空) {
取出一个信号引脚
找到该信号连接的所有目标引脚
更新目标元件的对应输入
如果目标元件所有输入就绪:
将目标元件加入计算队列
}
}
4:结果收集与输出
-
筛选有效元件:输出 output不为null的元件 -
排序规则: -
按类型顺序:AND → OR → NOT → XOR → XNOR -
同类按编号升序
-
-
格式化输出: 元件名-0:信号值


代码质量评估
1. 复杂度问题突出
-
最大复杂度26: Main.main()方法的复杂度高达26,远超正常范围(通常建议<10) -
平均复杂度11.8:整体复杂度偏高,表明代码逻辑嵌套过深 -
最复杂方法: Main.calculate()复杂度25,Main.main()复杂度26,这两个方法是重构的重点
2. 方法设计问题
-
方法过大:平均每个方法30.2条语句,远超过通常的推荐值(建议10-20条) -
方法数量不足:只有2个类,平均每个类2.5个方法,表明功能没有合理封装 -
方法职责不单一: Main.calculate()有69条语句,显然承担了过多职责
3. 代码结构问题
-
嵌套过深:最大块深度达到7层,平均块深度3.18 -
从块深度直方图看: -
深度4-5的块占比较大(共67条语句) -
这通常意味着过多的if-else嵌套或循环嵌套
-
4. 可维护性指标
-
注释率低:仅5.4%的行有注释,对于复杂逻辑的代码来说偏少 -
分支语句多:31.4%的语句是分支语句,增加了测试复杂度
具体问题分析
1. Main.main()方法(复杂度26)
-
作为入口方法如此复杂,说明: -
过多的业务逻辑放在main方法中 -
没有合理拆分初始化、解析、计算、输出等职责
-
2. Main.calculate()方法(复杂度25)
-
69条语句,显然是核心算法逻辑 -
高复杂度和深度表明: -
包含大量条件判断 -
可能需要拆分为多个子方法 -
可能存在重复逻辑
-
3. 整体架构问题
-
仅2个类,说明整个程序几乎都在Main类中 -
违反了单一职责原则 -
难以进行单元测试
程序二
题目:
数字电路是一种处理离散信号的电子电路。与处理连续变化信号(如声音、温度)的模拟电路不同,
数字电路只识别和运算两种基本状态:高电平(通常表示为“1”) 和 低电平(通常表示为“0”)。
这正好与二进制数制系统相对应,使得数字电路成为所有计算机和数字系统的物理实现基础。
请编程实现数字电路模拟程序。
以下内容中,首行用#号标注的为本次新增的题目要求,其余内容与“数字电路模拟程序-1”相同。
1、电路元件
电路中包含与门、或门、非门、异或门、同或门、三态门、译码器、数据选择器、数据分配器九种元件。元件特征如下:
与门:
包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是高电平,输出引脚才是高电平,只要有一个输入引脚为低电平,输出引脚输出低电平。
或门:
包含两个或多个输入引脚和一个输出引脚。所有输入引脚必须都是低电平,输出引脚才是低电平,只要有一个输入引脚为高电平,输出引脚输出高电平。
非门:
包含一个输入引脚和一个输出引脚。输出引脚的电平与输入引脚的电平相反,如输入为低电平,输出则为高电平。
异或门:
包含两个输入引脚和一个输出引脚。当两个输入引脚电平不一致时输出引脚输出高电平,否则输出低电平。
同或门:
包含两个输入引脚和一个输出引脚。当两个输入引脚电平一致时输出引脚输出高电平,否则输出低电平。
#三态门:
三态门的作用类似于电路中的开关。包含一个输入引脚、一个输入控制引脚、一个输出引脚。当控制引脚为高电平时,三态门输入输出之间导通,输出电平等于输入电平;当控制引脚为低电平时,三态门输入输出之间呈现高阻态(类似开关断开),输出为无效状态。
#译码器:
译码器的作用是讲输入的编码转换为一路有效信号。一个译码器包含两个或多个输入引脚(如图中的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时,译码器处于无效状态,所有输出为无效值。
#数据选择器:
数据选择器的作用是从多路输入信号中选择一个,并将其信号直接送往唯一的输出端,选择哪一路输入信号由控制端决定。如图所示控制端有两个则输入端有4个,S1\S0是两个控制端,D3~D0是输入端,S1\S0的4种信号组合00、01、10、11分别选择D3~D0其中一路输入。如S1S0=00,则Y=D0;S1S0=01,则Y=D1;S1S0=10,则Y=D2;S1S0=11,则Y=D3
根据输入引脚数量的不同有二选一数据选择器(1个控制端)、四选一数据选择器(2个控制端)、八选一数据选择器(3个控制端)等
#数据分配器:
数据分配器的作用与数据选择器正好相反,是将唯一的一路输入信号输出到多路输出引脚的其中之一,选择哪一路输出引脚输出由控制端决定。如图所示控制端有两个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、程序输入
1)#元件信息:
用A、O、N、X、Y、S 、M、Z、F分别用作
与门、或门、非门、异或门、同或门、
三态门、译码器、数据选择器、数据分配器九种元件的元件标识符。
电路中的每个与门、或门用“标识符(输入引脚数)+编号”作为其元件名。
例如:A(8)1表示一个8输入引脚的与门,O(4)2代表一个4输入引脚的或门。
电路中的每个非门、异或门、同或门用“标识符+编号”作为其元件名。
例如:X8表示一个异或门,Y4代表一个同或门,N1代表一个非门。
#电路中的数据选择器、数据分配器用“标识符(控制引脚数)+编号”作为其元件名。
例如:Z(2)2代表一个四选一数据选择器,F(3)2代表一个8路数据分配器。
译码器用“标识符(输入引脚数)+编号”作为其元件名。
例如:M(3)1表示一个3-8线译码器。
约束条件:
不同元件的编号可以相同,如X4、Y4。
同一电路中同种元件的编号不可重复,可以不连续
引脚信息由“元件名-引脚号”构成。
例如:A(8)1-2代表与门A(8)1的2号引脚。
#含控制引脚的元件如本次添加的所有元件,按控制-输入-输出的顺序排序,
每种类型的引脚按编号从小到大的顺序排序,
例如3-8线译码器M(3)1包含3个输入引脚、3个控制引脚、8个输出引脚,
M(3)1-0/1/2对应控制引脚S1/S2/S3,
M(3)1-3/4/5对应输入引脚A0/A1/A2,
M(3)1-6/7/8/9/10/11/12/13对应输出引脚Y0\~Y7。
又如三态门的三个引脚,0号引脚为控制端、1号引脚为输入端、2号引脚为输出端。
电路的输入格式:
INPUT:英文空格+输入1+”-”+输入信号1+英文空格+输入2+....+输入n+”-”+输入信号n
例如:
“INPUT: A-0 B-1 C-0”
代表整个电路包括3个输入:A、B、C 分别输入0,1,0信号。
引脚的连接信息格式:
[输出引脚+英文空格+输入引脚1+。。。。+英文空格+输入引脚]
例如:
[A A(8)1-1 A(8)1-3 X5-2]
代表信号从引脚A发送给与门A(8)1的1、3两个引脚,以及异或门X5的2号引脚。
[Y8-0 N1-1 O(4)2-3 Y2-1]
代表信号从引脚Y8-0发送给非门N1的1号引脚、或门O(4)2的3号引脚、同或门Y2的1号引脚。
一个输出引脚可以连接多个输入引脚,即将输出引脚的信号传给每一个输入引脚。但一个输入引脚不能连接多个输出引脚。
输出引脚不能短接在一起。
所有输入以end为结束标志,end之后出现的内容忽略不计
按照与门、或门、非门、异或门、同或门、三态门、译码器、数据选择器、数据分配器的顺序依次输出所有元件的输出引脚电平。
同类元件按编号从小到大的顺序排序。
#如果某个元件的引脚没有接有效输入、输入输出之间断开(如三态门)或控制引脚输入无效,元件输出无效,程序输出忽略该元件。
#译码器不输出引脚电平,输出其输出为0的引脚的编号。如“M(3)1:3”代表译码器M3的输出引脚Y3输出0,其他引脚输出1。
#数据分配器按引脚编号从小到大的顺序输出所有输出引脚的信号,无效状态引脚输出“-”。
如“F(2)1:--0-”代表分配器F1的输出引脚W2输出0信号,其他三个引脚为无效状态。
1)每个元件的输入引脚连续编号。假设元件有n个输入引脚,则其编号取值范围为\[1,n\],且引脚号不重复。
2)本题涉及的五种元件都只有一个输出引脚,输出引脚号默认为0。
数字电路模拟程序1:
基础逻辑门元件构成电路
数字电路模拟程序2(本次作业):
1、包含多输入输出的组合电路元件如数据选择器;
2、元件引脚类型除输入、输出之外,增加控制引脚,如三态门。
数字电路模拟程序3:
增加带反馈的电路、时序电路元件,如D触发器、JK触发器。
数字电路模拟程序4:
1、增加子电路;
2、增加程序异常输入的检测。
解题思路:
1:输入解析与电路构建
-
解析INPUT行:提取外部输入信号 -
解析连接信息:建立信号传播网络 -
元件自动创建:从引脚名中提取元件信息 -
引脚编号计算: -
基础门:输入引脚1~n,输出引脚0 -
三态门:0控制,1输入,2输出 -
译码器M(k)n:控制0~2,输入3~(2+k),输出(3+k)~(3+k+2^k-1) -
数据选择器Z(k)n:控制0~(k-1),输入k~(k+2^k-1),输出k+2^k -
数据分配器F(k)n:控制0~(k-1),输入k,输出(k+1)~(k+2^k)
-
2:信号传播算法
// 1. 初始化队列
Queue<SignalEvent> queue = new LinkedList<>();
for (每个输入信号) {
queue.add(新的SignalEvent(输入引脚, 信号值));
}
// 2. 传播信号
while (!queue.isEmpty()) {
SignalEvent event = queue.poll();
// 更新引脚值
引脚信号 = event.value;
// 检查关联元件是否可计算
Component comp = 引脚所属元件;
if (comp.isReadyToCalculate()) {
if (comp.calculate()) { // 计算成功
for (每个输出引脚) {
queue.add(新的SignalEvent(输出引脚, 输出值));
}
} else {
// 元件无效,不计算输出
}
}
}
3:元件计算规则
-
与门/或门:同程序-1 -
三态门(S): -
控制引脚=1:输出=输入 -
控制引脚=0:输出无效
-
-
译码器(M(k)): -
控制:S1=1, S2+S3=0 -
输入二进制值 → 十进制i -
Yi=0,其他输出=1
-
-
数据选择器(Z(k)): -
控制二进制值 → 十进制i -
输出 = 第i个输入
-
-
数据分配器(F(k)): -
控制二进制值 → 十进制i -
第i个输出 = 输入 -
其他输出 = 无效
-
4:结果收集与格式化
// 按类型和编号排序
List<Component> sortedComponents = components.stream()
.filter(c -> c.hasValidOutput()) // 过滤有效输出
.sorted(Comparator
.comparing((Component c) -> c.type.ordinal()) // 按类型顺序
.thenComparing(c -> c.number)) // 同类型按编号
.collect(Collectors.toList());
// 格式化输出
for (Component comp : sortedComponents) {
switch (comp.type) {
case DECODER:
// 格式:M(3)1:2 (输出为0的引脚编号)
int zeroPin = comp.getZeroOutputPin();
System.out.println(comp.name + ":" + zeroPin);
break;
case DEMUX:
// 格式:F(2)1:--0- (-表示无效)
String outputStr = comp.getOutputString();
System.out.println(comp.name + ":" + outputStr);
break;
default:
// 格式:A(2)1-0:1
int outputValue = comp.getOutputValue();
System.out.println(comp.name + "-0:" + outputValue);
}
}
类图如下:

使用SouceMonitor对代码进行静态分析:


设计模式应用
-
各种门电路都有对应的类: AndGate,OrGate,NotGate,XorGate,XnorGate -
新增元件类: TriStateGate,Decoder,Multiplexer,Demultiplexer -
工厂类: ComponentFactory -
基类/接口: Component,InputComponent
复杂度问题集中
-
最大复杂度15:集中在 ComponentFactory.createComponent()方法 -
这个方法有39条语句,调用34个其他方法,块深度5 -
这表明工厂类的创建逻辑过于复杂
注释严重不足
-
注释行比例仅1.5%:远低于推荐的20-30% -
对于复杂系统,缺乏注释会严重影响可维护性
部分方法复杂度偏高
-
Decoder.compute():复杂度11,语句20 -
Decoder.hasAllInputs():复杂度6 -
Demultiplexer.compute():复杂度6,语句19
踩坑心得
1. 从程序一到程序二的设计演进
-
职责分离:程序二将不同元件封装为独立类,遵循了单一职责原则 -
多态应用:通过抽象基类和具体子类,实现了元件行为的统一接口 -
工厂模式: ComponentFactory解耦了对象创建与使用
-
程序二注释率更低(1.5%),影响可读性 -
工厂类 createComponent()复杂度仍达15,可进一步优化
2. 复杂度控制的实践心得
3. 代码注释的重要性
-
复杂逻辑(如译码器的引脚计算、数据分配器的输出生成)缺乏注释 -
方法职责、参数含义、返回值说明都需要注释 -
特别是控制引脚逻辑、编码解码算法需要详细注释
4. 测试与调试的难点
-
信号传播依赖递归计算,错误难以定位 -
没有清晰的元件状态追踪
-
每个元件独立,便于单元测试 -
但增加了集成测试的复杂度
改进建议
1. 重构工厂类
// 当前问题:一个方法处理9种元件的创建
// 改进方案:使用注册表模式
interface ComponentCreator {
Component create(String name, Matcher matcher);
}
class ComponentFactory {
private static final Map<String, ComponentCreator> creators = new HashMap<>();
static {
register("A\\((\\d+)\\)(\\d+)", (name, matcher) ->
new AndGate(name, Integer.parseInt(matcher.group(1))));
// 注册其他创建器...
}
public static Component createComponent(String name) {
for (Map.Entry<String, ComponentCreator> entry : creators.entrySet()) {
Matcher matcher = Pattern.compile(entry.getKey()).matcher(name);
if (matcher.matches()) {
return entry.getValue().create(name, matcher);
}
}
return null;
}
}
2. 优化高复杂度方法
Decoder.compute()拆分方案:private boolean checkControlPins() {
// 提取控制引脚检查逻辑
}
private int calculateInputCode() {
// 提取输入编码计算逻辑
}
private void setOutputPins(int code) {
// 提取输出引脚设置逻辑
}
void compute() {
if (!checkControlPins()) {
valid = false;
return;
}
int code = calculateInputCode();
setOutputPins(code);
valid = true;
}
}
// 使用卫语句减少嵌套
// 重构前:
void someMethod() {
if (condition1) {
if (condition2) {
if (condition3) {
// 业务逻辑
}
}
}
}
// 重构后:
void someMethod() {
if (!condition1) return;
if (!condition2) return;
if (!condition3) return;
// 业务逻辑
}
浙公网安备 33010602011771号