2016-2017-2 《Java 程序设计》课堂实践项目

目录

做了几年教学改革,理论和形式上我感觉基本完备了:

很重要的一点是厘清“教是老师的责任,学是学生的责任”,也就是“老师当教练,学生做中学”。

有了SPOC平台蓝墨云班课 ,教学工具上也基本完善了:

我在程序设计学习上有一个基本的体会是:

  • 开始不会编程,最好的学习方式是抄教材,敲代码,还专门写了一篇指导《积极主动敲代码,使用Junit学习Java程序设计》,我认为积极主动敲5000行左右的代码,应该能解决基本语法的问题,基本程序设计的问题,基本工具(git,jdb,junit,idea...)的使用问题

  • 然后独立编写5000行左右的代码,解决程序逻辑错误的调试,模块分解,问题解决的一般过程等相关问题

  • 有了10000行代码的基础,后面的学习提高要依靠代码阅读了,比如JUnit的源码,JHotdraw的源码,Java Collection API的源码,Java JCE的源码等

教学中也是想通过这三个步骤进行训练。

看了范飞龙博士(博客微博)的“如何设计题目”和“近发展区/脚手架”,一方面感觉龙博不当老师真是亏了,另一方面是感觉自己的题目设计上还有改进的空间。

这篇指导想参考龙博的博客,解决第二层次的问题,从Hello World通过一个个小练习不断迭代,来一步一步提高大家使用Java来解决具体问题的能力。

返回目录

基本工具

这里面的工具每周都要求使用,我们同学的博客上也都提交了会使用的证据,课上默认大家熟练掌握了,没有掌握的课下努力了。

返回目录

基础内容

这些内容在实验课上都做过了,同学们也都提交了自己掌握了的证据,我们在课堂实践测试时每次都会使用相关内容。

返回目录

Hello World 和 模块分解

由Kernighan和Ritchie合著的经典教程《The C Programming Language》的开篇第一个C程序例子是打印简单的“Hello World!”。从此之后,“Hello World”就成了描述人们编写的第一个程序的代名词---不管你用什么编程语言。

简单的“Hello World”能够运行出来,说明你的开发环境是没有问题的,这就有了进一步学习的基础。通过“Hello World”程序你也能初步理解程序的基本结构。

Java程序设计也不例外,我们从"Hello World"起步,来看看你的开发环境是不是准备好了。如果开发环境都没有准备好,你号称本学期写了几千行代码基本上就可以归零了。

开发环境的准备有命令行和IDE两种,大家都要掌握,后面的练习会用到命令行和IDE,前面没有掌握好的复习上面的基础内容。

Java中的Hello World程序HelloWorld.java如下:

public class HelloWorld {
    public static void main(String[] args){
        System.out.println("Hello World!");
    }
}

我在Linux命令行中可以用javac HelloWorld.java进行编译上面的程序,用java HelloWorld运行上面的程序:

教材已经学习完毕,我们知道Java中子系统是以包实现的,我们现在写个带包的Hello World:

package ljp.is.besti.edu.cn;

public class HelloWorld {
    public static void main(String[] args){
        System.out.println("Hello World!");
    }
}

这时候,用javac编译时要加上-d参数,用java运行时类名前要加上包前缀:

为了方便管理实践考试代码,建议同学们建立一个以自己学号命名的文件夹,每次实践考试都要提交这个文件夹的码云链接.
我们养成在项目根目录下也就是2015520ljp文件夹下工作的习惯,这时候通过 javac -d bin src/HelloWorld.java把class文件编译到bin文件夹下,运行时要加上-cp或-classpath参数:

Java程序员有两个角色:

  • 类设计者
  • 类使用者

对一个模块,我们要设计出来一个类,还要有一个测试类,这个测试类是一个带main的public类

  • XXXX.java
  • XXXXTester.java
    • 带main

第一个任务是设计一个HelloWorld类,里面有一个方法public static void printHello()打印出“Hello World”字符串,在HelloWorldTester类补充代码调用printHello。

HelloWorld.java:

package ljp.is.besti.edu.cn;

public class HelloWorld {
    public static void sayHello(){
        System.out.println("Hello World!");
    }
}

HelloWorldTester.java:

package ljp.is.besti.edu.cn;

public class HelloWorldTester {
    public static void main(String[] args){
        //补充代码调用printHello
        ...
    }
}

下面是补充好的代码和编译运行过程:

package ljp.is.besti.edu.cn;

public class HelloWorldTester {
    public static void main(String[] args){
        //补充代码调用printHello
        HelloWorld.sayHell();
    }
}

返回目录

数组的使用

算法设计中很重要的一点是临时变量的使用。比如,我们定义了两个变量int a=5;int b=6; 我们怎么把a和b的值交换一下? 不少程序设计的初学者的做法是:

a = b;
b = a;

这样是不能实现两个变量的交换的,执行完第一条语句a的值为6,b的值也为6,执行完第二条语句之后a的值为6,b的值也为6,没什么变化。

正确的做法是定义一个临时变量:

int tmp = a;
a = b;
b = tmp;

不使用临时变量也有办法实现交换,参考交换两个变量的值,不使用第三个变量的四种法方法,那样代码不清晰,我们不提倡使用。

程序设计中有三种语句:

  • 顺序语句
  • 分支语句
  • 循环语句

我们解决问题时要提高抽象能力,要多使用循环解决问题,分支只是用来解决特殊情况的。

通过使用数组来学习循环的使用。

Java中遍历数组,for-each语法很好用:

  //定义一个数组,比如
  int arr[] = {1,2,3,4,5,6,7,8};
 
  //打印原始数组的值
  for(int item:arr){
      System.out.print(item + " ");
  }
  System.out.println();
  

我们还可以从前往后遍历数组:

  //定义一个数组,比如
  int arr[] = {1,2,3,4,5,6,7,8};
 
  //打印原始数组的值
  for(int i=0; i<arr.length; i++){
      System.out.print(arr[i] + " ");
  }
  System.out.println();
  

我们还可以从后往前遍历数组:

  //定义一个数组,比如
  int arr[] = {1,2,3,4,5,6,7,8};
 
  //打印原始数组的值
  for(int = arr.lenth; i>0; i--){
      System.out.print(arr[i-1] + " ");
  }
  System.out.println();
  

实践任务:

  //定义一个数组,比如
  int arr[] = {1,2,3,4,5,6,7,8};
 
  //打印原始数组的值
  for(int i:arr){
      System.out.print(i + " ");
  }
  System.out.println();
  
  // 添加代码删除上面数组中的5
  ...
  
  //打印出 1 2 3 4 6 7 8 0
  for(int i:arr){
      System.out.print(i + " ");
  }
  System.out.println();
 
  // 添加代码再在4后面5
  ...
  
  //打印出 1 2 3 4 5 6 7 8
  for(int i:arr){
      System.out.print(i + " ");
  }
  System.out.println();

删除一个元素时比较容易,插入时就要用到临时变量了,当然,你可以从数组的最后一个元素开始移动,就避免了临时变量的使用。

参考代码:

  1 public class ArrayOperation {
  2
  3     public static void main(String [] args){
  4         int arr[] = {1,2,3,4,5,6,7,8};
  5
  6         for(int i:arr){
  7             System.out.print(i + " ");
  8         }
  9         System.out.println();
 10
 11         for(int i=5; i<arr.length; i++)
 12             arr[i-1] = arr[i];
 13
 14         arr[7] = 0;
 15
 16         for(int i:arr){
 17             System.out.print(i + " ");
 18         }
 19         System.out.println();
 20
 21         for(int i=arr.length; i>4; i--)
 22             arr[i-1] = arr[i-2];
 23
 24         arr[4] = 5;
 25
 26         for(int i:arr){
 27             System.out.print(i + " ");
 28         }
 29         System.out.println();
 30
 31     }
 32 }

返回目录

命令行参数

我们再回到Hello World程序:

public class HelloWorld {
    public static void main(String[] args){
        System.out.println("Hello World!");
    }
}

以前只是说大家只要记住main方法的写法,现在要能深入理解了,“public static void”是什么意思要清楚。我们这里要说的是main方法的参数 String [] args. args是一字符串数组,它是从哪里来的?Java程序运行时,会调用main方法,args就是命令行参数。

我们写一个测试程序CommandLine.java:

  1 public class CommandLine {
  2     public static void main(String [] args) {
  3         for(String arg : args){
  4             System.out.println(arg);
  5         }
  6     }
  7 }
 

我们java CommandLine运行时,没有什么输出。

我们java CommandLine 1 2 3运行时,输出如下图,此时 args[0]"1", args[1]"2", args[2]=="3",args.lenth == 3。

在IDEA这种IDE中如何传递命令行参数?我们选择Run->Edit Configuration...

命令行中的参数通过 Programm argumetns传递。

实践内容

提交测试结果截图,课下把代码上传到码云。
求命令行传入整数参数的和。
public class CLSum {
	public static void main(String [] args) {

		int sum = 0;

		// 参考Integer类中的方法把字符串转为整数
		// 补充代码求命令行参数的args中整数数据的和
		...
		
    // 打印 
		System.out.println(sum);
	}
}

参考代码:

public class CLSum {
	public static void main(String [] args) {

		int sum = 0;

		// 参考Integer类中的方法把字符串转为整数
		// 补充代码求命令行参数的args中整数数据的和
		for(String arg: args)
		    sum += Interger.parseInt(arg);
		
    // 打印 
		System.out.println(sum);
	}
}

有同学想先把传入的字符串数组转化为一个临时的int 数组,可以这样:

  1 public class CLSum1 {
  2     public static void main(String [] args) {
  3         int sum = 0;
  4
  5         int [] tmp = new int [args.length];
  6         for(int i=0; i<args.length; i++) {
  7             tmp[i] = Integer.parseInt(args[i]);
  8         }
  9
 10         for(int t : tmp){
 11             sum += t;
 12
 13         }
 14
 15         System.out.println(sum);
 16     }
 17 }

同学们还有遇到Integer类中没有parseInt()的问题,这是我们实验二定义了自己的Integer等类,与Java重名了,就和两个班有重名的同学一样,区分必须加上班级名,我们这就要加上包名java.lang.Integer。

返回目录

递归

递归算法是一种直接或间接地调用自身的算法。在编写程序时,递归算法对解决一大类问题是十分有效的,它往往使算法的描述简洁而且易于理解。

递归用于解决形式相同,规模不同的问题,能用递归解决的问题都可以转化为循环。递归把一个大型复杂的问题层层转化为一个与原问题相似的规模较小的问题来求解,递归策略只需少量的程序就可描述出解题过程所需要的多次重复计算,大大地减少了程序的代码量。递归的能力在于用有限的语句来定义对象的无限集合。用递归思想写出的程序往往十分简洁易懂。

递归程序有两个要点:递归公式和结束条件。我们以求整数的阶乘为例:

有了公式,代码就容易写出来了:

  1 public class Factorial {
  2     public static void main(String [] args) {
  3         System.out.println(fact(5));
  4     }
  5
  6     public static int fact(int n) {
  7         if (n == 0)
  8             return 1;
  9         else
 10             return n * fact(n-1);
 11     }
 12 }

fact(5)的递推过程如下图:

JDB不但是个调试工具,还是一个学习工具,参考 《使用JDB调试Java程序》的递归调试部分,看看递归调用的动态过程。

实践:

public class CLSumRecursion {
	public static void main(String [] args) {

		int sum = 0;

		// 参考Integer类中的方法把字符串转为整数
		// 补充代码以递归的方式求命令行参数的args中整数数据的和
		...
		
    // 打印 
		System.out.println(sum);
	}
        
        //递归函数
        public static int  clSum(int [] arr) {
           ...
        }
}

参考代码:

  1 import java.util.Arrays;
  2
  3 public class CLSumRecursion {
  4     public static void main(String [] args) {
  5         int sum = 0;
  6
  7         if(args.length < 1){
  8             System.out.println("Usage: java CLSumRecursion num1 num2 ...");
  9             System.exit(0);
 10         }
 11
 12         int [] tmp = new int [args.length];
 13         for(int i=0; i<args.length; i++) {
 14             tmp[i] = Integer.parseInt(args[i]);
 15         }
 16
 17         sum =  clsum(tmp);
 18         System.out.println(sum);
 19     }
 20
 21     public static int clsum(int [] arr)
 22         if (arr.length == 1)
 23             return arr[0];
 24         else {
 25             int [] tmp = Arrays.copyOf(arr, arr.length-1);
 26             return clsum(tmp) + arr[arr.length-1];
 27         }
 28     }
 29 }

这个题目出的不太好,用递归求1+2+3+...+N就容易理解些:

int sum(int n){
    if (n==1)
        return 1;
    else
       return sum(n-1) + n;
}

返回目录

分支语句

相对于顺序语句来说,分支语句多用于处理特殊情况,Java中的分支语句有:

  • if... else if ... else
  • switch...case
    • jdk8中switch 可以是字符串类型
    • 每个case中不要忘了break

这两个语句是等价的。

使用分支语句时要注意MESE原则,MESE是Mutually Exclusive Collectively Exhaustive的缩写,意思是“相互独立,完全穷尽”。 也就是对于问题的分类要能够做到不重叠、不遗漏。if中的else, switch...case中的default对于不遗漏.

实践:

实现一个简易计算器Calc,支持+ - x / 和%运算, 从命令行传入计算数据,比如:

java Calc 2 + 3     结果为 2 + 3 = 5
java Calc 8 - 3     结果为 8 - 3 = 5
java Calc 2 x 3     结果为2 x 3 = 6
java Calc 10 / 2     结果为10 / 2 = 5
java Calc 10 % 3     结果为10 % 3 = 1

  
  1 public class Calc {
  2     public static void main(String [] args) {
  3
  4         int result = 0;
  5
  6         if (args.length != 3) {
  7             System.out.println("Usage: java Calc operato1 operand(+ - X / %) operator2");
  8         }
  9         //======以下补充代码=====
 10         //+ - x / 和%运算         
 11         //======以上补充代码======
 12         System.out.println(args[0] + " " + args[1] + " " + args[2] + " = " + result);
 13
 14     }
 15 }

参考代码如下:

  1 public class Calc {
  2     public static void main(String [] args) {
  3
  4         int result = 0;
  5
  6         if (args.length != 3) {
  7             System.out.println("Usage: java Calc operato1 operand(+ - x / %) operator2");
  8         }
  9
 10         switch (args[1]) {
 11         case "+":
 12             result = Integer.parseInt(args[0]) + Integer.parseInt(args[2]);
 13             break;
 14         case "-":
 15             result = Integer.parseInt(args[0]) - Integer.parseInt(args[2]);
 16             break;
 17         case "x":
 18             result = Integer.parseInt(args[0]) * Integer.parseInt(args[2]);
 19             break;
 20         case "/":
 21             result = Integer.parseInt(args[0]) / Integer.parseInt(args[2]);
 22             break;
 23         case "%":
 24             result = Integer.parseInt(args[0]) % Integer.parseInt(args[2]);
 25             break;
 26         default:
 27             System.out.println("Usage: java Calc operato1 operand(+ - x / %) operator2");
 28             break;
 29
 30         }
 31         System.out.println(args[0] + " " + args[1] + " " + args[2] + " = " + result);
 32
 33     }
 34 }

这个代码要注意的是乘法在命令行参数中不能用“*”, "*"是一个通配符,会返回当前目录的所有文件名。

还有同学拷贝上面的代码后要一行一行的删除行号,如果用Vim的话,使用列模式很容易就把删除了,Vim 中我们使用 Ctrl+v 进入列模式,选择前两列,按x就删除了。

IDEA中也支持Vim模式,熟悉Vim的同学可以安装IDEAVim插件:

String类的使用

我们通过String类的使用为例说明程序设计的几个问题

  • 程序设计是用来解决问题的,问题驱动的方式是个好的学习方式
  • 解决问题不是所有问题都要自己解决,可以借助类库,API等通过代码复用来加快开发
  • 要养成写伪代码,产品代码,测试代码的习惯

我们先回顾一个Linux命令sort:

我们关注下面几个选项:

-t<分隔字符>:指定排序时所用的栏位分隔字符; 
-k: 针对第几列进行排序
-n:依照数值的大小排序; 
-r:以相反的顺序来排序; 

如何实现Linux下Sort的功能对一个字符串数组进行排序?你可能学习过或者听说过冒泡排序,归并排序,插入排序,选择排序,快速排序等算法,我们要先实现这些算法吗?Java编程中我们可以先查查API文档:

我们发现java.util.Arrays类和java.util.Collections类中都实现了sort方法,我们不用自己编程实现排序算法了,调用这些方法就可以了。

调用java.util.Arrays.sort的示例:

  1 import java.util.*;
  2
  3 public class MySort1 {
  4     public static void main(String [] args) {
  5         String [] toSort = {"aaa:10:1:1",
  6                             "ccc:30:3:4",
  7                             "bbb:50:4:5",
  8                             "ddd:20:5:3",
  9                             "eee:40:2:20"};
 10
 11         System.out.println("Before sort:");
 12         for (String str: toSort)
 13                     System.out.println(str);
 14
 15         Arrays.sort(toSort);
 16
 17         System.out.println("After sort:");
 18         for( String str : toSort)
 19             System.out.println(str);
 20     }
 21 }

调用java.util.Collections.sort的示例:

  1 import java.util.*;
  2
  3 public class MySort1 {
  4     public static void main(String [] args) {
  5         String [] toSort = {"aaa:10:1:1",
  6                             "ccc:30:3:4",
  7                             "bbb:50:4:5",
  8                             "ddd:20:5:3",
  9                             "eee:40:2:20"};
 10
 11         System.out.println("Before sort:");
 12         for (String str: toSort)
 13                     System.out.println(str);
 14
 15         List<String> list = new ArrayList();
 16         for (String str: toSort)
 17             list.add(str);
 18
 19         Collections.sort(list);
 20
 21         System.out.println("After sort:");
 22         for( String str : list)
 23             System.out.println(str);
 24     }
 25 }

实践任务:

  • 模拟实现Linux下Sort -t : -nk 4的功能,补充第17,25行代码。提交码云链接和代码运行截图。
  1 import java.util.*;
  2
  3 public class MySort {
  4     public static void main(String [] args) {
  5         String [] toSort = {"aaa:10:1:1",
  6                             "ccc:30:3:4",
  7                             "bbb:50:4:5",
  8                             "ddd:20:5:3",
  9                             "eee:40:2:20"};
 10
 11         System.out.println("Before sort:");
 12         for (String str: toSort)
 13                     System.out.println(str);
 14
 15         Integer [] tmp = new Integer [toSort.length];
 16         for(int i=0; i<tmp.length; i++)
 17             tmp[i] = ...;
 18
 19         Arrays.sort(tmp);
 20
 21         System.out.println("After sort:");
 22
 23         for(int i=0; i<tmp.length; i++)
 24             for(int j=0; j<toSort.length; j++)
 25                 if(...)
 26                     System.out.println(toSort[j]);
 27     }
 28 }

参考代码

 1 import java.util.*;
  2
  3 public class MySort {
  4     public static void main(String [] args) {
  5         String [] toSort = {"aaa:10:1:1",
  6                             "ccc:30:3:4",
  7                             "bbb:50:4:5",
  8                             "ddd:20:5:3",
  9                             "eee:40:2:20"};
 10
 11         System.out.println("Before sort:");
 12         for (String str: toSort)
 13                     System.out.println(str);
 14
 15         Integer [] tmp = new Integer [toSort.length];
 16         for(int i=0; i<tmp.length; i++)
 17             tmp[i] = new Integer(Integer.parseInt(toSort[i].split(":")[3]));
 18
 19         Arrays.sort(tmp);
 20
 21         System.out.println("After sort:");
 22
 23         for(int i=0; i<tmp.length; i++)
 24             for(int j=0; j<toSort.length; j++)
 25                 if(Integer.parseInt(toSort[j].split(":")[3]) == tmp[i].intValue())
 26                     System.out.println(toSort[j]);
 27     }
 28 }

这里面有几个问题:

返回目录

类的定义与测试

返回目录

多态

返回目录

IO与异常

返回目录

数据库

返回目录

网络与安全

返回目录

数据结构应用

实验二 Java面向对象程序设计》提出编写程序要写三种代码:

  • 伪代码
  • 产品代码
  • 测试代码

我们学习编程是用来解决实际问题的,我们使用《别出心裁的Linux系统调用学习法》中的方法进行学习》

  • 学习一个Linux命令
  • 分析如何实现,写出伪代码
  • 用Java实现Linux命令,写出产品代码
  • 测试自己的实现,写出测试代码

栈的应用

栈 (Stack)是一种只允许在表尾插入和删除的线性表,有先进后出(FILO),后进先出(LIFO)的特点。允许插入和删除的一端称为栈顶(top),另一端称为栈底(bottom)。

Java中有Stack类,不熟悉的同学可以参考《积极主动敲代码,使用Junit学习Java程序设计》学习一下:

  • empty()
  • push()
  • pop()

栈的一个应用是用来对四则运算表达式进行求值。

表达式Exp = S1 + OP + S2(S1 ,S2是两个操作数,OP为运算符)有三种标识方法:

  • OP + S1 + S2 为前缀表示法
  • S1 + OP + S2 为中缀表示法
  • S1 + S2 + OP 为后缀表示法

例如:Exp = a * b + (c - d / e) * f

  • 前缀式: + * a b * - c / d e f
  • 中缀式: a * b + c - d / e * f
  • 后缀式: a b * c d e / - f * +

我们可以看出:

  1. 操作数之间的相对次序不变;
  2. 运算符的相对次序不同;
  3. 中缀式丢失了括弧信息,致使运算次序不确定;
  4. 前缀式的运算规则为:连续出现的两个操作数和在它们之前且紧靠它们的运算符构成一个最小表达式;
  5. 后缀式的运算规则为:运算符在式中出现的顺序恰为表达式的运算顺序;每个运算符和在它之前出现且紧靠它的两个操作数构成一个最小表达式。

后缀表示法是波兰逻辑学家J.Lukasiewicz于1929年提出的,又叫做逆波兰表达式。

Linux命令dc可以用来对逆波兰式表达式进行求值,dc的打印类命令:

  • p:打印栈顶元素并换行
  • n: 打印栈顶元素并将其弹出栈,完毕后不换行
  • P: putchar ( int(栈顶元素) % 256) 并弹栈顶,不换行
  • f: 从栈顶至栈底打印栈中所有值,每个一行

dc的运算符:

  • +: 依次弹出w1与w2,将w2+w1压栈。精度为结果值精度
  • -: 依次弹出w1与w2,将w2-w1压栈
  • *: 依次弹出w1与w2,将w2*w1压栈。精度为结果值精度与precision中较大值
  • / : 依次弹出w1与w2,将w2/w1压栈。精度为precision
  • % : 依次弹出w1与w2,将w2-w2/w1*w1压栈
  • ~ : 依次弹出w1与w2,依次将w2/w1与w2%w1压栈
  • ^ : 依次弹出w1与w2,将w2^((int)w1)压栈。精度为w2精度与precision中较大值
  • | : 依次弹出w1 w2与w3,将 w3 ^ ((int)w2) (mod w1) 压栈。w1 w3 需为整数
  • v : 弹出w1,将sqrt(v)压栈。精度为precision

dc支持栈操作:

  • c : 清空栈
  • d : 将栈顶元素复制并压栈
  • r : 交换栈顶两元素 XXX

我们看一下dc如何使用:

我们如何实现dc? 这要用到栈。对逆波兰式求值时,不需要再考虑运算符的优先级,只需从左到右扫描一遍后缀表达式即可。求值伪代码如下:

  • 设置一个操作数栈,开始栈为空;
  • 从左到右扫描后缀表达式,遇操作数,进栈;
  • 若遇运算符,则从栈中退出两个元素,先退出的放到运算符的右边,后退出的放到运算符左边,运算后的结果再进栈,直到后缀表达式扫描完毕。

此时,栈中仅有一个元素,即为运算的结果。

我们给出一个例子,求后缀表达式:1 2 + 8 2 - 7 4 - / * 的值:


MyDC.java: 补充代码31-40行

  1 import java.util.StringTokenizer;
  2 import java.util.Stack;
  3
  4 public class MyDC
  5 {
  6   /** constant for addition symbol */
  7   private final char ADD = '+';
  8   /** constant for subtraction symbol */
  9   private final char SUBTRACT = '-';
 10   /** constant for multiplication symbol */
 11   private final char MULTIPLY = '*';
 12   /** constant for division symbol */
 13   private final char DIVIDE = '/';
 14   /** the stack */
 15   private Stack<Integer> stack;
 16
 17   public MyDC() {
 18     stack = new Stack<Integer>();
 19   }
 20
 21   public int evaluate (String expr)
 22   {
 23     int op1, op2, result = 0;
 24     String token;
 25     StringTokenizer tokenizer = new StringTokenizer (expr);
 26
 27     while (tokenizer.hasMoreTokens())
 28     {
 29       token = tokenizer.nextToken();
 30
 31       //如果是运算符,调用isOperator
 32       if ()
 33       {
 34         //从栈中弹出操作数2
 35         //从栈中弹出操作数1
 36         //根据运算符和两个操作数调用evalSingleOp计算result;
 37         //计算result入栈;
 38       }
 39       else//如果是操作数
 40         //操作数入栈;
 41     }
 42
 43     return result;
 44   }
 45
 46   private boolean isOperator (String token)
 47   {
 48     return ( token.equals("+") || token.equals("-") ||
 49              token.equals("*") || token.equals("/") );
 50   }
 51
 52   private int evalSingleOp (char operation, int op1, int op2)
 53   {
 54     int result = 0;
 55
 56     switch (operation)
 57     {
 58       case ADD:
 59         result = op1 + op2;
 60         break;
 61       case SUBTRACT:
 62         result = op1 - op2;
 63         break;
 64       case MULTIPLY:
 65         result = op1 * op2;
 66         break;
 67       case DIVIDE:
 68         result = op1 / op2;
 69     }
 70
 71     return result;
 72   }
 73 }
 

测试类,MyDCTester.java ,代码不用修改

  1 import java.util.Scanner;
  2
  3 public class MyDCTester  {
  4
  5   public static void main (String[] args) {
  6
  7     String expression, again;
  8
  9     int result;
 10
 11     try
 12     {
 13       Scanner in = new Scanner(System.in);
 14
 15       do
 16       {
 17         MyDC evaluator = new MyDC();
 18         System.out.println ("Enter a valid postfix expression: ");
 19         expression = in.nextLine();
 20
 21         result = evaluator.evaluate (expression);
 22         System.out.println();
 23         System.out.println ("That expression equals " + result);
 24
 25         System.out.print ("Evaluate another expression [Y/N]? ");
 26         again = in.nextLine();
 27         System.out.println();
 28       }
 29       while (again.equalsIgnoreCase("y"));
 30     }
 31     catch (Exception IOException)
 32     {
 33       System.out.println("Input exception reported");
 34     }
 35   }
 36 }
 

参考代码

  1 import java.util.StringTokenizer;
  2 import java.util.Stack;
  3
  4 public class MyDC
  5 {
  6   /** constant for addition symbol */
  7   private final char ADD = '+';
  8   /** constant for subtraction symbol */
  9   private final char SUBTRACT = '-';
 10   /** constant for multiplication symbol */
 11   private final char MULTIPLY = '*';
 12   /** constant for division symbol */
 13   private final char DIVIDE = '/';
 14   /** the stack */
 15   private Stack<Integer> stack;
 16
 17   /**
 18    * Sets up this evalutor by creating a new stack.
 19    */
 20   public MyDC()
 21   {
 22     stack = new Stack<Integer>();
 23   }
 24
 25   public int evaluate (String expr)
 26   {
 27     int op1, op2, result = 0;
 28     String token;
 29     StringTokenizer tokenizer = new StringTokenizer (expr);
 30
 31     while (tokenizer.hasMoreTokens())
 32     {
 33       token = tokenizer.nextToken();
 34
 35       if (isOperator(token))
 36       {
 37         op2 = (stack.pop()).intValue();
 38         op1 = (stack.pop()).intValue();
 39         result = evalSingleOp (token.charAt(0), op1, op2);
 40         stack.push (new Integer(result));
 41       }
 42       else
 43         stack.push (new Integer(Integer.parseInt(token)));
 44     }
 45
 46     return result;
 47   }
 48
 49   private boolean isOperator (String token)
 50   {
 51     return ( token.equals("+") || token.equals("-") ||
 52              token.equals("*") || token.equals("/") );
 53   }
 54
 55   private int evalSingleOp (char operation, int op1, int op2)
 56   {
 57     int result = 0;
 58
 59     switch (operation)
 60     {
 61       case ADD:
 62         result = op1 + op2;
 63         break;
 64       case SUBTRACT:
 65         result = op1 - op2;
 66         break;
 67       case MULTIPLY:
 68         result = op1 * op2;
 69         break;
 70       case DIVIDE:
 71         result = op1 / op2;
 72     }
 73
 74     return result;
 75   }
 76 }44     }
 45
 46     return result;
 47   }
 48
 49   private boolean isOperator (String token)
 50   {
 51     return ( token.equals("+") || token.equals("-") ||
 52              token.equals("*") || token.equals("/") );
 53   }
 54
 55   private int evalSingleOp (char operation, int op1, int op2)
 56   {
 57     int result = 0;
 58
 59     switch (operation)
 60     {
 61       case ADD:
 62         result = op1 + op2;
 63         break;
 64       case SUBTRACT:
 65         result = op1 - op2;
 66         break;
 67       case MULTIPLY:
 68         result = op1 * op2;
 69         break;
 70       case DIVIDE:
 71         result = op1 / op2;
 72     }
 73
 74     return result;
 75   }
 76 }
 

大家注意两个private 方法isOperator, evalSingleOp的设计,体会一下方法分解。

对一般人来说,得到后缀表达式就是一件不容易的事。我们习惯的还是中缀表达式。Linux中另外一个计算器bc就是用来计算中缀表达式的:

我们如何编程实现bc? 把中缀式转化后缀式调用MyDC.java 中的evaluate方法就行了。这样问题转化为如何由中缀式求得后缀式?

由中缀式求得后缀式可以使用栈,伪代码如下:

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

将中缀表达式 (1+2)*((8-2)/(7-4)) 变成后缀表达式,栈的变化及输出结果:


算符优先法求解表达式:(生成后缀表达式+后缀表达式求值)

  • 步骤1:建立符号运算的优先级关系表

  • 步骤2
    • (1) 设操作数栈OPND,置空;运算符栈OPTR,最低符号#压进OPTR;
    • (2) 读入字符C,C若是操作数, 进OPND;若是运算符,与OPTR栈顶元素(A)比较,根据算符优先级,决定如何处理:
      • A<C, C压入OPTR栈;
      • A=C, A从OPTR出栈;
      • A>C,A出栈,从OPND依次弹出两个操作数y、x, 计算Z=x A y,Z压入OPND栈。C压进OPTR.
    • (3) 重复(2),直至表达式结束。

返回目录

Android开发

返回目录



如果你觉得本文对你有帮助,请点一下左下角的“好文要顶”和“收藏该文


posted @ 2017-04-26 07:11  娄老师  阅读(3821)  评论(8编辑  收藏