OOP题目集1-3总结

前言

学习java已经接近一个月了,完成了三次题目集,现对这三次题目集做个总结:

  • 题目集一共8题,因为初学Java,所以题目集一的难度并不大,算是比较简单了,主要涉及到的就是输入、输出,创建数组对象,选择结构与循环结构的简单运用,关于输入、创建对象与循环的一些我觉得值得划重点的地方我会在下面的内容中提到,也算是给自己做个笔记,忘了的时候回来看看更容易激起自己的记忆。

  • 题目集二共5题,首先第一题我就给难住了,第一题如下:

    一个IP地址是用四个字节(每个字节8位)的二进制码组成。请将32位二进制码表示的IP地址转换为十进制格式表示的IP地址输出。

    这题要做对其实很简单,但是我想另辟新径,也正是因为这第一题,这也是我学习正则表达式的契机,这里先mark一下,我后面也会说到关于这题的一个答题加学习的过程。

    然后从第3题开始,就都是一些关于日期的题目,难度相较于题目集一大了很多,不过也只是多花点时间的问题。

  • 题目集三共3题,题目少却正说明这次的题目集需要花费的时间之长,这次作业给了10天作为期限,需要花点时间系统得学习一下正则表达式,另外还有Java集合框架中ArrayList类的用法。

设计与分析

题目集一:7-8

​ 题目如下:

输入三角形三条边,判断该三角形为什么类型的三角形。

输入格式:
在一行中输入三角形的三条边的值(实型数),可以用一个或多个空格或回车分隔,其中三条边的取值范围均为[1,200]。

输出格式:
(1)如果输入数据非法,则输出“Wrong Format”; (2)如果输入数据合法,但三条边不能构成三角形,则输出“Not a triangle”; (3)如果输入数据合法且能够成等边三角形,则输出“Equilateral triangle”; (3)如果输入数据合法且能够成等腰直角三角形,则输出“Isosceles right-angled triangle”; (5)如果输入数据合法且能够成等腰三角形,则输出“Isosceles triangle”; (6)如果输入数据合法且能够成直角三角形,则输出“Right-angled triangle”; (7)如果输入数据合法且能够成一般三角形,则输出“General triangle”。

下图是利用SourceMonitor来度量代码分析出的数据:

2SjKcGE8n4
踩坑心得:

测出来的圈复杂度为17,因为这题的所有判断都是用的 if,if - else 语句,并且还是 if 嵌套。另外,这道题直到最后我都还有个测试点没过,题目集关闭后问了问测试点全过的同学,发现原来是在判断边长值相等时出了问题,因为三角形三边长的声明类型为double,所以在判断两数值是否是否相等时总会存在一点点误差,这时候应该判断这个误差值尽可能得小。

题目集二:7-1

题目如下:

7-1 IP地址转换 (10 分)

一个IP地址是用四个字节(每个字节8位)的二进制码组成。请将32位二进制码表示的IP地址转换为十进制格式表示的IP地址输出。

输入数据要求:

  • 必须为二进制数,即只能输入0或者1
  • 长度必须是32位

违背以上规则程序直接输出Wrong Format

输入格式:

在一行中给出32位二进制字符串。

输出格式:

在一行中输出十进制格式的IP地址,其由4个十进制数组成(分别对应4个8位的二进制数),中间用“.”分隔开。

分析代码:

2SjKcGE8n4
vvAHvrL0cI
String regex = "(0|1)+";
		boolean flag = num.matches(regex);
		if(flag) {
			if(num.length()==32) {
				for(int i=0;i<num.length();i+=8) {
					System.out.print(Integer.parseInt(num.substring(i , i+8) , 2));
					if((i != 24)&&(i % 8 == 0)) {//每八位二进制转换为十进制,中间用.隔开
						System.out.print(".");
					}
				}
			}
			else {
				System.out.println("Wrong Format");
			}
		}
		else {
			System.out.println("Wrong Format");
		}

圈复杂度为6,这个值算是可以说代码比较清晰了。在判断合法输入时,我想到了用正则表达式规范用户输入只能是0或1,其余输入全部算作非法输入。

题目集二:7-4

题目如下:

7-4 求下一天 (30 分)

输入年月日的值(均为整型数),输出该日期的下一天。 其中:年份的合法取值范围为[1820,2020] ,月份合法取值范围为[1,12] ,日期合法取值范围为[1,31] 。 注意:不允许使用Java中和日期相关的类和方法。

要求:Main类中必须含有如下方法,签名如下:

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) ; //求输入日期的下一天

下图是利用SourceMonitor来度量代码分析出的数据:

2SjKcGE8n4
踩坑心得:

这题测试点全部通过,圈复杂度为13,主要是在 checkInputValidity 这个方法中判断也都是用的if语句:

public static boolean checkInputValidity(int year,int month,int day){//判断输入日期是否合法,返回布尔值
		boolean flag = true;
		if(year>=1820&&year<=2020) {
			if(month>=1&&month<=12) {
				if(day>=1&&day<=31) {
					if(!isLeapYear(year)) {
						if(month == 2&&day == 29) {
							flag = false;
						}
					}
					
				}
				else {
					flag = false;
				}
			}
			else {
				flag = false;
			}
		}
		else {
			flag = false;
		}
		return flag;
	}
}

这段代码看起来就显得很累赘,学习了正则表达式之后,完全能用正则表达式代替这些累赘的if判断,并且能大大降低圈复杂度。

题目集二:7-5

题目如下:

7-5 求前N天 (30 分)

输入年月日的值(均为整型数),同时输入一个取值范围在[-10,10] 之间的整型数n,输出该日期的前n天(当n > 0时)、该日期的后n天(当n<0时)。
其中年份取值范围为 [1820,2020] ,月份取值范围为[1,12] ,日期取值范围为[1,31] 。
**注意:不允许使用Java中任何与日期有关的类或方法。

下图是利用SourceMonitor来度量代码分析出的数据:

2SjKcGE8n4
踩坑心得:

这题的圈复杂度简直了,22!看了分析结果,是主方法类中的圈复杂度达22,主方法类中写了一堆if语句判断,自从知道有圈复杂度这一说,真觉得自己代码简直不忍直视。这题测试点全过了,题目并不复杂,无非就是判断,但是该怎样写判断才能把代码的圈复杂度降低?我又想到了正则表达式。恰巧的是,在题目集三 7-2 中,又出现了一道关于日期类的题,在那题我用上了正则表达式。

话不多说,来看看题目集三。

题目集三:7-2

7-2 定义日期类 (28 分)

定义一个类Date,包含三个私有属性年(year)、月(month)、日(day),均为整型数,其中:年份的合法取值范围为[1900,2000] ,月份合法取值范围为[1,12] ,日期合法取值范围为[1,31] 。 注意:不允许使用Java中和日期相关的类和方法,否则按0分处理。

要求:Date类结构如下图所示:

类图.jpg

输入格式:

在一行内输入年月日的值,均为整型数,可以用一到多个空格或回车分隔。

输出格式:

  • 当输入数据非法及输入日期不存在时,输出“Date Format is Wrong”;
  • 当输入日期合法,输出下一天,格式如下:Next day is:年-月-日

按照题目要求,写了两个类,一个是主方法类,一个是Date类。本次作业用上了面向对象设计的三大基础特性之一:封装.

下面是利用SourceMonitor分析代码:

2SjKcGE8n4

可以看到,主方法类的圈复杂度为1,因为主方法中并无条件判断与循环,只是单纯的声明定义于调用方法.

public class Main {

	public static void main(String[] args) {
		Scanner input = new Scanner(System.in);
		int year = input.nextInt();
		int month = input.nextInt();
		int day = input.nextInt();
		Date date = new Date(year,month,day);
		date.getNextDate();
	}

}

主要来看Date类的分析数据。

2SjKcGE8n4

圈复杂度为11。这次题目在写checkInputValidity()方法来判定合法输入时,我用上了正则表达式,当然也不可避免的用了一个if-else。直接贴代码:

public boolean checkInputValidity() {
		boolean flag = false;
		String date = String.valueOf(this.year)
						+String.valueOf(this.month)
						+String.valueOf(this.day);
		/*年份的合法取值范围为[1900,2000] ,月份合法取值范围为[1,12] ,日期合法取值范围为[1,31] */
		if(isLeapYear(this.year)) {//当是闰年时
			String regex = "(19[0-9]{2}|2000)"
					+ "((([13578]|1[02])([1-9]|[12][0-9]|3[01]))"
					+ "|(([469]|11)([1-9]|[12][0-9]|30))"
					+ "|(2([1-9]|[1][0-9]|2[0-9])))";
			flag = date.matches(regex);
			
		}
		else {//平年
			String regex = "(19[0-9]{2}|2000)"
					+ "((([13578]|1[02])([1-9]|[12][0-9]|3[01]))"
					+ "|(([469]|11)([1-9]|[12][0-9]|30))"
					+ "|(2([1-9]|[1][0-9]|2[0-8])))";
			flag = date.matches(regex);
		}
		return flag;
	}

用了正则表达式来判断合法输入让代码看起来简单了很多,也不用一直用if或if-else来进行判断,这样不仅让代码冗长拖沓,而且毫无疑问得会使圈复杂度增大不少。这次对正则表达式的运用只是小试牛刀,在接下来的题目集三7-3中让我更加深入、系统得学习了正则表达式的相关内容。

题目集三:7-3

题目如下:

7-3 一元多项式求导(类设计) (50 分)

编写程序性,实现对简单多项式的导函数进行求解。详见作业指导书。 OO作业3-3题目说明.pdf

这题其实是要求用上类的设计来完成,然而我只用了两个类,一个主类,一个Item类(项类),在Item类中

进行了求导,以下是用PowerDesigner生成的类图:

2SjKcGE8n4

分析一下我这题的思路:**

先贴出代码:

public class Main {
	public static void main(String[] args) {
		Item item = new Item();
		Scanner input = new Scanner(System.in);
		ArrayList<String> list = new ArrayList();
		String string = input.nextLine();
		//滤掉所有空格
		String newString = string.replaceAll("\\s", "");
		if(item.checkValidity(newString)) {
			Matcher m = item.match(newString, item.pattern3);
			item.find_add(m, list);//各项分别放进list
		}
		else {
			System.out.println("Wrong Format");
			System.exit(0);
		}

		item.removeConstant(list);//把常数项从数组列表中移除
		
		item.deriv(list);//进行求导
	}
}

其实,从主类看,我的思路还是很明确的,首先输入表达式字符串(可带空格),然后滤掉所有空格,针对滤掉空格的字符串判断是否为合法输入,如果是,则把表达式中的每一项装进ArrayList中,否则输出Wrong Format,然后遍历ArrayList,把常数项从数组列表中移除,再针对移除了常数项的ArrayLIst遍历每一项进行求导运算。这就是我的主要思路。

踩坑心得:

在处理滤掉所有空格时,我首先想到的是用split()函数按照空格分割,然后把分割开的每一项放入字符串数组中:

String newString = string.split(" ");

然而很快就发现问题了,这样只能按照一个空格来分割,而题目要求表达式字符串中可输入多个空格。于是我开始转换思路,那么有没有能把表达式中的空格都替换掉的函数呢?经过我一番对java的String类函数查找,发现replace()或replaceAll()就能解决在这个问题。

string.replace(char oldChar, char newChar) 或 string.replace(CharSequence oldText, CharSequence newText)

返回一个新的字符串

string.replaceAll(String regex,String replacement)

使用给定的 replacement 字符串替换此字符串匹配给定的正则表达式的每个子字符串。

现在回过头来看这题时,已经知道其实split()函数是能按照多个空格来分割的,只要结合正则表达式使用:

String[] arr = str.split("\\s+");

然后是写了个checkValidity(String string)方法来判定非法输入,一开始我只是写了一个正则表达式来判断滤去空格的表达式字符串:

2SjKcGE8n4

但是这样的话,系数与指数为 0时也会匹配进去,于是在判断输入合法之前,我又增加了一个判断系数或指数为零的情况,当系数与指数为 0时return false,输出wrong format。

测试点中的”第一项系数为负值“和”大数测试“我始终未能通过,按照测试点描述的那样,我把符合第一项系数为负值的表达式输入后是能得到正确的求导结果的,另外,大数测试,我也相应的用上了BigInteger,可还是都没通过测试点。

最后贴出这题的代码分析:

2SjKcGE8n4

主要看Item类:

2SjKcGE8n4

圈复杂度为14,属于复杂范围了。经老师点拨后,我对类的设计有了新的想法,表达式中的项都可以是一个类,变量项、常数项,这些都可以写成类,这样也更能体现面向对象设计的封装性,代码的圈复杂度也能相应的得到降低。我将在下次作业中付诸于实现。

总结:

代码圈复杂度:

定义
圈复杂度 (Cyclomatic complexity) 是一种代码复杂度的衡量标准,也称为条件复杂度或循环复杂度,它可以用来衡量一个模块判定结构的复杂程度,数量上表现为独立现行路径条数,也可理解为覆盖所有的可能情况最少使用的测试用例数。简称 CC 。其符号为 VG 或是 M 。

计算方法

有一个简单的计算方法,圈复杂度实际上就是等于判定节点的数量再加上1。向上面提到的:if elseswitch casefor循环、三元运算符,&&,|| 等等,都属于一个判定节点。

质量标准

代码复杂度低,代码不一定好,但代码复杂度高,代码一定不好。

圈复杂度 代码状况 可测性 维护成本
1-10 清晰,结构化
10~20 复杂
20~30 非常复杂
>30 不可读 不可测 非常高

参考文章链接:https://blog.csdn.net/qq_35211818/article/details/104483333

for each循环:

使用增强的for语句来循环数组:

class EnhancedForDemo{
	public static void main(String[] args){
		int[] numbers = {1,2,3,4,5,6,7,8,9,10};
		for(int item : numbers){
			System.out.println("count is "+item);
		}
	}
}
/*for each 循环语句的循环变量将会遍历数组中的每个元素而不是下标值。
*有一个更加简单的凡是可以打印数组中的所有值,即利用Arrays类中的toString方法。
*调用Arrays.toString(a);返回一个包含数组元素的字符串,这些元素包围在中括号内,并用逗号分隔
*例如:"[2,3,4,5,7,11,13]"
*要想打印数组,只需要调用
*System.out.println(Arrays.toString(a));
*/

大数

BigInteger 转 int 方法:

import java.math.BigInteger;

public class Test {
    public static void main(String[] args) {
        BigInteger bi = new BigInteger("100");

       //第一种方法转
        int a = Integer.valueOf(bi.toString());

       //第二种方法转
        int b =bi.intValue();
        System.out.println(a);
        System.out.println(b);
    }
}
Scanner类nextLine()和next()

Scanner实现字符串的输入有两种方法,一种是next(),一种nextLine()。

next():一定要读取到有效字符后才可以结束输入,对输入有效字符之前遇到的空格键、Tab键或Enter键等结束符,next()方法会自动将其去掉,只有在输入有效字符之后,next()方法才将其后输入的空格键、Tab键或Enter键等视为分隔符或结束符。

简单地说:
next()查找并返回来自此扫描器的下一个完整标记。完整标记的前后是与分隔模式匹配的输入信息,所以next方法不能得到带空格的字符串。

nextLine()方法的结束符只是Enter键,即nextLine()方法返回的是Enter键之前的所有字符,它是可以得到带空格的字符串的。

创建数组对象:
  • java创建数组对象的方法:
  int[] arr=new int[6];
  int[] arr={1,2,3,4};
  int[] arr= new int[]{1,2,3,4,5};
  • 创建String数组的方法:
String[] arr = new String[10];  //创建一个长度为10的String 类型数组。 
String arr[] = new String[10];   
String arr[] = {"张三","李四"};
  • 创建String对象:
1、直接使用" "双引号创建; 			 String s1 = "first";
2、使用new String()创建;				String s2 = new String();
3、使用new String("string")创建;		String s3 = new String("string");
4、采用重载的字符串连接符创建; 		   String s4 = "first" + "second";
ArrayList:
  1. 创建对象:与其他普通的引用数据类型创建方式完全相同,但要指定容器中存储的数据类型:

    ArrayList<要存储元素的数据类型> 变量名 = new ArrayList<要存储元素的数据类型>();

  2. ArrayList类常用方法:

方法 说明
public ArrayList() 创建一个空的集合对象
public boolean remove(Object o) 删除指定的元素,返回删除是否成功
public E remove(int index) 删除指定索引处的元素,返回被删除的元素
public E set(int index,E element) 修改指定索引处的元素,返回被修改的元素
public E get(int index) 返回指定索引处的元素
public int size() 返回集合中的元素的个数
public boolean add(E e) 将指定的元素追加到此集合的末尾
public void add(int index,E element) 在此集合中的指定位置插入指定的元素
posted @ 2021-04-03 18:56  N3D2Y  阅读(154)  评论(0)    收藏  举报