Duyệt mảng
Sau bài này bạn sẽ làm được
- Duyệt mảng bằng chỉ số và bằng con trỏ
- Biết vì sao macro đếm phần tử vô dụng bên trong hàm
- Viết hàm in mảng có định dạng dễ đọc
- Chọn kiểu chỉ số phù hợp cho vòng lặp
Gần như mọi thao tác trên mảng đều bắt đầu bằng một vòng lặp duyệt qua các phần tử. Bài này gom lại những cách viết chuẩn, cùng một cái bẫy về macro đếm phần tử mà rất nhiều người mắc.
#Ba cách duyệt
#define N 5
int a[N] = { 1, 2, 3, 4, 5 };
/* Cách 1: theo chỉ số. Dễ đọc nhất, dùng được ở mọi nơi. */
for (size_t i = 0; i < N; ++i)
printf("%d ", a[i]);
/* Cách 2: theo con trỏ. Ngắn hơn, hợp khi không cần biết vị trí. */
for (int *p = a; p < a + N; ++p)
printf("%d ", *p);
/* Cách 3: hai con trỏ đầu và cuối. Kiểu của thư viện chuẩn. */
int *dau = a;
int *cuoi = a + N;
while (dau < cuoi)
printf("%d ", *dau++);| Cách | Ưu điểm | Khi nào chọn |
|---|---|---|
| Theo chỉ số | Biết được vị trí, dễ truy cập nhiều mảng cùng lúc | Mặc định. Khi cần in vị trí hoặc so hai mảng song song |
| Theo con trỏ | Không cần phép nhân để tính địa chỉ mỗi lượt | Khi chỉ quan tâm giá trị, và khi làm việc với chuỗi |
| Hai con trỏ | Diễn đạt được khoảng, gọn khi thu hẹp từ hai đầu | Đảo mảng, phân hoạch, tìm cặp |
#Macro đếm phần tử
#define SO_PHAN_TU(x) (sizeof (x) / sizeof (x)[0])
int a[10];
printf("%zu\n", SO_PHAN_TU(a)); /* 10, đúng */void xu_ly(int a[])
{
for (size_t i = 0; i < SO_PHAN_TU(a); ++i)
printf("%d ", a[i]);
}
xu_ly(a);void xu_ly(const int *a, size_t n)
{
for (size_t i = 0; i < n; ++i)
printf("%d ", a[i]);
}
xu_ly(a, SO_PHAN_TU(a)); /* macro dùng ở main, nơi a vẫn là mảng */#Hàm in mảng
Bạn sẽ cần hàm này ở mọi bài còn lại của Chương 9. Viết cho tử tế một lần rồi chép lại dùng.
void in_mang(const char *nhan, const int *a, size_t n)
{
printf("%s[", nhan);
for (size_t i = 0; i < n; ++i)
printf("%d%s", a[i], i + 1 < n ? ", " : "");
printf("]\n");
}truoc: [5, 1, 4, 2, 8] sau: [1, 2, 4, 5, 8] rong: []
Ba chi tiết đáng chú ý. Thứ nhất, biểu thức i + 1 < n ? ", " : "" đặt dấu phẩy vào giữa các phần tử chứ không đặt sau phần tử cuối. Thứ hai, hàm nhận const int * nên trình biên dịch bảo đảm nó không sửa dữ liệu của bạn. Thứ ba, với n bằng 0 vòng lặp không chạy lần nào và bạn nhận được cặp ngoặc rỗng, đúng như mong đợi.
Bản in theo bảng cho mảng dài
void in_bang(const int *a, size_t n, size_t cot)
{
for (size_t i = 0; i < n; ++i) {
printf("%6d", a[i]);
if ((i + 1) % cot == 0 || i + 1 == n)
putchar('\n');
}
} 1 2 3 4 5 6 7 8
9 10 11 12 13 14 15 16
17 18 19 20Định dạng %6d căn phải trong sáu ký tự nên các cột thẳng hàng. Bài 3.2 đã nói về độ rộng trường trong printf.
#Chọn kiểu cho biến chỉ số
| Kiểu | Ưu điểm | Nhược điểm |
|---|---|---|
| size_t | Đúng kiểu mà sizeof trả về, không bao giờ tràn với mảng thật | Không dấu nên dễ mắc bẫy trừ ra số khổng lồ |
| int | Có dấu nên duyệt ngược tự nhiên, quen thuộc | Cảnh báo so sánh khác dấu, tràn khi mảng lớn hơn hai tỷ phần tử |
| ptrdiff_t | Có dấu, đủ rộng, đúng kiểu của hiệu hai con trỏ | Ít người dùng nên trông lạ |
/* Chỉ số dùng size_t, nhưng kết quả tìm kiếm dùng kiểu có dấu */
long tim(const int *a, size_t n, int x)
{
for (size_t i = 0; i < n; ++i)
if (a[i] == x)
return (long)i;
return -1; /* âm một là quy ước không tìm thấy */
}#Bốn mẫu duyệt thường gặp
Mẫu 1: tích lũy
long long tong = 0;
for (size_t i = 0; i < n; ++i)
tong += a[i];Mẫu 2: đếm theo điều kiện
size_t so_chan = 0;
for (size_t i = 0; i < n; ++i)
if (a[i] % 2 == 0)
so_chan++;Mẫu 3: lọc và dồn tại chỗ
/* Xóa mọi phần tử bằng x, trả về số phần tử còn lại.
Hai chỉ số: doc chạy hết mảng, ghi chỉ tiến khi giữ lại phần tử. */
size_t xoa_gia_tri(int *a, size_t n, int x)
{
size_t ghi = 0;
for (size_t doc = 0; doc < n; ++doc)
if (a[doc] != x)
a[ghi++] = a[doc];
return ghi;
}Mẫu hai chỉ số này rất đáng nhớ. Nó giải quyết được xóa phần tử, loại bỏ trùng lặp, dồn số 0 về cuối, và nhiều bài khác, luôn trong một lượt duyệt duy nhất mà không cần mảng phụ.
Mẫu 4: dừng sớm
int co_so_am = 0;
for (size_t i = 0; i < n; ++i)
if (a[i] < 0) {
co_so_am = 1;
break; /* không cần duyệt tiếp */
}Tự làm thử
- Cài
in_mangnhư trong bài, thử với mảng rỗng, một phần tử và mười phần tử. - Viết cùng một vòng lặp tính tổng bằng cả ba cách duyệt, xác nhận kết quả giống nhau.
- Gọi macro đếm phần tử bên trong một hàm và ở
main, in cả hai kết quả rồi giải thích chênh lệch. - Cài
xoa_gia_tritheo mẫu hai chỉ số, dùng nó để loại bỏ mọi số 0 khỏi mảng. - Viết hàm loại bỏ phần tử trùng lặp liên tiếp bằng đúng mẫu hai chỉ số, trong một lượt duyệt.
- Viết hàm
in_bangnhận số cột từ tham số, in mảng một trăm phần tử theo tám cột và theo mười cột.
Trình chấm điểm tự động sẽ được bổ sung ở giai đoạn sau. Hiện tại bạn tự chạy thử trên máy.
Tóm tắt
- Ba cách duyệt là theo chỉ số, theo con trỏ và bằng hai con trỏ khoảng. Chọn theo độ dễ đọc, không theo tốc độ.
- Macro
sizeof(x) / sizeof(x)[0]chỉ đúng khixcòn là mảng thật, tức là ở nơi khai báo, không phải bên trong hàm. - Hàm nhận mảng luôn cần thêm tham số độ dài, và nên có const nếu không sửa.
- Dùng
size_tcho chỉ số vòng lặp tiến, dùng kiểu có dấu cho giá trị trả về có thể là âm một. - Mẫu hai chỉ số đọc và ghi giải quyết được xóa, lọc và dồn phần tử trong một lượt duyệt.