C#數組的使用
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("View Codeabc"); 5 list.Add(123); 6 //插入數據 7 list.Insert(0, "在第一個位置插入一條數據"); 8 foreach (var item in list) 9 { 10 Console.WriteLine(item); 11 }
在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轉化成int 5 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 else 39 { 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#數組的使用