標籤:它的 safe 存在 item 繼承 nsa 努力 部分 public
C#基礎文法中得數組,定義就是:數組是一種資料結構,包含同一個類型的多個元素。從CLR的角度來看,首先數組是參考型別,堆棧分配屬於分配於堆上,其次數組在記憶體中是連續的儲存的,所以索引速度很快,而且賦值與修改元素也很簡單。可以利用位移地址訪問元素,時間複雜度為O(1);可以用折半尋找法尋找元素,效率高。
上面這段話初看之下很莫名奇妙,解釋一下,首先基礎文法定義了數組是什麼,是一種資料結構,資料結構有很多種:棧,隊列,列表,字典,樹,圖,數組也是這樣一種資料結構,而且是很多其他資料結構的基本,但是數組的內的資料類型只能是一種,在一個數組中不能既存在int又存在string
數組,ArrayList,List區別
在從CLR的角度來看,CLR介紹數組時,就從其派生自System.Array,而System.Array派生自Object,所以數組是參考型別,分配於託管堆上,並且數組在託管堆上分配到的是一塊連續儲存的記憶體,可以通過索引尋找,雖然查詢的時間複雜度是O(1),但有個問題被忽略了,就是找的快未必就有用,數組分配在一塊連續的資料空間上,因此分配空間時必須確定大小。空間的連續,也導致了儲存效率低,插入和刪除元素效率比較低,而且麻煩。如果,要增添一個元素,需要移動大量元素,在記憶體中空出一個元素的空間,然後將要增加的元素放在其中。同樣,你想刪除一個元素,需要移動大量元素去填補被移動的元素。這就是數組的詬病,為瞭解決這個問題C#又推出了ArrayList
ArrayList是.Net Framework提供的用於資料存放區和檢索的專用類,它是命名空間System.Collections下的一部分。它的大小是按照其中儲存的資料來動態擴充與收縮的。所以,我們在聲明ArrayList對象時並不需要指定它的長度。但微軟又發現使用ArrayList存在一個更為致命的問題,看下面的代碼
1 //初始化ArrayList 2 ArrayList list = new ArrayList(); 3 //新增資料 4 list.Add("abc"); 5 list.Add(123); 6 //插入資料 7 list.Insert(0, "在第一個位置插入一條資料"); 8 foreach (var item in list) 9 {10 Console.WriteLine(item);11 }View Code
在list中,我們不僅插入了字串"abc",而且又插入了數字123。這樣在ArrayList中插入不同類型的資料是允許的。因為ArrayList會把所有插入其中的資料都當作為object類型來處理。這樣,在我們使用ArrayList中的資料來處理問題的時候,很可能會報類型不符的錯誤,也就是說ArrayList不是型別安全的。既使我們保證在插入資料的時候都很小心,都有插入了同一類型的資料,但在使用的時候,我們也需要將它們轉化為對應的原類型來處理。這就存在了裝箱與拆箱的操作,會帶來很大的效能損耗。
雖然ArrayList解決了數組長度問題,但是留下了型別安全問題,裝箱和拆箱操作一直是操作程式中所努力在避免的操作,因為其對效能的損耗確實是很大的,所以C#推出了Lits集合,下面的代碼是聲明實現一個List的集合,可以看到List指定了類型,避免了拆箱裝箱的操作,並且也無需指定元素個數。
1 List<int> list = new List<int>();2 //新增資料3 list.Add(123);4 //報錯,無法從string轉化成int5 list.Add("123");6 foreach (var item in list)7 {8 Console.WriteLine(item);9 }View Code
事實上,List來自對ArrayList的封裝,而ArrayList封裝自數組,數組是ArrayList和List的底層實現,那麼是不是使用List就可以完美的代替數組了呢?以後所有的代碼都只需要寫成List就好了,既然List是來自數組的封裝,繼承了數組的所有方法和行為,其實這裡有一次回到了效率問題,如果是固定的並且知道其長度的資料集合用數組,而不固定長度的用List。額講了這麼多,或許就是這句話最重要吧。
建立下限非零的數組
C#中有兩種數組,一種是下表從0開始的數組,一種是下標非0開始的,不推薦使用下標非0開始的,通過下面的代碼就可以建立一個指定下限的多維陣列
1 static void Main(string[] args) 2 { 3 // 建立和初始化多維陣列字串類型。 4 int[] myLengthsArray = new int[2] { 3, 5 }; 5 int[] myBoundsArray = new int[2] { 2, 3 }; 6 //CreateInstance 建立指定下標的數組的方法 7 //elementTypeType: System.Type要建立的 Array 的 Type。 8 //一維數組,它包含要建立的 Array 的每個維度大小。 9 //一維數組,它包含要建立的 Array 的每個維度下限(起始索引)。10 Array myArray = Array.CreateInstance(typeof(String), myLengthsArray, myBoundsArray);11 12 for (int i = myArray.GetLowerBound(0); i <= myArray.GetUpperBound(0); i++)13 for (int j = myArray.GetLowerBound(1); j <= myArray.GetUpperBound(1); j++)14 {15 int[] myIndicesArray = new int[2] { i, j };16 myArray.SetValue(Convert.ToString(i) + j, myIndicesArray);17 }18 19 //顯示每一個維度上限和下限20 Console.WriteLine("Bounds:\tLower\tUpper");21 for (int i = 0; i < myArray.Rank; i++)22 Console.WriteLine("{0}:\t{1}\t{2}", i, myArray.GetLowerBound(i), myArray.GetUpperBound(i));23 // 顯示的值的數組。24 Console.WriteLine("The Array contains the following values:");25 PrintValues(myArray);26 }27 public static void PrintValues(Array myArr)28 {29 IEnumerator myEnumerator = myArr.GetEnumerator();30 int i = 0;31 int cols = myArr.GetLength(myArr.Rank - 1);32 while (myEnumerator.MoveNext())33 {34 if (i < cols)35 {36 i++;37 }38 else39 {40 Console.WriteLine();41 i = 1;42 }43 Console.Write("\t{0}", myEnumerator.Current);44 }45 Console.WriteLine();46 }View Code
數組的傳遞和返回
其實傳遞就是當作參數傳遞給函數,返回就是可以作為類型返回接收,但有個問題,數組是參考型別,開篇我們就介紹了,傳遞給函數,如果函數改動了數組,原數組就會跟著一起變,這肯定不是我們想要的。解決方案是可以直接複製一個數組,通過Array.Copy()方法,複製一個新的數組傳遞給函數體,如果不希望原數組被改動的話。
不安全的數組訪問
我們每次訪問一個數組中的元素時,CLR都會確保索引不會超出數組的上下限。CLR的索引檢查會有一些效能方面的代價。如果大家對自己的代碼有足夠的信心,並且不介意使用非安全的程式碼,則可在訪問一個數組時不讓CLR執行索引檢查。
1 //unsafe 開啟不安全訪問標誌 2 //需要在你的項目屬性頁面裡面,把是否包含unsafe代碼的選項選上 3 //否則會報錯 4 unsafe static void main() 5 { 6 Int32[] arr = new Int32{1, 2, 3, 4, 5}; 7 //擷取一個指向數組第0元素的指標 8 fixed(Int32 * element = &arr[0]) 9 {10 for(Int32 x = 0, n = arr.Length; x < n; x++)11 {12 Console.WriteLine(element[x]);13 }14 }15 }View Code
小結
今天的內容理論偏多代碼偏少,因為關於數組我覺得以CLR的角度看,不需要寫聲明,增刪查,二維多維陣列這些概念,還有數組的排序和複製,如果把數組的一個一個方法拿出來介紹可能並沒有多少必要性,而是分析一下數組在底層是一個怎樣的分配,它和ArrayList還有List是什麼關係,其實數組可以說是很多資料結構的基礎。
C#數組的使用