《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)我們就可以比較元組間的內容,但是同樣的如果使用比較運算符“==”和“!=”我們還是隻能比較他們的引用。

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章