面向程序设计作业总结

一、题目集总结
作业一:数字电路模拟程序1(基础逻辑门)
刚开始觉得挺简单,不就五个门嘛,与门或门非门异或门同或门,初中都学过,真值表背得滚瓜烂熟。结果一上手就卡在字符串解析上,那个 A(8)1-2看着挺规整,真要写代码从里面抠出元件名和引脚号的时候,我发现用 split 拆括号和横杠特别容易搞混。比如 A(8)1这个元件名本身就带括号,用 (做分隔符会把 A和 8)1拆开,还得再拼回去。更坑的是 O(4)2这种,括号里是数字,后面还有编号,我试了好几种正则表达式才勉强搞定。好不容易解析完了,开始算输出,我又踩坑了——信号传播顺序没处理好。我按输入顺序一个一个门算,结果有的门输入还没确定呢我就算了,输出全是0。比如一个非门的输入来自另一个与门的输出,但那个与门还没算,非门就拿不到正确值。后来改成递归或者拓扑排序才解决。还有一个坑是输出排序,题目要求按与门、或门、非门、异或门、同或门顺序,同类按编号从小到大。我一开始用 HashMap 存元件,遍历的时候顺序完全不对,因为 HashMap 不保证顺序。后来换成 TreeMap 按自定义比较器排序,才算搞定。最烦的是如果某个门有引脚没接任何信号,题目说忽略它,我一开始没管,直接把没接的引脚当0处理。结果与门本来该输出1的,因为有个引脚没接被我当成0,输出就变成了0,跟正确答案差好多。后来我加了一个标志位,只有引脚接了信号才参与计算,这才通过测试。
作业二:数字电路模拟程序2(增加组合电路和控制引脚)
这道题我做了好久,前后改了三四版才算通过。首先那个引脚编号顺序就把我搞晕了,比如译码器 M(3)1,题目说0、1、2是控制引脚,3、4、5是输入,6到13是输出。我一开始想当然以为0就是第一个输入,结果连完线发现控制引脚根本没信号,白忙活了大半天。后来重新读题才发现引脚编号是按照“控制-输入-输出”的顺序排列的,每种类型内部再按编号从小到大。三态门也恶心,控制引脚为0的时候输出是高阻态,相当于这根线断了。我一开始没注意,直接把输出当成0往下传,导致后面一连串门都算错。比如三态门后面连着一个非门,按理说三态门断开了非门应该没有输入从而被忽略,但我把三态门输出当0传过去,非门就输出1,完全错了。数据选择器和分配器要根据控制引脚数量决定输入或输出数量,比如 Z(2)2表示2个控制脚所以有4个输入。我一开始写死了4个,结果碰到 Z(1)只有2个输入的程序直接数组越界。后来改成根据控制引脚数量动态计算输入数量,公式是 2的控制引脚数次方。输出格式也是折磨,译码器要输出哪个引脚是0,不是输出电平值。我一开始傻乎乎地输出了电平,比如译码器正常工作时光标指向的引脚输出0,其他输出1,我直接把所有输出引脚的电平都列出来了,格式完全不对。数据分配器要按顺序列出所有输出,无效的用 -,我一开始没处理无效状态,直接输出空字符串,格式对不上。还有那个数字转二进制的顺序问题,有的元件高位在前有的低位在前,我一开始没注意,导致译码器的输入编码对应关系全乱了。
作业三:数字电路模拟程序4(增加子电路与异常输入检测)
这道题我写到一半真想摔键盘。子电路那块,要先解析一堆 C2:这样的定义,里面还有自己的输入输出和连接,而且子电路内部元件编号可以和主电路重复,所以我得给每个子电路单独维护一套元件列表,不能跟主电路混在一起。我一开始想偷懒,把所有元件放在一个大列表里,结果子电路内部的元件编号和主电路的冲突了,计算全乱套。后来只好给每个子电路单独建一个 Map 来存它的元件。最烦的是在主电路里引用子电路的引脚,比如 C2-A,我得知道这个 A对应子电路内部的哪个输入节点,然后信号要从主电路传到子电路内部,再算完传回来。我一开始直接用字符串替换,结果子电路内部的门输出还要加上 C2-前缀,拼接的时候漏掉前缀就全乱了。比如子电路内部有个异或门 X1,它的输出在主电路里应该显示为 C2-X1-0:0,我一开始直接输出了 X1-0:0,格式完全不对。异常检测的优先级也坑,比如一条连接 [A B A2-1]既有多个输出又没有输入,题目说只报“include more than one input”,我一开始两个都报了。后来重新读题才发现要按照题目给的顺序逐个检查,匹配到第一个异常就停止,不再检查后面的。还有检测输入引脚冲突,我需要记录每个输入引脚被谁连过,但子电路内部的引脚也要单独记录。我一开始只记了全局的,结果子电路内部冲突没抓到。比如子电路内部两个不同的输出连到了同一个输入引脚,我完全没检测出来。最无语的是,如果多条输入都有异常,只处理排在最前面的那条,我还得记住输入的行号顺序。一开始我用的是 HashMap 存连接信息,遍历的时候顺序不确定,导致有时候报的异常不是最早的那条。后来改成 LinkedHashMap 才能按插入顺序遍历。还有那个空指针问题,子电路内部的引脚如果没连任何信号,访问的时候直接抛 NullPointerException,我加了一大堆判空才稳住。信号传播也只做一遍不行,多级门或者子电路嵌套,算一遍就停了,后面的门还没收到信号。我后来改成 while 循环反复计算,直到所有门的输出都不再变化为止,虽然效率低了点但总算能用了。号顺序。

二、代码分析
第一次:SourceMonitor的代码分析

DE34650F46BE8ED0A36E1908A29A31E5

代码一共529行,11个类,平均每个类有4个方法。平均复杂度2.67,最大复杂度5.41,说明逻辑不绕,结构还算清晰。平均嵌套深度2.32,不算太深。
类图

4C3F831D3EDA0638F5A8069E016C9934

第二次:SourceMonitor的代码分析
代码一共820行,16个类,平均每个类2.94个方法。平均复杂度4.43,最大复杂度8.98,相比第一次作业规模和复杂度都有所上升,符合新增三态门、译码器、选择器、分配器后的预期。平均嵌套深度2.77,最大深度9+。
类图

F0554AFA962CB3A94B34A3596E37C859

第三次:SourceMonitor的代码分析
代码一共543行,9个类,平均每个类2.89个方法。平均复杂度5.15,最大复杂度22,代码量减少,但复制度大大增加
类图

BBF64EE658EEE5A3739CAFAE387F0F83

三、踩坑心得
刚开始,我就被密密麻麻要求的题目吓到了,这是一个模拟电路问题,有几种元件,与门,或门,非门,同或门,异或门。我看到题目的第一反应就是这个电子原件就是一个抽象类是Component,然后输入需要单独一个叫做InputUtils处理,所有最后实现得到的就是一个InputUtils处理后,得到一个Map<String,Int>的一个哈希表和报文List字符串,然后把数据交给Manger这个类处理,现在我们需要实现这个原件类,我打算用boolean数组来模拟电子原件,0号数组作为输出引脚,然后我才知道,或门与门的引脚数是不一定的。所有我采取这样的策略:读完INPUT:后处理List每遇见一个电子元件就分析是不是INPUT符号,如果是就返回对应0或1,如果不是就访问manager内的map,检查是否有这个元件,如果有就返回这个Comparent,如果没有就创建一个返回这个Comparent。然后我设计了Calc接口给对应实现抽象子类继承来实现计算输出这个功能。最后实现一个Sort的接口,一个排序抽象类,一个实现拓扑排序把Map转List然后根据交给Print类输出,输出要检查X,Y,N电子元件的输入是否完整如果完整就输出否则不输出,当然当时好多测试点过不去,结果我一看怎么有那么一个非零返回然后我尝试从结尾向头注释,直到非零返回变成答案错误,结果发现在我的工厂方法附件方向,如果我想把原件名的后缀从字符串该为文字可能会错误,我于是到处问,最后学会了try-catch这个工具去尝试运行代码如果错了就跳过,于是稀里糊涂的完成了这次作业,虽然不知道为什么,但是我大为震撼。最后SourceMonitor跑出来的数据是529行代码,11个类,平均复杂度2.67,最大复杂度5.41,平均深度2.32,结构还行但注释一行都没有。

第二次作业追加了三态门、译码器、数据选择器、数据分配器,好在我们实现了注册工厂,创建4个Comparent,分别为S 、M、Z、F按照顺序注册,分别对应三态门、译码器、数据选择器、数据分配器,然后分别实现电子元件,这个时候我就遇到了第一个问题,那就是电子元件也可以作为输入端,然而三态门可以有空的状态,当然顿时如晴天霹雳,好在我脑袋一响,把boolean改为Boolean这个类就能兼容了,而且存在空的情况,然后在我的大量重构后,我成功完成了这个功能,接下来遇到第二个问题,原来我设计的输出引脚都是单输出而且都是0号,但是现在的有多输出的而且还有所谓的控制引脚,这真是苦煞我也,不过我尝试把output方法抽象为接口然后让每个电子元件重写,最后完成了这个问题,好景不长,还有第三关,我发现有大量空指针问题,所有我加了一堆try-catch,然后重构了连接方法也就是Agent方法,我尝试多次通过List来让连接动摇变化,也就是---力大砖飞。当然超时了,最后妥协的限制为3000转。最后在修了要检查数据选择器要检查输入,控制和选定输出引脚的状态选择是否输出还有有的元件的数字转二进制是高位的有的是低位的这些bug(老师太阴了)还有INPUT符号可能是字符串之类的bug后完成了这次作业。SourceMonitor跑出来是820行代码,16个类,平均复杂度4.43,最大复杂度8.98,平均深度2.77,规模翻了不少但最复杂的那个方法已经快到9了。

第三次作业我刚开始没认真看题目想当然了。然后在上一次作业里完成追加功能,当然结果是完工后就过了一个测试点。后面重写,全部推倒重来,这就是为什么我的代码量和注释都变少的原因。第三次的需求是处理子电路和错误提示。我当时产生了一个想法那就是子电路继承抽象类来实现,但是最后放弃了原因是子电路的引脚是字符串而且转数值到数组改起来很麻烦。然后我来总结一下我遇到的n个bug,首先我们的门电路乱给默认值——引脚没接信号,与门直接当 false 算,违背"没接就无效"的规则,输出全是错的。这个是因为我们说好的连接没有默认位0的规则但是后来可能有连倒空的情况了,所有我把默认为0的规则转到了calc里,其二,空指针到处炸——非门、异或门拿到 null 直接拆箱,当场崩溃。异常还被偷偷吞了,不报错也不输出,找 bug 找到吐血。这就是为什么一堆try-catch的原因。其三,连接方向搞反了——把源接到目标写成了 srcPin.connectPin(dstPin),应该是目标跟着源走,反了之后信号根本传不过去。无向图转有向图的硬伤。其四,子电路引脚傻傻分不清——主电路连 C1-A,程序不知道 A 是输入还是输出,全靠猜。猜错了连接就废了,心痛只有我知道。其五,传播只做一遍——多级门或者子电路嵌套,算一遍就停了,后面的门还没收到信号,输出当然错,为了解决连接问题我创建了Pin的这个概念然后尝试优化3000转这个雷霆玩意,当然结果变成了while和多次计算迭代,虽然没有解决这个算法方面的问题,但是起码我们优化了方式,从超级暴力变成了,稍微优雅的暴力,无论怎么说,结果是好的。最后SourceMonitor的数据是543行代码,9个类,平均复杂度5.15,最大复杂度22,平均深度2.65。代码量少了但复杂度反而飙上去了,最复杂那个方法22的圈复杂度就是被我那一堆try-catch和暴力迭代干出来的。

四、改进建议
下次遇到这种大作业,先花半小时把题目从头到尾读两遍,边读边把输入格式、输出格式、所有元件行为、异常情况和边界条件都划出来,别急着写代码。然后画张简单的流程图或者类图,把核心类和它们之间的调用关系理清楚,想明白信号怎么流、数据放哪、输出怎么收,免得写到一半发现架构撑不住。接着先搭一个能跑的最小框架,比如第一次作业就先只实现与门和或门,跑通一个测试点再加下一个,每次加功能就跑一遍已有的测试,确保没把之前的搞坏。写代码的同时顺手写几个硬编码的小电路做测试,手动验证输出对不对,这样出bug能立刻知道是新加的代码出了问题。遇到bug别到处加try-catch碰运气,先想清楚症状是什么,再用二分法注释或者加打印输出一步步缩小范围,比乱试快得多。最后一定要留够缓冲时间,比如截止前三天就做完第一版,剩下两天专门修bug和应对意外,别拖到最后一天才开始,越急越出错。

五、总结

1、学习收获与成长
首先,我对面向对象编程有了更深层次的理解。第一次作业时,我只是机械地把五种逻辑门封装成类,用boolean数组模拟引脚状态。到了第二次作业,面对三态门、译码器、数据选择器和数据分配器这些复杂元件,我意识到单纯靠继承已经不够用了。于是我引入了工厂模式和接口设计,把每种元件的计算逻辑抽象成独立的实现,这样新增元件时只需要注册一个新的工厂类,而不需要改动已有代码。这种“开闭原则”的实践让我真切感受到了设计模式的价值。
其次,我学会了如何处理复杂的数据结构和算法问题。电路本质上是一个有向图,信号从输入端出发,经过各个逻辑门,最终到达输出端。第一次作业时我用的是简单的递归计算,勉强能应付单级电路。但到了第三次作业,子电路的引入让电路变成了多层嵌套结构,递归已经无法处理。我不得不重新设计信号传播算法,虽然最终用的是暴力迭代加队列优化的方式,但这让我意识到了图论算法在实际问题中的重要性,也让我对广度优先搜索有了更直观的理解。
再次,我的问题排查能力得到了极大的锻炼。第一次作业时,遇到bug我只能靠瞎蒙和到处加try-catch。到了第三次作业,我已经学会了通过打印中间变量、逐步缩小排查范围的方式来定位问题。比如那次空指针异常,我通过在每个方法入口打印输入参数,很快就发现是子电路的引脚映射表没有初始化导致的。这种系统性的调试思维,比任何书本知识都来得珍贵。
2、存在的问题与不足
回顾这三次作业,暴露出的问题也不少,有些甚至到现在还没有彻底解决。
最大的问题是算法效率低下。第三次作业中,我为了处理信号传播,采用了暴力循环的方式,设置了3000次的上限。虽然最终通过了测试,但这种做法显然不够优雅。如果电路规模再大一些,或者嵌套层次再多一些,这种暴力算法肯定会超时。这反映出我在数据结构和算法方面的积累还不够,特别是图论相关的算法还需要系统学习。
其次是代码结构的问题。从SourceMonitor的分析数据可以看出,我的代码复杂度在逐次上升。第一次作业平均复杂度只有2.67,到了第三次作业就飙升到了5.15,最复杂的方法甚至达到了22的圈复杂度。这说明我在代码组织上还存在问题,经常把过多的逻辑塞进同一个方法里,导致方法臃肿、难以维护。比如那个负责解析连接信息的方法,既要处理字符串拆分,又要建立引脚映射,还要检查异常情况,几百行代码挤在一起,每次修改都心惊胆战。
还有一个问题是前期规划不足。第三次作业我一开始想当然地以为可以直接在第二次作业的基础上加功能,结果写完才发现子电路的引脚处理方式和普通元件完全不同,只能推倒重来。这浪费了大量时间,也让我深刻认识到了“磨刀不误砍柴工”的道理。
3、进一步学习及研究方向
针对以上不足,我计划在接下来的学习中重点加强以下几个方面。
第一,系统学习数据结构和算法。特别是图论相关的知识,比如拓扑排序、最短路径、广度优先搜索和深度优先搜索。这些算法在电路仿真、网络分析等领域都有广泛应用,掌握它们能让我写出更高效的代码。我打算找一本经典的算法书,配合LeetCode上的练习题,把基础知识打牢。
第二,深入学习设计模式和软件架构。虽然这次我用到了工厂模式和接口设计,但还远远不够。观察者模式可以用来实现信号的通知机制,状态模式可以用来管理元件的不同工作状态,策略模式可以让计算逻辑更加灵活。我希望通过阅读《Head First设计模式》这本书,把这些模式真正应用到实际项目中。
第三,养成良好的编码习惯。给自己定几条规矩:每个方法不超过30行,圈复杂度不超过10,关键逻辑必须写注释。同时学习使用静态代码分析工具,在提交代码之前自动检查这些问题。这样不仅能提高代码质量,也能让SourceMonitor的数据更好看。
第四,学习单元测试和持续集成。这次作业中,我大部分时间都花在了手动测试和调试上。如果能提前写好单元测试,每次修改代码后自动运行一遍,就能及时发现回归错误,大大减少debug的时间。我打算学习JUnit框架,为以后的每个项目都配上基本的测试用例。
总的来说,这三次作业是我编程道路上的一次重要历练。虽然过程充满了挫折和痛苦,但每一次debug成功的喜悦、每一个测试点通过的成就感,都让我觉得这一切值得。我相信,带着这些经验和教训,我能在未来的学习和工作中走得更远。

posted @ 2026-06-24 06:35  肖积灵  阅读(3)  评论(0)    收藏  举报