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開發

返回目錄



如果你覺得本文對你有幫助,請點一下左下角的“好文要頂”和“收藏該文



免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM