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)定義為
怎麼寫?