C語言的開發模式, 是編寫.c的Source Code, 再經由Compiler編譯成Object Code。所謂Object Code指的是和硬體相關的機器指令, 也就是說當我們想要把C程式移植到不同的硬體時, 必須要重新Compile,以產生新的執行檔。除了需要重新編譯外,新系統是否具備應用程式所需的程式庫,include的檔案是否相容, 也是程式能否在新機器上順利編譯和執行的條件之一。
在實務上,為了讓C程式能在不同的UNIX版本上都能順利編譯,原作者往往必須使用前置處理器的#ifdef指令,判斷不同環境的適當寫法。如果想把在UNIX上開發的C程式移植到Windows上,則有用到專屬程式庫的部分(如UNIX的使用者介面可能用到X Window的API,Windows就沒有支援,必須一台一台灌程式庫才行,很可能還要花錢買),就必須重寫才行。
解決此類問題的方法之一,是定義一種Virtual Machine(虛擬機器),讓程式語言編譯時不要翻成實體機器的指令,而是翻成Virtual Machine的目的碼。Virtual Machine一般是以軟體來模擬的,只要新的平台有Virtual Machine,則原始程式不用Compile,執行舊機器上已有的Virtual Machine目的碼,就可以了。當然要達到完全不用重新Compile就能執行的理想,還要配合標準的程式庫才行。
Java語言基於上述理念,定義了Java Virtual Machine,它所用的指令稱為byte code。使用Virtual Machine的缺點之一,是執行的速度較慢,代價是開發的速度變快了。以現在的硬體來說,大部分應用程式的執行速度已經沒有那麼重要,反倒是軟體的開發速度和品質越來越值得重視。
此外JVM的技術不斷進步, 諸如Just In Time(JIT) Compiler, 或HotSpot等技術都可以讓Java程式以非常接近原生碼(Native Code)的速度執行。因此不要因為某些偏頗的報告或直覺, 就不使用Java了。
開發Java應用程式的工具中,最常見的是由Java的原創公司Sun Micro所出版的JDK(Java Development Kit)。JDK可以免費下載。以Text Editor寫好的Hello.java原始檔:
public class Hello { public static int gvar; public static void say(String s) { int x = 10; System.out.print(s+x); } public static void main(String[] argv) { float y = 0; say("Hello, world\n"); } }這程式的C版本如下
#include <stdio.h> int gvar; void say(char[] s) { int x = 10; printf("%s%d", s, x); } int main(int argc, char** argv) { float y = 0; say("Hello, world\n"); }
經過:
javac Hello.java
編譯完成後會產生byte code格式的Hello.class,然後
java Hello
就可以利用Java Virtual Machine(此處是java這個執行檔)來執行了。
上述過程中幾個比較會發生的問題是
Java是由C++簡化來的。由於C++要和C完全相容,又很注重效能問題,因此C++算是很複雜的程式語言。Java在設計之初,考量的重點之一就是簡單,因此和C++比起來,不僅更為物件導向,而且比C++容易學習。
Java許多運算符號和敘述語法都是來自C語言,假設各位已經對C語言有所了解,本章後面的部分只將Java和C在運算符號和敘述語法上的差異點出來,相同的部分請參見C語言的課程內容。
Java語言所定義的基本資料型別有
型別名稱 | 位元長度 | 範圍 |
boolean | 1 | true或false |
byte | 8 | -128 ~ 127 |
short | 16 | -32768 ~ 32767 |
char | 16 | Unicode characters |
int | 32 | -2147483648 ~ 2147483647 |
long | 64 | -9223372036854775808 ~ 9223372036854775807 |
float | 32 | +-3.4028237*10+38 ~ +-1.30239846*10-45 |
double | 64 | +-1.76769313486231570*10+308 ~ 4.94065645841246544*10-324 |
Java的資料型態裡沒有unsigned。
Java對數值型態的轉換比C稍微嚴格一點,下列左邊的部分都可以指定(assignment)給右邊的型別:
byte --> short --> int --> long --> float --> double
除上述外,其他型別間的轉換都必須下達型別轉換(Type Casting)命令來處理,其形式為圓括弧裡寫上型別名稱,如(double)
由於Java在char的型態部分採用Unicode,因此字元常數的表示法,除因循C的規則外,也可以直接指定16bits Unicode編碼給char型別的變數。例如由Windows "字元對應表" 程式中可查到象棋中的紅車的unicode編碼為4FE5, Java可用 '\u4fe5' 來表達。Java的變數也可以用Unicode來命名,換句話說,你可以用中文取變數名稱。
除了這些基本資料型別外,Java還有一個稱為Reference(參考)的型別。Reference用來存取Object(物件),其功能和C語言的pointer用來存取記憶體有點像,但沒有pointer的&+-等運算符號,而且Reference只能存取型態相符合的類別。宣告Reference的語法是ClassName varName,例如
String s;
宣告s是一個型態為reference的變數,這表示我們可透過s來存取屬於String類別的物件(s is a reference to String object)。
要特別強調的是, s並不是物件, 而是用來指向String物件的reference。打個比方,
public class 動物 { 動物 手指頭; // java 因字元編碼使用unicode, 所以可用中文當變數名稱 public static void main(String[] arg) { 動物 手指頭2; 手指頭2 = new 動物(); } }
變數 "手指頭" 宣告為reference, 可指向屬於 class "動物" 的物件, 手指頭不是動物, 而是用手指頭指向某隻動物。
java.lang.Float f; java.lang.Double d; java.lang.Integer i;
以上變數的型態都是reference
Java語言在運算式的部分,和C語言極為類似, 除了沒有sizeof, pointer和struct相關的運算符號外, 另外新增了>>>向右無號shift, 以及用來判斷物件型態的instanceof。Java的常數的表示法也和C相同,而Java裡的新資料型態boolean的合法值為true和false兩個常數。
運算符號 | 功能敘述 |
+ | 加 |
* | 乘 |
- | 減 |
/ | 除 |
% | 餘數 |
++ | 加一 |
-- | 減一 |
運算符號 | 功能敘述 |
> | 大於 |
< | 小於 |
>= | 大於等於 |
<= | 小於等於 |
== | 等於 |
!= | 不等於 |
&& | logic AND |
|| | logic OR |
! | logic NOT |
instanceof | reference instanceof ClassName 判斷reference所指到的物件其型態是否和ClassName相容 |
Java語言和C語言有關邏輯運算最大的不同,在於Java以boolean資料型態(只有true和false兩種值)判斷條件是否成立,而C語言只能使用0或非0。
運算符號 | 功能敘述 |
& | bit AND |
<< | left bit shift |
| | bit OR |
>> | right bit shift with sign |
^ | bit XOR |
~ | 1補數 |
>>> | 同>>但左邊一律補零 |
運算元 | 功能敘述 |
= | 將右邊的值複製到左邊的變數 |
(type) | 將右邊的數值或reference轉換成type型別 |
+= | 將右邊的數值加上左邊的數值然後指定給左邊的變數 |
?: | 若?左邊成立則做:左邊否則做:右邊 |
, | 合併兩個運算視為一個敘述 |
(運算式) | 表示()內優先運算 |
. | Reference.ObjectMember或ClassName.ClassName 存取物件或類別成員 |
new | 產生物件 |
種類 | 運算符號 | 結合順序 |
group | (op) | left to right |
postfix | [] . (params) op++ op-- | right to left |
prefix | ++op --op +op -op ~ ! | right to left |
creation or casting | new (type)op | right to left |
multiplicative | * / % | left to right |
additive | + - | left to right |
shift | << >> >>> | left to right |
relational | < > <= >= instanceof == | left to right |
equality | == != | left to right |
bitwise and | & | left to right |
bitwise exclusive or | ^ | left to right |
bitwise inclusive or | | | left to right |
logical and | && | left to right |
logical or | || | left to right |
conditional | ? : | right to left |
assignment | = += -= *= /= %= &= ^= |= <<= >>= >>>= | right to left |
seperator | , | left to right |
Java的流程控制敘述和C語言極為類似,不同處在於break和continue兩個指令。Java的break和continue指令後面可以加上標籤,以指示要跳出或繼續的範圍。
public class BreakContinueExample { public static void main(String[] argv) { int i, j; outerLoop: for (i = 0; i < 100; i++) { innerLoop: for (j = 0; j < 100; j++) { if (j == 50 && i == 50) { break outerLoop; } } } System.out.println("Loop have been terminated."); } }
在上面的例子中,當j==50且i==50時,break指令會跳出最外面的迴圈,直接印出迴圈終止訊息。如果break後面沒有outerLoop的話, 只會跳出裡面的迴圈,然後i從51繼續做下去。
C語言定義以0結尾的字元陣列就是字串。但對Java來說, 字串是由String類別來表達, 也就是說String是物件而不是陣列。由於我們經常使用字串, 為了寫作程式方便起見, Java Compiler碰到+符號某一邊的型態是String時, 就會把+翻譯成StringBuffer類別裡相對應的append Method。例如:
public class StringTest { public static void main(String[] argv) { int x = 5; float y = 1.5; System.out.println("x = " + x + ", y = " + y); } }
會翻譯成:
public class StringTest { public static void main(String[] argv) { int x = 5; float y = 1.5; System.out.println((new StringBuffer("x = ")).append(x).append(", y = ").append(y).toString()); } }
如果你會C++, 看到Java字串+符號的語法, 千萬不要以為Java支援operator overloading。Java只是透過Compiler來做特別的轉換, 稱這種技術為Compiler Sugar比較適合。
/** * 第一行的兩個**用來告訴javadoc此部份註解要變成HTML文件的一部份 * 這段註解裡的所有文字都會變成此類別一開頭的說明 */ public class Hello { // Class Name首字大寫 /** * 此段註解會變成描述main方法的一部分 * @param argv 使用@param註記會產生參數(parameter)argv的相關說明 * @return 傳回值的意義說明 */ public static void main(String[] argv) { // Method Name首字小寫 // argv: array of references to String object int myVariable; // 變數宣告 int i, sum; for (i = 1, sum = 0; i <= 100; i++) { sum += i; } System.out.println("summation from 1 to 100 is "+sum); } }
public class Example { public static void main(String[] argv) { float degree = 100.0; System.out.println("100C=" + (degree * 9.0 / 5.0 + 32.0)); } }
public class Example { public static void main(String[] argv) { int n = 100; System.out.println("1+2+...+"+n+" = " + ( n * (n + 1) / 2)); } }
特別注意上述的運算式裡/2要放到最後面,如果寫成n/2*(n+1),從數學式子的角度看好像沒問題,但別忘了,binary operator的兩邊必須是同樣型別的資料,而且計算的結果也是同樣的型別。因此n/2*(n+1)會先計算n/2,如果n不能被2整除的話,那麼為了符合計算結果必須是整數的限制,則小數點的部份就會無條件捨去,使得計算的結果錯誤。下面的範例一樣要注意相同的問題。
怎麼寫?
Java語言規定浮點數轉整數時,小數點部分無條件捨去。如果要達到浮點數四捨五入為整數的效果,可以使用下面的小技巧
public class Example { public static void main(String[] argv) { double x = 20.6; System.out.println(x + " 四捨五入成為 " + (int)(x+0.5)); System.out.println(x + " 四捨五入成為 " + round(x)); } static int round(double y) { return (int)(y + 0.5); } }
import java.util.Scanner; public class Example { public static void main(String[] argv) { int sum = 0, i = 0; Scanner in = new Scanner(System.in); while (i < 5 && in.hasNextInt()) { sum = sum + in.nextInt(); i++; } System.out.println("sum is "+sum", average is "+(sum/5.0)); } }
public class Example { public static int sum(int n) { int total = 0; // 紀錄到目前為止的總和 for (int i = 1; i <= n; i++) { total += i; } return total; } public static void main(String[] argv) { System.out.println(sum(100)); } }
怎麼寫?
public class Example { public static void main(String[] argv) { for (int i = 1; i <= 9; i++) { for (int j = 1; j <= 9; j++) { System.out.print(" " + (i * j)); } System.out.println(); } } }
*** *** ***
解析
public class Example { public static void print(int size) { int i, j; // 第i列,第j行 for (i = 1; i <= size; i++) { // 印出第i列 for (j = 1; j <= size; j++) { // 第i列有size個* System.out.print("*"); } System.out.println(); } } public static void main(String[] argv) throws Exception { BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); print(Integer.parseInt(in.readLine())); } }
* ** ***
解析
怎麼寫?
* *** *****
解析
怎麼寫?
public class Example { public static void main(String[] argv) { System.out.println(gcd(12,18)); } public static int gcd(int x, int y) { int tmp; // 如果x < y 則下面的迴圈執行第一次時就會交換x,y了 while (x % y != 0) { tmp = y; y = x % y; x = tmp; } return y; } }
怎麼寫?
public class Example { public static void main(String[] argv) { System.out.println(fab(5)); } public static int fab(int n) { int fn_1 = 1, fn_2 = 0; // 紀錄最近找到的兩個費氏數 int i, tmp; // i表示目前要找F(i) if (n <= 1) return n; for (i = 2; i <= n; i++) { tmp = fn_1; // 先把fn_1紀錄在tmp fn_1 += fn_2; // 最新的費氏數是前面兩個相加 fn_2 = tmp; // 第二新的就是原先的fn_1 } return fn_1; } }
解析
public class Example { public static void main(String[] argv) { System.out.println(sum(100)); } public static int sum(int n) { if (n == 1) { return 1; } return n + sum(n - 1); } }
怎麼寫?
public class Example { public static void main(String[] argv) { System.out.println(power(2, 6)); } public static int power(int a, int b) { switch(b) { case 0: return 1; case 1: return a; default: return (a * power(a, b - 1)); } }
解析
怎麼寫?
解析
public class Example { public static void main(String[] argv) { System.out.println(fab(5)); } public static int fab(int num) { if (num <= 1) { return num; } return fab(num - 1) + fab(num - 2); } }
A(m, n)定義為
怎麼寫?