詳解c#索引(Index)和範圍(Range)
範圍和索引為訪問序列中的單個元素或範圍提供了簡潔的語法。
在本教程中,你將瞭解:
- 對某個序列中的範圍使用該語法。
- 瞭解每個序列開頭和末尾的設計決策。
- 瞭解 Index 和 Range 型別的應用場景。
對索引和範圍的語言支援
此語言支援依賴於兩個新型別和兩個新運算子:
- System.Index 表示一個序列索引。
- 來自末尾運算子 ^ 的索引,指定一個索引與序列末尾相關。
- System.Range 表示序列的子範圍。
- 範圍運算子 ..,用於指定範圍的開始和末尾,就像運算元一樣。
讓我們從索引規則開始。 請考慮陣列 sequence
。 0
索引與 sequence[0]
^0
索引與 sequence[sequence.Length]
相同。 表示式 sequence[^0]
不會引發異常,就像 sequence[sequence.Length]
一樣。 對於任何數字 n
,索引 ^n
與 sequence[sequence.Length - n]
相同。
string[] words = new string[] { // index from start index from end "The",// 0 ^9 "quick",// 1 ^8 "brown",// 2 ^7 "fox",// 3 ^6 "jumped",// 4 ^5 "over",// 5 ^4 "the",// 6 ^3 "lazy",// 7 ^2 "dog" // 8 ^1 }; // 9 (or words.Length) ^0
可以使用 ^1
索引檢索最後一個詞。 在初始化下面新增以下程式碼:
Console.WriteLine($"The last word is {words[^1]}");
範圍指定範圍的開始和末尾。 範圍是排除的,也就是說“末尾”不包含在範圍內。 範圍 [0..^0] 表示整個範圍,就像 [0..sequence.Length]
表示整個範圍。
以下程式碼建立了一個包含單詞“quick”、“brown”和“fox”的子範圍。 它包括 words[1] 到 words[3]。 元素 words[4] 不在該範圍內。 將以下程式碼新增到同一方法中。 將其複製並貼上到互動式視窗的底部。
string[] quickBrownFox = words[1..4]; foreach (var word in quickBrownFox) Console.Write($"< {word} >"); Console.WriteLine();
以下程式碼使用“lazy”和“dog”返回範圍。 它包括 words[^2]
和 words[^1]
。 結束索引 words[^0]
不包括在內。 同樣新增以下程式碼:
string[] lazyDog = words[^2..^0]; foreach (var word in lazyDog) Console.Write($"< {word} >"); Console.WriteLine();
下面的示例為開始和/或結束建立了開放範圍:
string[] allWords = words[..]; // contains "The" through "dog". string[] firstPhrase = words[..4]; // contains "The" through "fox" string[] lastPhrase = words[6..]; // contains "the,"lazy" and "dog" foreach (var word in allWords) Console.Write($"< {word} >"); Console.WriteLine(); foreach (var word in firstPhrase) Console.Write($"< {word} >"); Console.WriteLine(); foreach (var word in lastPhrase) Console.Write($"< {word} >"); Console.WriteLine();
還可以將範圍或索引宣告為變數。 然後可以在 [ 和 ] 字元中使用該變數:
Index the = ^3; Console.WriteLine(words[the]); Range phrase = 1..4; string[] text = words[phrase]; foreach (var word in text) Console.Write($"< {word} >"); Console.WriteLine();
下面的示例展示了使用這些選項的多種原因。 請修改 x、y 和 z 以嘗試不同的組合。 在進行實驗時,請使用 x 小於 y且 y 小於 z 的有效組合值。 在新方法中新增以下程式碼。 嘗試不同的組合:
int[] numbers = Enumerable.Range(0,100).ToArray(); int x = 12; int y = 25; int z = 36; Console.WriteLine($"{numbers[^x]} is the same as {numbers[numbers.Length - x]}"); Console.WriteLine($"{numbers[x..y].Length} is the same as {y - x}"); Console.WriteLine("numbers[x..y] and numbers[y..z] are consecutive and disjoint:"); Span<int> x_y = numbers[x..y]; Span<int> y_z = numbers[y..z]; Console.WriteLine($"\tnumbers[x..y] is {x_y[0]} through {x_y[^1]},numbers[y..z] is {y_z[0]} through {y_z[^1]}"); Console.WriteLine("numbers[x..^x] removes x elements at each end:"); Span<int> x_x = numbers[x..^x]; Console.WriteLine($"\tnumbers[x..^x] starts with {x_x[0]} and ends with {x_x[^1]}"); Console.WriteLine("numbers[..x] means numbers[0..x] and numbers[x..] means numbers[x..^0]"); Span<int> start_x = numbers[..x]; Span<int> zero_x = numbers[0..x]; Console.WriteLine($"\t{start_x[0]}..{start_x[^1]} is the same as {zero_x[0]}..{zero_x[^1]}"); Span<int> z_end = numbers[z..]; Span<int> z_zero = numbers[z..^0]; Console.WriteLine($"\t{z_end[0]}..{z_end[^1]} is the same as {z_zero[0]}..{z_zero[^1]}");
索引和範圍的型別支援
索引和範圍提供清晰、簡潔的語法來訪問序列中的單個元素或元素的範圍。 索引表示式通常返回序列元素的型別。 範圍表示式通常返回與源序列相同的序列型別。
若任何型別提供帶 Index 或 Range 引數的索引器,則該型別可分別顯式支援索引或範圍。 採用單個 Range 引數的索引器可能會返回不同的序列型別,如 System.Span<T>。
重要
使用範圍運算子的程式碼的效能取決於序列運算元的型別。
範圍運算子的時間複雜度取決於序列型別。 例如,如果序列是 string 或陣列,則結果是輸入中指定部分的副本,因此,時間複雜度為 O(N)(其中 N 是範圍的長度)。 另一方面,如果它是System.Span<T>
或System.Memory<T>
,則結果引用相同的後備儲存,這意味著沒有副本且操作為 O(1)。
除了時間複雜度外,這還會產生額外的分配和副本,從而影響效能。 在效能敏感的程式碼中,考慮使用Span<T>
或Memory<T>
作為序列型別,因為不會為其分配範圍運算子。
若型別包含名稱為 Length
或 Count
的屬性,屬性有可訪問的 Getter 並且其返回型別為 int
,則此型別為可計數型別。**** 不顯式支援索引或範圍的可計數型別可能為它們提供隱式支援。 有關詳細資訊,請參閱功能建議說明的隱式索引支援和隱式範圍支援部分。 使用隱式範圍支援的範圍將返回與源序列相同的序列型別。
例如,以下 .NET 型別同時支援索引和範圍:String、Span<T> 和 ReadOnlySpan<T>。 List<T> 支援索引,但不支援範圍。
Array 具有更多的微妙行為。 單個維度陣列同時支援索引和範圍。 多維陣列則不支援。 多維陣列的索引器具有多個引數,而不是一個引數。 交錯陣列(也稱為陣列的陣列)同時支援範圍和索引器。 下面的示例演示如何迴圈訪問交錯陣列的矩形子節。 它迴圈訪問位於中心的節,不包括前三行和後三行,以及每個選定行中的前兩列和後兩列:
var jagged = new int[10][] { new int[10] { 0,1,2,3,4,5,6,7,8,9},new int[10] { 10,11,12,13,14,15,16,17,18,19},new int[10] { 20,21,22,23,24,25,26,27,28,29},new int[10] { 30,31,32,33,34,35,36,37,38,39},new int[10] { 40,41,42,43,44,45,46,47,48,49},new int[10] { 50,51,52,53,54,55,56,57,58,59},new int[10] { 60,61,62,63,64,65,66,67,68,69},new int[10] { 70,71,72,73,74,75,76,77,78,79},new int[10] { 80,81,82,83,84,85,86,87,88,89},new int[10] { 90,91,92,93,94,95,96,97,98,99},}; var selectedRows = jagged[3..^3]; foreach (var row in selectedRows) { var selectedColumns = row[2..^2]; foreach (var cell in selectedColumns) { Console.Write($"{cell},"); } Console.WriteLine(); }
在所有情況下,Array 的範圍運算子都會分配一個數組來儲存返回的元素。
索引和範圍的應用場景
要分析較大序列的一部分時,通常會使用範圍和索引。 在準確讀取所涉及的序列部分這一方面,新語法更清晰。 本地函式 MovingAverage
以 Range
為引數。 然後,該方法在計算最小值、最大值和平均值時僅列舉該範圍。 在專案中嘗試以下程式碼:
int[] sequence = Sequence(1000); for(int start = 0; start < sequence.Length; start += 100) { Range r = start..(start+10); var (min,max,average) = MovingAverage(sequence,r); Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tMax: {max},\tAverage: {average}"); } for (int start = 0; start < sequence.Length; start += 100) { Range r = ^(start + 10)..^start; var (min,r); Console.WriteLine($"From {r.Start} to {r.End}: \tMin: {min},\tAverage: {average}"); } (int min,int max,double average) MovingAverage(int[] subSequence,Range range) => ( subSequence[range].Min(),subSequence[range].Max(),subSequence[range].Average() ); int[] Sequence(int count) => Enumerable.Range(0,count).Select(x => (int)(Math.Sqrt(x) * 100)).ToArray();
以上就是詳解c#索引(Index)和範圍(Range)的詳細內容,更多關於c# 索引和範圍的資料請關注我們其它相關文章!