陣列
作者: 許裕永
現在,我們已經學會了用變數來儲存資料,並加以運算。但是,每宣告一個變數,只能儲存一個值。如果我們需要在程式中儲存大量的值,那變數就不方便了。
假設我們要寫一支用來運算全班同學成績的程式,而同學有30位。那宣告30個變數來分別儲存30位同學的成績,方便嗎?就算不考慮變數命名的困擾。想想 看,光是運算合計時,要把30個變數用‘+’串在一起就頭痛了。平均的運算呢?最高分、最低分的運算呢?更何況同學還不見得是30位呢!
Java中的變數,以宣告的型別來分類,可以分成兩種:基本變數(primitive variable)及參考變數(reference variable)。基本變數一般我們簡稱為變數,它是用基本資料型別宣告,它代表一個記憶體空間,而記憶體空間中可以儲存一個,基本資料型別的值;參考 變數也代表一個記憶體空間,但是這個記憶體空間不能夠儲存值,它只能儲存一組Java專用的物件代號。而這個物件代號可以指向一個,連續的記憶體區塊,在 那個記憶體區塊中才可以儲存值。
int〔〕 student=new int〔30〕;
上面的敍述句表示:我們要求Java配置120byte的連續的記憶體區塊(30個int的值,每個值4byte)。Java配置完成後,會替這個記憶體 區塊編一組代號。再把這一組代號儲存在我們宣告的參考變數student之中。後續程式碼的撰寫,我們便可以利用參考變數student中所儲存的代號, 來存取該代號所代表的記憶體區塊中的任何一個值。
換個角度來說:「變數只能代表一個值。參考變數卻能代表一個或一個以上的值」。
只要不是用基本資料型別宣告的變數,就是參考變數。Java中可以宣告參考變數的有class、interface及任何型別的陣列。
本章除了讓讀者學習陣列的使用之外,也希望能讓讀者熟悉參考變數及物件的關係。而準備認證的讀者,更應該學會章節中,記憶體配置圖的繪製方式。測驗時在計 算紙上繪製,做答會比較輕鬆。而初學者應該把重點先放在宣告、建構、初始化的練習,並學會用迴圈控制一維陣列,最多二維陣列即可。
陣列,是執行時期Java配置的連續記憶體區塊。
陣列,是物件。
在Java中,每個物件都有一組代號,這一組代號設計師可以自行決定是否有必要儲存於參考數之中。
1-1 宣告(Declaration)
資料型別〔〕 參考變數;
資料型別 參考變數〔〕;
資料型別可以是任何基本資料型別、class,或是interface。中括號是型別的一部份,可以放在資料型別或參考變數的右側,但Java官方建議放在資料型別右側。
例一:
int a;
int[] b;
int c[];
本例中,a是基本變數,b和c是參考變數。所以,很明顯,中括號是型別的一部份,它用來讓編譯器知道b和c這兩個變數是參考變數,才能配置適當大小的記憶體空間(2byte)給b和c。
在此時,陣列物件尚未建立,b和c中並沒有任何值,也無法運算。
例二:
int[5] a;
int b[5];
本例的兩行宣告敍述句,都是錯誤的。因為中括號只是型別的一部份,所以中括號裏面不可以放值。這是學過其他程式語言的讀者,必須特別注意的。
1-2 建構(Construction)
new 資料型別〔陣列長度〕
new是一個指令,它命令Java配置記憶體區塊。配置的大小則是依資料型別及陣列長度運算的結果。配置完成的記憶體區塊,我們稱之為陣列物件,它會擁有一組Java提供的代號。
例一:
System.out.println(new int[5]);
若main方法中只有這一行程式碼,編譯會過嗎?執行正常嗎?答案是OK的。編譯時語法沒問題,執行時DOS視窗會列印:[I@35ce36。
這行敍述句是要求Java配置一個,可以儲存5個int值的連續記憶體區塊,並印列之。而列印出來的便是該陣列物件的物件代號。但是因為這個物件代號並沒 有指派給參考變數,所以後續無法再運算。也就是說:「雖然建構了一個陣列物件,但我們並沒有用參考變數儲存它的物件代號,也就沒有辦法繼續控制它」。這個 陣列物件將立刻被回收。(後續章節介紹)
例二:
int[] a;
a=new int[5];
int[] b=new int[6];
System.out.println(“a=”+ a);
System.out.println(“b=” + b);
本例共建立了兩個陣列物件,這兩個陣列物件的物件代號,分別指派給參考變數a及b。所以,後續的程式碼中就可以用參考變數a及b來控制這兩個陣列物件。
在這裏我要特別強調一個觀念:「物件不一定要有名稱」。名稱指的是:「程式碼中,能代表該物件的名字,也就是儲存該物件的物件代號的參考變數」。從上述的 範例中,我們可以發現:「參考變數的宣告及物件的建構是兩回事」。宣告參考變數時,不一定要立刻建構物件;而物件建構時,也不一定要把物件的物件代號指派 給參考變數。用參考變數來儲存物件的物件代號,是因為後續程式碼中有繼續運算該物件的必要。如果某一個物件建立後只執行一次運算,後續沒有再運算的必要 時,便不一定要宣告參考變數來儲存它的物件代號了!
1-3 初始化(Initialization)
物件是:「由多個可以單獨存取的記憶體空間,組合而成的連續的記憶體區塊」。以陣列物件而言,物件中的每一個可以單獨存取的記憶體空間,我們稱之為「陣列 元素」。在物件建構的時候,Java便會依序編列陣列元素的「序號」,讓設計師可以利用序號來存取每一個陣列元素。序號的編列為:0~(陣列長度-1)。
初始化指的是:「第一次把值指派給指定的陣列元素」。但事實上,在Java把程式碼指定的值,指派給陣列元素之前。在配置記憶體區塊的同時,Java便已 經將各資料型別的預設值,指派給所有的陣列元素了,也就說:「Java預設會初始化所有的陣列元素」。這樣的機制可以讓程式設計師不必在建構陣列物件之 後,還要逐一的初始化所有的陣列元素。
例一:
int[] a=new int[10];
for(int b=0;b<10;b++){
System.out.println(“第”+ (b+1) + “個元素值為” + a[b]);
}
本例中,參數變數a代表一個10個長度的int陣列物件(此後簡稱a陣列)。a陣列建構時(Java配置記憶體區塊時),便會將該型別之預設值指派給陣列 元素。因為陣列長度是10,所以陣列元素的序號為0~9。迴圈中的計數值b,它的值會從0跑到9,也就是這個迴圈會把a陣列中的所有陣列元素,依照序號的 順序,逐一列印。
各種基本資料型別的預設值如下表:
型別
|
預設值
|
byte,short,int,long | 0 |
float,double | 0.0 |
boolean | false |
char | ‘\u0000’ |
例二:
int[] a=new int[3];
a[0]=8;
a[1]=100;
a[2]=58;
for(int b=0;b<3;b++){
System.out.println(“第”+ (b+1) + “個元素值為” + a[b]);
}
本例中,設計師用指派運算式,來指派陣列元素的值。
中括號出現的時機有三種。第一種是在宣告句,中括號是型別的一部份,中括號中不可以有數字。第二種是建構句,中括號用來指定陣列的長度,裏面必須放一個整數值。第三種是指派運算式,中括號裏放的是序號,設計師用序號來指定存取陣列物件中的某一個元素。
1-4 宣告建構並初始化
資料型別[] 參數變數={值,值,......};
在陣列的宣告敍述句,可以用大括號直接指派陣列元素的值。但中括號中一樣不可以有數字,而指派值的個數,便是陣列長度。
例一:
int[] a={8,10,55};
for(int b=0;b<3;b++){
System.out.println(“第”+ (b+1) + “個元素值為” + a[b]);
}
例二:
int[3] a={8,10,55};
for(int b=0;b<3;b++){
System.out.println(“第”+ (b+1) + “個元素值為” + a[b]);
}
本例會造成編譯錯誤,請學過C語言的讀者特別注意。
例三:
int[] a;
a={8,10,55};
for(int b=0;b<3;b++){
System.out.println(“第”+ (b+1) + “個元素值為” + a[b]);
}
只有在和宣告敍述句並用的時候,才可以用大括號指派陣列元素值。本例會造成編譯錯誤。
1-5 匿名陣列(Anonymous Array)
new 資料型別[陣列長度]
new 資料型別[]{值,值,值……}
匿名陣列就是沒有名字的陣列,也就是沒有把物件代號指派給參考變數的陣列,也就是沒有參考變數可以代表的陣列。在本章前面所示範:
System.out.println(new int[10]);
小括號中便是匿名陣列,這個陣列中有10個陣列元素,每個元素的值均為0。若要自行指派陣列元素值,可以寫成這樣:
System.out.prinln(new int[]{8,10,55});
小括號中的匿名陣列長度為3,請注意中括號中不可以指定陣列長度。其值分別為8,10,55。但是上面的兩行敍述句,只是讓讀者了解匿名陣列的寫法。匿名陣列的用途是用在方法的參數或傳回值(已超出目前進度,初學者略看即可)。
例一:
public static void main(String[] args){
int[] a=anonymousArray(new int[]{8,10,12});
for(int b=0;b<3;b++){
System.out.println(“第”+ (b+1) + “個元素值為” + a[b]);
}
}
public static int[] anonymousArray(int[] x){
for(int b=0;b<3;b++){
a[b]*=5;
}
return x;
}
public static void main(String[] args){
int[] a=anonymousArray();
for(int b=0;b<3;b++){
System.out.println(“第”+ (b+1) + “個元素值為” + a[b]);
}
}
public static int[] anonymousArray(){
return new int[]{10,20,30};
}
anonymousArray方法的運算結果是:「建構了一個匿名陣列物件」。而這個物件的物件代號將指派給‘=’左側的參考變數a。
換個角度來看。一維陣列是用一個參考變數來代表一個或一個以上的值,那二維陣列就是用一個參考變數來代表一個或一個以上的一維陣列,三維陣列就是用一個參考變數來代表一個或一個以上的二維陣列。
int〔〕〔〕 a=new int〔2〕〔3〕;
此敍述句表示:「參考變數a代表一個二維陣列物件」。第一個維度2代表:「此二維陣列物件,儲存著2個一維陣列物件的物件代號」。第二個維度3代表:「每個一維陣列可以儲存3個int的值」。所以這個二維陣列總共可以儲存6個int的值。
要注意的是:「值一定是儲存在最後一個維度,也就是儲存在一維陣列物件之中」。那其他的維度儲存的是什麼呢?是:「物件代號」。每個陣列都是物件,所以每陣列都有物件代號,前面維度的陣列物件中所儲存的,是下一個維度陣列物件的物件代號。
以上面敍述句中的二維陣列為例:第一個維度2,代表Java會配置兩個可以儲存物件代號的空間,然後在這兩個空間中,分別儲存另外兩個一維陣列物件的物件代號。
以此類推,三維陣列物件中儲存的,是二維陣列物件的物件代號。四維陣列物件中儲存的,是三維陣列物件的物件代號。
那麼多維陣列的使用時機是什麼時候呢?以上一個章節的學生成績管理系統為例,30個長度的一維陣列可以儲存30個學生的成績。但如果現在要記錄的成績有5科呢?
int〔〕〔〕 student=new int〔30〕〔5〕;
這樣就可以記錄30個學生,每個學生5科的成績了。但如果現在要記錄的班級有3班呢?
int〔〕〔〕〔〕 student=new int〔3〕〔30〕〔5〕;
這樣就可以記錄3個班,每班30個學生,每個學生5科的成績了。不過如果你是初學者,建議你練習到二維陣列即可,三維陣列的原理知道就可以了。至於三維以上的陣列,等非常有空的時候再練習吧!
2-1 宣告
資料型別〔〕〔〕 參考變數;
資料型別〔〕 參考變數〔〕;
資料型別 參考變數〔〕〔〕;
一組中括號代表一個維度,中括號可分開置於資料型別或參考變數的後面,中括號的總數量,就是陣列的維度。
例:
int[][][] a; //三維陣列宣告,建議用法
int[] a[][]; //三維陣列宣告
int[][][][] a[]; //五維陣列宣告
int a[][][][][][]; //六維陣列宣告
此上均為合法之陣列宣告。
2-2 建構
參考變數=new 資料型別[陣列長度][陣列長度];
參考變數=new 資料型別[陣列長度][];
多維陣列建構時,至少要指定第一個維度的長度。而後面的維度可以在後續的程式碼中,依序建構。
例一:
int[][] a=new int[2][3];
for(int x=0;x<2;x++){
for(int y=0;y<3;y++){
System.out.print("a[" + x + "][" + y + "]=" + a[x][y] + "\t");
}
System.out.println();
}
宣告時立刻建構並指派所有維度的長度。外迴圈控制第一個維度,內迴圈控制第二個維度。
例二:
int[][] a;
a=new int[2][];
a[0]=new int[3];
a[1]=new int[4];
for(int x=0;x<3;x++){
System.out.print("a[0][" + x + "]=" + a[0][x] + "\t");
}
System.out.println();
for(int y=0;y<4;y++){
System.out.print("a[1][" + y + "]=" + a[1][y] + "\t");
}
建構時各個維度分開依序建構,缺點是比較複雜,而且建構時也要注意維度。例:指派給參考變數a的是二維陣列,而指派給a〔0〕及a〔1〕的是一維陣列。
但有一個很大的優點,就是同一個維度的陣列長度不一定要一樣,在某些情況下,可以結省大量的記憶體空間。
至於控制陣列的迴圈,上述兩個例子的寫法都是不好的,正確的寫法將在「陣列長度」的節次中討論。
2-3 初始化
要把值指派給多維陣列,就要詳細指定各個維度的序號。Java就會如同郵差先生一般,把信送到某某市的某某路的某某巷的某某號的某某樓。
例一:
int[][][] a=new int[2][3][5];
a[0][2][3]=100;
a[1][2][3]=200;
System.out.println("a[0][0][0]=" + a[0][0][0]); //未指派者為預設值0
System.out.println("a[0][2][3]=" + a[0][2][3]);
System.out.println("a[1][2][3]=" + a[1][2][3]);
在此例中,除了讓我們可以了解多維陣列的指派之外,也可以看到預設值的功能。當陣列元素很多時,設計師就不必一一指派陣列元素了。
而關於預設值,在這裏我們要討論進階一點的問題。在一維陣列時,有列出了所有基本資料型別的預設值。但如果不是基本資料型別呢?有預設值嗎?
假設有一個二維陣列,目前只建構了第一個維度,也就是說目前這個二維陣列物件中,並沒有儲存任何一維陣列物件的物件代號,那這個二維陣列物件中儲存的是什麼呢?答案是:null。Java在建構二維陣列物件的同時,便將預設值null,指派給每一個陣列元素。
例二:
int[][] a;
a=new int[2][];
System.out.println("a[0]=" + a[0]);
Java在建構二維陣列物件時,便指派預設值null。
2-4 宣告建構並初始化
資料型別[][] 參考變數={{值,值,值},{值,值,值,值}};
以大括號為維度的區分,總數即為該維度之陣列長度。
例:
int[][][] a={
{
{1,2,3},
{4,5,6,7}
},
{
{8,9,10,11},
{12,13,14}
}
};
System.out.println("a[0][1][2]=" + a[0][1][2]);
記得每組大括號也要用‘,’隔開。此例會列印6。
以我為例,我是一個屬於「人」這個型別的物件,我的名字叫做「許裕永」。但是在學校裏,為了讓學生容易記得我,我告訴學生可以叫我「許大笨」。上了網路,為了方便,取了個名字叫「許大呆」。也就是說,我這個物件,有三個物件名稱,可以分別在不同的場合代表我這個物件。
所以,物件名稱(參考變數)只是代表物件,但它不是物件。本節中要講的就是;「陣列物件建構完成後,可以任意的指派給型別相符的參考變數」。讓一個或一個以上的參考變數,可以控制(存取)這個陣列物件。也就是一個陣列物件,可以有一個以上的陣列名稱。
3-1 一維陣列的指派
只要型別相符,就可以任意指派,指派後,所有的參考變數都可以完全控制該陣列物件。
例:
int[] a=new int[5];
a[0]=8;
int[] b=a;
b[0]=10;
int[] c=b;
System.out.println(a[0]);
System.out.println(b[0]);
System.out.println(c[0]);
第三行敍述句的意思是:取得參考變數a所儲存的物件代號,複製後儲存於參考變數b之中。一定要記住,複製的是物件代號,不是陣列物件,物件只有一個(第五行敍述句也一樣)。
3-2 多維陣列的指派
維度是型別的一部份,一維陣列物件能指派給宣告一個維度的參考變數;但是,它也能指派給宣告兩個維度的參考變數的第一個維度。以此類推,只要資料型別及維度相符,就能指派。
例:
int[] a={1,2,3};
int[][] b=new int[2][];
b[0]=a; //將一維陣列指派給二維陣列的第一個維度
System.out.println(b[0][0]);
int[][][] c=new int[2][][];
c[0]=b; //將二維陣列指派給三維陣列的第一個維度
System.out.println(c[0][0][1]);
int[][][][] d=new int[2][][][];
d[0]=c; //將三維陣列指派給二維陣列的第一個維度
System.out.println(d[0][0][0][2]);
d[1]=new int[2][][]; //建構三維陣列物件
d[1][0]=b; //將二維陣列指派給四維陣列的第二個維度
System.out.println(d[1][0][0][0]+d[1][0][0][1]+d[1][0][0][2]);
初學者練到二維陣列即可。認證者須完全理解。例:六維陣列的第三個維度可以指派幾維的陣列?
4-1 參考變數的比對
兩個參考變數的比對,是比對這兩個參考變數中,所儲存的物件代號是否相同,也就是這兩個參考變數是否代表同一個陣列物件。
例一:
int[] a={1,2,3};
int[] b=a;
boolean c=(a==b);
System.out.println(c);
參考變數a和參考變數b,指向同一個陣列物件,所以變數c的值為true。
例二:
int[] a={1,2,3};
int[] b={1,2,3};
boolean c=(a==b);
System.out.println(c);
參考變數a和參考變數b,各自指向不同的陣列物件(雖然兩個陣列物件的元素值都一樣),所以變數c的值為false。
4-2 陣列物件的比對及其他運算
若要比對兩個陣列物件中的元素值是否完全一樣,可以用迴圈,將陣列元素逐一比對。另外,Java也設計了一個類別java‧util‧Arrars,這個 類別中,提供了一些運算一維陣列的方法,也是認證者必須熟記的。但初學者若對類別的使用沒有概念,可以先行略過,等學完「類別方法成員的呼叫」後再回來 看。
範例:陣列比對及排序運算。
檔名:ShowArray.java
import java.util.*;
public class ShowArray{
public static void main(String[] args){
int[] arrayA={8,2,5};
int[] arrayB={8,2,5};
//比對兩個陣列物件的內容是否完全一樣
boolean c=Arrays.equals(arrayA,arrayB);
System.out.println(c);
//將陣列物件中的元素由小到大排序
Arrays.sort(arrayA);
//將陣列物件中的所有元素轉為字串後列印
System.out.println(Arrays.toString(arrayA));
//比對5這個值在陣列物件中的位置,若值存在則傳回該值的序號
int d=Arrays.binarySearch(arrayA,5);
System.out.println(d);
//比對6這個值在陣列物件中的位置,若值不存在則傳回位置代號
int e=Arrays.binarySearch(arrayA,6);
System.out.println(e);
//將8這個值,填滿陣列物件
Arrays.fill(arrayA,8);
System.out.println(Arrays.toString(arrayA));
}
}
在本範例中,示範了一些Arrays類別提供的方法。其中binarySearch這個方法必須特別說明:1‧這個方法必須使用在已經排序完成的陣列物件 中,才能傳回正確的值。2‧若陣列物件中有要比對的值,則傳回該值的序號;否則傳回該值相對於本陣列物件元素的位置代號。如下表:
位罝代號 |
-1
|
-2
|
-3
|
-4
|
|||
陣列元素 |
2
|
5
|
8
|
||||
序號 |
0
|
1
|
2
|
想想看,如果我們的程式碼中,有5個陣列,而且陣列長度都不一樣,要記位這5個陣列的長度,是否會有些困擾?更何況,有時候陣列長度是執行時期才決定的!
例如:我們前面寫的範例StudetnSystem,預設的學生人數是30位,合理嗎?如果我們要把程式改為,執行時期才讓執行者輸入學生人數呢?那我們程式碼中的陣列長度要設為多少呢?迴圈要怎麼寫呢?
為了方便設計師控制陣列,Java針對陣列設計了一個指令:「length」,只要用參考變數(陣列名稱)‧length便可以得到該陣列物件的長度。
5-1 一維陣列
參考變數.length
請注意length後面沒有小括號。
例:
int[] a={1,2,3};
for(int b=0;b<a.length;b++){
System.out.println(a[b]);
}
a‧length之值為3,所以迴圈中的條件運算式等同於b<3。
5-2 多維陣列
參數變數[序號].length
這是取得第二個維度的某一個陣列物件的長度。
例:
Scanner s=new Scanner(System.in);
System.out.print("請輸入學生人數-->");
int count=s.nextInt();
int[][] student=new int[count][5];
for(int x=0;x<student.length;x++){
for(int y=0;y<student[x].length;y++){
System.out.print("請輸入第" + (x+1) + "位同學的第" + (y+1) + "科成績-->");
student[x][y]=s.nextInt();
}
}
請注意:參考變數‧length是取得第一個維度的長度,後面每加一組中括號就是取得下一個維度的長度。
5-3 用for each控制陣列
例:
Scanner s=new Scanner(System.in);
System.out.print("請輸入學生人數-->");
int count=s.nextInt();
int[][] student=new int[count][5];
int p=1;
for(int[] x:student){
int q=1;
for(int y:x){
System.out.println("第" + p + "位同學的第" + q + "科成績-->" + y);
q++;
}
System.out.println();
if(p++==count){
p=1;
}
}
外迴圈中宣告的x,可以代表二維陣列Student中的每一個一維陣列,內迴圈中宣告的y,可以代表一維陣列中的每一個值,所以學會這種寫法來控制集合物件,會比較簡單。
不過,這種寫法的使用時機卻也有相當限制。以本例而言,用這種寫法其實是不適當的。雖然表面上for後面小括號中的宣告看起來比較單純,但是因為輸出時有 要列印學生及科目的個數,我們反而要另外宣告計數值來計數(p及q),那不是多此一舉嗎?更何況,這種寫法中所宣告的變數(x及y),它們都只是集件物件 中元素值的複本,不是本尊。也就是說,我們只能取值,卻不能存值,所以一般只能用於輸出。
财 new是一個指令,它命令Java配置記憶體區塊。中括號裏的值為陣列長度,必須是整數。配置完成的記憶體區塊,我們稱之為陣列物件。
财 陣列元素的序號,編列為0~(陣列長度-1)。
财 在配置記憶體區塊的同時,Java便已經將各資料型別的預設值,指派給所有的陣列元素了。
财 宣告敍述句後,可以用大括號直接指派陣列元素的值。但中括號中一樣不可以有數字,而指派值的個數,便是陣列長度。
财 一組中括號代表一個維度,中括號可分開置於資料型別或參考變數的後面,中括號的總數量,就是陣列的維度。
财 多維陣列建構時,至少要指定第一個維度的長度。
财 非基本資料型別的陣列物件,其陣列元素的預設值為null。
财 java‧util‧Arrars,這個類別中,提供了一些運算一維陣列的方法。
财 參考變數‧length是取得第一個維度的長度,後面每加一組中括號就是取得下一個維度的長度。
沒有留言:
張貼留言
注意:只有此網誌的成員可以留言。