Mảng (array) chứa một số lượng cố định các phần tử cùng kiểu, nằm liền nhau trong bộ nhớ. Bạn chọn độ dài khi tạo mảng, đọc và ghi phần tử theo chỉ số bắt đầu từ 0, và độ dài đó không bao giờ thay đổi sau khi tạo.
Khai báo và khởi tạo mảng
Kiểu của một mảng là kiểu phần tử theo sau bởi []. Có nhiều cách để tạo mảng:
Đầu ra:
90, 0, 0, 72
Ben
2 prices, 3 days
new int[4] gán cho mọi phần tử giá trị mặc định của kiểu: 0 với số, false với bool, null với chuỗi và các kiểu tham chiếu khác. Cách viết ngắn { "Ana", "Ben" } chỉ dùng được khi khai báo; khi gán cho một biến đã có, bạn cần viết names = new[] { "Dev", "Eli" };.
Length, chỉ số và phần tử cuối
Chỉ số chạy từ 0 đến Length - 1. Length là một thuộc tính nên không có cặp ngoặc tròn (khác với phương thức Count() của LINQ).
Đầu ra:
Count: 5
First: 18
Last: 16
Caught IndexOutOfRangeException
Đọc hoặc ghi ra ngoài phạm vi sẽ ném ra IndexOutOfRangeException lúc chạy; trình biên dịch không bắt được lỗi này. Nguyên nhân thường gặp là điều kiện vòng lặp viết thành i <= arr.Length thay vì i < arr.Length.
Từ C# 8 trở đi có thêm cú pháp chỉ số tính từ cuối và range, ngắn gọn hơn khi lấy phần tử cuối hay cắt một đoạn mảng:
int last = temps[^1]; // 16
int[] middle = temps[1..4]; // { 21, 25, 19 }, a new array
Duyệt mảng bằng vòng lặp
Vòng lặp for cho bạn chỉ số; foreach cho bạn từng phần tử và rõ ràng hơn khi bạn chỉ cần đọc.
Đầu ra:
4.95 | 13.20 | 3.58
Total: 21.73
Biến của foreach là chỉ đọc: viết p = 0; trong vòng lặp sẽ gây lỗi biên dịch. Muốn thay đổi phần tử, hãy duyệt bằng chỉ số.
Sắp xếp, đảo ngược và tìm kiếm
Lớp static Array chứa các phương thức hỗ trợ. Array.Sort sắp xếp tại chỗ và không trả về gì, nên thứ tự ban đầu sẽ mất.
Đầu ra:
Ascending: 60, 72, 79, 88, 95
Descending: 95, 88, 79, 72, 60
Index of 60: 4
Index of 99: -1
Any above 90: True
First below 75: 72
Ana, ben, chloe
fig, kiwi, banana
Array.IndexOf trả về -1 khi không tìm thấy giá trị. Array.Find trả về giá trị mặc định của kiểu (ở đây là 0) khi không có phần tử nào khớp, điều này gây mơ hồ với số; hãy dùng Array.FindIndex khi chuyện đó quan trọng. Sắp xếp giảm dần chỉ với một lệnh gọi là Array.Sort(scores, (a, b) => b.CompareTo(a)).
Nếu cần kết quả đã sắp xếp mà không đụng đến mảng gốc, LINQ trả về một mảng mới: var sorted = scores.OrderBy(s => s).ToArray();. Xem thêm LINQ.
Thay đổi kích thước: mảng không thể dài thêm
Độ dài là cố định. Array.Resize tạo một mảng mới với kích thước bạn yêu cầu, sao chép các phần tử, rồi cập nhật biến mà bạn truyền vào bằng ref.
Đầu ra:
4
2
True
False
Mỗi lần resize đều sao chép toàn bộ phần tử. Tăng kích thước mảng từng phần tử một trong vòng lặp rất chậm, và đó chính là việc List<T> làm giúp bạn hiệu quả hơn, bằng cách nhân đôi mảng bên trong của nó.
Mảng là kiểu tham chiếu: sao chép
Gán một mảng cho biến khác chỉ sao chép tham chiếu, không sao chép phần tử. Khi đó cả hai tên đều trỏ đến cùng một mảng.
Đầu ra:
100
2
0, 100, 2, 3, 0
Điều tương tự xảy ra khi bạn truyền mảng vào một phương thức: phương thức đó có thể thay đổi phần tử của bên gọi. Clone, Array.Copy, CopyTo và ToArray() của LINQ đều tạo bản sao nông (shallow copy). Với mảng chứa đối tượng, mảng mới giữ tham chiếu đến chính các đối tượng cũ, nên thay đổi copy[0].Name vẫn làm thay đổi thứ mà original[0] nhìn thấy.
So sánh mảng bằng == là so sánh tham chiếu. Để so sánh nội dung, hãy dùng a.SequenceEqual(b) từ System.Linq.
Mảng 2 chiều (đa chiều)
Một lưới hình chữ nhật được khai báo bằng dấu phẩy bên trong cặp ngoặc vuông. Truy cập phần tử bằng [row, col].
Đầu ra:
Rows: 2
Cols: 3
Length: 6, Rank: 2
. X .
. . X
Length của mảng 2 chiều là tổng số phần tử (6 với lưới 2 nhân 3), vì vậy các vòng lặp lồng nhau dùng GetLength(0) và GetLength(1). Rank là số chiều. Mảng 3 chiều cũng làm tương tự: new int[4, 4, 4] và cube[x, y, z]. Vòng lặp foreach trên mảng 2 chiều duyệt qua mọi phần tử theo từng hàng.
Jagged array (mảng của các mảng)
Jagged array int[][] là mảng mà mỗi phần tử lại là một mảng, và mỗi mảng con có thể có độ dài riêng. Nó phù hợp với dữ liệu kiểu "đơn hàng của từng khách hàng", nơi các hàng có kích thước khác nhau.
Đầu ra:
Customer 0: 2 orders, 20, 35
Customer 1: 1 orders, 12
Customer 2: 4 orders, 8, 15, 40, 22
True
new int[3][] chỉ tạo mảng bên ngoài; mỗi hàng ban đầu là null, và truy cập orders[0][0] trước khi gán hàng sẽ ném ra NullReferenceException. Cú pháp new int[3][4] không được phép; bạn phải tự tạo từng hàng.
Dùng [,] cho một lưới thực sự (bàn cờ, ma trận, điểm ảnh) và [][] khi các hàng có độ dài khác nhau hoặc bạn muốn hoán đổi cả hàng.
Mảng và List
Mảng T[] | List<T> | |
|---|---|---|
| Kích thước | Cố định từ khi tạo | Tăng và giảm được |
| Số phần tử | Thuộc tính Length | Thuộc tính Count |
| Thêm hoặc xóa | Không được (chỉ có Array.Resize) | Add, Insert, Remove |
| Đa chiều | [,] và [][] | Chỉ có list chứa list |
| Dùng khi | Biết trước kích thước, bảng tra cứu, bộ đệm | Tập hợp thay đổi theo thời gian |
Chuyển đổi qua lại chỉ cần một lệnh gọi: new List<int>(arr) hoặc arr.ToList(), và list.ToArray(). List<T> là phiên bản có thể mở rộng.
Lỗi thường gặp
- Viết
i <= arr.Lengthtrong vòng lặp. Chỉ số hợp lệ cuối cùng làLength - 1; code này sẽ ném raIndexOutOfRangeException. - In trực tiếp mảng.
Console.WriteLine(arr)in raSystem.Int32[]; hãy dùngstring.Join. - Nghĩ rằng
Array.Sorttrả về mảng đã sắp xếp. Nó trả vềvoidvà sắp xếp tại chỗ. - Sao chép bằng
=. Cách này chỉ sao chép tham chiếu; hãy dùngClone()hoặcToArray(). - Dùng các hàng của jagged array trước khi tạo. Mỗi hàng của
new T[n][]lànullcho đến khi được gán. - Dùng
Lengthđể lấy số hàng của mảng 2 chiều. Thuộc tính này đếm mọi phần tử; hãy dùngGetLength(0).
Câu hỏi thường gặp
Làm sao lấy độ dài của mảng trong C#?
Dùng thuộc tính Length: scores.Length. Đây là thuộc tính, không phải phương thức, nên không có cặp ngoặc tròn. Với mảng 2 chiều, Length là tổng số phần tử; dùng GetLength(0) để lấy số hàng và GetLength(1) để lấy số cột.
Làm sao sắp xếp mảng trong C#?
Array.Sort(arr) sắp xếp mảng tại chỗ theo thứ tự tăng dần. Để sắp xếp giảm dần, gọi Array.Reverse(arr) sau khi sắp xếp, hoặc truyền vào một hàm so sánh: Array.Sort(arr, (a, b) => b.CompareTo(a)). Nếu muốn có một bản sao đã sắp xếp mà vẫn giữ nguyên mảng gốc, hãy dùng LINQ: arr.OrderBy(x => x).ToArray().
Mảng 2 chiều và jagged array trong C# khác nhau thế nào?
Mảng 2 chiều int[,] là một khối hình chữ nhật: mọi hàng có cùng số cột, và bạn truy cập bằng grid[row, col]. Jagged array int[][] là mảng chứa các mảng: mỗi hàng là một mảng riêng có độ dài riêng, và bạn truy cập bằng rows[i][j]. Mỗi hàng của jagged array phải được tạo trước khi dùng, nếu không nó sẽ là null.
Có thể thay đổi kích thước mảng trong C# không?
Không. Độ dài của mảng được cố định khi tạo. Array.Resize(ref arr, newSize) trông như thay đổi kích thước, nhưng thực ra nó cấp phát một mảng mới, sao chép các phần tử và trỏ biến của bạn sang mảng mới. Nếu kích thước thay đổi thường xuyên, hãy dùng List<T>.
Tại sao in mảng ra lại hiện System.Int32[]?
Console.WriteLine(arr) gọi arr.ToString(), và mảng không ghi đè phương thức này, nên bạn nhận được tên kiểu. Thay vào đó, hãy nối các phần tử thành một chuỗi: Console.WriteLine(string.Join(", ", arr)).
Nên dùng mảng hay List trong C#?
Dùng mảng khi số phần tử đã biết trước và cố định, chẳng hạn 12 tháng hay một bàn cờ. Dùng List<T> khi phần tử được thêm hoặc xóa theo thời gian. Cả hai đều truy cập theo chỉ số trong thời gian hằng số, và bên trong List<T> cũng lưu phần tử trong một mảng.