OO第三次博客作业

一、规格化设计的发展历史

首先来说一说规格化设计的发展历史,由于所能找到的资源实在有限,所以只能写这些了。

1950年代—1960年代初,手工艺式的程序设计方法,高德纳把程序称为艺术品。

1960年代末—1970年代初,出现软件危机:一方面需要大量的软件系统,如操作系统、数据库管理系统; 另一方面,软件研制周期长,可靠性差,维护困难。编程的重点:希望编写出的程序结构清晰、易阅读、易修改、易验证,即得到好结构的程序。

1968年,北大西洋公约组织(NATO)在西德召开了第一次软件工程会议,分析了危机的局面,研究了问题的根源,第一次提出了用工程学的办法解决软件研制和生产的问题,本次会议可以算做是软件发展史上的一个重要的里程碑。同时,Dijkstra 提出了“GOTO是有害的”,希望通过程序的静态结构的良好性保证程序的动态运行的正确性。

1969年,国际信息处理协会(IFIP)成立了“程序设计方法学工作组”,专门研究程序设计方法学,程序设计从手工艺式向工程化的方法迈进。同年,Wirth 提出采用“ 自顶向下逐步求精、分而治之” 的原则进行大型程序的设计。其基本思想是:从欲求解的原问题出发,运用科学抽象的方法,把它分解成若干相对独立的小问题,依次细化,直至各个小问题获得解决为止。

这便是程序设计初期的发展,而从程序设计发展开始至今,程序设计的方法一直在不断的进步中。我们期待会有越来越多的人认识与学习这种方法,并将其不断发展壮大。

二、规格bug分析

1、第九次作业

本次作业遇到了一个比较认真的测试者,再加之第八次作业的测试者没有对我的程序进行测试。导致这次作业被发现了不少的bug。在这里陈述,并进行反思。

JSF存在bug

REQUIRES不完整:2

MODIFIES不完整:1

虽然测试者只给调出这几个bug,但是实际上我写的JSF还是存在许多漏洞的,在这里挑典型分析一下。

MODIFIES不完整:

public boolean checksame(Req r) 
    /**
     * @EFFECTS:(\all Req r;r match;\result == true);
     *          (\all Req r;r not match;\result == false);
     */
    {
        if (ReqTray.isEmpty()) {
            ReqTray.add(r);
            return true;
        } else if (ReqTray.get(ReqTray.size() - 1).getTime() == r.getTime()) {
            for (Req now : ReqTray) {
                if (now.equals(r)) {
                    System.out.println("指令"+r.getReqnum()+"同质");
                    return false;
                }
            }
            ReqTray.add(r);
            return true;
        } else {
            ReqTray.clear();
            ReqTray.add(r);
            return true;
        }
修改前源代码

可以发现这段代码的MODIFIES写的并不完整,且EFFECTS也存在相应的问题。修改如下:

public boolean checksame(Req r) 
    /**
     * @REQUIRES: r != null;
     * @MODIFIES: ReqTray;
     * @EFFECTS: (\result == false)==>(\old(\this).contains(r) == true);
     *           (\result == true)==>(\this.size == \old(\this).size+1) && (\this.contains(a) == true)&&(\result == true);
     */
    {
        if (ReqTray.isEmpty()) {
            ReqTray.add(r);
            return true;
        } else if (ReqTray.get(ReqTray.size() - 1).getTime() == r.getTime()) {
            for (Req now : ReqTray) {
                if (now.equals(r)) {
                    System.out.println("指令"+r.getReqnum()+"同质");
                    return false;
                }
            }
            ReqTray.add(r);
            return true;
        } else {
            ReqTray.clear();
            ReqTray.add(r);
            return true;
        }
    }
修改后代码

REQUIRES不完整:

public synchronized void addTaxi(int amount)
    /**
     * @MODIFIES:ArrayList<Taxi> Taxi;
     * @EFFECTS:(\all Taxi e;0 <= x && x <= 80 && 0 <= y && y <= 80 && src.x - 2 <=x && x <= src.x + 2 && src.y - 2 <= y && y <= src.y + 2 && nowTaxi.getState() == 2;Taxi.add(e))
     */
    {
        long Start = gv.getTime();

        int i = 0;// 检查次数变量

        Point src = this.src;

        ShortWay.pointbfs(src);// 在抢单时计算路径长度

        while (gv.getTime() - Start <= 7500) {
            for (i = 0; i < amount; i++) {
                Taxi nowTaxi = Alltaxi.All.get(i);
                // System.out.println("检查出租车" + nowTaxi.getNum() + "能否接单");
                int x = nowTaxi.getLoc_i();
                int y = nowTaxi.getLoc_j();
                // System.out.println("出租车" + nowTaxi.getNum() + "检查接单时位置" + "(" + x + "," + y +
                // ")");
                if (0 <= x && x <= 80 && 0 <= y && y <= 80 && src.x - 2 <= x && x <= src.x + 2 && src.y - 2 <= y
                        && y <= src.y + 2 && nowTaxi.getState() == 2) {
                    // System.out.println(src.i);
                    // System.out.println(src.j);
                    if (Taxi_flag[i] == false) {
                        Catch.add(nowTaxi);
                        nowTaxi.addCredit(1);
                        Taxi_flag[i] = true;
                        System.out.println("将出租车" + nowTaxi.getNum() + "加入抢单队列");
                    }
                }
            }
            gv.stay(250);
        }
    }
修改前源代码

同样的这段代码的JSF缺少REQUIRES,同时MODIFIES和EFFECTS也存在不同程度上的问题。修改如下:

public synchronized void addTaxi(int amount)
    /**
     * @REQUIRES: amount >= 0;
     * @MODIFIES: Taxi;
     * @EFFECTS: (\all Taxi e;Catch.contain(e) == true) ==> (0<=a.x<=79 && src.x-2<=a.x<=src.x+2)
     * ||(0<=a.y<=79 && src.y-2<=a.y<=src.y+2);
     */
    {
        long Start = gv.getTime();

        int i = 0;// 检查次数变量

        Point src = this.src;

        ShortWay.pointbfs(src);// 在抢单时计算路径长度

        while (gv.getTime() - Start <= 7500) {
            for (i = 0; i < amount; i++) {
                Taxi nowTaxi = Alltaxi.All.get(i);
                // System.out.println("检查出租车" + nowTaxi.getNum() + "能否接单");
                int x = nowTaxi.getLoc_i();
                int y = nowTaxi.getLoc_j();
                // System.out.println("出租车" + nowTaxi.getNum() + "检查接单时位置" + "(" + x + "," + y +
                // ")");
                if (0 <= x && x <= 80 && 0 <= y && y <= 80 && src.x - 2 <= x && x <= src.x + 2 && src.y - 2 <= y
                        && y <= src.y + 2 && nowTaxi.getState() == 2) {
                    // System.out.println(src.i);
                    // System.out.println(src.j);
                    if (Taxi_flag[i] == false) {
                        Catch.add(nowTaxi);
                        nowTaxi.addCredit(1);
                        Taxi_flag[i] = true;
                        System.out.println("将出租车" + nowTaxi.getNum() + "加入抢单队列");
                    }
                }
            }
            gv.stay(250);
        }
    }
修改后代码

 程序存在的bug

本次程序的LoadFilename这项功能好像写的不是很好,出现了许多奇怪的bug,之后本人在第十次作业的时候重写了LoadFilename的功能使之完成了其功能。在这里简单叙述一下被调出的bug。

1、在判断指令是否符合要求时,本人一不小心将地图上路径点的坐标范围写成了0<=&&<=80(实际地图点是0<=&&<=79)。被报了3个bug,痛失分数,在这种小地方一定要注意,且边界条件要多多测试。

2、还有一处是关于指导书理解的bug,LoadFilename中初始化出租车的状态为接乘客或服务状态时,处理自定义。我将其处理为全部默认为等待状态(其实我是以偷懒的故意这么做的)。结果被报了一个Incomplete的bug,看来还是不能偷懒。

3、其余的bug均为LoadFilename没有处理好,我找了许久没能找到问题,就重写解决了。在此处不再赘述。

 2、第十次作业

这次作业比较幸运,没有被挑出bug(可能是上次被扣掉的分数有些多,导致掉段了吧)。但是这不代表本次作业没有问题,相反这次作业仍存在着众多问题。我会在后面前置条件与后置条件的例子分析中指出,并改正。

3、第十一次作业

这次的作业JSF未被挑出bug,但是如同上次作业一样,并不代表没有问题。相反也存在许多不尽人意的地方。同样我会在后面指出并修改。

三、规格bug产生的原因

主要是不太会写,虽然这个理由很笼统,但确实是这样。在经过了几次OO上机课之后,接触了一些例子,感觉JSF的书写才规范了起来。所有我觉得主要原因是之前接触这些东西比较少,需要一些实例的说明才能写好吧。

四、写法改进

实例1

public boolean checksame(Req r) 
    /**
     * @EFFECTS:(\all Req r;r match;\result == true);
     *          (\all Req r;r not match;\result == false);
     */
    {
        if (ReqTray.isEmpty()) {
            ReqTray.add(r);
            return true;
        } else if (ReqTray.get(ReqTray.size() - 1).getTime() == r.getTime()) {
            for (Req now : ReqTray) {
                if (now.equals(r)) {
                    System.out.println("指令"+r.getReqnum()+"同质");
                    return false;
                }
            }
            ReqTray.add(r);
            return true;
        } else {
            ReqTray.clear();
            ReqTray.add(r);
            return true;
        }
病例典型

存在问题:REQUIRES根本没有,其实本方法是需要前置条件的,后置条件接近自然语言,改为逻辑表达式会更好一些。

public boolean checksame(Req r) 
    /**
     * @REQUIRES: r != null;
     * @MODIFIES: ReqTray;
     * @EFFECTS: (\result == false)==>(\old(\this).contains(r) == true);
     *           (\result == true)==>(\this.size == \old(\this).size+1) && (\this.contains(a) == true)&&(\result == true);
     */
    {
        if (ReqTray.isEmpty()) {
            ReqTray.add(r);
            return true;
        } else if (ReqTray.get(ReqTray.size() - 1).getTime() == r.getTime()) {
            for (Req now : ReqTray) {
                if (now.equals(r)) {
                    System.out.println("指令"+r.getReqnum()+"同质");
                    return false;
                }
            }
            ReqTray.add(r);
            return true;
        } else {
            ReqTray.clear();
            ReqTray.add(r);
            return true;
        }
    }
修改代码

实例2

public synchronized void addTaxi(int amount)
    /**
     * @MODIFIES:ArrayList<Taxi> Taxi;
     * @EFFECTS:(\all Taxi e;0 <= x && x <= 80 && 0 <= y && y <= 80 && src.x - 2 <=x && x <= src.x + 2 && src.y - 2 <= y && y <= src.y + 2 && nowTaxi.getState() == 2;Taxi.add(e))
     */
    {
        long Start = gv.getTime();

        int i = 0;// 检查次数变量

        Point src = this.src;

        ShortWay.pointbfs(src);// 在抢单时计算路径长度

        while (gv.getTime() - Start <= 7500) {
            for (i = 0; i < amount; i++) {
                Taxi nowTaxi = Alltaxi.All.get(i);
                // System.out.println("检查出租车" + nowTaxi.getNum() + "能否接单");
                int x = nowTaxi.getLoc_i();
                int y = nowTaxi.getLoc_j();
                // System.out.println("出租车" + nowTaxi.getNum() + "检查接单时位置" + "(" + x + "," + y +
                // ")");
                if (0 <= x && x <= 80 && 0 <= y && y <= 80 && src.x - 2 <= x && x <= src.x + 2 && src.y - 2 <= y
                        && y <= src.y + 2 && nowTaxi.getState() == 2) {
                    // System.out.println(src.i);
                    // System.out.println(src.j);
                    if (Taxi_flag[i] == false) {
                        Catch.add(nowTaxi);
                        nowTaxi.addCredit(1);
                        Taxi_flag[i] = true;
                        System.out.println("将出租车" + nowTaxi.getNum() + "加入抢单队列");
                    }
                }
            }
            gv.stay(250);
        }
    }
病例典型

存在问题:依旧是没有前置条件,后置条件为自然语言。

public synchronized void addTaxi(int amount)
    /**
     * @REQUIRES: amount >= 0;
     * @MODIFIES: Taxi;
     * @EFFECTS: (\all Taxi e;Catch.contain(e) == true) ==> (0<=a.x<=79 && src.x-2<=a.x<=src.x+2)
     * ||(0<=a.y<=79 && src.y-2<=a.y<=src.y+2);
     */
    {
        long Start = gv.getTime();

        int i = 0;// 检查次数变量

        Point src = this.src;

        ShortWay.pointbfs(src);// 在抢单时计算路径长度

        while (gv.getTime() - Start <= 7500) {
            for (i = 0; i < amount; i++) {
                Taxi nowTaxi = Alltaxi.All.get(i);
                // System.out.println("检查出租车" + nowTaxi.getNum() + "能否接单");
                int x = nowTaxi.getLoc_i();
                int y = nowTaxi.getLoc_j();
                // System.out.println("出租车" + nowTaxi.getNum() + "检查接单时位置" + "(" + x + "," + y +
                // ")");
                if (0 <= x && x <= 80 && 0 <= y && y <= 80 && src.x - 2 <= x && x <= src.x + 2 && src.y - 2 <= y
                        && y <= src.y + 2 && nowTaxi.getState() == 2) {
                    // System.out.println(src.i);
                    // System.out.println(src.j);
                    if (Taxi_flag[i] == false) {
                        Catch.add(nowTaxi);
                        nowTaxi.addCredit(1);
                        Taxi_flag[i] = true;
                        System.out.println("将出租车" + nowTaxi.getNum() + "加入抢单队列");
                    }
                }
            }
            gv.stay(250);
        }
    }
修改代码

实例3

public void outputInfo(int Req_num)
    /**
     * @MODIFIES:None;
     * @EFFECTS:Output Info;
     */
    {

        File file = new File("E:\\Bill");
        if (!file.exists())
            file.mkdirs();

        FileWriter outputStream = null;

        try {
            outputStream = new FileWriter("E:\\Bill\\Check_Bill.txt", true);
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

        if (Req_num > this.getLength() - 1) {
            try {
                outputStream.write("不存在该订单");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        Bill now_Bill = this.Taxi_bill.get(Req_num);

        try {
            outputStream.write("出租车编号:" + this.Num + "\n");
            outputStream.write("此为该出租车接到的第" + Req_num + "号单");
            outputStream.write("本次账单号为:" + now_Bill.Reqnum + "\n");
            outputStream.write("派单时出租车所在位置:" + "(" + now_Bill.Rec.x + "," + now_Bill.Rec.y + ")" + "\n");
            outputStream.write("派单时刻:" + sdf.format(new Date(now_Bill.RecT)) + "\n");
            outputStream.write("到达乘客位置时刻:" + sdf.format(new Date(now_Bill.StartT)) + "\n");
            outputStream.write("乘客位置坐标:" + "(" + now_Bill.src.x + "," + now_Bill.src.y + ")" + "\n");
            outputStream.write("到达目的地坐标:" + "(" + now_Bill.dst.x + "," + now_Bill.dst.y + ")" + "\n");
            outputStream.write("到达目的地时刻:" + sdf.format(new Date(now_Bill.EndT)) + "\n");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            System.out.println("监控出租车输出错误");
            e.printStackTrace();
        }
        
        for(Bill_path path:this.Path) {
            if(path.getReq_num()==Req_num) {
                try {
                    outputStream.write(path.getpath());
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

    }
病例典型

没有REQUIRES(我当时怎么想的就不写REQUIRES),因为输出是文件所以MODIFIES和EFFECTS应该是None吧。

public void outputInfo(int Req_num)
    /**
     * @REQUIRES: Req_num >= 0;
     * @MODIFIES: None;
     * @EFFECTS: None;
     */
    {

        File file = new File("E:\\Bill");
        if (!file.exists())
            file.mkdirs();

        FileWriter outputStream = null;

        try {
            outputStream = new FileWriter("E:\\Bill\\Check_Bill.txt", true);
        } catch (IOException e1) {
            // TODO Auto-generated catch block
            e1.printStackTrace();
        }

        SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss");

        if (Req_num > this.getLength() - 1) {
            try {
                outputStream.write("不存在该订单");
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }

        Bill now_Bill = this.Taxi_bill.get(Req_num);

        try {
            outputStream.write("出租车编号:" + this.Num + "\n");
            outputStream.write("此为该出租车接到的第" + Req_num + "号单");
            outputStream.write("本次账单号为:" + now_Bill.Reqnum + "\n");
            outputStream.write("派单时出租车所在位置:" + "(" + now_Bill.Rec.x + "," + now_Bill.Rec.y + ")" + "\n");
            outputStream.write("派单时刻:" + sdf.format(new Date(now_Bill.RecT)) + "\n");
            outputStream.write("到达乘客位置时刻:" + sdf.format(new Date(now_Bill.StartT)) + "\n");
            outputStream.write("乘客位置坐标:" + "(" + now_Bill.src.x + "," + now_Bill.src.y + ")" + "\n");
            outputStream.write("到达目的地坐标:" + "(" + now_Bill.dst.x + "," + now_Bill.dst.y + ")" + "\n");
            outputStream.write("到达目的地时刻:" + sdf.format(new Date(now_Bill.EndT)) + "\n");
        } catch (IOException e) {
            // TODO Auto-generated catch block
            System.out.println("监控出租车输出错误");
            e.printStackTrace();
        }
        
        for(Bill_path path:this.Path) {
            if(path.getReq_num()==Req_num) {
                try {
                    outputStream.write(path.getpath());
                } catch (IOException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            }
        }

    }
修改代码

实例4

public long getTime()
    /**
     * @MODIFIES:this.Time;
     * @EFFECTS:(\all long temp;temp >50) ==> \result == Time % 100 * 100 + 100;
     *                (\all long temp;temp <= 50) ==> \result == Time % 100 * 100;
     */
    {
        long temp = this.Time % 100;
        if (temp > 50)
            return Time % 100 * 100 + 100;
        else
            return Time % 100 * 100;
    }
原始代码
public long getTime()
    /**
     * @REQUIRES: None;
     * @MODIFIES: this.Time;
     * @EFFECTS: (\all long temp;temp >50) ==> (\result == Time % 100 * 100 + 100);
     *           (\all long temp;temp <= 50) ==> (\result == Time % 100 * 100);
     */
    {
        long temp = this.Time % 100;
        if (temp > 50)
            return Time % 100 * 100 + 100;
        else
            return Time % 100 * 100;
    }
修改代码

实例5

public synchronized Taxi FetchTaxi()
    /**
     * @REQUIRES:(ArrayList<Taxi> Taxi!=null);
     * @MODIFIES:ArrayList<Taxi> Taxi;
     * 
     * @EFFECTS:\result==(\exist Taxi e;\max Taxi.Credit&&\min D[][]);
     */
    {
        if (!Catch.isEmpty()) {
            int i = 1;
            Taxi now = Catch.get(0);

            while (i <= Catch.size() - 1 && !Catch.isEmpty()) {
                if (now.getCredit() < Catch.get(i).getCredit()) {
                    int j = 0;
                    for (j = i; j > 0; j--)
                        Catch.remove(0);
                    i = 0;
                    now = Catch.get(i);
                } else if (now.getCredit() > Catch.get(i).getCredit()) {
                    Catch.remove(i);
                } else if (now.getCredit() == Catch.get(i).getCredit()) {
                    now = Catch.get(i);
                    i++;
                }
            }
            i = 1;
            if (Catch.size() > 1) {
                now = Catch.get(0);
                while (i <= Catch.size() - 1 && !Catch.isEmpty()) {
                    if (ShortWay.distance(now.getLoc_i(), now.getLoc_j(), src.x, src.y) > ShortWay
                            .distance(Catch.get(i).getLoc_i(), Catch.get(i).getLoc_j(), src.x, src.y)) {
                        int j = 0;
                        for (j = i; j > 0; j--)
                            Catch.remove(0);
                        i = 0;
                        now = Catch.get(i);
                    } else if (ShortWay.distance(now.getLoc_i(), now.getLoc_j(), src.x, src.y) < ShortWay
                            .distance(Catch.get(i).getLoc_i(), Catch.get(i).getLoc_j(), src.x, src.y)) {
                        Catch.remove(i);
                    } else if (ShortWay.distance(now.getLoc_i(), now.getLoc_j(), src.x, src.y) == ShortWay
                            .distance(Catch.get(i).getLoc_i(), Catch.get(i).getLoc_j(), src.x, src.y)) {
                        now = Catch.get(i);
                        i++;
                    }
                }
            }
            if (Catch.get(0).getState() == 2) {
                int Rondom = (int) (Math.random() * Catch.size());
                Taxi Final = Catch.get(Rondom);
                Catch.clear();
                return Final;
            } else {
                Catch.clear();
                return null;
            }

        } else
            return null;

    }
原始代码

修改REQUIRES与EFFECTS。

public synchronized Taxi FetchTaxi()
    /**
     * @REQUIRES: Catch != null;
     * @MODIFIES: Catch;
     * @EFFECTS: (\result == null) ==> (Catch.isEmpty() == true);
     */
    {
        if (!Catch.isEmpty()) {
            int i = 1;
            Taxi now = Catch.get(0);

            while (i <= Catch.size() - 1 && !Catch.isEmpty()) {
                if (now.getCredit() < Catch.get(i).getCredit()) {
                    int j = 0;
                    for (j = i; j > 0; j--)
                        Catch.remove(0);
                    i = 0;
                    now = Catch.get(i);
                } else if (now.getCredit() > Catch.get(i).getCredit()) {
                    Catch.remove(i);
                } else if (now.getCredit() == Catch.get(i).getCredit()) {
                    now = Catch.get(i);
                    i++;
                }
            }
            i = 1;
            if (Catch.size() > 1) {
                now = Catch.get(0);
                while (i <= Catch.size() - 1 && !Catch.isEmpty()) {
                    if (ShortWay.distance(now.getLoc_i(), now.getLoc_j(), src.x, src.y) > ShortWay
                            .distance(Catch.get(i).getLoc_i(), Catch.get(i).getLoc_j(), src.x, src.y)) {
                        int j = 0;
                        for (j = i; j > 0; j--)
                            Catch.remove(0);
                        i = 0;
                        now = Catch.get(i);
                    } else if (ShortWay.distance(now.getLoc_i(), now.getLoc_j(), src.x, src.y) < ShortWay
                            .distance(Catch.get(i).getLoc_i(), Catch.get(i).getLoc_j(), src.x, src.y)) {
                        Catch.remove(i);
                    } else if (ShortWay.distance(now.getLoc_i(), now.getLoc_j(), src.x, src.y) == ShortWay
                            .distance(Catch.get(i).getLoc_i(), Catch.get(i).getLoc_j(), src.x, src.y)) {
                        now = Catch.get(i);
                        i++;
                    }
                }
            }
            if (Catch.get(0).getState() == 2) {
                int Rondom = (int) (Math.random() * Catch.size());
                Taxi Final = Catch.get(Rondom);
                Catch.clear();
                return Final;
            } else {
                Catch.clear();
                return null;
            }

        } else
            return null;

    }
修改代码

五、bug聚合关系

第九次作业

方法名:InputThread;功能bug:2;规格bug:0;

    LoadFilename;功能bug:3;规格bug:0;

第十次作业

方法名:无;功能bug:无;规格bug:无;

第十一次作业

方法名:Req;功能bug:2;规格bug:0;

看起来功能bug和规格bug关系不大,但事实上规格可以帮助我们确定边界条件,使我们的程序更不容易出错。

六、思路与体会

首先OO课程的困难阶段可能算是过去了吧。回想一下,虽然过程很累,但是还是有不少收获,至少push着我们学了不少的知识。关于JSF的问题,我已经看到不少同学在吐槽这个课程设置了,虽然我在编写程序的时候也没有体会到它的特殊作用。但是,我们应该意识到一个事实,规范化设计已经存在且蓬勃发展了这么长时间,那就一定有它的优势所在。可能因为我们还没有上升到认识到其重要性的高度,所以做这些事情的时候,只是觉得浪费时间。但我觉得学习这部分的知识还是有必要的。

 

 

posted @ 2018-05-30 10:28  A_47  阅读(128)  评论(0)    收藏  举报