一、前言
这段时间先后完成了两次数字电路模拟程序题目集和课堂测验,从基础语法到复杂系统设计都有涉及,既有挑战,也有收获。
先说说两次数字电路模拟程序题目集。第一次题目集,核心是实现简单门电路与门、或门、非门等的模拟,以及基础组合逻辑电路的搭建,知识点集中在类的封装、对象创建、方法重载等基础面向对象特性,难度偏向入门级。当时主要是熟悉将电路器件抽象为对象的思维,比如每个门电路作为独立类,拥有输入端口、输出端口等属性,以及计算输出值的方法。
第二次题目集则明显提升了复杂度,不仅要求实现更复杂的复合门电路异或门、同或门,还引入了时序电路触发器和简单时序逻辑系统的模拟,知识点扩展到继承、多态、接口、异常处理等进阶内容。这次需要考虑的问题更多,比如不同门电路的复用、输入输出信号的时序同步、错误输入的处理等,代码量增加不少。
课堂测验题目覆盖了Java基础语法、面向对象核心特性(封装、继承、多态)、数组与集合、异常处理、接口与抽象类等核心知识点,侧重对概念的理解和细节的考察。测验题目不算难,但很多选项都很容易混淆的知识点(比如构造方法的修饰符、抽象类与接口的区别、多态的前提条件等),这也暴露了我在基础概念掌握上的薄弱环节。
整体难度上逐步提升,这种梯度设计让我在实践中不断突破,对编程能力的提升很有帮助。
二、设计与分析
课堂测验
1. 判断题
共56道,错5道,正确率约91%,错题主要集中在抽象类与接口,给出其中几道错题分析:
1-4 接口只包含常量和抽象方法,接口中定义的方法只能是抽象方法。
正确答案应为T,在传统 Java 语法中(JDK 8 之前),接口中只能包含公共的静态常量(默认被public static final修饰)或公共的抽象方法(默认被public abstract修饰),不能包含非抽象方法。
题目描述的是接口的基础定义规则,符合 JDK 8 之前接口的核心特性;即使 JDK 8 后接口支持默认方法、静态方法,但题目表述的 “只包含常量和抽象方法” 是接口的经典定义,因此该表述是正确的。
1-32 Java中,类与接口之间是继承关系,即类继承接口。
正确答案是F,跟上一题一样,基于 JDK 8 之前的语法体系,“接口中可以定义非抽象方法” 的表述确实错误,因此答案为F。
1-42 Java中,接口可以定义为抽象接口。例如:public abstract interface Inter{}
正确答案应为T,Java 中接口的默认修饰规则:接口的定义默认隐含abstract修饰符,即使不显式写abstract,接口也会被视为抽象的。
显式在接口定义前添加abstract关键字(如public abstract interface Inter{})是语法允许的,编译器会正常识别,因此 “接口可以定义为抽象接口” 的表述是正确的。
1-50 Java中,以下代码会抛出异常。
package kaoshi;
public class Demo_Exception {
public static void main(String[] args) {
System.out.println(1.0 / 0);
}
}
正确答案F,浮点数除法的特性:在Java中,整数除以 0会抛出ArithmeticException(算术异常),但浮点数(如double类型的1.0)除以 0不会抛出异常,而是会得到一个特殊的浮点值Infinity(无穷大)。
该代码中1.0 / 0属于浮点数除法,运行后会输出Infinity,所以不会抛出异常。
2. 单选题
共29道,错5道,正确率约83%,错题主要集中在类方法静态调用、类编译运行文件、类修饰符,给出其中几道错题分析:
2-20 假设类A有如下定义,且a是A类的一个实例,则必定错误的选项是( )。
class A {
int i;
static String s;
void method1() { }
static void method2() { }
}
A. System.out.println(a.i);
B. a.method1();
C. A.method1();
D. A.method2();
答案是 C
首先分析类 A 的成员:
i:实例变量(非静态),需通过对象调用;
s:静态变量,可通过类或对象调用;
method1():实例方法,需通过对象调用;
method2():静态方法,可通过类或对象调用。
选项 A:a.i是通过对象调用实例变量,合法。
选项 B:a.method1()是通过对象调用实例方法,合法。
选项 C:A.method1()是通过类调用实例方法,错误(实例方法必须通过对象调用,不能直接通过类调用)。
选项 D:A.method2()是通过类调用静态方法,合法。
2-23 对于类与对象的关系,以下说法错误的是( )。
A.类是对象的类型
B.对象由类来创建
C.类是同类对象的抽象
D.对象是创建类的模板
答案:D
类是对象的 “模板”,对象是类的 “实例”;类是同类对象的抽象,对象由类创建,类是对象的类型。D 的描述颠倒了类与对象的关系,错误。
2-29 关于Java语言的描述,错误的是()。
A.每一个.java文件编译后对应一个.class文件。
B..java文件编译后,每一个class对应一个.class文件
C.Java源代码编译后产生的.class是字节码文件
D..class文件在JVM上运行
答案:A
A 错误:一个.java文件中可以包含多个class(但只能有一个public class),编译后每个class对应一个.class文件,而非一个.java对应一个.class。
B 正确:每个class编译后生成独立的.class文件。
C 正确:.class是 Java 编译后的字节码文件。
D 正确:.class文件在 Java 虚拟机(JVM)中运行。
3. 多选题
共38道,错8道,正确率约79%,给出其中几道错题分析:
接口相关3-5
正确选项:ABC
A:接口的方法默认是public abstract(JDK8 前),描述正确。
B:接口的属性默认是public static final,描述正确。
C:接口引用指向实现类对象,调用方法时会执行实现类的重写方法(多态),描述正确。
D:接口是独立的类型(不是类类型),类需 “实现” 接口,描述错误。
字节数组与字符串转换3-6
正确选项:AB
A/B:new String(byte[])或new String(byte[], 起始索引, 长度)可将字节数组按默认编码转为字符串,符合需求。
C:bytes.toString()是调用数组的toString()(返回对象地址,不是字符),错误。
D:Arrays.toString(bytes)是将数组转为 “[元素 1, 元素 2...]” 格式的字符串,不是字符拼接,错误。
类型转换3-9
正确选项:AC
A:Integer.parseInt(str)是将字符串转为 int 的标准方法,正确。
B:new Integer(str)返回Integer对象,需自动拆箱为 int,但语法不规范(应写Integer i2 = new Integer(str);),错误。
C:Integer.parseInt(str, 10)指定进制为 10(十进制),正确。
D:String 类没有toInteger()方法,错误。
封装3-20
正确选项:ABC
A:封装的核心是 “属性私有,通过公共方法访问”,正确。
B/C:访问私有属性的方法通常是 setter(赋值)和 getter(取值),正确。
D:类的属性不强制封装,不封装也能编译,错误。
位运算3-22
正确选项:A
左移():按位右移,正数补 0、负数补 1。110000...是负数(最高位为 1),右移 5 位应补 1,结果是1111111000...,C/D 错误。
多态3-34
正确选项:ABC
多态的前提:① 存在继承关系;② 子类重写父类方法;③ 父类引用指向子类对象(向上转型)。
D:向下转型是多态的后续操作,不是前提,错误。
4. 填空题
共27道,错7道,正确率约74%,给出其中几道错题分析:
4-1 设计一个Flyable接口,一个实现Flyable接口的Duck类和Duck的子类RedheadDuck。
____ Flyable {
void fly();
}
____ class Duck ____ Flyable {
public ____ quack() {
System.out.println("我会呱呱呱");
}
public void ____() {
System.out.println("我会游泳");
}
public ____ void display() ____
@Override
____ void fly() {
System.out.println("我会飞");
}
}
class RedheadDuck ____ Duck {
public void ____ () {
System.out.println("我是一只红头鸭");
}
}
public class Main {
public static void main(String[] args) {
Duck ____ = new ____ ();
rduck.display();
____.quack();
rduck.swim();
rduck.fly();
}
}
运行以上程序,输出如下结果:
我是一只红头鸭
我会呱呱呱
我会游泳
我会飞
位置
| 答案
| 原因
| ____ Flyable
| interface
| 定义接口必须用interface关键字
| ____ class Duck
| public
| Duck 类需被 Main 类访问,用 public 修饰(也可省略,默认包访问权限)
| Duck ____ Flyable
| implements
| 类实现接口用implements(继承用 extends,实现接口用 implements)
| public ____ quack()
| void
| quack 方法无返回值,返回值类型为 void
| public void ____()
| swim
| 方法体输出 “我会游泳”,对应方法名 swim
| public ____ void display()
| abstract
| display 是抽象方法(子类 RedheadDuck 重写),需 abstract 修饰
| display() ____
| ;
| 抽象方法无方法体,以分号结尾
| ____ void fly()
| public
| 接口方法默认 public,重写时访问权限不能降低(不能用 protected/private)
| RedheadDuck ____ Duck
| extends
| 子类继承父类用 extends 关键字
| public void ____ ()
| display
| 方法体输出 “我是一只红头鸭”,对应重写父类的 display 方法
| Duck ____ = new ...
| rduck
| 变量名需与后续调用的rduck一致
| new ____ ()
| RedheadDuck
| 多态:父类引用指向子类实例,调用 display 时执行子类重写逻辑
| ____.quack()
| rduck
| 调用 quack 方法的变量名需与声明的rduck一致
| 4-3 设有以下Java程序段,请阅读程序并完成注释处的程序逻辑填空。
class Person {
String name, department;
int age;
public Person(String n){ name = n; }
public Person(String n, int a){ name = n; age = a; }
public Person(String n, String d, int a) {
// 完成Person(String n, int a)的逻辑
____
department = d;
}
}
答案:this(n, a); 注意这里不能写name = n; age = a;
Java 中构造方法可以通过this(参数列表)调用本类中其他重载的构造方法,实现代码复用,避免重复编写相同逻辑。
此处this(n, a)会调用public Person(String n, int a)这个构造方法,自动完成name = n; age = a;的赋值逻辑,再执行后续的department = d;,符合 “复用已有构造方法逻辑” 的需求。
this(...)必须是构造方法中的第一条语句,不能放在department = d;之后,否则编译报错。
不能直接写name = n; age = a;(虽然结果正确,但违背 “复用已有构造逻辑” 的设计意图,题目要求 “完成 Person (String n, int a) 的逻辑”,本质是要求调用该构造方法)。
4-9答案:extends
在 Java 中,定义子类继承父类的关键字是extends;而super是用于在子类中调用父类的构造方法、成员变量或方法的关键字,并非继承的关键字。
4-10答案:抛出对应的准确表述应为抛出(throw),但更贴合题干语境的答案是抛出的动作关键字表述 ——throw(或题干空处应填 “抛出” 对应的动作描述,但更准确的是 “抛出” 对应的关键字逻辑,实际 Java 异常机制中是 “以throw抛出特定异常”)。
数字电路模拟程序1
1. 架构
核心设计思路是“面向对象抽象”,将每个电路元件抽象为独立的类,通过统一的电路模拟类(CircuitSimulator)管理所有元件、输入信号和连接关系。整体分为四个核心模块:
输入解析模块:负责读取 INPUT 语句中的输入信号和方括号中的连接信息,通过正则表达式提取元件名、引脚号等关键数据。
元件管理模块:通过 HashMap 存储所有元件,支持根据元件名动态创建不同类型的门电路(与门、或门、非门等)。
电路构建模块:根据解析的连接信息,建立元件之间的输入输出连接,确保信号能正确传递。
结果输出模块:按照指定顺序(与门→或门→非门→异或门→同或门)计算并输出所有有效元件的输出电平。
2. 类设计
类的设计遵循了单一职责原则,每个类只负责自己的功能:
Gate 抽象类:所有门电路的父类,封装了通用属性(名称、编号、输入信号、输入连接)和通用方法(添加输入连接、更新输入信号、判断输入是否有效),定义了抽象方法 getOutput (),要求子类实现自己的输出计算逻辑。
具体门电路类:AndGate、OrGate 继承自 MultiInputGate(多输入门基类),XorGate、XnorGate 继承自 DualInputGate(双输入门基类),NotGate 直接继承自 Gate。每个子类都实现了对应的逻辑计算,比如 AndGate 的 calculateOutput () 方法会判断所有输入是否为高电平,只要有一个低电平就返回 false。
InputGate 和 OutputGate:分别表示电路的原始输入和最终输出,InputGate 存储外部输入的信号,OutputGate 作为电路的输出终端。
Connection 类:记录输出引脚与输入引脚的连接关系,确保信号传递的正确性。
CircuitSimulator 类:作为程序的核心控制器,协调输入解析、电路构建、结果计算和输出的整个流程。
3. SourceMonitor 分析
代码总行数:440 行,语句数 260 行
类数量:9 个,平均每个类 6.22 个方法
平均每个方法 4.48 条语句,最大复杂度 3,平均深度 1.26
这些指标反映出程序 1 的设计相对简洁,类和方法的职责划分清晰,没有过度复杂的逻辑嵌套。平均方法语句数较少,说明代码的可读性和可维护性较好;最大复杂度较低,意味着程序的逻辑分支不复杂,不容易出现隐藏的逻辑错误。
4. 心得
程序 1 的设计核心在于 “抽象与复用”。通过创建 Gate 抽象类,将所有门电路的通用属性和方法封装起来,避免了代码重复;通过 MultiInputGate 和 DualInputGate 两个中间基类,进一步细化了不同输入类型门电路的共性,让子类的实现更简洁。比如 AndGate 和 OrGate 只需要实现自己的 calculateOutput () 方法,输入有效性判断、输入信号更新等通用逻辑都继承自 MultiInputGate,大大减少了重复代码。
另外,输入解析模块的正则表达式设计也很关键。通过正则表达式匹配不同类型的元件名,比如多输入门的 A (8) 1、双输入门X5,可以快速提取元件类型、输入引脚数和编号,确保输入解析的准确性和效率。
数字电路模拟程序2
1. 架构
程序 2 的整体架构在程序 1 的基础上进行了扩展和优化,核心模块保持不变(输入解析、元件管理、电路构建、结果输出),但每个模块都增加了对新元件的支持。
输入解析模块:新增了对三态门、译码器、数据选择器、数据分配器的元件名和引脚号解析,比如译码器的M (3) 1、数据选择器的Z (2) 2等。
元件管理模块:新增了 TriStateGate、DecoderGate、MultiplexerGate、DemultiplexerGate 四个类,并通过多态机制整合到现有架构中,Gate 抽象类新增了 isValid () 方法,用于判断元件输出是否有效。
电路构建模块:新增了对控制引脚的处理逻辑,确保控制引脚、输入引脚、输出引脚的正确连接和信号传递。
结果输出模块:按照新的元件顺序(与门→或门→非门→异或门→同或门→三态门→译码器→数据选择器→数据分配器)输出,并且针对不同元件设计了差异化的输出格式,比如译码器输出为 0 的引脚编号,数据分配器输出包含无效状态的字符串。
2. 类设计
程序 2 的类设计利用继承和多态,有一定扩展:
TriStateGate 类:继承自 Gate,封装了三态门的逻辑 —— 控制引脚为高电平时导通,输出等于输入;控制引脚为低电平时输出无效。通过重写 getOutput () 和 isValid () 方法,实现了三态门的特殊逻辑。
DecoderGate 类:继承自 Gate,包含输入引脚数、输出引脚数两个额外属性,重写了 getOutput () 方法(返回 null,因为译码器有多个输出),新增了 getDecoderOutput () 方法,专门处理译码器的输出逻辑 —— 先判断控制引脚是否满足工作条件(S1=1,S2+S3=0),再根据输入引脚编码确定输出为 0 的引脚编号。
MultiplexerGate 类:继承自 Gate,根据控制引脚数确定数据引脚数(2^ 控制引脚数),通过 getControlValue () 方法计算控制端的二进制值,进而选择对应的数据源,返回该数据源的信号。
DemultiplexerGate 类:与数据选择器逻辑相反,将一路输入信号分配到多路输出中的一路,通过控制端信号确定输出通道,其他通道输出无效状态(用“-”表示),新增了 getDemuxOutput () 方法处理这种特殊输出格式。
Gate 抽象类新增了 getControlValue () 辅助方法,用于计算控制引脚的二进制值,供数据选择器、分配器等元件复用,减少了代码重复。
3. SourceMonitor 分析
代码总行数:788 行,语句数 435 行
类数量:13 个,平均每个类 7.8 个方法
平均每个方法 5.46 条语句,最大复杂度 4,平均深度 1.30
与程序 1 相比,代码行数和类数量都有明显增加,但平均方法语句数和复杂度控制得较好,说明在扩展功能的同时,保持了代码的整洁性。最大复杂度仅增加到 4,主要是因为新元件的逻辑虽然复杂,但通过合理的方法拆分,避免了过度嵌套。比如 DecoderGate 的 getDecoderOutput () 方法,虽然逻辑步骤多,但每个步骤都清晰独立,没有复杂的嵌套分支。
4. 心得
程序 2 的设计难点在于处理新元件的复杂逻辑和差异化的输入输出格式,而多态和抽象类的灵活运用是解决这些问题的关键。通过让所有新元件都继承自 Gate 抽象类,并实现各自的输出逻辑,确保了现有架构不需要大的改动就能兼容新功能,体现了 “开闭原则”—— 对扩展开放,对修改关闭。
另外,针对不同元件的输出格式差异,在结果输出模块采用了 “分类处理” 的思路:普通门电路输出 “元件名 - 0: 信号”,译码器输出 “元件名:输出 0 的引脚号”,数据分配器输出 “元件名:包含无效状态的字符串”。这种设计既满足了题目要求,又保持了输出逻辑的清晰性。
还有一个重要的设计点是 “无效状态的处理”,题目要求输出时忽略输出无效的元件。通过在 Gate 抽象类中新增 isValid () 方法,让每个元件自行判断是否有效(比如三态门输出为 null 时无效,译码器控制条件不满足时无效),在输出时只需过滤掉 isValid () 返回 false 的元件即可,这种设计让无效状态的处理逻辑更加统一和高效。
三、采坑与改进
在两次数字电路模拟程序的编码和调试过程中,我踩了不少坑,这些坑有的是语法细节问题,有的是逻辑设计问题,还有的是边界情况考虑不周。每解决一个坑,都能让我对编程有更深的理解,下面就详细说说一些主要的踩坑。
程序1
1. 多输入引脚的信号获取问题
在实现 AndGate 类时,遍历所有输入引脚的信号,只要有一个为 false 就返回 false。但测试时发现,当多输入门的部分输入引脚没有连接时,程序会抛出空指针异常。当输入引脚没有连接时,对应的 inputSignals 中的值为 null,遍历的时候直接判断!getInputSignal (i),会导致空指针异常。这是因为没有考虑到输入引脚未连接的情况,违背了题目中 “某个元件的引脚没有接有效输入,程序输出忽略该元件” 的要求。
解决方法:在 MultiInputGate 类的 isInputValid () 方法中,增加对所有输入引脚信号的非空判断,确保所有输入引脚都有有效信号后,才进行输出计算。如果有任何一个输入引脚信号为 null,isInputValid () 返回 false,getOutput () 返回 null,该元件会在输出时被忽略。
2. 元件输出顺序的排序问题
排序逻辑是先按元件类型排序,再按编号排序,但测试时发现,同或门的排序总是在异或门之前,不符合题目要求。检查 sortGates () 方法中的排序逻辑,发现 getTypeOrder () 方法中,XorGate 的顺序是 4,XnorGate 的顺序是 5,逻辑上是正确的。但在提取元件编号时,发现对于 XorGate 和 XnorGate,编号提取逻辑是正确的,但在排序时,编号是字符串类型,导致 “X10” 会排在 “X2” 前面(字典序排序)。
解决方法:修正编号提取后的排序逻辑,确保编号以整数形式进行排序。在 Gate 类中增加 getNumber () 方法,返回整数类型的编号,排序时直接使用该方法返回的整数进行比较,这样就能保证 “X2” 排在 “X10” 前面。
程序2
1. 三态门的输出无效处理问题
三态门的逻辑是 “控制引脚为低电平时,输出为高阻态(无效)”,一开始的实现是当控制引脚为低电平时,getOutput () 返回 false,但这样会导致程序误将无效输出当作低电平输出,不符合题目要求。题目要求 “如果某个元件的输入输出之间断开(如三态门),元件输出无效,程序输出忽略该元件”,发现问题出在 getOutput () 的返回值设计上。三态门导通时返回输入信号(true/false),断开时应该返回 null,表示输出无效,而不是返回 false。
解决方法:修正 TriStateGate 的 getOutput () 方法逻辑:当控制引脚为 false 时,返回 null;当控制引脚为 true 时,返回输入信号。同时,重写 isValid () 方法,判断 getOutput () 是否为 null,为 null 则返回 false,该元件会在输出时被忽略。
2. 优化元件创建逻辑
目前的 createGate () 方法通过很多 if-else 判断和正则表达式匹配创建不同类型的元件,代码冗长,不易维护。可以使用工厂模式优化元件创建逻辑:
创建 GateFactory 类,提供静态方法 createGate (String gateName)。
在 GateFactory 类中,为每种元件注册对应的创建器(Creator),创建器包含一个匹配元件名的正则表达式和一个创建元件的方法。
调用 createGate () 方法时,遍历所有创建器,找到匹配的创建器,调用其创建方法生成元件。
这种设计可以简化元件创建逻辑,后续新增元件时,只需新增一个创建器并注册到 GateFactory 中,无需修改现有代码,扩展性更好。
四、总结
通过这两次数字电路模拟程序和课堂测验,然后博客整理一遍,收获挺多的。
课堂测验考察了一些概念、Java语法、面向对象这些知识点,考的很全面,错题集中在接口、构造方法这些细节。比如混淆了extends和super的用法,把继承父类的关键字答成super,实际该用extends;还有构造方法复用,一开始直接写重复赋值代码,后来才懂要用this(n,a)调用重载构造,而且必须放在第一行。
第一次程序400多行代码,核心是把与门、或门等元件抽象成类。用Gate抽象类封装name、number等通用属性和getOutput()抽象方法,AndGate、OrGate继承MultiInputGate,重写calculateOutput()实现“全1出1”“有1出1”逻辑,体现了封装和继承思想。
第二次程序新增三态门、译码器等复杂元件,代码700多行。重点用了多态,所有新元件都继承Gate,重写isValid()判断输出是否有效,比如三态门控制引脚为低电平时返回null,输出时自动忽略。译码器要处理控制引脚(S1=1、S2+S3=0)和输入编码的逻辑,数据选择器靠getControlValue()计算控制端二进制值选择数据源,这些都让我体会了抽象类和多态具体是怎么使用的。
这段时间掌握了类的抽象设计、多态的复用等,后续希望多结合案例讲设计模式,作业中多些代码测试点,最大的感悟还是编程得多练才会进步。
来源:程序园用户自行投稿发布,如果侵权,请联系站长删除
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作! |