OOP PTA题目集1-3总结性Blog

发布时间 2023-03-25 21:39:07作者: 小日立

一、前言

总结:

第一次题目集:一共12道编程题,题量较多但难度不大,既没有复杂的算法设计,也没有复杂的类设计,有的只是最简单的最基础的语法运用。除了个别题目由于不清楚Java里的string类及其方法导致没有通过全部测试点或没有思路,其他题目没有压力。
第二次题目集:一共9道编程题,题量较多但难度也不大,主要考察的是Java里的循环结构和选择结构,最后还考察了一下方法的创建以及使用,目的依然是为了熟悉语法。
第三次题目集:一共4道编程题,体量不多但难度增大,这次题目集主要考察类的设计,类是一种用来定义对象的模板,它可以包含属性和方法,用来描述对象的状态和行为。Java是一种面向对象的编程语言,所以一切都和类有关,这一点与C语言不同,C语言是面向过程,因此写这次题目集会感觉很陌生,不熟练。

感悟:

1)C语言是一种过程式编程语言,而Java是一种面向对象的编程语言。这意味着在Java中,我需要更多地考虑对象的概念,如何封装数据和方法等等。这需要我适应新的编程思想,并学会使用Java的面向对象编程特性。
2)我会觉得Java比C语言更复杂,因为Java有很多的类库和框架,需要学习和掌握很多的知识和技能 。
3)我也会觉得Java比C语言更简单,因为Java有更多的内置的数据类型和函数,不需要自己写很多底层的代码 。

二、设计和分析

OOP训练集03

7-3 定义日期类

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

输入格式:

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

输出格式:

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

输入样例1:

在这里给出一组输入。例如:

1912 12 25

输出样例1:

在这里给出相应的输出。例如:

Next day is:1912-12-26

输入样例2:

在这里给出一组输入。例如:

2001 2 30

输出样例2:

在这里给出相应的输出。例如:

Date Format is Wrong

源代码:

import java.util.Scanner;
public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int year = scanner.nextInt();
        int month = scanner.nextInt();
        int day = scanner.nextInt();
        Date date = new Date(year,month,day);
        date.getNextDate();

    }
}
class Date{
    private int year;
    private int month;
    private int day;
    public int[] mon_maxnum = new int[] { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    public Date(){

    }
    public Date(int year ,int month , int day){
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public int getYear(){
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }
    public boolean isLeapYear(int year){
        if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)return true;
        else return false;
    }
    public boolean checkInputValidity(){
        if (isLeapYear(year)){
            if(year > 2000 || year < 1900 || month > 12 || month < 1 || day > 31 || day < 1 || (month == 2 && day > 29))return false;
            else return true;
        } else {
            if(year > 2000 || year < 1900 || month > 12 || month < 1 || day > 31 || day < 1 || (month == 2 && day > 28))return false;
            else return true;
        }
    }
    public void getNextDate(){
        if(checkInputValidity()){
            if(isLeapYear(year)){
                mon_maxnum[2] = 29;
                if(day + 1 > mon_maxnum[month]){
                    day = 1;
                    if(month + 1 > 12){
                        month = 1;
                        year = year + 1;
                    } else month = month + 1;
                } else day = day + 1;
            } else {
                if(day + 1 > mon_maxnum[month]){
                    day = 1;
                    if(month + 1 > 12){
                        month = 1;
                        year = year + 1;
                    } else month = month + 1;
                } else day = day + 1;
            }
            System.out.println("Next day is:" + year + "-" + month + "-" + day);
        } else System.out.println("Date Format is Wrong");
    }
}

代码质量的分析报告:

1.可读性
代码中的变量、函数名都比较直观,易于理解。代码风格符合 Java 语言规范,包括驼峰命名、缩进等。但缺少注释,不利于其他开发人员理解和维护代码。总体来说,这段代码的可读性一般。
2.可维护性
代码的结构比较清晰,函数划分也比较合理,方便后续修改和扩展。但是,函数之间的依赖关系较为复杂,修改某个函数可能会影响到其他函数,增加了代码维护的难度。同时,代码中存在硬编码的情况,例如判断是否为闰年的函数,这样的代码在后续修改时比较容易出错。总体来说,代码的可维护性一般。
3.可测试性
代码结构清晰,函数之间的依赖关系较为明确,这有助于编写测试用例。同时,代码中的函数都是独立的,方便单元测试。总体来说,这段代码的可测试性比较好。
4.性能
代码中主要包含一些简单的算术运算和条件判断,没有涉及到复杂的算法,因此性能方面不会有太大问题。但是,代码中使用了数组来存储每个月的最大天数,这样的做法在内存占用方面可能会有一定的浪费。总体来说,这段代码的性能表现一般。
5.可靠性
代码中的大部分函数都有参数检查和异常处理的机制,这可以避免因为参数错误或异常导致程序崩溃。但是,代码中存在硬编码的情况,例如判断是否为闰年的函数,这样的代码在特定的场景下可能会出现错误。总体来说,这段代码的可靠性一般。
6.安全性
代码中没有涉及到与外部系统或网络交互的情况,因此安全性方面没有太大问题。但是,代码中没有对输入数据进行足够的校验,这可能导致输入数据错误或非法输入的情况。总体来说,这段代码的安全性表现一般。
7.可扩展性
代码中的函数划分较为合理,方便后续的扩展和修改。但是,函数之间的依赖关系比较复杂,增加了代码扩展的难度。总体来说,这段代码的可扩展性一般。

类图:

解释和心得:

(1)
方法getNextDate的解释:
首先,检查输入的日期是否合法,如果不合法则输出错误信息"Date Format is Wrong",直接返回;如果输入日期合法,那么就要计算下一天日期了。根据输入日期的年份是否为闰年,来决定二月份的天数。在这里,程序通过调用isLeapYear()方法来判断输入年份是否为闰年。如果是闰年,将二月份的天数设置为29,否则设置为28。接下来判断输入日期加1天后的日期是否大于本月最大天数,如果是,将日期day置为1,并将月份加1;如果月份超过12,将年份加1,并将月份置为1。如果日期加1天后不超过本月最大天数,只需将日期day加1即可。最后,输出计算出的下一天日期。
心得:
在getNextDate方法中,进行了大量的if-else判断,使得代码可读性稍差。

(2)
在类Date中,isLeapYear方法和checkInputValidity方法可以放到一个单独的工具类中,使得代码更加模块化和可维护性更好。

OOP训练集03

7-4 日期类设计

参考题目3和日期相关的程序,设计一个类DateUtil,该类有三个私有属性year、month、day(均为整型数),其中,year∈[1820,2020] ,month∈[1,12] ,day∈[1,31] , 除了创建该类的构造方法、属性的getter及setter方法外,需要编写如下方法:

public boolean checkInputValidity();//检测输入的年、月、日是否合法
public boolean isLeapYear(int year);//判断year是否为闰年
public DateUtil getNextNDays(int n);//取得year-month-day的下n天日期
public DateUtil getPreviousNDays(int n);//取得year-month-day的前n天日期
public boolean compareDates(DateUtil date);//比较当前日期与date的大小(先后)
public boolean equalTwoDates(DateUtil date);//判断两个日期是否相等
public int getDaysofDates(DateUtil date);//求当前日期与date之间相差的天数
public String showDate();//以“year-month-day”格式返回日期值

应用程序共测试三个功能:
1.求下n天
2.求前n天
3.求两个日期相差的天数
注意:严禁使用Java中提供的任何与日期相关的类与方法,并提交完整源码,包括主类及方法(已提供,不需修改)
程序主方法如下:

import java.util.Scanner;

public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        int year = 0;
        int month = 0;
        int day = 0;

        int choice = input.nextInt();

        if (choice == 1) { // test getNextNDays method
            int m = 0;
            year = Integer.parseInt(input.next());
            month = Integer.parseInt(input.next());
            day = Integer.parseInt(input.next());

            DateUtil date = new DateUtil(year, month, day);

            if (!date.checkInputValidity()) {
                System.out.println("Wrong Format");
                System.exit(0);
            }

            m = input.nextInt();

            if (m < 0) {
                System.out.println("Wrong Format");
                System.exit(0);
            }

            System.out.print(date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " next " + m + " days is:");
            System.out.println(date.getNextNDays(m).showDate());
        } else if (choice == 2) { // test getPreviousNDays method
            int n = 0;
            year = Integer.parseInt(input.next());
            month = Integer.parseInt(input.next());
            day = Integer.parseInt(input.next());

            DateUtil date = new DateUtil(year, month, day);

            if (!date.checkInputValidity()) {
                System.out.println("Wrong Format");
                System.exit(0);
            }

            n = input.nextInt();

            if (n < 0) {
                System.out.println("Wrong Format");
                System.exit(0);
            }

            System.out.print(
                    date.getYear() + "-" + date.getMonth() + "-" + date.getDay() + " previous " + n + " days is:");
            System.out.println(date.getPreviousNDays(n).showDate());
        } else if (choice == 3) {    //test getDaysofDates method
            year = Integer.parseInt(input.next());
            month = Integer.parseInt(input.next());
            day = Integer.parseInt(input.next());

            int anotherYear = Integer.parseInt(input.next());
            int anotherMonth = Integer.parseInt(input.next());
            int anotherDay = Integer.parseInt(input.next());

            DateUtil fromDate = new DateUtil(year, month, day);
            DateUtil toDate = new DateUtil(anotherYear, anotherMonth, anotherDay);

            if (fromDate.checkInputValidity() && toDate.checkInputValidity()) {
                System.out.println("The days between " + fromDate.showDate() + 
                        " and " + toDate.showDate() + " are:"
                        + fromDate.getDaysofDates(toDate));
            } else {
                System.out.println("Wrong Format");
                System.exit(0);
            }
        }
        else{
            System.out.println("Wrong Format");
            System.exit(0);
        }        
    }
}

输入格式:

有三种输入方式(以输入的第一个数字划分[1,3]):

  • 1 year month day n //测试输入日期的下n天
  • 2 year month day n //测试输入日期的前n天
  • 3 year1 month1 day1 year2 month2 day2 //测试两个日期之间相差的天数

输出格式:

  • 当输入有误时,输出格式如下:
Wrong Format
  • 当第一个数字为1且输入均有效,输出格式如下:
year1-month1-day1 next n days is:year2-month2-day2
  • 当第一个数字为2且输入均有效,输出格式如下:
year1-month1-day1 previous n days is:year2-month2-day2
  • 当第一个数字为3且输入均有效,输出格式如下:
The days between year1-month1-day1 and year2-month2-day2 are:值

输入样例1:

在这里给出一组输入。例如:

3 2014 2 14 2020 6 14

输出样例1:

在这里给出相应的输出。例如:

The days between 2014-2-14 and 2020-6-14 are:2312

输入样例2:

在这里给出一组输入。例如:

2 1834 2 17 7821

输出样例2:

在这里给出相应的输出。例如:

1834-2-17 previous 7821 days is:1812-9-19

输入样例3:

在这里给出一组输入。例如:

1 1999 3 28 6543

输出样例3:

在这里给出相应的输出。例如:

1999-3-28 next 6543 days is:2017-2-24

输入样例4:

在这里给出一组输入。例如:

0 2000 5 12 30

输出样例4:

在这里给出相应的输出。例如:

Wrong Format

源代码:

class DateUtil{
    private int year;
    private int month;
    private int day;
    public int[] mon_maxnum = new int[] { 0,31,28,31,30,31,30,31,31,30,31,30,31 };
    public DateUtil(){

    }
    public DateUtil(int year ,int month , int day){
        this.year = year;
        this.month = month;
        this.day = day;
    }
    public int getYear(){
        return year;
    }

    public void setYear(int year) {
        this.year = year;
    }

    public int getMonth() {
        return month;
    }

    public void setMonth(int month) {
        this.month = month;
    }

    public int getDay() {
        return day;
    }

    public void setDay(int day) {
        this.day = day;
    }
    public boolean checkInputValidity(){
        if (isLeapYear(year)){
            if(year > 2020 || year < 1820 || month > 12 || month < 1 || day > 31 || day < 1 || (month == 2 && day > 29))return false;
            else return true;
        } else {
            if(year > 2020 || year < 1820 || month > 12 || month < 1 || day > 31 || day < 1 || (month == 2 && day > 28))return false;
            else return true;
        }
    }
    public boolean isLeapYear(int year){
        if((year % 4 == 0 && year % 100 != 0) || year % 400 == 0)return true;
        else return false;
    }
    public DateUtil getNextNDays(int n){
        for (int i = 0; i < n; i++){
            if(isLeapYear(year)) mon_maxnum[2] = 29;
            else mon_maxnum[2] = 28;
            if(day + 1 > mon_maxnum[month]){
                day = 1;
                if(month + 1 > 12){
                    month = 1;
                    year = year + 1;
                } else month = month + 1;
            } else day = day + 1;
        }
        return new DateUtil(year,month,day);
    }
    public DateUtil getPreviousNDays(int n){
        for (int i = 0; i < n; i++){
            if(isLeapYear(year)) mon_maxnum[2] = 29;
            else mon_maxnum[2] = 28;
            if(day - 1 == 0){
                if(month - 1 == 0){
                    month = 12;
                    year = year - 1;
                    day = mon_maxnum[month];
                } else {
                    month = month - 1;
                    day = mon_maxnum[month];
                }
            } else day = day - 1;
        }
        return new DateUtil(year,month,day);
    }
    public boolean equalTwoDates(DateUtil date){
        if(year == date.year && month == date.month && day == date.day)return true;
        else return false;
    }
    public boolean compareDates(DateUtil date){
        if(year > date.year)return true;
        else if(year == date.year){
            if(month > date.month)return true;
            else if(month == date.month){
                if(day > date.day)return true;
            }
        }
        return false;
    }
    public int getDaysofDates(DateUtil date){
        if(equalTwoDates(date))return 0;
        else {
            int times = 0;
            if(compareDates(date)){
                while (true){
                    date = date.getNextNDays(1);
                    times ++;
                    if(equalTwoDates(date))return times;
                }
            } else {
                while (true){
                    date = date.getPreviousNDays(1);
                    times ++;
                    if(equalTwoDates(date))return times;
                }
            }
        }
    }
    public String showDate(){
        return year + "-" + month + "-" + day;
    }
}

代码质量的分析报告:

1.可读性:
代码中的变量和函数名基本符合命名规范,但是有些方法名的含义不太明确,比如checkInputValidity(),没有清晰地说明其检查的是什么有效性。此外,代码中缺少注释和说明,可能会给阅读者带来困扰。可以增加注释来说明每个方法的作用,使得代码更容易理解。
2.可维护性:
代码中的变量和函数使用较为合理,但是代码的复杂度较高,可能会增加维护的难度。建议将一些逻辑复杂的代码拆分成独立的函数,增加代码的可读性和可维护性。
3.可测试性:
代码中的函数没有进行单元测试或者集成测试,可能存在一些潜在的问题。建议在代码中加入单元测试或者集成测试,以确保代码的正确性和稳定性。
4.性能:
代码中的性能表现较好,主要是由于该类的函数没有使用复杂的算法和数据结构。但是有一些可以进行优化的地方,比如在getNextNDays()和getPreviousNDays()中,可以避免在每次迭代时都判断是否为闰年,而是在初始化时判断一次,并将闰年的月份天数存储起来。
5.可靠性:
代码中没有明显的错误,但是在getNextNDays()和getPreviousNDays()中可能会出现一些潜在的边界问题。例如,当当前日期为2月29日,而要求往后1天时,如果年份不是闰年,应该返回3月1日而不是3月0日,代码中需要对这种情况进行处理,以确保函数的正确性和可靠性。
6.安全性:
代码中没有涉及到敏感信息和安全问题,因此不需要特别考虑安全性。
7.可扩展性:
代码中的函数都比较独立,可以方便地进行扩展。例如,可以添加一个函数来计算当前日期是周几,或者计算两个日期之间相差的小时数等。

类图:

解释和心得:

(1)
方法getPreviousNDays(int n)的解释:
方法首先根据当前年份是否为闰年来确定 2 月的最大天数,然后进入一个 for 循环,循环 n 次。每次循环中,如果当前日期的前一天为该月的第一天,那么日期需要减一,月份也需要变化为上个月,而如果上个月为 12 月,那么年份也需要减一。如果当前日期的前一天不是该月的第一天,那么日期只需要减一即可。最后,该方法返回一个新的 DateUtil 对象,表示当前日期前 n 天的日期。
心得:
设计想法很简单,未来n天等价于n倍的未来1天,但复杂度较高,若输入数据较大,所消耗时间则较多,若数据过大甚至可能无法得到正确值。
(2)
方法getPreviousNDays(int n)的解释:
在这个方法中,它首先使用isLeapYear()方法检查该日期的年份是否为闰年,如果是,则将二月份的最大天数更改为29,否则更改为28。然后它检查该日期的前一天是否在当前月份,如果是,则将天数减去1即可。如果该日期的前一天是上一个月的最后一天,则需要将月份和年份相应地更改为上一个月和上一年,并将天数更改为上一个月的最后一天。它使用for循环n次以便获取指定日期之前的n天。最后,它返回一个新的DateUtil对象,该对象代表指定日期之前的n天。
心得:
设计想法与求未来n天类似,前n天等价于n倍的前1天,但同样复杂度较高,若输入数据较大,所消耗时间则较多,若数据过大甚至可能无法得到正确值。
(3)
方法getDaysofDates(DateUtil date)的解释:
该方法的实现逻辑是先判断当前DateUtil实例与另一个DateUtil实例是否相等,如果相等则返回0,否则根据日期先后顺序,不断调用getNextNDays(1)或getPreviousNDays(1)方法,直到两个日期相等,同时记录经过的天数,最终返回经过的天数。
心得:
该方法内部会调用getNextNDays(1)和getPreviousNDays(1)方法,因此其时间复杂度为O(n),其中n为当前DateUtil实例与另一个DateUtil实例之间的天数差。如果需要计算大量日期之间的天数差,该方法的效率可能比较低。

三、踩坑心得

OOP训练集03

7-3 定义日期类

这个题目比较简单,设计程序时没有踩到坑。

OOP训练集03

7-4 日期设计类

(1)
最初设计方法getNextNDays(int n)时,在判断是否为闰年时,只对是闰年时的2月份天数进行了赋值29天,而忘记对于不是闰年时的2月份重新赋值为28天,导致若有一次循环加1天使得年份更改为闰年时,2月份的天数由28天更改为29天后会恒为29天而不会因为判断年份不是闰年后重新更改为28天。
修改前的源代码:

    public DateUtil getPreviousNDays(int n){
        for (int i = 0; i < n; i++){
            if(isLeapYear(year)) mon_maxnum[2] = 29;
            else mon_maxnum[2] = 28;
            if(day - 1 == 0){
                if(month - 1 == 0){
                    month = 12;
                    year = year - 1;
                    day = mon_maxnum[month];
                } else {
                    month = month - 1;
                    day = mon_maxnum[month];
                }
            } else day = day - 1;
        }
        return new DateUtil(year,month,day);
    }

输入输入样例3得到的结果:

与正确输出相比少了12天,这是由于经过闰年之后的每一年的2月份都比真实情况多一天。
(2)
最初设计方法getPreviousNDays(int n)时,当往前减1天时,需要判断是否为0,若为0,则应该是月数减1,天数变为减1之后的月份的最大天数,而由于自己的不严谨,忘记已经对月份进行了减1的操作而在赋值天数时再一次对月份减1,致使天数变为第前2个的月份最大天数。
修改前的源代码:

    public DateUtil getPreviousNDays(int n){
        for (int i = 0; i < n; i++){
            if(isLeapYear(year)) mon_maxnum[2] = 29;
            else mon_maxnum[2] = 28;
            if(day - 1 == 0){
                if(month - 1 == 0){
                    month = 12;
                    year = year - 1;
                    day = mon_maxnum[month];
                } else {
                    month = month - 1;
                    day = mon_maxnum[month - 1];
                }
            } else day = day - 1;
        }
        return new DateUtil(year,month,day);
    }

输入输入样例2得到的结果:

输出格式都乱码了,属于严重的逻辑错误了。

总结

(1) 在设计Java程序时,一定要注意边界条件和特殊情况,避免出现逻辑错误和bug。同时,及时进行测试和调试也是非常重要的,这样可以及早发现并解决潜在的问题。
(2) 当涉及到日期运算时,要注意日期的边界情况,特别是当日期向前移动时,需要考虑到月底的特殊情况。在处理日期运算时,常常会涉及到月份和天数的变化,而这些变化是与闰年、月份天数等因素有关的。如果没有考虑到这些因素,就容易出现逻辑错误。代码中使用mon_maxnum[month - 1]将day设置为上个月的最后一天,这个做法看似可行,但实际上可能会导致访问未初始化的数组元素。这是因为mon_maxnum数组的长度是13,但是在使用数组元素时,需要使用的是1到12月的元素,而月份是从1开始计数的。

四、改进建议

OOP训练集03

7-3 定义日期类

(1) 对于计算下一个日期的逻辑,可以提取一个独立的方法,使getNextDate方法更简洁和易读。
(2) 可以添加一些注释来提高代码的可读性。

7-4 日期设计类

(1) 在比较日期大小时,可以将年、月、日依次进行比较,而不是在同一个if语句块中进行所有比较。
(2) 可以将getNextNDays()和getPreviousNDays()方法的实现分解成更小的方法。
(3) 使用更加详细的变量名和方法名,以提高代码的可读性。
(4) 可以添加一些注释来提高代码的可读性。

总结

在本阶段的三次题目集中,我学习了很多有关Java编程语言的知识,从基础的数据类型、控制结构、数组到面向对象编程,包括对类进行了初步的学习。在学习过程中,我发现我需要进一步学习和研究的地方,例如如何优化代码性能、如何处理异常和错误等问题。关于教师、课程、作业、实验、课上及课下组织方式等方面的改进建议及意见,我认为教师可以在授课时注重理论和实践相结合,增加实际应用案例的讲解,这样可以更好地帮助学生理解Java编程语言的特点和应用场景。在课上和课下组织方式上,可以采用更多的小组合作、讨论和分享方式,让学生更好地相互学习和交流经验。