第三次博客

一、前言

​ 对于PTA题目集7和8,主要是对课程成绩统计程序的继续迭代,还有一些关于其他知识点的小题目,比如HashMap。难度也是集中在课程成绩统计程序上,第一次的课程成绩统计程序当时没有来得及写完,后来老师开了补练,然后重新整理了思路把第一次的写出来了,所以在写后续的二三的时候就没有那么无从下手。总体来说,整体的难度感觉没有前面的难。主要涉及到的知识点就是就是抽象类以及类的继承之间的关系还有HashMap使用以及已有接口的使用还有自定义接口。

​ 两次作业的题量都是4到5题左右,都是一道难题加上几道基础题。

  • 接口

1.Java接口的定义

接口在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明。一个类通过继承接口的方式,从而来继承接口的抽象方法。

  接口并不是类,编写接口的方式和类很相似,但是它们属于不同的概念。类描述对象的属性和方法。接口则包含类要实现的方法。

  除非实现接口的类是抽象类,否则该类要定义接口中的所有方法。

  接口无法被实例化,但是可以被实现。一个实现接口的类,必须实现接口内所描述的所有方法,否则就必须声明为抽象类。另外,在 Java 中,接口类型可用来声明一个变量,他们可以成为一个空指针,或是被绑定在一个以此接口实现的对象。

二、设计与分析

7-3 课程成绩统计程序-2

题目(新增部分):

实验的总成绩等于课程每次实验成绩的平均分

必修课的考核方式必须为考试,选修课可以选择考试、考察任一考核方式。实验课的成绩必须为实验。

实验课程成绩信息包括:学号、姓名、课程名称、实验次数、每次成绩

实验次数至少4次,不超过9次

实验课程信息格式:学号+英文空格+姓名+英文空格+课程名称+英文空格+实验次数+英文空格+第一次实验成绩+...+英文空格+最后一次实验成绩

代码类图:

分析过程:

类的构造:

成绩一的代码是创建了表示课程、成绩、班级和学生的类(Course、Score、Class、Student)。其中成绩类是一个抽象类,然后考试成绩和考察成绩都是继承成绩类。然后还定义了输入和输出类。

本次的代码在原来的基础上增加了实验成绩类。其他两类成绩类似,它也是继承成绩类。但是在成绩类中新添加了一个类型属性。在这个地方因为还是用了继承但是因为实验成绩的构造其实与其他两类成绩的差别还是有点大的。所以这里用的继承导致代码中有些部分其实是多余的。但是又不能没有,因为成绩类是一个抽象类,方法用有计算期中成绩和期末成绩的方法,所以实验成绩类中也必须有这个。

其中Experiment_score类的属性包括

  • scoresString:保存实验成绩的字符串
  • experimentNumber:信息中给定的实验次数
  • experimentScoreNumber:实际实验次数
  • TotalGrade:总成绩
  • scores:保存实验成绩的数组

方法包括:

  1. Experiment_score 类的构造方法接受实验次数和实验成绩字符串作为参数,然后调用了 getScoresCount 方法来计算实际的实验次数,并初始化了 scores 数组。
  2. Experiment_score 类包含了 getUsualGradegetFinal_score 方法的重写,它们目前都返回了固定的值。
  3. Experiment_score 类提供了 getExperimentScoreNumber 方法用来获取实验成绩的个数。
  4. getTotalGrade 方法根据实验成绩字符串计算了总成绩,并返回了平均分。
  5. getScoresCount 方法用来计算实际的实验次数。
  6. check_Experiment_score 方法用来判断输入的成绩数量和实际的实验次数是否匹配。

在Input类中,也要加入实验成绩输入的相关的正则表达式。

处理代码的部分逻辑大体不变,但需要加入实验类成绩的处理函数

7-4 动物发声模拟器(多态)

题目:

设计一个动物发生模拟器,用于模拟不同动物的叫声。比如狮吼、虎啸、狗旺旺、猫喵喵……。
定义抽象类Animal,包含两个抽象方法:获取动物类别getAnimalClass()、动物叫shout();
然后基于抽象类Animal定义狗类Dog、猫类Cat和山羊Goat,用getAnimalClass()方法返回不同的动物类别(比如猫,狗,山羊),用shout()方法分别输出不同的叫声(比如喵喵、汪汪、咩咩)。
最后编写AnimalShoutTest类测试,输出:
猫的叫声:喵喵
狗的叫声:汪汪
山羊的叫声:咩咩

代码类图:

代码分析:

//动物发声模拟器
public class Main {
    public static void main(String[] args) {
        Cat cat = new Cat();
        Dog dog = new Dog();
        Goat goat = new Goat();
        speak(cat);
        speak(dog);
        speak(goat);
    }

    //定义静态方法speak()
    public static void speak(Animal animal) {
        String animalClass = animal.getAnimalClass();
        String shout = animal.shout();
        System.out.println(animalClass + "的叫声:" + shout);
    }
}
//定义抽象类Animal
abstract class Animal{
    public abstract String getAnimalClass();
    public abstract String shout();
}
//基于Animal类,定义猫类Cat,并重写两个抽象方法
class Cat extends Animal{
    public String getAnimalClass() {
        return "猫";
    }
    public String shout() {
        return "喵喵";
    }
}
//基于Animal类,定义狗类Dog,并重写两个抽象方法
class Dog extends Animal{
    public String getAnimalClass() {
        return "狗";
    }
    public String shout() {
        return "汪汪";
    }
}
//基于Animal类,定义山羊类Goat,并重写两个抽象方法
class Goat extends Animal{
    public String getAnimalClass() {
        return "山羊";
    }
    public String shout() {
        return "咩咩";
    }
}

  1. Animal 是一个抽象类,里面包含了两个抽象方法 getAnimalClassshout,用于获取动物类别和发出叫声。
  2. CatDogGoat 类分别继承了 Animal 类,并重写了抽象方法 getAnimalClassshout。这样做的好处是可以根据具体的动物类别来返回不同的类别和叫声,体现了多态的特性。
  3. Main 类中定义了静态方法 speak,该方法接收一个 Animal 对象作为参数,然后调用 getAnimalClass 方法获取动物类别,调用 shout 方法获取叫声,并将其输出。这样做很好地体现了多态的应用,无需关心具体是哪种动物,只需要调用统一的方法即可输出叫声。
  4. Mainmain 方法中,创建了 CatDogGoat 对象,并分别调用了 speak 方法输出了它们的叫声,符合题目要求。

7-2 课程成绩统计程序-3

题目:

课程成绩统计程序-3在第二次的基础上修改了计算总成绩的方式,

要求:修改类结构,将成绩类的继承关系改为组合关系,成绩信息由课程成绩类和分项成绩类组成,课程成绩类组合分项成绩类,分项成绩类由成绩分值和权重两个属性构成。

完成课程成绩统计程序-2、3两次程序后,比较继承和组合关系的区别。思考一下哪一种关系运用上更灵活,更能够适应变更。

题目最后的参考类图未做修改,大家根据要求自行调整,以下内容加粗字体显示的内容为本次新增的内容。

1、输入:

包括课程、课程成绩两类信息。

课程信息包括:课程名称、课程性质、考核方式、分项成绩数量、每个分项成绩的权重。

考试课信息格式:课程名称+英文空格+课程性质+英文空格+考核方式+英文空格+平时成绩的权重+英文空格+期末成绩的权重

考察课信息格式:课程名称+英文空格+课程性质+英文空格+考核方式

实验课程信息格式:课程名称+英文空格+课程性质+英文空格+考核方式+英文空格+分项成绩数量n+英文空格+分项成绩1的权重+英文空格+。。。+英文空格+分项成绩n的权重

代码类图:

代码分析:

相比于成绩二,成绩三不仅加入了权重值导致计算成绩方面以及输入上面发生了大的改变。同时题目要求将原来的成绩类的继承关系改成组合关系。

在成绩Course类中加入权重的一个动态数组。

private List<Double> weights;

这里我们要加入一个新的类,ScoreItem,分项成绩类。

  • ScoreItem 类包含了两个私有属性:scoreweight,分别表示分项成绩的分数和权重。
  • 构造方法 ScoreItem(int score, double weight) 用于初始化分项成绩对象,通过传入分数和权重来设置对象的初始状态。
  • getScore 方法用于获取分项成绩的分数。
  • getWeight 方法用于获取分项成绩的权重。

将成绩类作为其他成绩类的属性,以达到组合的效果。同样因为考试,考察,实验的成绩数量都是不一样的,所以我们选择用一个ScoreItem类的动态数组来保存每个成绩。根据数组的长度也能够很容易判断出是为这些当中的考试,考察,实验哪一个。同时我们就不用再将考试,考察,实验这三个的成绩个分为一个类。使得代码也变得更加灵活。

在Input类中,识别是识别成绩消息的正则表达式也要做出相应的改变。

对应成绩的处理代码也要相应做出改变。

在不同的考核方式下,对应不同的处理逻辑:

  • 对于考试类型的课程,判断输入的 courseParts 数组长度为5,并且课程属性为 “考试”,然后根据权重构造平时成绩和期末成绩的 ScoreItem 对象,然后将其添加到 scoreItems 列表中,并创建 Score 对象,然后创建 Choose_course 对象,并添加到 chooseCourseList 中。这部分代码还包括了对是否已存在相同选择课程的判断。
  • 对于考察类型的课程,判断输入的 courseParts 数组长度为4,并且课程属性为 “考察”,然后根据权重构造期末成绩的 ScoreItem 对象,然后将其添加到 scoreItems 列表中,并创建 Score 对象,然后创建 Choose_course 对象,并添加到 chooseCourseList 中。这部分代码同样包括了对是否已存在相同选择课程的判断。
  • 对于实验类型的课程,根据权重的数量判断输入的实验成绩数量,如果数量匹配,则根据权重构造实验成绩的 ScoreItem 对象,并将其添加到 scoreItems 列表中,并创建 Score 对象,然后创建 Choose_course 对象,并添加到 chooseCourseList 中。这部分代码同样包括了对是否已存在相同选择课程的判断。如果数量不匹配,则输出错误信息 “学号 姓名 : access mode mismatch”。
if(course != null){//课程存在

                List<ScoreItem> scoreItems = new ArrayList<>();
                if(course.getText_type().equals("考试") && courseParts.length == 5){
                    ScoreItem usual_score = new ScoreItem(Integer.parseInt(courseParts[3]),course.getWeights().get(0));
                    ScoreItem final_score = new ScoreItem(Integer.parseInt(courseParts[4]),course.getWeights().get(1));
                    scoreItems.add(usual_score);
                    scoreItems.add(final_score);
                    Score score = new Score(scoreItems);
                    Choose_course chooseCourse = new Choose_course(course,student,score);
                    if (!searchChooseCourse(student_name, course_name))
                        chooseCourseList.add(chooseCourse);
                }else if(course.getText_type().equals("考察") && courseParts.length == 4){
                    ScoreItem final_score = new ScoreItem(Integer.parseInt(courseParts[3]),course.getWeights().get(0));
                    scoreItems.add(final_score);
                    Score score = new Score(scoreItems);
                    Choose_course chooseCourse = new Choose_course(course,student,score);
                    if (!searchChooseCourse(student_name, course_name))
                        chooseCourseList.add(chooseCourse);
                } else if (course.getText_type().equals("实验")) {
                    int experimentNumber = course.getWeights().size();
                    int practical_experimentNumber = getNumberScore(message);//实际输入成绩的数量
                    if(experimentNumber == practical_experimentNumber){
                        for(int i = 0; i < experimentNumber ;i++){
                            ScoreItem scoreItem = new ScoreItem(Integer.parseInt(courseParts[3+i]),course.getWeights().get(i));//获取每个成绩
                            scoreItems.add(scoreItem);//加入分项成绩数组
                        }
                        Score score = new Score(scoreItems);
                        Choose_course chooseCourse = new Choose_course(course,student,score);
                        if (!searchChooseCourse(student_name, course_name))
                            chooseCourseList.add(chooseCourse);
                    }else {
                        System.out.println(student_ID + " " + student_name + " : access mode mismatch");
                    }
                } else {
                    System.out.println(student_ID + " " + student_name + " : access mode mismatch");
                }
            }else {
                System.out.println(course_name + " does not exist");
            }

比较继承和组合关系的区别

​ 通过这两次的代码编写,我觉的将成绩类的继承关系改为组合关系,能够更灵活地处理成绩信息的变更。尤其是对于不同类型的课程以及不同的考核方式,组合关系可以更好地满足需求变更的灵活性。因为每种课程和考核方式的分项成绩计算方式不同,使用组合关系可以更方便地定制每种类型的课程和考核方式的成绩计算逻辑。在原来继承关系的代码中,实验成绩类因为是继承了成绩类导致它的代码很多事本来不需要的,功能上不起什么作用,但是逻辑上必须把那些不用的方法加上。这里可以明显地看出继承关系没有特别的灵活。

​ 继承的特点是可以实现代码的复用,子类可以继承父类的属性和方法,从而减少重复代码。但是继承也有其局限性,一旦继承了某个类,子类就和父类产生了紧耦合,导致了继承关系的脆弱性。当父类修改时,子类也会受到影响,可能会导致代码的脆弱性和耦合性增强。

​ 而组合关系则是通过一个类包含另一个类的对象来实现代码的复用,这种关系更加灵活和松耦合,因为两个类之间的关联不是硬性的继承关系。使用组合关系,当一个类的功能需要变更时,只需要修改组合的类,不会影响其他类。因此,组合关系更加灵活,更能够适应变更。

7-4 jmu-Java-04面向对象进阶-03-接口-自定义接口ArrayIntegerStack

题目:

定义IntegerStack接口,用于声明一个存放Integer元素的栈的常见方法:

public Integer push(Integer item);
//如果item为null,则不入栈直接返回null。如果栈满,也返回null。如果插入成功,返回item。

public Integer pop();   //出栈,如果为空,则返回null。出栈时只移动栈顶指针,相应位置不置为null
public Integer peek();  //获得栈顶元素,如果为空,则返回null.
public boolean empty(); //如果为空返回true
public int size();      //返回栈中元素个数

定义IntegerStack的实现类ArrayIntegerStack,内部使用数组实现。创建时,可指定内部数组大小。

main方法说明

  1. 输入n,建立可包含n个元素的ArrayIntegerStack对象
  2. 输入m个值,均入栈。每次入栈均打印入栈返回结果。
  3. 输出栈顶元素,输出是否为空,输出size
  4. 使用Arrays.toString()输出内部数组中的值。
  5. 输入x,然后出栈x次,每次出栈均打印。
  6. 输出栈顶元素,输出是否为空,输出size
  7. 使用Arrays.toString()输出内部数组中的值。

思考

如果IntegerStack接口的实现类内部使用ArrayList来存储元素,怎么实现?测试代码需要进行什么修改?

代码类图:

代码分析:

  1. 在主方法main中,首先创建了一个Scanner对象用于读取输入。
  2. 然后通过scanner读取一个整数,作为整数栈的大小,创建了一个ArrayIntegerStack对象stack。
  3. 接着读取一个整数count,表示后续要push的元素个数,然后通过循环读取并调用stack的push方法将这些整数push进栈,并输出push的结果。
  4. 接着输出栈顶元素的值、栈是否为空的状态和栈的大小。
  5. 然后通过循环读取一个整数popCount,表示接下来要进行pop操作的次数,再通过循环调用pop方法将元素pop出栈,并输出pop的结果。
  6. 最后再次输出栈顶元素的值、栈是否为空的状态和栈的大小,以及整个栈的元素情况。

思考

如果要使用ArrayList来存储元素,可以对ArrayIntegerStack类进行修改,将内部的数组替换为ArrayList。下面是相应的修改:

import java.util.ArrayList;

class ArrayIntegerStack implements IntegerStack {
    private ArrayList<Integer> stack;

    public ArrayIntegerStack() {
        stack = new ArrayList<>();
    }

    @Override
    public Integer push(Integer item) {
        if (item == null) {
            return null;
        }
        stack.add(item);
        return item;
    }

    @Override
    public Integer pop() {
        if (stack.isEmpty()) {
            return null;
        }
        return stack.remove(stack.size() - 1);
    }

    @Override
    public Integer peek() {
        if (stack.isEmpty()) {
            return null;
        }
        return stack.get(stack.size() - 1);
    }

    @Override
    public boolean isEmpty() {
        return stack.isEmpty();
    }

    @Override
    public int size() {
        return stack.size();
    }

    @Override
    public String toString() {
        return stack.toString();
    }
}

在这个修改后的ArrayIntegerStack类中,使用了ArrayList来存储整数栈的元素。push将元素添加到ArrayList的末尾,pop从ArrayList的末尾删除元素,peek直接获取ArrayList末尾的元素,isEmpty和size方法也直接调用ArrayList的对应方法。

在测试代码中,由于ArrayIntegerStack类的实现改变了内部数据结构,需要对创建ArrayIntegerStack对象的方式进行修改,原来的传入整数大小的方式应该被移除。修改后的测试代码如下:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        ArrayIntegerStack stack = new ArrayIntegerStack();

        int count = scanner.nextInt();
        for (int i = 0; i < count; i++) {
            System.out.println(stack.push(scanner.nextInt()));
        }

        System.out.println(stack.peek() + "," + stack.isEmpty() + "," + stack.size());
        System.out.println(stack.toString());

        int popCount = scanner.nextInt();
        for (int i = 0; i < popCount; i++) {
            System.out.println(stack.pop());
        }

        System.out.println(stack.peek() + "," + stack.isEmpty() + "," + stack.size());
        System.out.println(stack.toString());
    }
}

在修改后的测试代码中,创建ArrayIntegerStack对象时不再需要传入整数大小,并且输入的操作方式保持不变,因为使用了ArrayList来存储元素,无需事先确定栈的大小。

5.期末考试

题目:

7-1 立体图形问题

编程求得正方体和正三棱锥的表面积和体积,要求必须体现扩展性(继承)和多态性。

其中,display(Solid solid)方法为定义在Main类中的静态方法,作用为体现程序的多态性。

注:正三棱锥的体积计算公式为底面积*高/3。

输入一个实型数,分别作为正方体的边长和正三棱锥的边长。

分别输出正方体的表面积、体积以及正棱锥的表面积和体积。保留两位小数,建议使用String.format(“%.2f”,value)

进行小数位数控制。

7-2 魔方问题

问题描述:本问题中的魔方有两种,一种是正方体魔方,一种是正三棱锥魔方,其中,正方体或正三棱锥魔方是由单元正方体或正三棱锥组成,单元正方体或正三棱锥的个数由阶数(即层数)决定,即魔方边长=阶数*单元边长。利用“立体图形”问题源码,实现如下功能:

魔方有三个属性:颜色,阶数,类型(正方体魔方、正三棱锥魔方),程序要求输出魔方的颜色、表面积和体积。

其中,display(RubikCube cube)方法为Main类中定义的静态方法,用户输出魔方的信息,用于体现多态性。

输入格式:

第一部分:正方体魔方颜色、阶数、单元正方体边长,以空格或回车分隔;

第二部分:正三棱锥魔方颜色、阶数、单元正三棱锥边长,以空格或回车分隔。

输出格式:

正方体魔方颜色

正方体魔方表面积

正方体魔方体积

正三棱锥魔方颜色

正三棱锥魔方表面积
正三棱锥魔方体积

注:小数点保留两位

7-3 魔方排序问题

在魔方问题的基础上,重构类设计,实现列表内魔方的排序功能(按照魔方的体积进行排序)。

输入魔方类型(1:正方体魔方;2:正三棱锥魔方;0:结束输入)

魔方颜色、魔方阶数、魔方单元正方体、正三棱锥边长

按魔方体积升序输出列表中各魔方的信息(实型数均保留两位小数),输出样式参见输出样例。

7-4 销售步枪问题(附加题)

前亚利桑那州境内的一位步枪销售商销售密苏里州制造的步枪机(lock)、枪托(stock)和枪管(barrel)。枪机卖45美元,枪托卖30美元,枪管卖25美元。销售商每月至少要售出一支完整的步枪,且生产限额是销售商在一个月内可销售70个枪机、80个枪托和90个枪管。

根据每个月的销售情况,计算销售商的佣金(提成)算法如下:

  • 不到(含)1000美元的部分为10%;
  • 1000(含)~1800美元的部分为15%;
  • 超过1800美元的部分为20%。

佣金程序生成月份销售报告,汇总销售商的销售总额和佣金。

代码分析:

因为第三题差不多是在前两题的基础上进行迭代的,所以这里对第三题的代码进行分析。

第三题:

import java.util.ArrayList;
import java.util.Comparator;
import java.util.Scanner;

// 魔方抽象类,并实现 Comparable 接口
abstract class RubikCube implements Comparable<RubikCube> {
    protected String color; // 魔方颜色
    protected int layer; // 阶数
    protected double unitSideLength; // 单元边长

    // 构造方法
    public RubikCube(String color, int layer, double unitSideLength) {
        this.color = color;
        this.layer = layer;
        this.unitSideLength = unitSideLength;
    }

    // 获取魔方表面积的抽象方法
    public abstract double getSurfaceArea();

    // 获取魔方体积的抽象方法
    public abstract double getVolume();

    // 获取魔方颜色
    public String getColor() {
        return color;
    }

    // 实现 Comparable 接口的 compareTo 方法
    @Override
    public int compareTo(RubikCube other) {
        double volumeDiff = this.getVolume() - other.getVolume();
        if (volumeDiff < 0) {
            return -1;
        } else if (volumeDiff > 0) {
            return 1;
        }
        return 0;
    }
}

// 正方体魔方类
class SquareCube extends RubikCube {

    // 构造方法
    public SquareCube(String color, int layer, double unitSideLength) {
        super(color, layer, unitSideLength);
    }

    // 实现获取表面积方法
    @Override
    public double getSurfaceArea() {
        double sideLength = layer * unitSideLength; // 魔方边长
        return 6 * sideLength * sideLength; // 计算表面积
    }

    // 实现获取体积方法
    @Override
    public double getVolume() {
        double sideLength = layer * unitSideLength; // 魔方边长
        return sideLength * sideLength * sideLength; // 计算体积
    }
}

// 正三棱锥魔方类
class RegularPyramidCube extends RubikCube {
    private final double SIDE_LENGTH_RATIO = Math.sqrt(3) / 2; // 正三棱锥的边长比例

    // 构造方法
    public RegularPyramidCube(String color, int layer, double unitSideLength) {
        super(color, layer, unitSideLength);
    }

    // 实现获取表面积方法
    @Override
    public double getSurfaceArea() {
        double sideLength = layer * unitSideLength; // 魔方边长
        double baseArea = ((Math.sqrt(3) / 4) * sideLength * sideLength)*4; // 底面积
        return baseArea; // 表面积公式
    }

    // 实现获取体积方法
    @Override
    public double getVolume() {
        double sideLength = layer * unitSideLength; // 魔方边长
        double height = Math.sqrt(6) / 3 * sideLength; // 高
        return ((Math.sqrt(3) / 4) * sideLength * sideLength) * height / 3; // 体积公式
    }
}

public class Main {

    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);

        String color;
        int layer;
        double side;
        RubikCube cube;

        ArrayList<RubikCube> list = new ArrayList<>();

        int choice = input.nextInt();

        while (choice != 0) {
            switch (choice) {
                case 1: // SquareCube
                    color = input.next();
                    layer = input.nextInt();
                    side = input.nextDouble();
                    cube = new SquareCube(color, layer, side);
                    list.add(cube);
                    break;
                case 2: // RegularPyramidCube
                    color = input.next();
                    layer = input.nextInt();
                    side = input.nextDouble();
                    cube = new RegularPyramidCube(color, layer, side);
                    list.add(cube);
                    break;
            }
            choice = input.nextInt();
        }

        list.sort(Comparator.naturalOrder()); // 按魔方体积升序排序

        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i).getColor() + " " +
                    String.format("%.2f", list.get(i).getSurfaceArea()) + " " +
                    String.format("%.2f", list.get(i).getVolume()));
            System.out.println();
        }
    }
}

  1. RubikCube 是表示魔方的抽象类,其中包括了颜色、阶数和单元边长等属性,并定义了获取表面积和体积的抽象方法,以及实现了 Comparable 接口的 compareTo 方法。
  2. SquareCube 和 RegularPyramidCube 分别表示正方体魔方和正三棱锥魔方,它们都继承自 RubikCube 并实现了父类中定义的抽象方法。其中,RegularPyramidCube 还定义了一个辅助常量 SIDE_LENGTH_RATIO。
  3. 在 Main 类中,通过 Scanner 获取用户输入的选择和参数,然后创建对应的魔方对象(SquareCube 或 RegularPyramidCube),并将其加入到 ArrayList 中。
  4. 通过输入的选择,用户可以不断地创建魔方对象,直到选择输入 0 结束对象创建过程。
  5. 接着,对 ArrayList 中的魔方对象根据体积进行升序排序,并依次输出每个魔方对象的颜色、表面积和体积。
  6. 在输出表面积和体积时,使用了格式化输出,保留小数点后两位。

三、采坑心得

1.在成绩3的那道题中有一个测试点一直过不去,后老师把哪个测试点公布了,才知道自己的代码问题到底出在哪里。

测试用例为:

但是我的程序运行出来的李四的成绩是84,然后我进行调试,发现在取整前的成绩是84.99999999。然后除掉小数之后就变成了84.

但是实际取整前并不是84.99999999,而是85多一点,后来才知道是因为浮点数运算时不精准导致的。

我们知道计算机的底层世界都是由0和1组成的,而浮点数值就是采用二进制系统表示,常见两种基本的浮点类型: float 和 double。

其中单精度float为32位浮点数,1位符号,8位指数和23位尾数(小数部分)。

双精度double则为64位浮点数,1位符号,11位指数和52位尾数(小数部分)。

接下来我们先看下十进制小数转二进制的例子,例如将 0.3 转为二进制

0.32=0.6 //取整数0
0.6
2=1.2 //取整数1
0.22=0.4 //取整数0
0.4
2=0.8 //取整数0
0.82=1.6 //取整数1
0.6
2=1.2 //取整数1
0.22=0.4 //取整数0
0.4
2=0.8 //取整数0
0.8*2=1.6 //取整数1
......
二进制表示为:010011001......
1
2
3
4
5
6
7
8
9
10
11
可以看到计算开始循环,永远无法消除小数部分,根据精度不同会截取对应有效数字,所以小数的二进制有时候是不能精确的,就和我们十进制里不能准确表示1/3=0.33333333…是一个道理。

这种情况在计算时会造成了精度丢失,也就是舍入误差,对于计算会产生严重的后果。

解决方法:

Java中使用 BigDecimal

四、改进建议

对于我自己的代码,在主函数中if-else的嵌套是由的过多,导致可读性差,过多的嵌套会导致代码结构复杂,可读性较差。阅读和理解代码会变得困难,尤其是在嵌套层数较深时。

可以使用多态、策略模式、状态模式等设计模式来提供更清晰、可扩展和可维护的代码结构。另外,合理使用代码重构技术,如提取方法和类,可以将复杂的嵌套结构拆解成更简洁清晰的代码片段。

五、总结

​ 通过这些题目,我对了继承和多态,组合,接口,浮点数精度等概念有了更深入的理解,并学会了如何应用这些知识点进行程序设计。

​ 在写代码的过程中,我通过分析题目要求,设计了合适的类和方法,并根据需求逐步完善和优化程序。我学会了如何进行类的封装和继承,合理组织类之间的关系,使代码更加清晰和易于理解。同时,我也学会了如何处理异常情况,保证程序的稳定性和健壮性。在实验过程中,我遇到了一些问题和困难。有时由于对题目要求理解不准确,导致代码设计出错;有时由于逻辑复杂,代码中嵌套过多的if-else语句,导致代码可读性较差;还有一些测试点没有给出,需要自己进行测试和调试。但是通过不断的学习和思考,我解决了这些问题,并不断改进和优化代码。通过本次实验,我不仅学会了如何设计和实现菜单计价程序,还锻炼了自己的编程和问题解决能力。我学会了如何合理划分类和方法,如何设计良好的代码结构,如何处理各种异常情况。这些知识和经验将对我今后的编程学习和实践有很大的帮助。我深刻认识到了编程的重要性和挑战性。编程需要细心、耐心和持续学习的精神,我将继续努力学习和提高自己的编程能力,以应对更复杂和挑战性的问题。同时,我也认识到合作和交流的重要性,通过与同学共同学习和讨论,相互帮助和分享经验,我收获了更多的知识和思路。

​ 本次大作业让我从理论到实践地运用了继承和多态,组合,接口等知识点。通过不断地迭代和优化代码,我提高了自己的编程能力和代码质量。我相信这些经验和知识对我的编程学习和职业发展都会起到积极的推动作用。