第2章 Arrays與ArrayLists
數組是最常見的資料結構,出現在幾乎所有的程式設計語言中。在C#中使用資料本質上是建立了一個System.Array類型的數組對象。System.Array類是所有數組的抽象基底類型,它提供了一系列的方法用於完成如排序與尋找之類在過去程式員不得不手工實現的任務。
C#中數組的一個有趣的替代者是ArrayList類。ArrayList是一個可以根據需要自動增加儲存空間的數組。針對你不能準確的決定數組的最終大小或數組的大小在程式的生命週期中會改變很多次的情況,arraylist是一個比array更好的選擇。
在本章中,我們將快速探索C#中數組的基本使用,然後我們將轉向更進階的話題,包括對ArrayList類對象的拷貝,複製,測驗品質及使用它們的靜態方法。
數組基礎
數組是基於索引的集合資料。數組既可以是內建類型也可以是使用者自訂類型。事實上,把數組中資料看成是對象可能是最簡單的。C#中的資料本身也是對象,因為它們繼承自System.Array類。既然數組是System.Array類的執行個體,使用數組時你可以使用System.Array類的方法與屬性。
聲明與初始化數組
數組使用下面的文法聲明:
type[] array-name;
type是數組元素的資料類型,例如:
string[] names;
第二行是必需的,其用來初始化數組(因為其是System.Array類型的對象)並決定數組的大小,下面代碼初始化了剛聲明的names數組:
names = new string[10];
並預留了10個字串的記憶體空間。
在需要的時候你可以像下面這樣將兩個語句合并為一行:
string[] names = new string[10];
有時候你想將聲明,初始化,賦值數組的操作放在一個語句中,在C#中,你可以通過初始化列表來完成。
int[] numbers = new int[] { 1, 2, 3, 4, 5 };
這個數位列表,稱作初始化列表,其放於一對花括弧之間,元素之間以逗號分隔。當使用這種技術聲明一個數組時,你不需要指定元素的數目,編譯器由初始化列表的項目的數目來推斷這個數值。
設定與訪問數組元素
儲存於數組的元素既可以直接存取也可以調用Array類的SetValue方法。直接存取通過指派陳述式中左邊的索引關聯指定位置的數組元素。
names[2] = "Raymond";
sales[19] = 23123;
SetValue方法提供了一個更物件導向的方式設定數組元素的值,此方法接受兩個參數,一個索引值及一個元素值。
names.SetValue("Raymond", 2);
sales.SetValue(23123, 19);
數組元素既可通過索引直接存取也可以通過GetValue方法訪問。GetValue方法接受單一參數 - 一個索引值。
myName = names[2];
monthSales = (int)sales.GetValue(19);
使用一個for迴圈遍曆一個數組的元素是很常見的。程式員在編寫迴圈語句時常犯的一個錯誤有兩個,其一是寫入程式碼迴圈的上限值(這是一個錯誤,因為如果數組是動態,他的上界會改變)。另一個錯誤是調用一個函數訪問每一次迴圈的迭代迴圈的上界。
for (int i = 0; i <= sales.GetUpperBound(0); i++)
totalSales = totalSales + sales[i];
檢索數組中繼資料的方法及屬性
Array類提供了幾個屬性用於檢查一個數組的中繼資料。
- Length: 返回數組中所有維度全部元素的個數。
- GetLength: 返回數組指定維度元素的個數。
- Rank: 返回數組的維度。
- GetType: 返回當前數組執行個體的類型。
Length屬性用來統計多維陣列元素的個數,其返回那個數組中準確的元素數目。另外,你也可以使用GetUpperBound方法得到的值加上1得到數組元素數。
Length返回數組元素的總數目,GetLength方法統計數組中單個維度元素數目。這個方法與Rank屬性一起使用,可以用來在運行時沒有遺失資料的風險下調整數組的大小,這項技術將在本章後面部分討論。
GetType方法用來在你不確定數群組類型的情況下明確數組的資料類型,如將數組作為參數傳給一個方法時。在下面的程式碼片段中,我們建立了Type類型的變數,可以使用其IsArray方法判斷對象是否為一數組。如果對象是數組,代碼返回數組的資料類型。
int[] numbers;
numbers = new int[] { 0, 1, 2, 3, 4, };
Type arrayType = numbers.GetType();
if (arrayType.IsArray)
Console.WriteLine("The array type is: {0}", arrayType);
else
Console.WriteLine("Not an array");
Console.Read();
GetType方法不僅返回數組的類型,同時讓我們知道對象本質是一個數組。如下是上述代碼的輸出:
The array type is: System.Int32[]
方括弧指示了對象是一個數組。同樣注意在顯示資料類型時使用的格式。我們不得不這樣做,因為我們不能將Type資料轉型為string以便串連在待顯示字串的尾部。
多維陣列
到目前我們只討論了一位元組。在C#中,數組可以多達32維,雖然超過三維的數組很少見(且令人迷惑)。
聲明多維陣列是通過指定數組每個維度上界,二維數組聲明:
int[,] grades = new int[4, 5];
聲明了一個由4行5列元素組成的數組。二維數組常用作建模矩陣,你也可以聲明一個多維陣列而不指定維度界。完成這樣的工作,你可以使用逗號指定維度數。例如:
double[,] Sales;
聲明了一個二維數組,反之:
double[, ,] sales;
聲明了一個三維數組。當你在不提供維度上界的情況下聲明數組後,你需要在稍後重新使用界值定義數組:
Sales = new double[4, 5];
多維陣列可以使用初始化列表完成初始化。看下面的語句:
int[,] grades = new int[,]{{1,82,74,89,100},
{2,93,96,85,86},
{3,83,72,95,89},
{4,91,98,79,88}
};
首先,注意數組的上界沒有被指定。當你使用初始化列表初始化數組時。你不能指定數組的界。編譯器由初始化列表中的資料計算出每個維度上界。初始化列表本身以花括弧標記,就像數組的每一行。一行中各個元素均以逗號分隔。
訪問多維陣列的元素同訪問一維數組的元素相似。你可以使用傳統的數組訪問技術。
grade = grades[2, 2];
grades[2, 2] = 99;
或者你可以使用Array類的方法:
grade = grades.GetValue(2, 2);
你不可以使用SetValue方法對一個多維陣列賦值,因為這個方法只接受兩個參數:一個值與一個單一索引。(原文錯誤)
在多維陣列的所有元素上執行計算是很常見的操作,雖然大部分情況下這些操作基於儲存於數組行中或儲存於數組列中的值。
使用grades數組,如果數組每一行是一個學生記錄,我們可以計算這個年級中學生的平均成績。代碼如下:
int[,] grades = new int[,]{{1,82,74,89,100},
{2,93,96,85,86},
{3,83,72,95,89},
{4,91,98,79,88}
};
int last_grade = grades.GetUpperBound(1);
double average = 0.0;
int total;
int last_student = grades.GetUpperBound(0);
for (int row = 0; row <= last_student; row++)
{
total = 0;
for (int col = 0; col <= last_grade; col++)
total += grades[row, col];
average = total / last_grade;
Console.WriteLine("Average: " + average);
}
參數數組
許多方法的定義需要接受一系列的數值作為參數。但是有些情況下寫一個可以接受數目可變的參數方法定義,可以使用一種稱作參數數組的構造。
參數數組是一個使用params關鍵字定義的用於方法的參數列表。下面的方法定義允許任意數量的值作為參數提供給方法,函數的傳回值是傳入方法的數值的和。
static int sumNums(params int[] nums)
{
int sum = 0;
for (int i = 0; i <= nums.GetUpperBound(0); i++)
sum += nums[i];
return sum;
}
該方法可以使用下面兩種方式調用:
total = sumNums(1, 2, 3);
total = sumNums(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
當你在方法定義中使用參數數組,作為參數數組形式的參數必須放在參數列表的最後一位以便編譯器可以正確處理參數列表。否則,編譯器不能知道參數數組元素的結束位置,也就沒法獲得方法中其它參數的開始位置。
不規則數組
當你建立一個多維陣列,你通常建立了一個每一行有相同數目元素的結構。例如,下列數組的聲明。
int[,] sales = new int[12, 30]; //Sales for each day of each month
這個數組假定每一行(月份)擁有相同數目的元素(日),然而我們知道一些月份有30天,一些有31天,還有一個月份有29天。使用剛才聲明的這個數組,將會產生一些空元素。對這個數組還算不上一個問題,但是使用一個大的多的數組時會浪費許多空間。
這個問題的解決方案就是使用一個不規則數組代替一個二維數組。不規則數組是一個由數組組成的數組,也即數組的每一行又由一個數組組成。不規則數組的每一維都是一個一維數組。我們稱其"不規則"數組的原因是其每一行的元素個數可能不同。一個不規則數組的映像將不是正方形或矩形,反之會有一個形狀不規則的邊。
不規則數組的聲明方法是在陣列變數名稱的後面放置兩對方括弧。第一對方括弧中的數代表數組的行數,第二對方括弧留空來為每一行中的一維數組留出空間。一般情況下,行數在聲明語句中使用初始化列表設定,如下:
int[][] jagged = new int[12][];
這個語句看起來很奇怪,但是當你將其分開來看將會明白。這個交錯數組是一個有12個元素的整型數組,其中每一個元素又是一個整型數組。初始化列表實際上用來初始化數組的每一行,這表示數組12個元素的每一個子項目即一行子數組中的每一個元素都被初始化為預設的值。
一旦一個不規則數組被聲明,每一個單獨的行數組的元素都可以被賦值。下面的程式碼片段向不規則數組賦值:
jagged[0][0] = 23;
jagged[0][1] = 13;
//...
jagged[7][5] = 45;
第一對方括弧指示行號,第二對方括弧指示行數組的元素。上文代碼中,第一行語句訪問第一行數組的第一個元素,第二行語句訪問第一行數組的第二個元素,第三條語句訪問第八行數組的第六個元素。
舉一個使用不規則數組的例子,下面的程式建立了一個名為sales(記錄兩個月中每周的銷售情況)的數組,將銷售數字賦給其元素,然後遍曆數組來計算數組中儲存的兩個月中每周的平均銷售記錄。
using System;
class class1
{
static void Main()
{
int[] Jan = new int[31];
int[] Feb = new int[29];
//註:此處使用原文的寫法無法編譯通過,特改寫為C#3.0的初始化列表寫法。
int[][] sales = new int[][] { Jan, Feb };
int month, day, total;
double average = 0.0;
sales[0][0] = 41;
sales[0][1] = 30;
sales[0][0] = 41;
sales[0][1] = 30;
sales[0][2] = 23;
sales[0][3] = 34;
sales[0][4] = 28;
sales[0][5] = 35;
sales[0][6] = 45;
sales[1][0] = 35;
sales[1][1] = 37;
sales[1][2] = 32;
sales[1][3] = 26;
sales[1][4] = 45;
sales[1][5] = 38;
sales[1][6] = 42;
for (month = 0; month <= 1; month++)
{
total = 0;
for (day = 0; day <= 6; day++)
{
total += sales[month][day];
}
average = total / 7;
Console.WriteLine("Average sales for month:" + month + ": " + average);
}
}
}
接下來要翻譯的部分介紹了ArrayList類,本書中很少有涉及到C#2.0出現的泛型類,對於這點我將在整理完此書後,根據相應的章節補充一下泛型的內容,並且將有些程式使用C#3.0重新實現。 |