《C#高階程式設計》【第六章】陣列 -- 學習筆記
1、普通陣列
在C#中普通陣列又可以分為一維陣列、多維陣列和鋸齒陣列。
<1>一維陣列
我們現在先看看一維陣列的宣告語法:
型別[] 變數名;
知道怎麼聲明瞭,現在我們繼續看看陣列的初始化吧,在C#中有4種初始化的方式:
//n為陣列長度,an為陣列內部元素 型別[] 陣列名 = new 型別[n]; //為陣列分配記憶體,但是沒有賦初值(vs會自動為其賦初值為0) 型別[] 陣列名 = new 型別[n]{a1, a2, …, an} //初始化,並賦初值 型別[] 陣列名 = new 型別[]{a1, a2 ,…, an} //還可以不指定陣列長度,編譯器會自動統計元素個數 型別[] 陣列名 = {a1, a2,…, an} //C風格的初始化並賦值
訪問陣列時,以”陣列名[i]”的方式訪問第 i-1個元素。如果不知道陣列的長度,可以使用Length屬性。
注意:如果陣列中的元素型別是引用型別,就必須為每個元素分配記憶體。在C#中”型別[]”是一個不可分割的整體,”型別[]”可以看成是 陣列型別。
<2>多維陣列
看完一維陣列,現在我們推廣到多維陣列,宣告語法:
型別[,] 陣列名; //二維陣列
型別[,,] 陣列名; //三維陣列
相信你也發現了吧,方括號內的逗號數 + 1 就是陣列的維數。我們以二維陣列為例,來看看多維陣列的初始化:
int[,] arr = new int[2,3]{{1,2,3},{4,5,6}};
借這個例子我想說明多維陣列和一維陣列初始化的區別就是,多維陣列初始化時,每一維度都必須使用大括號括起來。其餘的和一維陣列初始化方法一樣。
<3>鋸齒陣列
在使用多維陣列的過程中,我們有時並不需要每一維度都一樣,於是我們就引入了鋸齒陣列。(在C++中的Vector也有類似的功能)。上一幅圖說明二維陣列與鋸齒陣列的區別:
現在我們看看他的宣告語法:
型別[][] 陣列名 = new 型別[n][]; //n為鋸齒陣列的維度,後一個方括號為空
我們用一個具體例項來說看看他的使用方法:
int[][] Testarray = new int[2][]; Testarray[0] = new int[3]{1,2,3}; //當然也可以先不賦初值,建議都先賦初值 Testarray[1] = new int[4]{1,2,3,4};
這時候有些人可能會有疑問,每一維度的長度不同,那樣怎麼簡單的遍歷整個陣列呢?這時Length屬性就可以發揮它的優勢了。我們以上述的為例:
for(int i = 0; i < Testarray.Length; i++){
for(int j = 0; j < Testarray[i].Length; j++){
//TODO:
}
}
<4>陣列作為引數
既然我們將陣列看成一個型別,那麼它自然也是可以作為引數傳遞給方法,也可以從方法中返回。C#陣列還支援協變,但是陣列協變只能用於引用型別,不能用於值型別。
<5>陣列段ArraySegment<T>
ArraySegment<T>可以和陣列之間建立一個對映,直接針對陣列的某一片段進行操作,其操作後的結果會直接反映在陣列上,反之陣列上的變化也會反映到陣列段上。我們來看看具體的使用吧:
ArraySegment<int> Test = new ArraySegment<int>(arr, 1, 4);
上述例子,表示Test,從arr[1]開始引用了4個元素。Test.Offset就表示第一個引用的元素,也就是arr[1]。
2、Array類
我們之前使用方括號宣告陣列,實際上就是隱式的使用了Array類。換一個角度看,我們使用的,例如:int[], double[] 我們都可以把他們看成是派生自Array的子類,這樣我們可以使用Array為陣列定義方法和屬性。
<1>建立陣列
Array是抽象類,所以不能例項化。但是可以使用靜態方法CreateInstance()來建立陣列。因為CreateInstance()有多個過載版本,我們就其中一個為例:
//建立一個int型,長度為5的陣列,Test
Array Test = Array.CreateInstance(typeof(int), 5);
//我們將Test[3]的值,賦值為5
Test.SetValue(5, 3);
//我們要返回 Test[3]的值
Test.GetValue(3);
//將它變為int[]的陣列
int[] T1 = (int[])Test;
<2>複製陣列
我們可以使用Clone()方法來複制陣列,但是如果陣列是引用型別那麼就只能複製對方的引用。如果陣列是值型別,那麼才能完整的將對方複製過來。我們還可以使用Copy()方法建立淺表副本。
注意:Clone()和Copy()最大的區別:Copy()方法必須使用與原陣列相同階數且有足夠的元素空間,但是Cone()方法會建立一個和原陣列等大的陣列。
<3>排序
Array類還提供了QuickSort排序演算法。使用Sort()方法可以對陣列進行排序。但是使用Sort()方法需要實現IComparable介面(.Net已經為基本資料型別實現了IComparable介面,預設從小到大)。對於自定義型別,我們就必須實現IComparable<T>介面,這個介面只用一個方法CompareTo()。如果兩者相等,就返回0。如果該例項在引數物件的前面,就返回小於0的值,反之就返回大於0的值。
我們也可以是通過實現IComparer<T>和IComparer介面。我們現在著重看看這個和IComparable介面的區別:
①IComparable在要比較物件的類中實現,可以比較該物件和另一個物件。
②IComparer要在單獨一個類中實現,可以比較任意兩個物件。
3、列舉
在foreach語句中使用列舉,可以迭代集合中的元素,而且不需要知道集合中的元素個數。foreach語句使用了一個列舉器,我們需要實現IEnumerable介面就可以使用foreach來迭代集合。(陣列和集合已經預設實現了IEnumerable介面)。
<1>foreach原理 和 IEnumerator 介面
foreach使用了IEnumerator介面的方法和屬性。
//per為Person類的物件
foreach(var p in per)
{
Consle.WriteLine(p);
}
C#編譯器會將這段程式碼解析為
IEnumerator<Person> em = per.GetEnumerator();
while(em.MoveNext())
{
Person p = em.Current;
Console.WriteLine(p);
}
IEnumerator介面的MoveNext()方法作用是:移動到集合的下一個元素,如果有則返回true,否則為false。Current屬性為當前的值。
<2>yield語句
由於建立列舉器的工作過於繁瑣,於是我們就引入了yield語句,來幫助我們減輕工作量,yield return 返回集合的一個元素,yield break 可停止迭代。
下面我們可以通過一個簡單的例子,來了解yield的用法:
public class TFoo
{
public IEnumerator<string> GetEnumerator()
{
yield return “Hello”;
yield return “World”;
}
}
現在我們通過foreach迭代集合
int cnt = 0; //我們用這個來看看集合在foreach中迭代了幾次
var Test = new TFoo();
foreach(var s in Test)
{
cnt++;
Console.WriteLine(s);
}
最後我們可以得到cnt = 2且會輸出Hello World。通過這個例項我們就可以大致的瞭解yield的工作方式。在之前學習泛型的時候我們在連結串列中已經使用過一次yield了。
注意:yield不能出現在匿名方法中
4、元組(Tuple)
陣列是為了處理大量的同類型資料,那麼我們要對不同型別的資料可以用什麼類似的方法處理嗎?當然,為此我們就引入了Tuple類。.Net中定義了8個泛型Tuple類,和一個靜態的Tuple。例如:Tuple<T1>包含一個型別為T1的元素,Tuple<T1,T2>則包含兩個型別為T1,T2的元素,依次類推。
如果元組元素超過8個那麼第8個就可以使用Tuple類定義,例如:
Tuple<T1, T2, T3, T4, T5, T6, T7, TRest> //TRest為另一個元組
我們通過這樣的方法就可以建立帶任意多個的元組了。
我們使用Create()方法建立元組,例如:
var Test = Tuple.Create<int,int>(2,5);
5、結構比較
陣列和元組都實現介面IStructuralEquatable 和 IStructuralComparable。這兩個介面不僅僅可以用來比較引用,還可以比較內容。因為這些介面是顯示實現的,所以在使用時需要把陣列和元組強制轉化為這個介面。
IStructuralEquatable介面用於比較兩個陣列或元組是否具有相同的內容。
IStructuralComparable介面用於給陣列或者元組排序。
我們用一個例項來簡單的認識IStructuralEquatable介面的用法:
public class Test
{
public int Id { get; set; }
public override bool Equals(object obj)
{
if (obj == null)
return base.Equals(obj);
else
return this.Id == (obj as Test).Id;
}
}
我們現在再定義兩個類內容相同的類物件t1,t2。
var t1 = new Test[2]{new Test{Id = 2}, new Test{Id = 3}};
var t2 = new Test[2]{new Test{Id = 2}, new Test{Id = 3}};
如果我們直接使用“==”或者“!=”比較那麼編譯器只會把我們的引用進行比較。這是我們就需要用到IStructuralEquatable介面了。(t1 as IStructuralEquatable).Equals(t2, EqualityComparer<Test>.Default)
這樣我們比較的就是t1,t2的內容了,因為是內容的比較所以它們將會返回True。EqualityComparer<Test>.Default呼叫的是Test預設的Equals()方法,所以我們只要重寫它預設的Equals()方法,給重寫的Equals()方法類內容比較的規則,那麼我們就可以比較類物件間,是否具有相同的內容。
對於元組e1,e2,我們直接使用e1.Equals(e2)我們就可以比較元組間的內容,但是同樣的如果使用比較運算子“==”和“!=”我們還是隻能比較他們的引用。
(如有錯誤,歡迎指正,轉載請註明出處)