1. 程式人生 > >《C#高階程式設計》【第六章】陣列 -- 學習筆記

《C#高階程式設計》【第六章】陣列 -- 學習筆記

       為了解決大量的同類型元素,於是陣列就孕育而生了。陣列是具有一定順序關係的若干物件的集合體,一維陣列可以看作是定長的線性表。反之,n為的陣列可以看作線性表的推廣。從儲存結構上來看,陣列是一段連續的儲存空間。現在我們看看在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)我們就可以比較元組間的內容,但是同樣的如果使用比較運算子“==”和“!=”我們還是隻能比較他們的引用。

(如有錯誤,歡迎指正,轉載請註明出處)