數組
數組
數組實際上是由一個變量名稱表示的一組同類型的數據元素。每個元素通過變量名稱和一個或多個方括號中的索引來訪問:
數組名 索引 ↓ ↓ MyArray[4]
定義
讓我們從C#中與數組有關的重要定義開始
- 元素 數組的獨立數據項稱為元素。數組的所有元素必須是同類型的,或繼承自相同的類型
- 秩/維度 數組可以有任何為正數的維度數。數組的維度數稱作秩(rank)
- 維度長度 數組的每個維度都有一個長度,就是這個方向的位置個數
- 數組長度 數組的所有維度中的元素的總和稱為數組的長度
重要細節
關於C#數組的一些要點:
- 數組一旦創建,大小就固定了。C#不支持動態數組
- 數組索引號從0開始。如果維度長度是n,索引號范圍就是0到n-1


數組的類型
C#提供兩種類型的數組
- 一維數組可以認為是單行元素或元素向量
- 多維數組是由主向量中的位置組成的。每個位置本身又是一個數組,稱為子數組(subarray)。子數組向量中的位置本身又是一個子數組
另外,有兩種類型的多維度數組:矩形數組(rectangular array)和交錯數組(jagged array),它們有如下特性:
- 矩形數組
- 某個維度的所有子數組具有相同長度的多維數組
- 不管有多少個維度,總是使用一組方括號
int x=myArray2[4,6,1]
- 交錯數組
- 每個子數組都是獨立數組的多維度數組
- 可以有不同長度的子數組
- 為數組的每個維度使用一對方括號
jagArray1[2][7][4]


數組是對象
數組實例是從System.Array繼承的對象。由於數組從BCL(Base Class Library)基類繼承,它們也繼承了很多有用的方法。
- Rank 返回數組維度數
- Length 返回數組長度(數組中所有元素的個數)
數組是引用類型,與所有引用類型一樣,有數據的引用以及數據對象本身。引用在棧或堆上,而數組對象本身總在堆上。

盡管數組總是引用類型,但是數組元素可以是值類型也可以是引用類型。
- 如果存儲的元素都是值類型,數組被稱作值類型數組
- 如果存儲的元素都是引用類型,數組被稱作引用類型數組


一維數組和矩形數組
一維數組和矩形數組的語法非常相似,因此放在一起闡述。然后再單獨介紹交錯數組。
聲明一維數組或矩形數組
聲明一維數組或矩形數組,在類型和變量名稱間使用一對方括號。
矩形數組聲明示例:
- 可以使用任意多的秩說明符
- 不能在數組類型區域中放數組維度長度。秩是數組類型的一部分,而緯度長度不是類型的一部分
- 數組聲明后,維度數就固定了。然而,緯度長度直到數組實例化時才確定
秩說明符 ↓ int[,,] firstArray; //三維整型數組 int[,] arr1; //二維整型數組 long[,] arr3; //三維long數組 long[3,2,6] SecondArray;//編譯錯誤 ↑ 聲明時不允許設置維度長度
和C/C++不同,方括號在基類型后,而不是在變量名稱后。
實例化一維數組或矩形數組
要實例化數組,我們可以使用數組創建表達式。數組創建表達式由new運算符構成,后面是基類名稱和一對方括號。方括號中以逗號分隔每個維度的長度。
例:
int[] arr2=new int[4];//包含4個int的一維數組 MyClass[] maArr=new MyClass[4];//包含4個MyClass引用的一維數組 int[,,] arr3=new int[3,6,2];//三維數組,數組長度是3*6*2=36


與對象創建表達式不一樣,數組創建表達式不包含圓括號-即使是對於引用類型數組。
訪問數組元素
在數組中使用整型值作為索引來訪問數組元素。
- 每個維度的索引從0開始
- 方括號內的索引在數組名稱之后
int[] intArr1=new int[15]; intArr1[2]=10; int var1=intArr1[2]; int[,] intArr2=new int[5,10]; intArr2[2,3]=7; int var2=intArr2[2,3];
int[] myIntArray; myIntArray=new int[4]; for(int i=0;i<4;i++) { myIntArray[i]-i*10; } for(int i=0;i<4;i++) { Console.WriteLine("Value of element {0} = {1}",i,myIntArray[i]); }


初始化數組
數組被創建后,每個元素被自動初始化為類型的默認值。對於預定義類型,整型默認值是0,浮點型默認值是0.0,布爾型默認值是false,而引用類型默認值則是null。
例:一維數組的自動初始化
int[] intArr=new int[4];


顯式初始化一維數組
- 初始值必須以逗號分隔,並封閉在一組大括號內
- 不必輸入數組長度,因為編譯器可以通過初始化值的個數來推斷長度
- 注意,在數組創建表達式和初始化列表中間沒有分隔符。即,沒有等號或其他連接運算符
int[] intArr=new int[]{10,20,30,40};


顯式初始化矩形數組
要顯式初始化矩形數組,需遵守以下規則:
- 每個初始值向量必須封閉在大括號內
- 每個維度也必須嵌套並封閉在大括號內
- 除了初始值,每個維度的初始化列表和組成部分也必須使用逗號分隔
int[,] intArray2=new int[,]{{10,1},{2,10},{11,9}};


快捷語法
在一條語句中使用聲明、數組創建和初始化時,可以省略語法的數組創建表達式部分。快捷語法如下:
int[] arr1=new int[3]{10,20,30}; int[] arr1= {10,20,30}; int[,] arr=new int[2,3]{{0,1,2},{10,11,12}}; int[,] arr= {{0,1,2},{10,11,12}};
隱式類型數組
上面聲明的數組都是顯式指定數組類型。然而,和其他局部變量一樣,數組可以是隱式類型的。
- 當初始化數組時,我們可以讓編譯器根據初始化語句的類型來推斷數組類型。只要所有初始化語句能隱式轉換為單個類型,就可以這么做
- 和隱式類型的局部變量一樣,使用var關鍵字
雖然用var替代了顯式數組類型,但是仍然需要在初始化中提供秩說明符。
int[] intArr1=new int[]{10,20,30,40}; var intArr2=new []{10,20,30,40}; int[,] intArr3=new int[,]{{10,1},{2,10},{11,9}}; var intArr4=new [,]{{10,1},{2,10},{11,9}}; string[] sArr1=new string[]{"life","liberty","pursuit of happiness"}; var sArr2=new []{"life","liberty","pursuit of happiness"};
綜合內容
例:綜合示例
var arr=new int[,]{{0,1,2},{10,11,12}}; for(int i=0;i<2;i++) { for(int j=0;j<3;j++) { Console.WriteLine("Element [{0},{1}] is {2}",i,j,arr[i,j]); } }


交錯數組
交錯數組是數組的數組。與矩形數組不同,交錯數組的子數組的元素個數可以不同。
例:二維交錯數組
- 第一個維度長度是3
- 聲明可以讀作“jagArr是3個int數組的數組”
- 圖中有4個數組對象-其中一個針對頂層數組,另外3個針對子數組
int[][] jagArr=new int[3][];


聲明交錯數組
交錯數組的聲明語法要求每個維度都有一對獨立的方括號。數組變量聲明中的方括號數決定了數組的秩。
和矩形數組一樣,維度長度不能包括在數組類型的聲明部分。
int[][] SomeArr;//秩等於2 int [][][] OhterArr;//秩等於3
快捷實例化
我們可以將數組創建表達式創建的頂層數組和交錯數組的聲明相結合。
int[][] jagArr=new int[3][];//3個子數組
不能在聲明語句中初始化頂層數組之外的數組。
int[][] jagArr=new int[3][4];//不允許,編譯錯誤
實例化交錯數組
和其他類型數組不一樣,交錯數組的完全初始化不能在一個步驟中完成。由於交錯數組是獨立數組的數組-每個數組必須獨立創建。
- 首先,實例化頂層數組
- 其次,分別實例化每個子數組,把新建數組的引用賦給它們所屬數組的合適元素
例:實例化交錯數組
int[][] Arr=new int[3][]; Arr[0]=new int[]{10,20,30}; Arr[1]=new int[]{40,50,60,70}; Arr[3]=new int[]{80,90,100,110,120};


比較矩形數組和交錯數組
矩形數組和交錯數組的結構區別非常大。
例如,下圖演示了3X3的矩形數組以及由3個長度為3的一維數組構成的交錯數組的結構。
- 兩個數組都保存9個整數,但是它們結構很不相同
- 矩形數組只有1個數組對象,而交錯數組有4個數組對象

在CIL(Common Intermediate Laguage,公共中間語言)中,一維數組有特定的指令用於性能優化。矩形數組沒有這些指令,並且不在相同級別進行優化。因此,有時使用一維數組(可以被優化)的交錯數組比矩形數組(不能被優化)更有效率。
另一方面,矩形數組的編程復雜度要小得多,因為它會被作為一個單元而不是數組的數組。
foreach語句
foreach語句允許我們連續訪問數組中的每個元素。
有關foreach語句的重點如下:
- 迭代變量是臨時的,並且和數組中元素的類型相同。foreach語句使用迭代變量來相繼表示數組中的每個元素
- foreach語句的語法如下
- Type是數組中元素的類型。我們可以顯式提供它的類型,或者使用var
- Identifier是迭代變量名
- ArrayName是數組名
- Statement是要為數組中每個元素執行一次的單條語句或語句塊
foreach(Type Identifier in ArrayName)//顯式 { Statement } foreach(var Identifier in ArrayName)//隱式 { Statement }
foreach如下方式工作:
- 從數組第一個元素開始並把它賦值給迭代變量
- 執行語句主體。在主體中,我們可以把迭代變量作為數組元素的只讀別名
- 主體執行后,foreach語句選擇數組中的下一個元素並重復處理
例:foreach示例
int[] arr1={10,11,12,13}; foreach(int item in arr1) { Console.WriteLine("Item Value: {0}",item); }


迭代變量是只讀的
迭代變量是只讀的。但是,對於值類型數組和引用類型數組效果不一樣。
對於值類型數組,這意味着在用迭代變量時,我們不能改變它們。
例:嘗試修改迭代變量報錯
int[] arr1={10,11,12}; foreach(int item in arr1) { item++; }
對於引用變量,迭代變量保存的是數據的引用,而不是數據本身。所以,我們雖然不能改變引用,但是我們可以改變數據。
例:引用變量修改迭代變量
class MyClass { public int MyField=0; } class Program { static void Main() { MyClass[] mcArray=new MyClass[4]; for(int i=0;i<4;i++) { mcArray[i]=new MyClass(); mcArray[i].MyField=i; } foreach(var item in mcArray) { item.MyField+=10; } foreach(var item in mcArray) { Console.WriteLine("{0}",item.MyField); } } }


foreach語句和多維數組
多維數組里,元素處理順序是從最右邊的索引號依次遞增。
例:矩形數組示例
class Program { static void Main() { int total=0; int[,] arr1={{10,11},{12,13}}; foreach(var element in arr1) { total+=element; Console.WriteLine("Element:{0},Current Total:{1}",element,total); } } }

例:交錯數組示例
class Program { static void Main() { int total=0; int[][] arr1=new int[2][]; arr1[0]=new int[]{10,11}; arr1[1]=new int[]{12,13,14}; foreach(int[] array in arr1) { Console.WriteLine("Starting new array"); foreach(int item in array) { total+=element; Console.WriteLine("Item:{0},Current Total:{1}",item,total); } } } }


數組協變
某些情況下,即使某對象不是數組的基類型,我們也可以把它賦給數組元素。這種屬性叫做數組協變(array covariance)。在下面情況下使用數組協變。
- 數組是引用類型數組
- 在賦值的對象類型和數組基類之間有隱式轉換或顯式轉換
由於派生類和基類之間總有隱式轉換,因此總是可以把派生類對象賦值給基類聲明的數組。
例:派生類、基類 數組協變
class A{...} class B:A{...} class Program { static void Main() { A[] AArray1=new A[3]; A[] AArray2=new A[3]; AArray1[0]=new A();AArray1[1]=new A();AArray1[2]=new A(); AArray2[0]=new B();AArray2[1]=new B();AArray2[2]=new B(); } }


值類型數組沒有協變
數組繼承的有用成員
C#數組從System.Array類繼承。它們從基類繼承了很多有用的屬性和方法。

public static void PrintArray(int[] a) { foreach(var x in a) { Console.WriteLine("{0}",x); } Console.WriteLine(""); } static void Main() { int[] arr=new int[]{15,20,5,25,10}; PrintArray(arr); Array.Sort(arr); PrintArray(arr); Array.Reverse(arr); PrintArray(arr); Console.WriteLine(); Console.WriteLine("Rank = {0},Length = {1}",arr.Rank,arr.Length); Console.WriteLine("GetLength(0) = {0}",arr.GetLength(0)); Console.WriteLine("GetType() = {0}",arr.GetType()); }


Clone方法
Clone方法為數組進行淺復制。即,它只創建了數組本身的克隆。如果是引用類型數組,它不會復制元素引用的對象。對於值類型數組和引用類型數組,效果不同。
- 克隆值類型數組會產生兩個獨立數組
- 克隆引用類型數組會產生指向相同對象的兩個數組
Clone方法返回object類型的引用,它必須被強制轉換成數組類型。
int[] intArr1={1,2,3}; int[] intArr2=(int[])intArr1.Clone();
例:Clone值類型數組示例
class Program { static void Main() { int[] intArr1={1,2,3}; int[] intArr2=(int[])intArr1.Clone(); intArr2[0]=100; intArr2[1]=200; intArr2[2]=300; } }

例:Clone引用類型數組示例
class A { public int Value=5; } class Program { static void Main() { A[] AArray1=new A[3]{new A(),new A(),new A()}; A[] AArray2=(A[])AArray1.Clone(); AArray2[0].Value=100; AArray2[1].Value=200; AArray2[2].Value=300; } }


比較數組類型
下表總結了3中類型數組的重要相似點和不同點。


