CodeGym /Các khóa học /C# SELF /Làm việc nâng cao với mảng

Làm việc nâng cao với mảng

C# SELF
Mức độ , Bài học
Có sẵn

1. Mảng kiểu string

Giờ mình nói sơ qua về mảng kiểu string nhé.

Như đã nói, mảng có thể là bất kỳ kiểu nào. Vậy nên, bạn hoàn toàn có thể tạo mảng kiểu string. Ví dụ, nếu cần viết chương trình "nhập từ bàn phím 10 dòng và in ra màn hình theo thứ tự ngược lại", code sẽ như này:

string[] array = new string[10];	// Tạo object-mảng có 10 phần tử
for (int i = 0; i < 10; i++)		// Vòng lặp từ 0 đến 9
{
   array[i] = Console.ReadLine();	// Đọc dòng từ bàn phím và lưu vào mảng
}
for (int i = 9; i >= 0; i--)		// Vòng lặp từ 9 về 0
{
    Console.WriteLine(array[i]);	// In ra từng phần tử của mảng
}

Code gần như không đổi luôn! Chỉ cần đổi kiểu int thành string khi tạo mảng thôi. Còn lại thì y chang!

2. Mảng kiểu string trong bộ nhớ

Thêm một điều hay nữa. Xem 3 hình sau nha:

Hình 1. Cách object string nằm trong bộ nhớ:

Mảng kiểu string trong bộ nhớ

Lưu ý là text của string không lưu trực tiếp trong biến: nó được cấp vùng nhớ riêng. Biến kiểu string chỉ lưu địa chỉ (tham chiếu) đến object chứa text thôi.

Hình 2. Cách mảng số nguyên nằm trong bộ nhớ:

int array in memory

Hình này bạn cũng từng thấy rồi.

Hình 3. Cách mảng string nằm trong bộ nhớ:

How a string array is stored in memory

Bên trái là biến-mảng kiểu string[] (lưu địa chỉ object-mảng).
Ở giữa là object-mảng kiểu string.
Bên phải là các object-string chứa text.

Trong các ô của object-mảng kiểu string không lưu text trực tiếp mà là địa chỉ (tham chiếu) đến text. Giống hệt như biến kiểu string lưu địa chỉ text.

3. Khởi tạo mảng nhanh trong C#

Mảng là thứ cực kỳ hữu ích, nên dev C# đã làm cho việc dùng mảng tiện nhất có thể. Đầu tiên là đơn giản hóa việc khởi tạo mảng, gán giá trị ban đầu cho nó.

Vì nhiều khi ngoài dữ liệu đọc từ đâu đó, chương trình còn cần dữ liệu nội bộ. Ví dụ, cần lưu độ dài các tháng trong năm vào mảng. Code sẽ như này:

int[] months = new int[12];
months[0] = 31; // thang mot
months[1] = 28; // thang hai
months[2] = 31; // thang ba
months[3] = 30; // thang tu
months[4] = 31; // thang nam
months[5] = 30; // thang sau
months[6] = 31; // thang bay
months[7] = 31; // thang tam
months[8] = 30; // thang chin
months[9] = 31; // thang muoi
months[10] = 30; // thang muoi mot
months[11] = 31; // thang muoi hai

Nhưng có cách viết ngắn hơn nhiều — cảm ơn dev C#:

// do dai cac thang trong nam
int[] months = new int[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

Chỉ cần liệt kê các giá trị, cách nhau bằng dấu phẩy là xong!

Tiện ghê chưa? Nhưng vẫn còn nữa.

Thực ra compiler có thể tự xác định kiểu object-mảng dựa vào kiểu biến-mảng. Độ dài mảng thì chỉ cần đếm số phần tử trong dấu ngoặc nhọn thôi.

Nên code này còn ngắn hơn nữa:

// do dai cac thang trong nam
int[] months = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

Đỉnh chưa? 🙂

Cách này gọi là "khởi tạo mảng nhanh". Nó dùng được cho cả kiểu int...

// ten cac thang trong nam
string[] months = { "Thang mot", "Thang hai", "Thang ba", "Thang tu", "Thang nam", "Thang sau", "Thang bay", "Thang tam", "Thang chin", "Thang muoi", "Thang muoi mot", "Thang muoi hai"};

4. Vòng lặp foreach

Duyệt qua tất cả phần tử mảng là thao tác quá thường xuyên, nên có hẳn một vòng lặp riêng(!) cho nó. Đó là foreach. Viết như này nè:


foreach (int score in scores)
{
    Console.WriteLine($"Diem: {score}");
}
        
Duyệt phần tử mảng bằng foreach

Ở đây vòng lặp tự duyệt phần tử — không cần index thủ công. Không thể sửa phần tử trực tiếp (chỉ đọc được trong vòng lặp), nhưng cực kỳ tiện để "đi qua" mảng.

Compiler sẽ biến code vòng lặp foreach thành code dưới đây:


for (int i = 0; i < scores.Length; i++)
{
    int score = scores[i];
    Console.WriteLine($"Diem: {score}");
}
        
Cách foreach hoạt động "bên trong"

Không có gì thần thánh đâu. Giờ bạn cũng hiểu vì sao không thể sửa phần tử mảng qua biến score rồi nhé.

Viết thêm ví dụ nữa: tính tổng tất cả điểm.


int sum = 0;
foreach (int score in scores)
{
    sum += score;
}
Console.WriteLine($"Tong diem: {sum}");
        
Tính tổng phần tử mảng bằng foreach

Khi nào dùng vòng lặp nào?

  • Nếu cần index hoặc cần sửa phần tử — dùng vòng lặp for.
  • Nếu chỉ cần duyệt giá trị — foreach nhanh và gọn hơn.

5. Index từ cuối: toán tử ^

Từ một thời điểm nào đó, C# cho phép lấy phần tử từ cuối mảng. Trước đây, muốn lấy phần tử cuối phải viết kiểu như:


int lastScore = scores[scores.Length - 1];
        
Lấy phần tử cuối mảng kiểu cũ

Giờ thì dễ hơn nhiều. Chỉ cần thêm toán tử ^ trước index, là đếm từ cuối:


int lastScore = scores[^1];        // Phan tu cuoi cung
int penultimate = scores[^2];      // Phan tu ap chot
        
Index từ cuối bằng ^
  • ^1 — là phần tử đầu tiên từ cuối (cuối cùng).
  • ^2 — là phần tử thứ hai từ cuối.

Minh họa

Index Gia tri trong mang scores Index binh thuong Index tu cuoi
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 — dễ hiểu luôn.

Lưu ý! Index từ cuối đếm từ 1, không phải từ 0 nha.

6. Range: toán tử .. (range)

Đôi khi cần lấy mảng con từ mảng gốc. Trong C#, bạn có thể dùng toán tử range .. — cực kỳ gọn và tiện để lấy "slice" của mảng.

Mảng con này còn gọi là slice (từ tiếng Anh "slice"), xác định bởi hai biên: bắt đầu và kết thúc.
Lưu ý quan trọng: biên phải không được lấy vào kết quả!

Trông như này nè


int[] scores = { 10, 15, 8, 22, 17 };
int[] top3 = scores[0..3]; // Lay phan tu tu 0 den 2: {10, 15, 8}
        
Lấy slice của mảng bằng ..

  • scores[0..3] — lấy phần tử từ index 0 (bao gồm) đến index 3 (KHÔNG bao gồm).
  • Mảng kết quả sẽ chứa phần tử index 0, 1 và 2.

Viết tắt

Nếu không ghi biên trái, sẽ lấy từ đầu luôn:


int[] firstTwo = scores[..2]; // giong scores[0..2], la {10, 15}
        

Nếu không ghi biên phải, sẽ lấy đến cuối luôn:


int[] fromThird = scores[2..]; // {8, 22, 17}
        

Index âm

Dùng ký hiệu ^ để đếm index từ cuối:


int[] lastTwo = scores[^2..]; // {22, 17}
        

Ở đây ^2 — là "phần tử thứ hai từ cuối".

Cũng có thể lấy range "từ đâu đến đâu", dùng index âm ở cả hai bên:


int[] mid = scores[1..^1]; // {15, 8, 22} (tu phan tu thu 2 den ap chot)
        

Cần nhớ gì

  • Biên phải luôn không lấy vào — nghĩa là nếu ghi a..b, sẽ lấy phần tử từ a đến b-1.
  • Nếu ghi biên vượt ngoài mảng, sẽ bị lỗi IndexOutOfRangeException.
  • Slice — là mảng mới, mảng gốc không bị đổi. Sửa mảng mới không ảnh hưởng mảng gốc đâu.

Ví dụ thực tế

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} (ban sao day du)
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Sử dụng chỉ mục từ cuối và phạm vi
Sử dụng chỉ mục từ cuối và phạm vi
2
Nhiệm vụ
C# SELF, mức độ, bài học
Đã khóa
Đảo ngược mảng chuỗi và cắt mảng
Đảo ngược mảng chuỗi và cắt mảng
Bình luận
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION