1. string 類型的陣列
簡單說一下 string 類型的陣列。
就像之前說過的,陣列可以是任何型別。所以你可以建立 string 類型的陣列。假如我們要寫一個「從鍵盤輸入 10 行字串並反向輸出」的程式,程式碼大概會長這樣:
string[] array = new string[10]; // 建立一個 10 個元素的陣列物件
for (int i = 0; i < 10; i++) // 0 到 9 的迴圈
{
array[i] = Console.ReadLine(); // 從鍵盤讀取一行字串,存進陣列
}
for (int i = 9; i >= 0; i--) // 9 到 0 的迴圈
{
Console.WriteLine(array[i]); // 輸出陣列的每個元素
}
程式碼幾乎沒變!只要把陣列型別從 int 換成 string 就好。其他都一樣!
2. string 類型的陣列在記憶體中的樣子
還有一個實用小知識。來看三張圖:
圖 1. string 物件在記憶體中的樣子:
注意,字串的內容不是直接存在變數裡:它會被分配到一塊獨立的記憶體空間。而 string 型別的變數裡存的是指向那個物件的位址(參考)。
圖 2. int 陣列在記憶體中的樣子:
這張圖你應該也看過。
圖 3. string 陣列在記憶體中的樣子:
左邊是 string[] 陣列變數(存的是陣列物件的位址)。
中間是 string 類型的陣列物件。
右邊是存放文字的 string 物件。
string 類型的陣列物件裡存的不是字串本身(內容),而是它們的位址(參考)。就像 string 變數裡存的是字串(內容)的位址一樣。
3. C# 陣列的快速初始化
陣列超級實用,所以 C# 的開發者讓它用起來超方便。首先就是 簡化陣列初始化,也就是一開始就給它一些值。
很多時候,除了從外部讀取資料,程式還需要一些內部資料。比如我們要存每個月的天數,程式碼可以這樣寫:
int[] months = new int[12];
months[0] = 31; // 一月
months[1] = 28; // 二月
months[2] = 31; // 三月
months[3] = 30; // 四月
months[4] = 31; // 五月
months[5] = 30; // 六月
months[6] = 31; // 七月
months[7] = 31; // 八月
months[8] = 30; // 九月
months[9] = 31; // 十月
months[10] = 30; // 十一月
months[11] = 31; // 十二月
但其實有更短的寫法——感謝 C# 的設計師:
// 一年每個月的天數
int[] months = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
直接用逗號把所有值列出來就好!
是不是很方便?但還不只這樣。
其實編譯器可以根據變數型別自動判斷陣列的型別,長度也會自動算(看你大括號裡有幾個元素)。
所以這段程式碼還可以更短:
// 一年每個月的天數
int[] months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
是不是很美?🙂
這種寫法叫「快速初始化陣列」。而且不只 int 型別可以這樣用...
// 一年每個月的名稱
string[] months = { "一月", "二月", "三月", "四月", "五月", "六月", "七月", "八月", "九月", "十月", "十一月", "十二月"};
4. foreach 迴圈
遍歷陣列所有元素超常見,所以有專門的迴圈!它叫 foreach,寫法長這樣:
foreach (int score in scores)
{
Console.WriteLine($"分數: {score}");
}
這個迴圈會自動幫你遍歷元素——不用自己寫 index。不能直接改元素(在迴圈裡只能讀),但要單純「走一遍」超方便。
編譯器會把 foreach 迴圈轉成下面這種寫法:
for (int i = 0; i < scores.Length; i++)
{
int score = scores[i];
Console.WriteLine($"分數: {score}");
}
所以沒什麼魔法啦。這也解釋了為什麼你不能用 score 變數直接改陣列內容。
再來一個例子:算所有分數的總和。
int sum = 0;
foreach (int score in scores)
{
sum += score;
}
Console.WriteLine($"所有分數總和: {sum}");
什麼時候用哪種迴圈?
- 如果 需要 index 或 要改元素——用 for 迴圈。
- 如果只是單純走一遍——foreach 比較快又乾淨。
5. 從後面開始的索引:^ 運算子
最近 C# 可以直接從陣列尾端取元素。以前如果要拿最後一個元素,要這樣寫:
int lastScore = scores[scores.Length - 1];
現在更簡單了。只要在 index 前面加 ^,就會從後面數:
int lastScore = scores[^1]; // 最後一個元素
int penultimate = scores[^2]; // 倒數第二個元素
- ^1 —— 倒數第一個(最後一個)。
- ^2 —— 倒數第二個。
視覺化
| 索引 | scores 陣列的值 | 一般索引 | 從後面開始的索引 |
|---|---|---|---|
| 0 | 10 | 0 | ^5 |
| 1 | 15 | 1 | ^4 |
| 2 | 8 | 2 | ^3 |
| 3 | 22 | 3 | ^2 |
| 4 | 17 | 4 | ^1 |
score[^1] = 17, score[^2] = 22 —— 超直觀。
注意! 從後面開始的索引是從 1 開始,不是從 0。
6. 範圍:.. (range) 運算子
有時候你會想從原本的陣列裡拿出一個 子陣列。C# 有個超方便的範圍運算子 ..,可以很簡潔地拿到你要的「slice」。
這種子陣列有時候叫 slice(英文就是 slice),它有兩個邊界:開始跟結束。
重點: 右邊界不包含在結果裡!
範例
int[] scores = { 10, 15, 8, 22, 17 };
int[] top3 = scores[0..3]; // 取 0 到 2 的元素: {10, 15, 8}
- scores[0..3] —— 取 index 0(包含)到 index 3(不包含)。
- 結果陣列會有 index 0、1、2 的元素。
簡寫
如果 沒寫左邊界,就是從頭開始:
int[] firstTwo = scores[..2]; // 跟 scores[0..2] 一樣,就是 {10, 15}
如果 沒寫右邊界,就是到最後:
int[] fromThird = scores[2..]; // {8, 22, 17}
負索引
用 ^ 可以從後面開始算 index:
int[] lastTwo = scores[^2..]; // {22, 17}
這裡 ^2 就是「倒數第二個」。
你也可以兩邊都用負索引,拿一段範圍:
int[] mid = scores[1..^1]; // {15, 8, 22}(從第二個到倒數第二個)
要記得什麼?
- 右邊界永遠不包含——寫 a..b,就是拿 a 到 b-1。
- 如果邊界超出陣列範圍,會丟出 IndexOutOfRangeException。
- slice 是 新陣列,原本的沒變。你改新陣列不會影響原本的。
實用範例
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
int[] arr = { 1, 2, 3, 4, 5, 6, 7 };
int[] start = arr[..3]; // {1, 2, 3}
int[] end = arr[^2..]; // {6, 7}
int[] middle = arr[2..5]; // {3, 4, 5}
int[] all = arr[..]; // {1, 2, 3, 4, 5, 6, 7}(完整複製)
GO TO FULL VERSION