HIT-2022春软件构造-Lab1 实验总结

       这里是哈工大2022春第一次软件构造的实验记录,其实现在实验二已经结束了XD,主要是之前一段时间学校课程排的比较多,还有一门CSAPP的考试。。。这两天终于考完试了,可以有时间写写博客。

       顺便做Lab1的时候其实对java还很不熟悉,当时做的时候很多东西其实都不太理解,现在做完Lab2之后对这门课和java语言的了解都有了一定程度的加深,现在就相当于是对Lab1进行一次复盘了。(以下内容非粗体字主要是对实验具体内容的描述,加粗字体是我个人的一些看法)

1 实验目标概述

  1. 基本的Java OO编程
  2. 基于Eclipse/Idea IDE进行Java编程
  3. 基于Junit的测试
  4. 基于Git的代码配置管理
  • 这次实验其实主要就是简单熟悉一下编写java程序的流程,比如IDE的使用,熟悉java语言,了解Junit测试以及Git。编写的代码其实大部分和一些java的特有的属性比如Lab2强调的OOP和ADT关系不大。
  • 这门课使用java作为实现语言,在此之前我没有正式学习过java,只在较早之前的大一小学期上过java ee课跟着老师敲过一点代码,当时用的是eclipse,但其实相关内容忘得都差不多了。。。这次实验因为java ee课选用了idea,也很推荐使用idea(IDEA,YES!),因为更美观方便(老ide颜控和懒癌晚期了,感觉这两个有点像codeblocks和vscode的区别hhh),之前其实课程上没有讲太多关于语言的内容,所以在做实验前自己简单又重新学习了一些简单的java语法。(但是这次实验课之后就有很多讲解java一些具体实现的课堂内容了,确实学到很多东西)

2 实验环境配置

配置开发环境:IntelliJ IDEA和测试环境Junit。(社区版就ok,有条件(不嫌麻烦)的话使用专业版IDEA当然更好)

(1)  官网下载IntelliJ IDEA,并根据网上的教程官网下载JDK11并配置路径等。

(2)  根据老师给出的教程下载Junit的官方jar包,在File->Settings中选择Plugin,下载JunitGenerator,之后在other settings中选择junit进行配置。并且在IDEA中点击File->Project Structure->Project Libraries,将两个jar包加入。之后就完成了测试环境的配置。

  • 之前从来没接触过Junit这类的测试插件,使用完之后:只能说是真的很好用并且很有用!测试真的很重要,以前那种全写完代码然后找不到错误在哪个部分的痛苦真是历历在目。。。Junit使得能够对每个方法进行测试,哪里有错找哪里,这真的很重要)
  • 看到测试全打勾是快乐的,编写测试的过程是痛苦的,主要是得自己设计测试用例,就是设计可能的输入的情况,这大概就是程序员不能容忍别人没写代码注释和自己写代码注释这两件事吧(x

3 实验过程

3.1 Magic Squares

幻方是一个由n*n数字构成的方形结构,满足每一行、每一列以及每条对角线上的数字之和都相等。

要求编写Java程序isLegalMagicSquare()来计算矩阵的行、列、对角线和并且判断它是否是一个幻方。要求将实验指导书中提供的代码加入MagicSquare类作为静态函数generateMagicSquare()。

  • 这是第一个任务,比较友好,实际上编写方式跟之前的c语言差不多,编写过程中主要值得注意的是从这里开始编写java程序就要注意程序要对输入进行各种检查了,java中有很多捕捉错误,抛出错误的方式,比如try catch语句,assert之类的,更多方法目前课程还没有介绍到,在这门课中学到的很重要的一部分就是这些内容,比如如何提高程序的健壮性等等,强调要编写一个真的能提供给用户使用的程序,从这门课开始,让人真正有一种接触到以后如果要做业务是要怎么做的感觉。

3.1.1 isLegalMagicSquare()

1.  读入并处理文件

以UTF-8编码读入文件,得到一个字符串。之后使用split函数将字符串处理为一个字符串数组,每行存储矩阵一行的内容。

2.处理输入文件的各种特殊情况

按行遍历,依次处理字符串数组中的每个元素(每一行)。

A)存在非\t分割的字符串:

针对由一行的元素组成的字符串,对其使用split通过\t字符进行分割,得到的每个元素存储进入一个字符串数组中,之后将得到的每个元素通过类型转换方法Integer.valueOf转换为int类型,同时使用try catch语句捕捉数字格式不正确的错误,即如果该字符并非数字格式则会报错。那么如果字符串中有字符之间没有通过\t分割,则一定有分割后的字符并非数字格式(包含其他类型的分割字符),此时会抛出错误。

同时在catch语句中自定义报错语句,提示这个错误的原因,核心代码如下:

 

  • 这里主要就是以前基本只写c语言,所以这次实验开始熟悉java强大的升级版for循环:for(类型  x:x所在的数组或其他),唯一的限制就是这个是顺序依次列举元素集合中的每个元素,如果需要取特定下标目前可能还是得使用正常的的for循环。
  • 这里还使用了try catch捕捉错误,这个语句能够自定义捕捉到错误后的反应,比如上图中的打印错误信息

B)行列数不同,并非矩阵:

针对由一行的元素组成的字符串,对其使用split通过\t字符进行分割,计算分割后自动生成的字符串数组的长度,即列数,如果长度不等于之前得到的行数,再分情况看待:如果只有一行(其他情况下,只要出现某行列数与行数不同都同时满足了两个非法判定,因此这里只想到这样处理判定非矩阵),则说明输入的并不是矩阵,这时输出错误提示并返回false,如果不是这种情况,那么输出存在某行行数与列数不同,返回false。

C)输入非正整数:

针对由一行的元素组成的字符串,对其使用split通过\\.和-进行分割,即判断是否出现了小数或者负数,如果出现,那么分割完成后自动生成的数组长度必然不为1,由此可以判断矩阵中是否存在非正整数。

3.将矩阵元素转换为数字并计算行,列,对角线和,由此判断文件输入的矩阵是否为幻方:

首先创建一个新的二维数组,通过之前转换为数字的方法将处理好的行字符串\t分割后转换为数字存储进这个二维数组。之后新建数组来存储行只和,列之和以及对角线之和,计算代码如下:

 

之后先用变量存储第一行之和作为常量标准值,将每一个和与该值对比,如果均相等则说明输入的矩阵是一个幻方,返回true。

3.1.2 generateMagicSquare()

 函数流程图

 

2.生成幻方的流程

将从1到n*n的数字i从幻方第一行中间的数开始赋值,每赋值一次i+1,i如果是边长n的倍数,赋值的行数+1,如果不是倍数,那么如果在第一行,则下一次赋值从最后一行开始,如果不在第一行,下次向上一行。列数如果在最后一列,下次从第一列开始,如果不是,向后一列。直到n*n个数都被赋值进入幻方为止。

3.查看输入偶数和负数的报错分别如下:

 

 

  • java报错应该和之前接触过的python报错类似,得从下往上看,上面的才是真正出错的地方

输入偶数时报错为下标越界。当输入的n为偶数时,在if(i % n == 0)时若i = n会出现数组越界的情况。

输入负数时报错为负的数组大小,因为我们将输入值作为初始化二维数组的大小,故这个值不能为负数。

4.函数扩展:

(1)将产生的magic square写入文件:每一行之间写入\r\n(win下的换行),每一列之间写入\t。

(2)读入奇数和负数时报错,返回false:使用条件判断,之后打印错误,退出。

5.判断生成的是否是幻方

直接在main函数进行测试,可以看出满足条件。

 

 

3.2 Turtle Graphics

本任务给出了一个简单的画图程序,给出了turtle类和相关方法,要求clone已有程序,之后利用turtle相关类按照要求补全turtlesoup的相关方法,完成各种不同的画图要求。

  • 从第二个实验开始就是接触到java编写的模式了,这个任务已经提供了代码雏形,要做的就是对里面的一些方法进行填充,填充的内容其实大部分是一些偏向于数学/算法类的实现。
  • 不过可以注意一下这个原本目录的结构,通俗的说这样的一个包(package)下有很多文件,在写程序的时候这些包之中的内容可以互相引用,这样就可以把不同的类(比如操作画笔的类,操作颜色的类等等)放到不同的文件下,看起来结构很清晰。

3.2.1 Problem 1: Clone and import

在lab0创建仓库过程中,根据新建空仓库后github上的创建指令git init等初始化仓库并加入README.md文件。

之后将新建的仓库从github上clone到本地,首先在个人仓库获取url,之后打开git bash进入想要存放项目的目录,使用命令git clone [url]将项目克隆到本地目录下,接着就可以在idea中打开项目进行修改并提交了。之后在git bash中进入该目录会显示如下:

 

Problem1中要求获取代码,这里由于无法打开外网,故直接获取课程提供的代码,代码不提供url,故选择直接下载,之后将文件复制到相对应目录下。

3.2.2 Problem 3: Turtle graphics and drawSquare

要求实现函数,只能使用forward和turn方法,达成输入指定边长后画出一个正方形的目的。实现方法是通过一个循环实现,每次转角90度一共四次,每次转角后前进指定长度即可。代码如下:

 

3.2.3 Problem 5: Drawing polygons

要求首先完成已知正多边形变数的情况下正多边形内角度数的计算,使用数学公式角度 = 180*(边数-2)/边数即可。之后再完成已知内角计算边数的函数,这里可以利用多边形外角和为360度来计算,同样利用数学公式边数等于360 / (180 -内角)计算即可。

接下来,使用之前写好的方法作为辅助,按照和上一题中画正方形类似的方法直接绘制正多边形即可,按照循环进行旋转角度,每次旋转后前进输入的距离即可。核心代码如下:

3.2.4 Problem 6: Calculating Bearings

这道题目的要求稍微复杂一些,要求为给出两个点(起点,终点)的坐标,并且已知当前方向和海龟初始朝向的偏移角度,要求计算起点朝向到终点朝向需要旋转的角度。

实现思路如下:

1.首先通过Math.atan2函数计算两个点连线与x轴正方向之间的弧度值,之后进行处理变为角度值,代码如下:

 

2.将角度转换为360减去该角度,因为海龟顺时针旋转,该函数计算的偏移角度是逆时针的。3.之后将角度减去90度(若变成负数再360减去处理)再减去初始偏移值,因为海龟初始角度是向上,而函数计算的是和x轴正方向的偏移。4.最后通过判断条件使角度处于0-360度之间。

3.2.5 Problem 7: Convex Hulls

本题要求实现输出输入点集中位于凸包上的点。

凸包问题的解决这里我上网了解了官方文档中给出的比较易于理解的gift-wrapping算法并加以实现。

Gift-warpping算法的主要思路是找到凸包点满足的规律,会发现以任意凸包上的点建立极角坐标系,该点连接其他所有点中,这个逆时针方向第一个凸包点到这个点的极角最小。

在实现过程中,首先找到最左边的点,即遍历所有点,找到x,y坐标都最小的点,这个点在凸包上,之后利用之前计算转角的函数找到连接这个点的角最小的点,加入凸包,之后循环继续,直到返回原点。

找的过程中可以使用临时变量来保存当前转角以供下一轮使用,并且每一轮都要更新记录最小转角和最短距离的点(因为找的是射线到达的角度最小的点,若两个点角度相同则比较哪个点更近)。核心代码如下:

 

3.2.6 Problem 8: Personal art

本题要求自由发挥使用之前的方法绘制图形,并且可以更改画笔颜色。

思路是创建一个循环,再在中间遍历画笔颜色,每选择一个颜色就绘制一个长度逐渐递增的六边形,然后转角30度,画出来的效果如图:

 

  • 这里没什么限制,可以自己整活(bushi,只要注意使用各种颜色,旋转角度,画线长度等就ok

3.2.7 Submitting

1.首先打开git bash,使用命令git status查看当前代码修改情况(本次演示是提交后的某次修改),可以看到changes not staged for commit中红色的即为还未添加到暂存区的有改变的代码。

 

2.之后进行提交,使用命令git add src/P1/MagicSquare.java(这里如果是提交了很多修改文件,可以将文件名改为*,但是可能会提交不需要提交的out文件夹,这时候可以使用命令git rm -rf out删除添加的out文件夹)。

 

3.之后再使用git status查看暂存区的状态,这时改变变成绿色,说明已经添加到暂存区,但是还没有commit:

 

4.接下来commit改变的代码,使用命令commit -m “这里是这次commit的说明”,提交完成后可以看到这次commit造成的变化。

5.最后,使用命令git push origin master将项目代码提交到远程仓库即可。

 

(在实际提交过程中,多次出现疑似网络原因造成的提交失败如errno 10054和time out等失败提示。经上网查询后,可能的解决方案是输入命令git config –global –unset-all remote.origin.proxy。但实际上使用时还是有时能顺利提交有时报错,因此如果提交失败,可以尝试多进行几次push)

6.之后可以进入仓库,刷新查看代码是否顺利提交。还可以在git bash上通过git log查看提交历史。

  • 推荐一些别的提交方法,比如IDEA自带的git提交操作,上网就可以搜索到相关操作,这里是为了熟悉git操作所以选择使用git命令行(提交时可能会出现很多奇怪的错误,我个人认为简单的操作使用IDEA自带的提交界面更简单,但是一些比较复杂的操作可能使用命令行会更好处理(上网容易搜到更多命令行相关内容,以及各种错误的处理)
  • 可以使用github desktop(一些同学的做法,我自己没有尝试,听说很方便)

3.3 Social Network

本任务要求设计一个社交网络图,可以添加人进入社交网络,更新他们之间是否有关系的连线并且计算任意两个人之间的关系(有关系则返回关系长度)。

通过Person类和FriendShipGraph类实现。

  • 这里可以自己设计简单的类和操作这个类得到的实现(社交关系图),Person类在一个文件中,是自己设计一个对象,里面有这个类你想赋给它的属性和一些操作它的方法,实现关系图则是在其中也有一些内部属性,和一些操作这个关系图的方法,这些方法的实现要使用到之前编写的Person类和操作它的方法。

3.3.1 设计/实现FriendshipGraph类

FriendshipGraph类主要是需要实现一个社交网络图,做到加入人,在人与人之间建立连接,以及计算连接的距离。在这个类中有两个全局变量分别是用来存储社交网络中的人的List<Person>和用来存储人名防止人名重复的Set<String>,至于每个人的朋友(社交关系)则是Person自带的属性,在Person类中定义。

1.设计加入人的方法:

方法addVertex用于在图中加入节点(加入人),调用方法传入的参数是一个Person对象,首先通过set的contains方法确定是否重名,是则打印错误并通过System.exit()终止程序,否则将这个人加入社交网络中。

2.设计加入两个人之间的关联的方法addEdge

addEdge直接调用Person类自带的方法添加关系,即将输入的参数中的第二个人加入第一个人的好友列表中。

3.设计计算两个人之间距离的函数getDistance

没有关系则距离为-1。这里采用BFS来实现。首先建立队列people来存储遍历对象,之后建立HashMap<Person, Integer>来记录和输入的第一个人有关系的人以及它们之间的距离。之后将输入的第一个人加入队列,初始化距离,在队列非空时循环,取出队列中的人via,使用Person自带的方法得到via的朋友,之后遍历所有的朋友,如果朋友中有人还没有加入HashMap则将其加入,并将他和第一个人的距离更新为via和第一个人的距离加一,如果这个人就是要找的第二个人,则返回他们之间的距离。这样就实现了通过BFS更新距离并找到第二个人。遍历的核心代码如下:

 

3.3.2 设计/实现Person类

Person类的变量即为该人的姓名和朋友列表。传入姓名可以进行对Person的初始化。这个类的方法则为姓名属性的getter和setter方法,以及返回朋友列表和将一个人加入朋友列表的方法。

3.3.3 设计/实现客户端代码main()

    客户端代码参考实验手册中给出的代码。初始化关系网之后初始化几个Person,之后调用addVertex方法将他们加入关系网,之后调用addEdge将他们的关系初始化,最后调用getDistance一打印一部分两个人之间的关系(距离)。

3.3.4 设计/实现测试用例

1.测试addVertex:

初始化关系网,加入一个人Test进入图节点,之后使用assertEquals检验图的person列表长度是否为1以及第一个元素是否为test。之后再加入第二个人test2检验加入后person长度是否为2。

2.测试addEdge:

初始化并加入两个人test1和test2,之后再两个人之间添加边,由于是可扩展的无向图,因此要添加两次(一次1->2,一次2->1)之后检验另个人的朋友列表中是否有这个人。

3.测试getDistance:

初始化并加入五个人,这五个人中t1,t2,t3相互连接构成一个三角形,t2和t4连接,t5独立,之后依次添加边并测试五个人两两之间的距离是否满足条件。

4.测试结果:

 

测试编写前要有@Test,编写就是设计一些测试用例,之后使用assertEquals方法(参数是目标输出和实际输出)来确定自己的方法编写测试有没有问题

 

总结

这次实验刚上手还是很不熟悉的,但做完之后对java也算有种初步的了解了,过程还是比较有意思的。但这次实验显然只是一个试水,在Lab2中将正式开始接触java的强大功能。

 

变量存
posted @ 2022-05-29 23:27  redTide  阅读(220)  评论(0)    收藏  举报