面向对象程序设计——前三次作业集总结
第一次作业——题目集01 7-8
题目集01 7-8 的题目是判断三角形类型,考察的是对整型.实型数据的处理和选择语句的综合使用。
整体思路介绍
1.题目集01 7-8 从思路上理解起来很清晰,花费的总时长在3个小时左右。
2.本题只涉及到输入三角形的三条边的边长,通过判断三边关系来确定三角形的类型。
3.题目考察的三角形类型为:直角三角形,等腰直角三角形,等腰三角形,等边三角形,一般三角形。
4.通过多个 if 语句判断三角形类型。题目输入的范围是[1,200],故数据类型定义为double。
5.double 型的数据在进行平方计算时会丢失精度导致无法判断是否为直角三角形,所以需判断某边的平方减去另外两边平方和的结果小于0.00001即可。
度量分析
圈复杂度分析使用 SourceMonitor 软件

核心代码分析
先判断输入的三条边长是否符合题目要求[1,200]。
if((a < 1)||(a > 200)||(b < 1)||(b > 200)||(c < 1)||(c > 200))
System.out.println("Wrong Format");
判断三条边能构成三角形
else if((a + b <= c)||(c + b <= a)||(a + c <= b))
System.out.println("Not a triangle");
判断是否构成直角三角形
else if((a * a + b * b == c * c)||(b * b + c * c == a * a)||(a * a + c * c== b * b))
System.out.println("Right-angled triangle");
判断是否存在两条边相等
else if((a == b)||(b == c)||(a == c)) {
在已有两条边存在的前提下还存在另外两条边相等则构成等边三角形
if( (a == b)&&( b == c))
System.out.println("Equilateral triangle");
在已有两条边存在的前提下还存在两边平方和等于第三边平方(double型变量在保存无理数的时候会失去部分精度)
else if((Math.abs( c * c - a * a - b * b)<= 0.00001)||(Math.abs(a * a - c * c - b * b)<= 0.00001)||(Math.abs(b * b - a * a - c * c)<= 0.00001))
System.out.println("Isosceles right-angled triangle");
else
System.out.println("Isosceles triangle");
}
不满足特殊三角形的条件则输出一般三角形
else
System.out.println("General triangle");
}
优点
1.判断条件清晰,可修改性高。
2.考虑到了构成直角三角形的边长可能为无理数,将直角三角形的判断语句改为某边的平方减去另外两边平方和的结果小于0.00001 :Math.abs( c * c - a * a - b * b)<= 0.00001。
缺点
1.使用的if - else 语句较多,导致代码结构复杂。
2.算法是先判断是否构成直角三角形,在判断是否存在两边相等且判断直角三角形的语句没有用到:Math.abs( c * c - a * a - b * b)<= 0.00001,导致输入的含无理数且能构成等腰直角三角形的数据被略过。
3.不能解决直角三角形和等腰三角形的结果输出顺序。
第二次作业——题目集02 7-4
题目集02 7-4 考察对类与对象概念的理解程度.
整体思路介绍
1.要求输入年月日的值(均为整型数),输出该日期的下一天。数据类型均为整型数,设输入 year - month - day。
2.求日期的下一天考虑特殊情况:12.31下一天,平年2.28的下一天,闰年2.29的下一天。
3.当日期的下一天为下一个月一号时要将 month +1,day 改为1;其它时候均为 day +1。
4.定义一个整型数组储存十二个月分别含有多少天,即:这个天数为当月 MaxDay。将输入的数据与最大值进行比对即可。
度量分析
圈复杂度为7

题目提供了三个自定义方法的签名,参数和返回值均已定义
public static void main(String[] args);//主方法
public static boolean isLeapYear(int year) ;//判断year是否为闰年,返回boolean类型
public static boolean checkInputValidity(int year,int month,int day);//判断输入日期是否合法,返回布尔值
public static void nextDate(int year,int month,int day) ; //求输入日期的下一天
核心代码分析
1.isLeapYear 方法返回值为 boolean 类型,判断 year 是否为闰年
定义一个布尔变量p,如果year为闰年则 p 为 true 反之为 false
boolean p = false;
if(((year % 4 == 0 && year % 100 != 0) || year % 400 == 0))
p = true;
else
p = false;
最后return p 即可。
2.checkInputValidity 方法返回值为 boolean 类型,判断输入的year - month - day日期是否合法
定义一个布尔变量date,如果输入数据合法则date 为 true反之为false
年份的合法取值范围为[1820,2020] ,月份合法取值范围为[1,12] ,日期合法取值范围为[1,31]
if((year < 1820)||(year > 2020)||(month < 1)||(month > 12)||(day< 1)||(day > 31))
date = false;
else
date = true;
最后return date 即可。
3.nextDate 方法无返回值,用于求给定日期的下一天,该方法为题目的核心。
定义两个数组用于存储月份的日期的最大值
monthDay[]为平年
monthDay1[]为闰年
两个数组只有在第三项即二月份的日期最大值有不同
考虑到可以将 month 作为数组的下标,所以将数组的第一项设为0。那么 monthDay1[2] = 29
int monthDay[] = new int[]{0,31,28,31,30,31,30,31,31,30,31,30,31};
int monthDay1[] = new int[]{0,31,29,31,30,31,30,31,31,30,31,30,31};
定义 nextDay 用于保存求出来的下一天的 day
int nextDay = 0;
定义 maxDay 用于从数组中提出该月的日期最大值
int maxDay = 0;
判断year是否为闰年,然后读取该月日期的最大值赋值给maxDay.
if(isLeapYear(year)){
maxDay = monthDay1[month];
}
else{
maxDay = monthDay[month];
}
如果 day 等于该月日期的最大值且 month 小于 12,则 month向后+1,nextDay 改为 1
if(day == maxDay && month<12){
month = month + 1;
nextDay = 1;
//System.out.println("hello");检查点
}
(12.31情况)如果day 等于该月日期的最大值且 month = 12,则month 改为 1,year 改为 1,nextDay 改为 1
else if (month == 12){
month = 1;
year = year + 1;
nextDay = 1;
}
如果不是特殊情况则只是 nextDay 改为 day + 1
else
nextDay = day + 1;
System.out.println("Next date is:"+ year + "-" + month + "-" + nextDay);
直接输出即可。
优点
1.每一个类方法的定义都很清晰,所用到的 if - else 语句较少,圈复杂度较低。
2.利用数组储存月份日期的最大值,便于使用 month 作为下标提取最大值。
缺点
1.没有将输入的日期:年-月-日 作为一个类进行面向对象程序设计
题目集02 7-5 求前N天
题目集02 7-5 考察方法之间参数传递
整体思路设计
1.与题目集02 7-4 类似,都是输入相应的 year - month -day ,先判断输入数据的合法性,然后再求该日期的前 n 天。
2.先找出特殊情况:①1月份的前 n天在去年 ②12月份的前 n天(n为负数)在明年 ③闰年2月28的后一天 ④平年2月28的后一天。
3.其它的请况大都是日期的加加减减,顺便带上月份的增减。
度量分析
圈复杂度
核心代码分析
先判断特殊情况:12月份的后 n天,年份变为 year +1
if(month == 12)
{
主要是靠 day 减去当月日期的最大值再减去 n (在自定义方法中 n 为daysAgo)
day = day - daysAgo -31;
month = 1;
year = year + 1;
System.out.println(daysAgo + " days ago is:"+ year + "-" + month + "-" + day);
System.exit(0);
}
判断特殊情况 1 月份的前 n 天,年份 year -1
else if (month ==1 && day - daysAgo <0){
day = 31 +day-daysAgo; 如法炮制,求出在去年12月份的日期。
month = 12;
year =year - 1;
System.out.println(daysAgo + " days ago is:"+ year + "-" + month + "-" + day);
System.exit(0);
}
判断是否为闰年,调用之前定义的数组(数组中保存了每个月份日期的最大值)
else if( isLeapYear(year) ){
前一个月
if(day - daysAgo <1){
day = monthDay1[month-1]+day-daysAgo;
month = month - 1 ;
}
后一个月
else if(day - daysAgo > monthDay1[month]){
day = -daysAgo- (monthDay1[month] - day) ;
month = month + 1;
}
else
day = day - daysAgo;
System.out.println(daysAgo + " days ago is:"+ year + "-" + month + "-" + day);
System.exit(0);
}
PTA测试点
1.”求前0天“ 这个测试点当时没过,是因为输入的 n 为 0,导致求前 n 天的代码不能顺利运行,所以在方法的第一行就判断 n是否为0,并原封不动的将输入的 year - month - day 再输出。
2.有一个无效边界测试的点在测试的时候一直过不了,发现输入一个平年的2月29号竟然能够运行,所以在主方法中加了一个if 语句来判断当 month = 2时,day必须小于28(平年)。
优点
1.求前 n 天的核心方法就是day = -daysAgo- (monthDay1[month] - day) ,不需要判断 n的正负性,直接做减法。
2.通过定义数组保存每个月份的日期最大值,把月份当成下标来找还是很方便的。
缺点
1.还是一样的问题,反复使用 if - else 语句,导致代码执行度不高,执行时间长。
题目集03 7-2 定义日期类
与题目集02 7 - 4的题目是一样的,求输入日期的下一天。但是考察自定义类,并且调用类里的各种方法。
整体思路设计
1.题目给了一个类图,Date 有三个属性 year month day都是 private

2.看上去给了9个方法,但是setter和getter都是使用 IDE 自动写的,相当于我只要写一个判断闰年,检查输入是否合法,和求下一天的方法即可。
度量分析
8个圈复杂度,也还算符合13个复杂度以内吧。
主要是因为代码是迭代的,上次写过的代码可以直接套上来用,稍微调一下就可以了

分析
此题与题目集02 7-4 题目基本一样,只不过需要使用类来进行编程需要考虑到类的封装性的特点
根据题意设计一个 Date 类将 7 - 4中写好的代码包装进类中,并重新调一下参数的传递就行,值得注意的是,类的私有属性不能直接在别的类里直接引用或修改,一定要写一个公共的方法来做这件事。
题目集03 7-3 一元多项式求导、
题目集03 7-3主要考察对字符串的处理能力和正则表达式的应用。很遗憾我在还没有学会正则表达式之前就有了自己的思路。
整体设计
1.通过遍历整个字符串获取“函数”中所有特殊字符和字符X的下标,保存于链表中,因为链表可以不需要在一开始声明长度这样可以在面对未知个含X项的函数中也能保存需要的下标。
2.通过正则表达式匹配字符串中的空格(空白),.replaceAll("\\s*", "") 然后使用replaceAll()方法将空格用 “” 代替达到删除空格的效果。
3.通过两个链表分别保存获取到的系数和指数,主要是最近用链表有点上头,java里的链表都是封装好了的,比c语言用起来顺手多了,毕竟add()方法就直接做好了尾接法,get()方法就直接能够返回指定下标的元素。(泛型才是永远滴神!)不过泛型的数据不能直接和int型的数字进行比较,所以我还是选择声明链表的数据类型为Integer类型。
4.求导的过程到很简单:系数 = 系数 * 指数,指数 = 指数 - 1 就行了。
5.特殊情况:求导前系数为1,指数为1;求导后系数为1,指数为1;然后输出的时候系数指数同时为1时输出“x”,指数为0的时候输出该项的系数,还有第一项的正负号是否要加(第一项的正数系数不用加 “ + ” 号)。
细节设计
1.直接定义两个Integer型的链表用于存分离出来的系数和指数。(我一直觉得我对变量的命名不够规范来着的)
List<Integer> listXI = new ArrayList<Integer>();
List<Integer> listZHI = new ArrayList<Integer>();
2. 定义一个名为 en 的链表用于获取 x 在字符串(已除去空格)中的位置下标。
List<Integer> en = new ArrayList();
3.分析一波:
①.不考虑常数项,当 x 之前的字符不是 * 号而是 ± 号时就可知这一项的系数为 ±1;当 x 之后的字符不是 ^ 号时,这一项的指数就为1。
②.获取系数:从x当前下标向左用charAt()方法进行匹配,匹配到±号时停止,获取当前下标,然后使用substring()方法获取这个正负号与x下标-1之间的字符串,这个字符串在转为Integer型存入listXI即可。
③同理,获取指数也如法炮制,从^号也就是x的后一位开始进行匹配,匹配到±号时停止,获取当前下标,然后使用substring()方法获取这个正负号与x下标-1之间的字符串,这个字符串在转为Integer型存入listZHI即可
④输出的时候注意系数位为整数的时候可能需要在它的前一位输出加号,负数则不需要。
度量分析
61的圈复杂度,这是我写过最烧脑的题目。写代码的时候思路可能不够清晰导致出了很多bug。

我没有定义类,全将代码放在了主函数中。(一开始我定义好了一个有关项的类,但是在传递参数的时候就觉得有点困难)。
核心代码分析
分离系数阶段
for(int i = 0;i <en.size();i++) {
判断x前面否为±号也就是看它的系数是否为±1,然后添加到链表中
if(equation.charAt(en.get(i)-1) =='+') {
listXI.add(1);
}
else if(equation.charAt(en.get(i)-1) =='-') {
listXI.add(-1);
}
else {
从x的所在下标向左找正负号找到正负号则停止循环,此时的k就是正负号的下标
for( k = en.get(i);k > 0 ; k--) {
if(equation.charAt(k) =='+' || equation.charAt(k)== '-'){
break;
}
}
获取正负号到x之间的字符串
str[i] = equation.substring(k+1, en.get(i) - 1);
listXI.add(-Integer.parseInt(str[i]));
如果k代表+号的下标则添加一个正数进入链表
if (equation.charAt(k) == '+') {
listXI.set(i, Integer.parseInt(str[i]));
}
}
}
分离指数阶段
for(int i = 0;i < en.size();i++){
判断x的下一位是否位^号,不是^号,则它的指数为1
if(equation.charAt(en.get(i)+1) !='^') {
listZHI.add(1);
}
else {
en.add(0); //防止出现x在最后一项循环异常停止的情况出现
j = i + 1;
仍然是从x的下标向右找找到正负号就停下来
for( k = en.get(i) + 3; k < en.get(j) ; k++) {
if(equation.charAt(k) == '+' || equation.charAt(k)== '-'){
break;
}
}
str[i] = equation.substring(en.get(i) + 2, k);
listZHI.add(Integer.parseInt(str[i]));
}
}
这道题最关键的地方就是分离系数和指数,在一般的求导过程中,可以先忽略常数项。
在输出之前先将系数指数改变(求导过程)
for(int i = 0; i < listXI.size();i++){
listXI.set(i,listXI.get(i)* listZHI.get(i));
listZHI.set(i,listZHI.get(i)-1);
}一个循环解决求导问题
然后就是输出了。题目给了要求:当输入进来的系数指数存在0时,输出“Wrong Format”
所以遍历系数指数两个链表只要里面有一个时是 0 就输出“Wrong Format”,退出程序就行,这项功能必须在求导之前完成。
有了指数和系数,输出不就伸手就来?
第一项的输出是一个重点,系数为正数时不需要输出加号,而后面的都需要添上加号(负数自带减号)
if(listZHI.get(0) != 0){
if(listXI.get(0) == 1){
这个情况时当指数系数均为1时
System.out.print("x");
}
else{
指数为1时
System.out.print(listXI.get(0)+"*x");
}
}
else{
指数为0时
System.out.print(listXI.get(0));
}
指数系数均不为0时,正常输出即可
if(listZHI.get(0) != 1 && listZHI.get(0) != 0){
System.out.print("^" + listZHI.get(0));
}
从第二项开始输出
for (int i = 1;i < listXI.size();i++){
if(listZHI.get(i) == 0 && listXI.get(i) > 0){
System.out.print(listXI.get(i));
}
else if(listZHI.get(i) ==0 && listXI.get(i) < 0){
System.out.print(listXI.get(i));
}
else{
if(listXI.get(i) == 1 && listZHI.get(i) == 1){
System.out.print("+x");
}
else if(listXI.get(i) == 1){
System.out.print("+x^"+listZHI.get(i));
}
else if(listZHI.get(i) == 1 && listXI.get(i) < 0){
System.out.print(listXI.get(i)+"*x");
}
else if(listZHI.get(i) == 1 && listXI.get(i) > 0) {
System.out.print("+" + listXI.get(i) + "*x");
}
else {
if(listXI.get(i) > 0){
System.out.print("+"+listXI.get(i)+"*x^"+listZHI.get(i));
}
else{
System.out.print(listXI.get(i)+"*x^"+listZHI.get(i));
}
}
}
}
PTA测试分析
1.测试点10 合法综合测试 一直改了很久都没发现有啥合法不合法的。自己来了一个测试点,自以为这个测试点能包含除大数意外的很多情况。但这个点我过了(自定义点),后来发现一次项求导后得到常数,如果得到了正数则不需要输出加号,也就是说在求导后的指数为0的情况中不需要对系数的正负性进行判断然后输出正号。直接输出这个正数即使觉得在数学上不合理也能过这个测试点。
输入样例:
-2* x^-2+ 5*x^12-4*x+ 12
输出样例:
4*x^-3+60*x^11-4
但是针对这个测试点我自定义了一个测试样点
输入样例:
-2*x^1+2*x
输出样例:
理论上应该是输出 -2+2的
但是过点时我把加号去掉 输出 -22,就莫名奇妙的能过测试点,我怀疑老师给的测试数据有误,一直觉得这个测试点不符合数学规范……
2. 测试点08 大数测试 我的想法是使用BigInteger类型作为指数和系数的储存,因为这个类型理论上可以存无穷大的数(只要内存管够)。
但是Biglnteger类型的数字无法与int型的数字比较大小,搞得我写的那些if - else 语句无法正确执行,但是可以用valueOf方法。
3.测试点03 带空格且指数为负值 这个测试点应该是只有一个含x项,因为我的代码只能处理多个含x的项。
优点
1.可以很方便的分离系数和指数(至少我理解起来很轻松)。
2.链表的存在是得代码可以分离一条很长的字符串。
缺点
1.没有使用正则表达式匹配各个项,导致代码写的很啰嗦。
2.没有使用类,也就是没有面向对象程序设计。
3.在含x的项只有一项时,代码无法正确求出它的系数和指数。
4.圈复杂度过高,不利于下次迭代和维护。
小心得
1.说实话,自己写出了不少的bug,主要的原因是没有进行足够严格的测试,PTA上的测试点我又不知道它测了什么,所有的测试数据都是我跟同学一起写的。
2.在跟一位好兄弟写代码的时候,他给了我一个测试数据:-1* x^-2+ 1*x^2- 5*x^-1+12 -6*x^9 +x^9 + 9*x^-4 +9*x^1 +8*x +8*x^1,他使用这个测试数据运行后能够在PTA上拿了90分,然后推荐我试一下,我也通过了。但是在PTA提交后却只有80分,果然:测试数据都是自己根据自己的代码给出来的,用别人的测试数据只能得到自己代码的bug(虽然说能测到自己bug是件好事,但是改bug真的难受)。
学习心得
面向对象程序设计要对题目给的对象先分析它有什么属性然后根据属性再创建类,类里面再创建特定的方法。
根据这一个月的程序设计(主要是研究了10天的一元函数求导问题),我深刻意识到:两个类之间关系的紧密(依赖)程度(关联——调用方法)耦合性越低越好。正所谓“高聚合,低耦合”。
老师上课讲的东西很少,但是很精;java的大多数东西都是靠自学学的,我认为这也是培养能力的一种方式,这才叫主动学习。
第一单元终于结束了。走了些弯路,花了不少时间
希望以后可以更加面向对象。
------------恢复内容结束------------

浙公网安备 33010602011771号