20202307 2020-2021-2《数据结构与面向对象程序设计》课程总结

20202307 2020-2021-2《数据结构与面向对象程序设计》课程总结

课程内容总结(按照教材顺序整理)

第一章 绪论

  1. 简要介绍计算机组成(基本结构)及Java程序
    设计基础(入门)
  • Java历史、Java平台

  • 什么是Java编程语言

  1. Java应用程序的结构
  • 注释:"//"或"/* xxx /"或"/* xxx */"
  • 标识符:字母、数字、_、$(不能以数字开头且大小写敏感
  • 保留字:只能按预先定义的方式来使用,不能用于其他目的(如作为类名或方法名)
  1. 面向对象程序设计
  • 对象

  • 类:对象由类定义,类是对象的蓝图。一个类可以创建多个对象。

  • 面向对象三要素——封装、继承、多态

  • 属性

  • 方法

  • 软件开发:确定软件需求→软件设计→实现软件设计→软件测试
    伪代码→产品代码→测试代码

  • OO设计——SOLID原则
    SRP 单一职责原则
    OCP 开放-封闭原则
    LSP 替代原则
    ISP 接口分离原则
    DIP 依赖倒置原则

第二章 数据与表达式

  1. 字符串的输出
  • print与println:print输出不换行,println输出且换行。
  1. 转义字符:\t制表符,\n换行,\r回车,\"双引号,\'单引号,\\反斜杠
  2. 变量赋值
  • 变量
  • 赋值语句
  • 常量:在声明前使用了关键字final的标识符,命名时常用大写字母。
  1. 基本数据类型(8种)
  • 整型与浮点型:byte、short、int、long、float、double
  • 字符型:char、'A'
  • boolean型(true/false)
  1. 运算符用法及优先级
  • 算数运算符:+-*/%
  • 运算符优先级:具有相同优先级的算数运算符从左到右依次计算,圆括号中的任何表达式都最先计算。
  • 自增及自减运算符
  • 赋值运算符
  1. 数据转换:加宽转换与缩窄转换
  • 缩窄转换对比加宽转换,很可能会丢失信息
  • 通过赋值语句仅能实现加宽转换
  • 强制类型转换 例:dollars = (int)money;
  1. Scanner类

第三章 使用类和对象

  1. 如何查阅和使用JDK文档
  2. 常用的类及其常用方法
  • String类(java.lang
  • Random类(java.util包):伪随机数生成器
  • Math类静态类——类名.xxx 可以直接调用)
  • :Java标准类库中的类被划分为包。每个类属于一个具体的包,例如String类与System类都属于java.lang包(不需import,自动引入),Scanner类属于java.util包(需要import)。
  1. 格式化输出(java.text)
  • NumberFormat 静态类
  • DecimalFormat 非静态类
  • printf
  1. 枚举类型
  • enum Xxx
  1. 包装类
  • 例:Integer xxx = new Integer(40);

第四章 条件和循环

  1. if语句
  2. 数据比较
  • 浮点数的比较:计算两个差值的绝对值,再与公差进行比较。
  • 字符比较:Java中字符的相对顺序由Unicode字符集定义。
  • 对象比较:equals。
  1. switch语句
  2. while语句
  3. do语句
  4. 迭代器与for循环
  • Iterator接口
    接口:Comparable接口/Iterator接口
  • hasNext():判断是否有下一个元素
  • next():取出集合中的下一个元素
    hasNext()next()均为Iterator接口中的方法)

第五章 编写类

  1. 类和对象:类是对象的的蓝图
  • 每个类包括为该类的对象定义的属性和操作:对象的状态由与对象相关的属性定义,对象的行为由与对象相关的操作定义。
  • 类的数据和方法都成为类的成员。
  1. 构造方法
  2. UML类图
  3. 封装
  • 可见性修饰符:public(类外部)、private(只能在类内部访问)、protected(在继承类中访问)
  • 访问方法(getX)和设值方法A(setX)
  1. 参数
  • 形式参数/实际参数
  1. 静态类成员
  • 静态变量(static)
  • 静态方法
    例:通过Math类名来调用sqrt方法:System.out.println("Square root of 27:" + Math.sqrt(27));
  1. 类关系
  • 依赖
  • 聚合
  • this

三个类——普通类、接口类、抽象类

  • 接口类=属性(常量)+方法(抽象abstract方法) (abstract可以省略)
    关键字:implement(必须实现所有的方法)

三要素——封装、继承、多态

第七章 数组

数组的特性:随机存取

第八章 继承

  1. 父类与子类(is-a关系):子类继承父类的特征和行为,使得子类对象(实例)具有父类的实例域和方法,或子类从父类继承方法,使得子类具有父类相同的行为。
  2. 关键词
  • extends
  • implements
  • super/this
    super:引用当前对象的父类;this:指向自己的应用。
  • final
  1. 方法重写(override)
  • 重写:重写父类的方法/
    重载:方法(不同参数、参数的个数、参数的类型等)
  • 参数列表必须完全与被重写方法的相同。
  • 访问权限不能比父类中被重写的方法的访问权限更低。例如:如果父类的一个方法被声明为public,那么在子类中重写该方法就不能声明为protected。
  • 父类的成员方法只能被它的子类重写。
  • 构造方法不能被重写。
  • 声明为final的方法不能被重写。
  • 声明为static的方法不能被重写,但是能够被再次声明。
  • 子类和父类在同一个包中,那么子类可以重写父类所有方法,除了声明为private和final的方法;子类和父类不在同一个包中,那么子类只能够重写父类的声明为public和protected的非final方法。
  • 当需要在子类中调用父类的被重写方法时,要使用super关键字。
  1. Object类:在Java中,所有的类最终都是从Object类拍生的。
  • Java所有类都直接或间接地派生于Object类,都继承了toString和equals方法。
  1. 抽象类=属性(变量/常量/对象)+方法(抽象abstract/非抽象方法/也可以没有)
  • 抽象类必须使用abstract明确声明
  • 如有,抽象类里的方法也必须使用abstract进行声明
  • 也可以没有抽象方法
  • 抽象类不能实例化instantiated
  • 抽象类必须被继承,才能被使用。实现抽象方法使用extends关键字。

第九章 多态

创建多态引用:使用继承机制/接口

  1. 后绑定:多态引用在运行时才将方法调用与它的定义绑定在一起,由调用时所指向的对象的类型来确定要用到那个方法定义。(多态引用在不同的时候可以指向不同类型的对象)
  2. 用继承实现多态:父类的对象指向子类的对象,调用子类的方法。
  3. 接口
  • 接口是一组抽象方法,不能被实例化。
  • 多个累可以实现同一个接口,一个类可以实现多个接口。
  • implements
  • 接口层次:接口可以继承接口;类可以实现接口,但不能继承接口。
  • Iterator接口
  1. 通过接口实现多态
  • 接口名可用来声明对象引用变量
  • 接口引用可以指向实现这个接口的任意类的任何对象
  • 方法的参数可以是多态的

第十章 异常

  1. 异常处理
  • 不处理异常
  • 在异常发生的地方处理
  • 在程序的其他地方处理异常
  1. 异常的传递
  2. 自定义异常
  3. 抛出异常
  4. 必检异常/免检异常
  5. 异常的处理方式
  • try-catch:用于标识可能抛出异常的一个语句块
  • finally子句:无论是正常退出try块,还是因抛出一个异常而退出,都要执行——即无论是否发生异常,finally子句中的代码总会被执行。

第十二章 算法分析

  1. 算法效率:通常用CPU的使用时间表示
  2. 算法分析:从效率的角度对算法进行分类
  3. 渐进复杂度与大O记法
  • O——增长函数中增长最快的次数
  1. 增长函数比较

第十三章 查找与排序

  1. 查找
  • 线性查找:(设置哨兵
    从表头开始,依次将每个值与目标元素进行比较。
    不要求查找池中的元素按某种特定的次序保存在数组中。
    平均时间复杂度为O(n)。
public static Comparable linearSearch (Comparable[] data, Comparable target) {
        Comparable result = null;
        int index = 0;
        while (result == null && index < data.length){
            if (data[index].compareTo(target) == 0)
                result = data[index];
            index++;
        }
        return result;
    }
  • 二分查找:(查找池有序
    在一个已排序的项目组中,从列表的中间开始查找,如果中间元素不是要找的指定元素,则削减一半查找池,从剩余一半的查找池(可行候选项)中继续以与之前相同的方式进行查找,多次循环直至找到目标元素或确定目标元素不在查找池中。
    平均时间复杂度为O(log2n)。
public static Comparable BinarySearch(Comparable[] data,Comparable target)
    {
        Comparable result=null;
        int first=0,last=data.length,mid;

        while(result==null&&first<=last)
        {
            mid=(first+last)/2;
            if(data[mid].compareTo(target)==0)
                result=data[mid];
            else
            if(data[mid].compareTo(target)>0)
                last=mid-1;
            else
                first=mid+1;
        }
        return result;
    }
  1. 排序
  • 选择排序:通过反复将某一特定值放到它在列表中的最终已排序位置来实现排序。
    ① 简单选择排序
    堆排序
  • 插入排序:将一个具体的值插入到表中已有序的子系列中,从而完成一组值的排序。
    ① 直接插入排序
    ② 折半插入排序
    ③ 二路插入排序
    ④ 希尔排序(缩小增量) d:希尔增量
  • 冒泡排序:通过反复比较相邻元素的大小并在必要时进行互换。
  • 快速排序:根据一个任意选定的划分元素来对表进行划分,然后再递归地对划分元素两边的字段进行排序,从而完成对表的排序。
  • 归并排序:通过将列表进行递归式分区直至最后每个列表中都只剩余一个元素后,将所有列表重新按顺序重组完成排序。
  • 基数排序:基于排序关键字结构来排序。
  1. 分析比较查找和排序算法

第十四章 栈

  • 栈是一种特殊的线性表,采用后进先出(LIFO)的方法处理元素。

  • 栈的操作:入栈:Push(插入) 出栈:Pop(删除)
    public void push(T element); //入栈
    public T pop() //出栈
    public T peek() //取栈顶的元素
    public boolean isEmpty() ///判断栈是否为空
    public int size() //栈有多少个元素

  • 栈的应用:
    ① 数值转换
    ② 表达式求值——计算后缀表达式
    P.S.表达式的三种表达方式:

    例:Exp = a × b + (c - d / e) × f
    后缀式:ab×cde/-f×+

  • 异常
    ① 入栈时栈满
    ② 出栈时栈空
    ③ 扫描完表达式时栈中的值多于1个

  • ArrayStack类——使用数组实现栈

  • LinkedStack类——使用链实现栈

第十五章 队列

  • 元素处理方式:先进先出(FIFO),第一个进入的元素也将是第一个退出的元素。
  • 队头(front或head):取出数据元素的一端。
    队尾(rear或tail):插入数据元素的一端。
  • 队列的操作
    enqueue:将元素插入至队尾
    dequeue:从队头删除一个元素
    first:检测队头的元素
    isEmpty:判断队列是否为空
    size:判断队列中的元素数目
    toString:返回队列的字符串表示

第十六章 树

  1. 非线性结构
  • 结点:子结点、父节点、兄弟结点、叶结点、根结点。
  • 结点的度、层次,树的深度
  1. 树的基本操作
  • 查找类
  • 插入类
    tree() //构造函数 初始化置空树
    assign() //给当前结点赋值
    insert() //插入操作 可以是结点或者子树
  • 删除类
    makeEmpty()
    delete()
  1. 树的存储结构
  • 双亲表示法
  • 孩子表示法
    (1)多重链表
    (2)孩子链表
  • 孩子双亲表示法——二叉链表(左边放孩子 右边放兄弟)
  1. 树的实现
  • 数组:存储在数组中位置为 n 的元素,元素的左子结点存储在(2n + 1)的位置,右子结点存储在(2 x(n+1))的位置。
  • 链式结点:使用一个单独的类来定义树结点。
  1. 二叉树(BinaryTree):结点度数至多为2
  • 有序树

  • 满二叉树:深度为k,层数为i,根结点在第1层,则结点总数为2k-1,每层结点数2(i-1)

  • 完全二叉树
    ① 具有n个结点的完全二叉树的高度为log2(n+1)
    ② 如果将一棵有n个结点的完全二叉树自顶向下,同一层自左向右连续给结点编号1, 2, …, n,则对于任意结点 i (1 ≤ i ≤ n),有:
    若i=1,则该i结点是树根,它无双亲;
    若2i>n,则编号为i的结点无左孩子,否则它的左孩子是编号为 2i 的结点;
    若2i+1>n, 则编号为i的结点无右孩子, 否则其右孩子结点编号为2i+1;

  • 二叉树的ADT及实现

public interface BinaryTree<T> extends Iterable<T> {
    //  Returns the element stored in the root of the tree.
    public T getRootElement();

    //  Returns the left subtree of the root.
    public BinaryTree<T> getLeft();

    //  Returns the right subtree of the root.
    public BinaryTree<T> getRight();

    //  Returns true if the binary tree contains an element that matches the specified element.
    public boolean contains(T target);

    //  Returns a reference to the element in the tree matching the specified target.
    public T find(T target);

    //  Returns true if the binary tree contains no elements,and false otherwise.
    public boolean isEmpty();

    //  Returns the number of elements in this binary tree.
    public int size();

    //  Returns the string representation of the binary tree.
    public String toString();

    //  Returns a preorder traversal on the binary tree.
    public Iterator<T> preorder();

    //  Returns an inorder traversal on the binary tree.
    public Iterator<T> inorder();

    //  Returns a postorder traversal on the binary tree.
    public Iterator<T> postorder();

    //  Performs a level-order traversal on the binary tree.
    public Iterator<T> levelorder();
}
  1. 树的遍历
  • 先序遍历:访问根,从左到右遍历子树。
  • 中序遍历:遍历左子树,然后访问根,之后从左到右遍历余下子树。
  • 后序遍历:从左到右遍历各子树,最后访问根。
  • 层序遍历:从上到下,从左到右访问树中每层的每个结点。
  1. 决策树
  • 结点表示判定点,结点的孩子表示那一刻可用的选择。叶结点表示根据选择得到的结论。
  • 左子结点表示“否”,右子结点表示“是”。
  1. 哈夫曼树

第十七章 二叉查找树

左子树上的元素小于父节点的值,右子树上的元素大于等于父节点的值。

  • 查找、插入
  • 删除
    ① 叶结点
    ② 只有左子树/右子树
    ③ 既有左子树,又有右子树:用前驱/后继替换并删除前驱/后继
    (前驱:左子树中最右的值 后继:右子树中最左的值)
  • AVL树(平衡二叉树):二叉树中每个结点的左、右子树的深度之差的绝对值不超过1
    二叉排序树的平衡化:① 树根左孩子的左子树过长→右旋
    ② 树根右孩子的左子树过长→右左旋
    ③ 树根左孩子的右子树过长→左右旋
  • 红黑树

第十八章 堆

  • 堆是一颗完全二叉树,每个元素大于等于其所有子结点的值
  • 小顶堆/大顶堆(最小堆/最大堆)
    小顶堆:二叉树的所有根结点值都小于或等于其左右孩子的值
    大顶堆:二叉树的所有根结点值都大于或等于其左右孩子的值
  1. 堆的操作
  • addElement() 向堆中添加一个新元素
  • removeMin() 删除最小值并重构堆
  • findMin() 寻找最小值
  1. 堆的实现
  • 基于LinkedBinaryTree
public class MaxHeap<T extends Comparable<T>> extends BinaryTree<T> {
    public void add(T obj);
    public T getMax();
    public T removeMax();
}
  • 使用数组实现
  1. 堆排序
  • 顺序存储结构最为合适
  • 堆排序算法
void heapsort (SqList &L,int n ) {    //0号单元为暂存单元
     for ( i=n/2; i>=1; i-- )
         sift (L, i ,n );    //初始建堆
     for ( i=1; i<n; i++ ) {
         L.r[1]        L.r[n-i+1]; 
         sift (L, 1 , n-i );
     }
}

第十九章 图

  1. 图的定义及基本术语
  • 顶点、边
  • 有向图/无向图
  • 出度/入度
  • 权:与图的边相关的数字。网:带权的图。
  1. 图的存储结构及实现
  • 邻接矩阵
  • 边集数组
  • 邻接表
  • 十字链表
  1. 图的遍历
  • 深度优先遍历:从图中某个顶点v出发,访问此顶点,然后从v的未被访问的邻接点出发深度优先遍历图,直至图中所有和v有路径相通的顶点都被访问到。
  • 广度优先遍历:以v为起始点,由近至远,依次访问和v有路径相通且路径长度为1,2,...的顶点的过程。
  1. 最小生成树
  • Prim算法
  • Kruskal算法
  1. 单源最短路径——Dijkstra算法

做过的作业总结

作业1:虚拟机的安装、Linux操作系统的环境配置及JDK的安装

作业2:Linux基本操作命令的学习

作业3:初步掌握单元测试、TDD与UML建模

作业4:编写一组程序,体现以下知识点:

  • 继承
  • 多态
  • 重写
  • 重载
  • 目录创建
  • 文件创建
  • 字节流读写
  • 字符流读写

作业5:编写自己的ArrayList类并测试

  • 实现增加、删除、修改、查找、判断是否为空、返回list长度等操作。

作业6:参考头插法原理及伪代码,举一反三,试根据原理写出尾插法的伪代码。

作业7:撰写自己的ArrayStack类并测试

  • 据所提供的StackADT,ArrayStack(框架),实现ArrayStack中的剩余方法;

作业8:栈的应用——进制转换

  • 算法基于原理:N = (N div d) × d + N mod d

  • 要求输入一个十进制数,转换成任意进制数并输出。

作业9:最小生成树测试

  • 画出Prim算法的最小生成树的生成过程
  • 画出Kruscal算法的最小生成树的生成过程
  • 计算最小权值

作业10:Dijkstra(迪杰斯特拉)算法测试

  • 使用Dijkstra(迪杰斯特拉)算法计算单源(V1出发)最短路径。

作业11:快速排序测试

  • 3,44,38,5,47,15,36,26,27,2,46,4,19,50,48
    写出第一趟快速排序的结果

实验报告链接汇总

实验一

Linux基础与Java开发环境:

  1. 基于命令行进行简单的Java程序编辑、编译、运行和调试;
  2. 练习Linux基本命令;
  3. 学习Java程序的JDB调试技能:https://www.cnblogs.com/rocedu/p/6371262.html
  4. 编写简单的Java程序。

实验二

Java基础:

  1. 编写简单的计算器,完成加减乘除模运算;
  2. 要求从键盘输入两个数,使用判定语句选择一种操作,计算结果后输出,然后使用判定和循环语句选择继续计算还是退出;
  3. 编写测试代码,测试验证。

实验三

面向对象程序设计:

  1. 初步掌握单元测试和TDD;
  2. 理解并掌握面向对象三要素:封装、继承、多态;
  3. 初步掌握UML建模;

实验四

Java Socket编程:

  1. 学习Java Socket编程
    学习蓝墨云上教材《Java和Android编程》“第16章 输入/输出 ”和“第22章 网络”,学习JavaSocket编程,并与结对伙伴余博雅完成结对编程。
  2. Java和密码学
    参考学习 http://www.cnblogs.com/rocedu/p/6683948.html 中的代码与相关知识点,以结对的方式完成Java密码学相关内容的学习;
  3. 编写有理/复数计算器
  4. 远程有理数计算器
    结对编程,结对伙伴A编程实现客户端,结果伙伴B实现服务器端;
    客户端通过键盘输入一个有理数计算的公式1/4 + 1/6 =,并把该公式以字符串的形式发送给伙伴B(服务器端),服务器端根据字符串计算出结果为5/12,并把结果返回给客户端A,A收到结果后输出结果。
  5. 远程复数计算器
    结对编程,结对伙伴B编程实现客户端,结果伙伴A实现服务器端;
    客户端通过键盘输入一个复数计算的公式3+4i*5+6i,并把该公式以字符串的形式发送给伙伴B(服务器端),服务器端根据字符串计算出结果为9+38i,并把结果返回给客户端A,A收到结果后输出结果。

实验五和六

线性结构之链表:链表练习与Android实现

  1. 通过键盘输入一些整数,建立一个链表;
  2. 实现节点插入、删除、输出操作;
  3. 使用冒泡排序法或者选择排序法根据数值大小对链表进行排序;
  4. 在Android上实现实验(1)和(2);
  5. 在Android上实现实验(3);

实验七

查找和排序:

  1. 定义一个Searching和Sorting类,并在类中实现linearSearch,SelectionSort方法,并完成测试;
  2. 重构代码—:把Sorting.java Searching.java放入 cn.edu.besti.cs2023.F307,把测试代码放test包中,重新编译,运行代码;
  3. 参考 http://www.cnblogs.com/maybe2030/p/4715035.html ,学习各种查找算法并在Searching中补充查找算法并测试。
  4. 实现排序方法等(至少3个)并测试实现的算法(正常,异常,边界);
  5. 编写Android程序对实现各种查找与排序算法进行测试。

实验八

树:

  1. 参考教材PP16.1,完成链树LinkedBinaryTree的实现(getRight,contains,toString,preorder,postorder)
    用JUnit或自己编写驱动类对自己实现的LinkedBinaryTree进行测试;
  2. 基于LinkedBinaryTree,实现基于(中序,先序)序列构造唯一一棵二㕚树的功能,用JUnit或自己编写驱动类对自己实现的功能进行测试;
  3. 自己设计并实现一颗决策树;
  4. 输入中缀表达式,使用树将中缀表达式转换为后缀表达式,并输出后缀表达式和计算结果。

实验九

图:

  1. 初始化:根据屏幕提示(输入1为无向图,输入2为有向图)初始化无向图和有向图(这里我采用了邻接矩阵和邻接表两种方式分别实现);
  2. 图的遍历:有向/无向图的深度/广度优先遍历;
  3. 有向图的拓扑排序;
  4. 无向图的最小生成树(Prim算法及Kruscal算法);
  5. 有向图的单源最短路径求解(迪杰斯特拉算法)。

代码托管链接:https://gitee.com/besti2023javads/fan-yuhan-20202307

给出statistic.sh的运行结果,统计本学期的代码量。

课程收获与不足

自己的收获(投入,效率,效果等)

从最开始模仿学习别人的代码,仿照改造实现自己的功能,到后面慢慢可以自主创造(虽然创造能力也不是很强),达成学习目标,在学科学习和增长学习经验上都收获满满。从安装VMware和Ubuntu操作系统,IDEA,配置环境变量等等开始,到开始学习命令行基础操作、git push等基础技能,markdown语法(这里也学到了不少小技巧)、UML建模等辅助技能,到后续在IDEA继承开发环境下编写代码,每一步都遇到了不少困难,每次实验过程中也是一步一坎(一直在知识的苦海里遨游......)。其间,通过各种途径的学习,比如我在网页中搜索浏览了非常多的帖子,其中包括相关知识介绍和拓展、功能的实现与测试等等等等,举步维艰的时候也求助了同学和学姐很多次哈哈哈,不过更多的时候是自己反复琢磨、反复试验之后,最终到达了成功的彼岸(每次看到运行成功的界面都是快要激动落泪的程度)。几次实验之后,自主学习能力感觉有了很大提升,比起以前会更有勇气和耐心直面困难,虽然也会有一些小小的畏难情绪以及不计其数的深夜破防时刻,但解决问题运行成功的时刻都充满了成就感和欣慰(功夫不负苦心人💧)。虽然经常会花掉很多很多的时间,但学习起来的确越来越有劲头,从一开始学习别人的代码到逐渐“积极主动敲代码”(虽然积极主动了起来,但也还总是在赶ddl...),经过一个学期的学习,也许我的编程水平还不如别人,但通过努力实现了很多以前觉得无法达成的学习目标,掌握了一定的编程能力,也是收获颇丰。
这学期在Java上投入的时间也着实不少,经常从白天写到晚上,从晚上编到半夜(也流失了不少头发TT)......但有时候效率的确不高,IDEA总是在一些令人意想不到的地方报出一堆又一堆的错误,有一些在网页上也搜索不到有效的解决办法,通常只能一步一步地试验、分析原理和问题所在,这时候对知识掌握不深或是操作不熟悉就会浪费掉很多时间,甚至可能在歪掉的道路上越走越偏,最终走投无路只能放弃这棵歪脖子树另寻他法,开启新一轮的挑战。

自己需要改进的地方

效率不太高可以说是我学习所有学科时候的一大“痛点”,很多时候不是不能完成好,但是完成一项作业、一项实验往往需要耗费大量的时间,尤其是面对实验,每次都是焦头烂额,一不小心就和疯狂报错的代码奋战到天明。如果从一开始敲代码能更加积极主动一些,每次完成作业赶在ddl前面多打一些提前量出来,把教材和网页中的相关知识点研读的再透彻一些,也许就可以省去很多被浪费掉的时间,现在的编程能力可能也会有更大的进步和提升。(头发也能回来一些...)

posted @ 2022-01-02 20:55  20202307范宇涵  阅读(87)  评论(1编辑  收藏  举报