题目集1~3的总结
一.前言
这三次大作业虽然题目数量不多,但却写得很不容易,有的时候甚至能说得上痛苦,
尤其是不知道最后一题的测试点为什么没过的时候,但收获很多,在努力与协作下最终也完成了这三次作业
nchu-software-oop-2024-上-1 ~知识点
- 类和对象的概念
- 类的创建和调用方式
- 修饰符的使用
- 数组的使用
- 正则表达式
nchu-software-oop-2024-上-2 ~知识点
- 类的设计
- ArrayList的了解和使用
- 正则表达式
nchu-software-oop-2024-上-3 ~知识点
- 面向对象编程的封装性
- 日期类的基本使用
- 正则表达式
在这三次作业中,我们首先强化了类和对象的概念,学会了类的创建和调用方式,途中对修饰符也进行了学习,然后对类与类之间的关系有了基础的了解,当然,三次作业中,印象最深的还得是首次接触的正则表达式,其次是类的设计以及类与类之间的关系。
接下来我会按照设计与分析、采坑心得、改进建议、总结四部分进行这次PTA题目集1~3的总结。
二.设计与分析
对于题目集1~3最后一题的分析
一.答题判题程序-1
第一次作业最后一题的设计建议给了我很大的指导,所以我基本也是按老师给的这个设计建议写的。
接下来我会介绍一下自己在作业中类的设计思路。
以下是我第一次作业的类图。
这道题主要提到了两个名词,也就是题目、答题,根据这两个名词,也很容易能联想到第三个名词,答卷。然后就可以根据这三个名词创建类:
1.题目类
首先是题目类。题目作为大家每日都会接触的东西,设计题目类时,应该是很容易就能想到它需要用于查找的题目编号,题目内容以及标准答案这三个最基本的属性,而写题自然也就有判题,所以也应创建一个判题方法。
Topic:
属性:题目编号、题目内容、标准答案-standardAnswer
方法:数据读写set\get方法、判题方法(答案-answer)
class Topic{ private int num; private String content; private String answer; public Topic(){} public Topic(int a,String b,String c){ this.num = a; this.content = b; this.answer = c; } public void set(int a,String b,String c){ this.num = a; this.content = b; this.answer = c; } public int getnum(){ return num; } public String getcontent(){ return content; } public String getanswer(){ return answer; } public boolean judge(String a){ ...... } }
2.试卷类
第二是试卷类。它应该用于储存一张试卷所需要的题目,还应具有保存题目以及判题的功能。
Testpaper:
属性:题目列表(题目类的对象集合)、题目数量
方法:判题方法(题号-num、答案-answer),保存题目(题号-num、题目-question)
class Testpaper{ private int num; static Topic[] test = new Topic[100]; public Testpaper(){} public Testpaper(int a){ this.num = a; } public boolean judge(int a,String b){ return test[a].judge(b); } public void preserve(int a,String b,String c){ test[a] = new Topic(a,b,c); } }
3.答卷类
最后是答卷类。
说实话,对我而言答卷类应该是这三个类里最难设计的了。它首先应该有对应的试卷,然后还需要有学生的答案列表,至于判题列表……其实一开始我本来打算用试卷类的判题方法及println去输出对错来着,还是根据设计建议才搞了判题列表。
至于判题列表为什么应该作为答卷类属性而不应像我所说的那样直接输出,我想了想,在面向对象的编程中,属性是指类对象所具有的特征或数据,而对错确实也是答卷的最最最基础且重要的特征之一,所以,它应归为答卷类的属性,这是我个人的一点小心得。
然后就是判题、输出、保存答案这三个方法了。
Testanswer:
属性:试卷(试卷类的对象)、答案列表(保存每一题的答案)、判题列表(保存每一题的判题结果true/false)
方法:判题方法(题号-num),输出方法(题号-num),保存一个答案(题号-num,答案-answer)
class Testanswer{ int num; static Testpaper question; String[] answer = new String[100]; boolean[] tf = new boolean[100]; public Testanswer(){} public Testanswer(int a){ this.num = a; } public boolean judge(int a){ return tf[a]; } public void print(int a){ String str = question.test[a].getcontent()+"~"+answer[a]; System.out.println(str); } public void preserve(int a,String b){ answer[a] = b; tf[a] = question.judge(a,answer[a]); } }
因为题目不算难,在了解了正则表达式后,用正则表达式匹配到了各部分所需要的数据并保存,最后还算顺利地通过了。
附上我当时写的正则表达式以及SourceMonitor生成的报表内容:
String regex1 = "(?<=#N:\\s*)\\d+(?=\\s+#Q)";//匹配题号 String regex2 = "(?<=#Q:\\s*)[^\\s].+(?=#A)";//匹配题目 String regex3 = "(?<=#A:).+";//匹配答案
二.答题判题程序-2
因为第一次大作业告诉了题目具体数量并且按照信息顺序输入,所以当时我是用for循环进行保存操作的,而题目2是三种信息乱序输入,循环不能用了,导致我主函数几乎全部推翻重写。
苦于没有对信息的判断而导致的重写,所以这次我写了对信息进行匹配及解析的方法,因而即使后面第三次作业添加了删除的功能及其他一些信息,我也没有改太多地方,也算是一个小小的进步。
1.类的新增内容
试卷类:保存和处理一张试卷的信息以及处理的方法
int[] mark = new int[100];//用于纪录每一题的分数
public void fenshu(){}//用于判断试卷是否满100分并输出 |
public void print(int a){}//用于输出题目内容、学生答案及判题信息 public void fen(){}//用于输出学生各题得分及最终总分 |
2.利用正则表达式解析输入信息
从现在来看,当时第二次作业写的正则表达式并不严谨,这个问题在第三次作业暴露得很彻底
首先是利用正则表达式对输入信息进行判断,然后对不同的信息进行不同的解析及保存
String regex1 = "#N(.+)#Q(.+)#A(.+)";//匹配题目 String regex2 = "#T(.+)";//匹配试卷 String regex3 = "#S(.+)";//匹配答卷
附上对题目输入信息进行解析的方法:
(试卷类及答卷类的解析与此相似,就不赘述了)
//解析题目 public static Topic jiexi1(String str){ String regex1 = "(?<=#N:\\s*)\\d+(?=\\s+#Q)";//匹配题号 String regex2 = "(?<=#Q:\\s*)[^\\s].+(?=#A)";//匹配题目 String regex3 = "(?<=#A:).+";//答案 }
虽然因为各种原因第二次大作业几乎是全部推翻重写,但利用一天时间重写完后很快就通过了,在此附上类图及SourceMonitor生成的报表内容:
(关于类设计及程序复杂度的反思我会综合写在第四部分——“改进建议”)
二.答题判题程序-3
本此作业最后一题引入了“删除信息”,即使新添代码所花时间不长,甚至可以说是比第一二次大作业还要短,但最终通过这题所花的时间却可能比第一二次大作业加起来还要多:
一来是对java日期类的基本使用并不了解,花了很多时间写日期题,最后又总有两三个测试点过不了,反复用各种方法重写,唉;二是过半的时间都花在了最后一题的理解题目及测试点上,在第三题踩的坑会具体集中在第三部分“踩坑心得”总结。
这里展示一下第三次大作业的三题,但只分析最后答题判题程序。
7-1 面向对象编程(封装性) |
1.类的新增内容
本次大作业新增了“学生信息”的输入,即学号及姓名。
最初我是打算新添一个Student类,但写的途中,突然意识到姓名和学号又何尝不是答卷的特征之一?于是把新添的学生信息糅合进了答卷类,最后输出时进行判断。
附上新添内容的代码:
class Testanswer{ String num2;//考生学号 String name;//考生姓名 public void fen(){ ......; } }
2.类图及报表
三.踩坑心得
这一部分会着重讲第三次大作业的答题判题程序3
1.非零返回
以上是我我第一、二次大作业答题判题程序的一些评测详情,基本都是非零返回。对于非零返回,起初我是一头雾水的,但后来见多了改多了,大概就知道为什么会非零返回了。
几乎所有我得到的非零返回原因都是程序的语法在PTA执行的过程中抛出了异常,导致程序没能运行到return,就显示了非零返回。如果仔细阅读PTA的提示,回到代码去看异常的地方就会发现,在调用某些类的方法的时候,这些方法可能会返回null。
我的非零返回几乎都是在判断一道题是否正确时才会产生,请看示例:
附上出错地方的代码:
for(int i=0;i<answer1[c].num1;i++){//判断对错 answer1[c].judge(i); } //下面这段代码是上面那段代码调用的方法 public void judge(int a){ tf[a] = question.judge(a,answer[a]); }
这个时候如果去测试样例就会发现PTA显示非零返回,而这个非零返回的原因是我的answer[a]根本就不存在,也就无法正确运行方法。大家遇到这个问题时,可以在调用方法时判断返回值是否为空,再进行你需要的操作,不过这样也未必能解决问题,毕竟问题一般都出在前面的代码里。
所以我建议大家如果遇到非零返回的情况,可以去Eclipse等程序运行一下自己的代码,它们一般会显示错误的具体原因,你再根据具体原因去改代码,这比你对着PTA模里模糊的“非零返回”改代码要方便一点。
因为我这个非零返回时因为前面的代码没有正确存入答案,所以我后来解决这个问题的方法是找出前面错误的代码并更改,让答案正确存进answer[a]里,也就没有非零返回了。
2.正则表达式
正则表达式作为贯穿三次大作业的重要部分,肯定会是很多人踩坑的地方,其中就包括我。
在第三次大作业答题判题程序的28个测试点里,就有很多对于错误信息的判断的测试点。我起初把正则表达式写得很松,只要开头符合格式就算它过,即使这样,大部分对格式进行判断的测试点我也都过了,但始终有一个错误格式判断的测试点不过了。
于是我不断改正则表达式,试图把它改得更严谨。当我的正则表达式匹配变得更严格后,我发现这个测试点还是过不了,两三天后我才知道,原因可能是我使用的是find()而非matches()。
话不多说,我们看示例:
//这是我刚开始对题目信息进行解析的方式,使用的是find()方法,即找到合格的题目输入信息就开始对信息进行解析 //但如果一堆无用信息内包括题目信息,它仍会判这条信息符合格式,如:abcdede #N:1 #Q:1+1= #A:2 deada if(matcher1.find()){ Topic to = jiexi1(str); topic[to.num]=jiexi1(str); }
//所以后来我使用了matches(),才通过了测试点 if(matcher1.matches()&&str.startsWith("#N:")){ Topic to = jiexi1(str); topic[to.num]=jiexi1(str); }
3.解析输入信息
解析输入信息应该是这三次大作业里最为关键的地方,我第三次大作业就一直错在解析输入信息上。
这三个测试点是我最为头痛的,因为前面的测试点都过了,实在搞不明白这三个到底错哪了。但我猜测,大抵是我对信息的解析及存入还不够完善,比如说只保存了一部分内容,或者错位保存到了其他地方的内容……于是我又开始改正则表达式,试图让它匹配得更全面一点。
但最后令这三个测试点通过的关键不是我写的正则表达式(虽然也有它的功劳),而是split()方法的调用。话不多说,直接上代码:
//有个很明显但我一直没发现的问题,那就是我对答案的匹配只包括字母、数字、汉字、下划线及空格 //如果答案输入#A:2-1964.10.16,我的答案只能匹配到1964这几个数字,因为遇到了符号.所以后面的内容都未匹配上
//最后我改用了split()方法才通过这三个测试点 if(matcher6.find()){ String zhengti=matcher6.group(); i=s1.length; for(int k=1;k<s1.length;k++){ String[] s2=s1[k].split("-"); int l=Integer.parseInt(s2[0]); answer.preserve(l,s2[1]); }
4.改进建议
其实从第一部分“设计与分析”中就能看出一些非常明显的问题,我总结与此
- 类的数量少,并未做到单一职责原则
- 储存方式未择优选择,每次都用循环去找题号等信息,使圈复杂度非常非常高
- 正则表达式还可再学习并改进
- 封装性不够
- 不必要的代码太多
- Main里面的代码混乱且长
对于上面这些问题,可能主要原因是对Java本质面向对象性了解不深入,无法灵活地理清楚各个类之间的关系,所以很多代码复用性不高。我会于后续尽量进行更改,毕竟再不改就越写越麻烦了。
5.总结
收获:
|
在这三次作业的不断迭代中,我又有了一种做课设的感觉,就是去设计自己的代码该如何实现自己所需功能的感觉。在这种感觉及课程的引导下,我也逐渐意识到Java的一些特性,或者说是面向对象的一些特性。
前两次作业基本是练手的,最后一次作业就有点不同,多了个对信息的判断,可以从用户的角度去测代码、改代码。然后感触就是做题时要思考,知道自己为什么要这么写,错在哪,对在哪,以及做功能时要先分析理清需求,多去考虑实现路径,择优选择。
当然,还有一点我觉得必不可少的,那就是同学之间的互相交流。有一些测试点,如果不是旁人多加提醒,或许我到现在还没过。以及蔡老师说的很有道理,大家互测代码确实是一个很好的检验代码的方式,自己平时也可以多测一下代码。
总的来说,这轮作业收获很大,但可改进的地方也不少,日后还需多加学习并实践。
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 清华大学推出第四讲使用 DeepSeek + DeepResearch 让科研像聊天一样简单!
· 推荐几款开源且免费的 .NET MAUI 组件库
· 易语言 —— 开山篇
· 实操Deepseek接入个人知识库
· Trae初体验