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)我們就可以比較元組間的內容,但是同樣的如果使用比較運算符“==”和“!=”我們還是隻能比較他們的引用。
(如有錯誤,歡迎指正,轉載請註明出處)