20165310_JavaExp1

20165310_JavaExp1_Java开发环境的熟悉

一、Exp1

Exp1_1

实验目的与要求:

  1. 使用JDK编译、运行简单的Java程序;
  2. 使用Vim进行Java源代码编译;
  3. 利用Git上传代码到远程仓库;

实验内容与步骤:

  • 命令行进入文件目录Gitlib/20165310exp1/exp,ls命令查看exp1目录下两个目录srcbin,用Vim进行代码编辑:

    幕快照 2018-04-01 下午2.09.1

  • 编辑代码:

    幕快照 2018-04-01 下午2.12.1

  • exp1目录下利用javacjava命令进行编译运行代码:

    幕快照 2018-04-01 下午2.10.3

  • 运用git命令上传代码(这里将exp1_1与exp1_2代码上传):

    幕快照 2018-04-01 下午2.11.2

Exp1_2

实验目的与要求

  1. IDEA进行代码运行调试
  2. 设置普通断点、条件断点、一次性断点并进行运行

实验内容与步骤

  • IDEA编写代码,并编译运行:

    幕快照 2018-04-01 下午2.43.2

  • 左键设定普通断点:

    幕快照 2018-04-01 下午2.43.5

  • 左键设置普通断点后,右键设置条件断点:

    幕快照 2018-04-01 下午2.45.5

  • 设置一次性断点直接完成循环:

    幕快照 2018-04-01 下午2.48.3

    幕快照 2018-04-01 下午2.49.0

  • 运行普通断点:

    幕快照 2018-04-01 下午2.46.0

  • 运行条件断点:

    幕快照 2018-04-01 下午2.46.3

  • 运行一次性断点后,一次性断点消失:

    幕快照 2018-04-01 下午2.49.1

Exp1_3

实验目的与要求

要求:实现简单四则运算(能支持分数,加减乘除,支持括号),并进行测试(正常情况,异常情况,边界情况)。

实验内容与步骤

思路:
  • 进行带括号的四则运算:需要将输入的字符串更改为后缀式并进行计算。学习了[2016-2017-2 《Java 程序设计》课堂实践项目]之后,发现老师的参考代码MyDC.java,原理是:利用空格作为分隔符将后缀式表达的字符串进行分割,遇到操作数就压栈,遇到操作符就弹出栈顶的两位操作数进行运算,再将运行结果压栈,直到没有下一个分割好的字符串,输出结果:
import java.util.StringTokenizer;
 import java.util.Stack;

 public class MyDC
 {
   /** constant for addition symbol */
   private final char ADD = '+';
   /** constant for subtraction symbol */
   private final char SUBTRACT = '-';
   /** constant for multiplication symbol */
   private final char MULTIPLY = '*';
   /** constant for division symbol */
   private final char DIVIDE = '/';
   /** the stack */
   private Stack<Integer> stack;//存放操作数的栈,且只能存放Integer型
   public MyDC()
   {
     stack = new Stack<Integer>();
   }

   public int evaluate (String expr)
   {
     int op1, op2, result = 0;
     String token;
     StringTokenizer tokenizer = new StringTokenizer (expr);//划分表达式

     while (tokenizer.hasMoreTokens())
     {
       token = tokenizer.nextToken();//将算数表达式以空格为分隔符进行分解

       if (isOperator(token))//见下方isOperateor方法,当是运算符的时候进入if语句
       {
         op2 = (stack.pop()).intValue();
         op1 = (stack.pop()).intValue();//弹出最上面两个操作数
         result = evalSingleOp (token.charAt(0), op1, op2);//见下方evaSingleOp方法
         stack.push (new Integer(result));//将计算结果压栈
       }
       else
         stack.push (new Integer(Integer.parseInt(token)));//操作数入栈
     }

     return result;//输出结果
   }

   private boolean isOperator (String token)//判断是否为运算符,注意用equal语句比较字符串
   {
     return ( token.equals("+") || token.equals("-") ||
              token.equals("*") || token.equals("/") );
   }

   private int evalSingleOp (char operation, int op1, int op2)
   {
     int result = 0;

     switch (operation)
     {
       case ADD:
         result = op1 + op2;
         break;
       case SUBTRACT:
         result = op1 - op2;
         break;
       case MULTIPLY:
         result = op1 * op2;
         break;
       case DIVIDE:
         result = op1 / op2;
     }

     return result;
   }
 }

  • 考虑题目要求为能进行分数运算,想起来教材第四章代码Rational.java可以保留分式进行加、减、乘、除、分数约分等运算,但书上代码

    a. 没有考虑到分母为零或者除数为零的情况,所以加以改动,在此情况下打印错误“分子/除数不能为0并退出运算”;

    b. 分母为负分子为正时的输出没有将符号提前,进行符号提前:

public class Rational{//有理数
  int numerator=1;//分子
  int denominator=1;//分母
  void setNumerator(int a){//设置分子
    int c=f(Math.abs(a),denominator);//计算最大公约数
    numerator=a/c;
    denominator=denominator/c;
    if (numerator<0&&denominator<0) {
      numerator=-numerator;
      denominator=-denominator;
    }
  }
  void setDenominator(int b){//设置分母
    int c=f(numerator,Math.abs(b));//计算最大公约数
    numerator=numerator/c;
    denominator=b/c;
    if (numerator<0&&denominator<0) {
      numerator=-numerator;
      denominator=-denominator;
    }
    else if (numerator>0&&denominator<0){
        numerator=-numerator;
        denominator=-denominator;
    }
  }
  int getNumerator(){
    return numerator;
  }
  int getDenominator(){
    return denominator;
  }
  int f(int a,int b){//求a,b的最大公约数
    if (a==0) {
      return 1;//c为分母不能为0
    }
    if (a<b) {//令a>b
      int c=a;
      a=b;
      b=c;
    }
    int r=a%b;
    while (r!=0) {
      a=b;
      b=r;
      r=a%b;
    }
    return b;
  }
  Rational add(Rational r){//加法运算
    int a=r.getNumerator();//返回有理数r的分子
    int b=r.getDenominator();//返回有理数r的分母
    int newNumerator=numerator*b+denominator*a;//计算出新分子
    int newDenominator=denominator*b;//计算出新分母
    Rational result=new Rational();
    result.setNumerator(newNumerator);
    result.setDenominator(newDenominator);
    return result;
  }
  Rational sub(Rational r){//减法运算
    int a=r.getNumerator();
    int b=r.getDenominator();
    int newNumerator=numerator*b-denominator*a;
    int newDenominator=denominator*b;
    Rational result=new Rational();
    result.setNumerator(newNumerator);
    result.setDenominator(newDenominator);
    return result;
  }
  Rational muti(Rational r){//乘法运算
    int a=r.getNumerator();
    int b=r.getDenominator();
    int newNumerator=numerator*a;
    int newDenominator=denominator*b;
    Rational result=new Rational();
    result.setNumerator(newNumerator);
    result.setDenominator(newDenominator);
    return result;
  }
  Rational div(Rational r){//除法运算
    int a=r.getNumerator();
    int b=r.getDenominator();
    Rational result=new Rational();
    if (a==0) {
      System.out.println("分母/除数不能为0");
     result.setNumerator(0);
      System.exit(0);
    }
    else{
      int newNumerator=numerator*b;
      int newDenominator=denominator*a;
      result.setNumerator(newNumerator);
      result.setDenominator(newDenominator);
    }
    return result;
  }
}

  • 根据MyDC.javaRational.java进行综合与改动,完成代码MyDcRational.java,将整数与小数运算改为分数与整数的后缀式运算:
import java.util.StringTokenizer;
 import java.util.Stack;

 public class MyDcRational
 {
   /** constant for addition symbol */
   private final char ADD = '+';
   /** constant for subtraction symbol */
   private final char SUBTRACT = '-';
   /** constant for multiplication symbol */
   private final char MULTIPLY = '*';
   /** constant for division symbol */
   private final char DIVIDE = '/';
   /** the stack */
   private Stack stack;//存放操作数的栈
   public MyDcRational()
   {
     stack = new Stack();
   }

   public Rational evaluate (String expr)
   {
     Rational op1=new Rational();
     Rational op2=new Rational();
     Rational result=new Rational();
     result.setNumerator(0);
     String token;
     StringTokenizer tokenizer = new StringTokenizer (expr);//划分表达式

     while (tokenizer.hasMoreTokens())
     {
       token = tokenizer.nextToken();//将算数表达式分解的

       if (isOperator(token))//见下方isOperateor方法,当是运算符的时候进入if语句
       {
         op2 = (Rational) stack.pop();
         op1 = (Rational)stack.pop();//弹出最上面两个操作数
         result = evalSingleOp (token.charAt(0), op1, op2);//见下方evaSingleOp方法
         stack.push (result);//将计算结果压栈
       }
       else{
            Rational num=new Rational();
            num.setNumerator(Integer.parseInt(token));//将操作数由string转变为Rational
            stack.push (num);//操作数入栈
       }

     }

     return result;//输出结果
   }

   private boolean isOperator (String token)//判断是否为运算符,注意用equal语句比较字符串
   {
     return ( token.equals("+") || token.equals("-") ||
              token.equals("*") || token.equals("/") );
   }

   private Rational evalSingleOp (char operation, Rational op1, Rational op2)
   {
     Rational result=new Rational();
     result.setNumerator(0);
     switch (operation)
     {
       case ADD:
         result = op1.add(op2);
         break;
       case SUBTRACT:
         result = op1.sub(op2);
         break;
       case MULTIPLY:
         result = op1.muti(op2);
         break;
       case DIVIDE:
         result = op1.div(op2);
         break;
        default:
          System.out.println("Error!");
     }
     return result;
   }
 }

  • 设立一个栈,存放运算符,首先栈为空;
  • 从左到右扫描中缀式,若遇到操作数,直接输出,并输出一个空格作为两个操作数的分隔符;
  • 若遇到运算符,则与栈顶比较,比栈顶级别高则进栈,否则退出栈顶元素并输出,然后输出一个空格作分隔符;
  • 若遇到左括号,进栈;若遇到右括号,则一直退栈输出,直到退到左括号止。
  • 当栈变成空时,输出的结果即为后缀表达式。

根据上述思路,完成代码ChangeExpress.java,将前缀式改为后缀式,并且完成分析括号匹配的功能,若左右括号不匹配,输出错误并退出程序运行:

import java.util.*;
public class ChangeExpress{
  String originalExpression;
  String changedExpression= "";
  int countLeft=0,countRight=0;
  public void setOriginalExpression(String str){
    originalExpression=str;
  }
  public void changedWay(){
    Stack stackChange=new Stack();//创立栈
    int opValue []=new int[100];
    for (int i=0;i<originalExpression.length() ;i++) {
      char chi=originalExpression.charAt(i);
      if (chi>='0'&&chi<='9'){
          changedExpression=changedExpression+chi;
      }
      else if (chi=='+'||chi=='-'||chi=='*'||chi=='/') {
        changedExpression=changedExpression+" ";//有运算符,数字之间就要有空格,否则是一个整体
        if (stackChange.empty()){//栈为空直接压栈
            stackChange.push(chi);
        }
        else if (judgeValue(chi)>=judgeValue((char)stackChange.peek())) {//运算级别高或者相等压入栈
          stackChange.push(chi);
        }
        else{
          changedExpression=changedExpression+ String.valueOf(stackChange.pop())+" ";//否则直接进入字符串,空格分割运算符
          i--;
        }
      }
      else if(chi=='('){
        countLeft++;
        stackChange.push(chi);//左括号压栈
      }
      else if(chi==')'){
        changedExpression+=" ";
        countRight++;
        while((char)stackChange.peek()!='('){//直到(为止
          changedExpression=changedExpression+ String.valueOf(stackChange.pop())+" ";//弹出栈内东西,空格分割
        }
        stackChange.pop();
      }
    }
    changedExpression+=" ";
    while(!stackChange.empty()){
        changedExpression=changedExpression+String.valueOf(stackChange.pop())+" ";
    }
    if (countLeft!=countRight) {
      System.out.println("括号不匹配");
      System.exit(0);
    }
  }
  public int judgeValue(char c){
    int value=0;
    switch(c){
      case '(':
        value=1;
        break;
      case '+':
      case '-':
        value=2;
        break;
      case '*':
      case '/':
        value=3;
        break;
      case ')':
        value=4;
      default:
        value=0;
    }
    return value;
  }
}

  • 最后编写主函数代码Calculation.java,实现功能有:运算式输入、运算、结果输出:
import java.util.*;
public class Calculation{
  public static void main(String[] args) {
    Scanner reader=new Scanner(System.in);
    Rational result=new Rational();
    System.out.println("请输入运算式");
    String str=reader.nextLine();
    ChangeExpress change=new ChangeExpress();
    change.setOriginalExpression(str);
    //System.out.println(change.originalExpression);
    change.changedWay();//后缀式化
    //System.out.println(change.changedExpression);
    MyDcRational calculate=new MyDcRational();//后缀式计算
    result=calculate.evaluate(change.changedExpression);
    int a=result.getNumerator();
    int b=result.getDenominator();
    if (b==1){
        System.out.println("result="+a);
    }
    else{
        System.out.println("result="+a+"/"+b);
    }
  }
}

运行结果测试与截图
  • 分母或者除数为零的时候报错并退出

幕快照 2018-04-01 下午3.30.1

  • 括号不匹配时,报错并退出:

幕快照 2018-04-01 下午3.30.3

  • 简单的整数四则运算:

幕快照 2018-04-01 下午3.31.1

  • 简单地分数四则运算

幕快照 2018-04-01 下午3.31.4

  • 带负号的分数四则运算

幕快照 2018-04-01 下午3.36.5

二、实验过程中遇到的问题与解决方法

  • 对于Stack的相关函数不了解,运用pop()push()函数时有时会忽略类型转换:

    解决方法:利用IDEA调试报错修改程序,学习函数相关用法

  • ChangeExpress.java中进行后缀式转换的时候,出现符号不进栈的情况:

    解决方法:发现自己在进行符号压栈时,判断条件中忽略了空栈的情况,只考虑了与Stack.peek()进行比较,但是空栈时为null,增加判断条件Stack.Empty()时,直接进栈。

  • ChangeExpress.java中进行后缀式转换的时候,出现符号缺失的情况:

    解决方法:依然是忽略了空栈的判断条件,在循环结束后,栈中元素应该依次弹出,忽略了这一步骤,导致循环结束后,栈内元素没有弹出,所以有符号的缺失。

  • ChangeExpress.java中进行后缀式转换的时候,为了将多位数字不误拆分开,例如:123+34,进行后缀式改变的时候逻辑为:下一个字符仍然是数字时,数字之间不以空格隔开,否则用空格隔开。因此出现了以下代码:

     for (int i=0;i<originalExpression.length() ;i++) {
          char chi=originalExpression.charAt(i);
          char chj=originalExpression.charAt(i);
          if (chi>='0'&&chi<='9'){
              if(chi>='0'&&chi<='9')//下一位字符仍然是数字
              changedExpression=changedExpression+chi;//不以空格分割
              else
              changedExpression=changedExpression+chi+" ";//加上空格
          }
    

    但是由于chji=originalExpression.length()-1时发生溢出,导致报错无法进行运行。

    解决方法:一开始我仍然按照自己的逻辑,增加条件,在chj溢出的时候给它赋数字以外的字符,但是还是有错误,后来和学长交流以后,学长指出我的逻辑没有错,但是可以逆向思维一下:连续的数字不打空格,变为遇到运算符就打空格,由于在MyDcRational.java中以空格为分割符,但并不限制空格的个数,因此修改了程序,有时更改的后缀式有效运算子符间隔得空格可能是一个也可能是两个,但是不影响最后程序的运算。

  • ChangeExpress.java中进行后缀式转换的时候,进行操作符与栈顶的操作符的优先级比较,直到优先级高于栈顶元素才进栈,应该是利用while进行循环判断,以“优先级低于栈顶元素”作为终止条件,但是我忽略了循环要求,仅仅利用if语句与栈顶元素比较,导致符号有所丢失。

    解决方法:利用while循环替代if语句,但是在临界点判断上出错了,调试的时候与学长进行交流:提出了更简单地语句:i--,利用for循环中的i++抵消i参数的变化,从而达成循环的目的,减低了语句复杂度的同时,减少了出错率,避免了临界点判断失误的错误。

实验心得与总结

实验心得:

收获:

  • 学会了Stack类的运用与相关函数运用。
  • 学会了四则运算后缀式化与计算的思维逻辑方式与代码编写方法。
  • 熟悉了IDEA的单步调试功能。

不足:

  • 由于MyDcRational.java是根据老师的参考代码和书上代码修改的,所以基本没什么错误,但是改成后缀式的ChangeExpress.java是自己独立编写的,出现了很多错误,感觉自己的代码编写水平和设计思维能力还需要提高。
  • 只能完成较为简单的四则运算,复杂化后就会容易出问题。
  • ChangeExpress.java中感觉“高内聚,低耦合”的思想没能很好的体现,思维逻辑还需要锻炼。

PSP表格

步骤 需求分析 百分比
需求分析 30min 7.1%
设计 30min 7.1%
代码实现 2h 28.6%
测试 3h 42.9%
分析总结 1h 14.3%

总结:

  • 需求分析与设计时间相对较少,在编程中也出现不少逻辑错误,如果实现能够思考的更加详尽,其实可以减少实现代码与测试的时间。
  • 另外对于Java还有很多语法、已经自带的类和方法学习的不足与错误,需要在实践中学习与改进。
posted @ 2018-04-01 17:23  AOI  阅读(225)  评论(3编辑  收藏  举报