Bài 6.318 phút đọc
for
Sau bài này bạn sẽ làm được
- Giải thích thứ tự chạy của ba phần trong ngoặc for
- Viết được các biến thể: đếm ngược, bước nhảy khác 1, hai biến chạy
- Chọn đúng giữa for và while cho từng tình huống
- Tránh bẫy vòng lặp vô hạn khi đếm ngược bằng size_t
for là vòng lặp bạn sẽ dùng nhiều nhất. Nó không mạnh hơn while, nhưng nó gom cả ba thành phần vào một dòng nên gần như không thể quên phần cập nhật. Đổi lại, nó có một cạm bẫy chết người khi kết hợp với kiểu không dấu.
#Cú pháp và thứ tự chạy
for (khoi_tao; dieu_kien; cap_nhat) {
/* thân vòng lặp */
}Thứ tự chạy đầy đủ, viết ra cho rõ:
| Bước | Việc | Lặp lại? |
|---|---|---|
| 1 | Chạy phần khởi tạo | Không, chỉ một lần |
| 2 | Kiểm tra điều kiện | Có, trước mỗi lượt |
| 3 | Nếu sai thì thoát khỏi vòng lặp | Có |
| 4 | Nếu đúng thì chạy thân | Có |
| 5 | Chạy phần cập nhật | Có, sau mỗi lượt |
| 6 | Quay lại bước 2 | Có |
#Các biến thể
for (int i = 0; i < 10; ++i) { } /* đếm lên, dạng chuẩn */
for (int i = 10; i > 0; --i) { } /* đếm ngược */
for (int i = 0; i < 100; i += 5) { } /* bước nhảy 5 */
for (int i = 1; i < 1000; i *= 2) { } /* cấp số nhân, chạy 10 lượt */
for (int i = 0, j = 9; i < j; ++i, --j) { } /* hai biến chạy ngược nhau */
for (char *p = s; *p != '\0'; ++p) { } /* duyệt chuỗi bằng con trỏ */
for (;;) { } /* vô hạn, xem Bài 6.6 */Ba phần đều được phép bỏ trống
int i = 0;
for (; i < 10; ++i) { } /* bỏ khởi tạo, biến đã có sẵn */
for (int i = 0; ; ++i) { } /* bỏ điều kiện, mặc định là đúng, thành vô hạn */
for (int i = 0; i < 10; ) { i += buoc(); } /* cập nhật nằm trong thân */
for (;;) { } /* bỏ cả ba */#Khai báo biến trong for
Từ C99, bạn khai báo biến ngay trong phần khởi tạo được. Biến đó chỉ sống trong vòng lặp và biến mất khi vòng lặp kết thúc.
for (int i = 0; i < 5; ++i) {
printf("%d ", i);
}
/* printf("%d", i); */ /* LỖI: i đã hết phạm vi */Nếu bạn cần giá trị của biến chạy sau khi vòng lặp kết thúc, ví dụ để biết vòng lặp dừng ở đâu, thì phải khai báo bên ngoài.
size_t i;
for (i = 0; i < n; ++i) {
if (a[i] == muc_tieu) {
break;
}
}
if (i < n) {
printf("Tìm thấy tại %zu\n", i);
} else {
printf("Không tìm thấy\n");
}#Bẫy đếm ngược với size_t
Đây là cạm bẫy quan trọng nhất của bài, và nó xuất hiện rất nhiều trong mã C thật vì size_t là kiểu chuẩn cho chỉ số mảng.
Không nên
size_t n = 5;
for (size_t i = n - 1; i >= 0; --i) {
printf("%zu ", i);
}
/* VÒNG LẶP VÔ HẠN.
size_t là kiểu KHÔNG DẤU, nên i >= 0 luôn đúng.
Khi i bằng 0 rồi giảm tiếp, nó quấn về 18446744073709551615. */Nên
size_t n = 5;
for (size_t i = n; i-- > 0; ) {
printf("%zu ", i);
}
/* In ra: 4 3 2 1 0 rồi dừng đúng. */Vì sao thành ngữ i-- > 0 lại đúng
Điều kiện dùng dạng hậu tố nên nó so sánh giá trị cũ của
i với 0, rồi mới giảm. Ở lượt cuối, i đang bằng 1: phép so sánh 1 lớn hơn 0 cho kết quả đúng, i giảm về 0, thân chạy với giá trị 0. Lượt sau, i bằng 0: phép so sánh 0 lớn hơn 0 cho kết quả sai nên thoát, và việc i quấn vòng sau đó không còn ảnh hưởng gì.Ba cách viết đúng
/* Cách 1: thành ngữ hậu tố, gọn nhất, rất phổ biến trong mã thật */
for (size_t i = n; i-- > 0; ) {
printf("%zu ", i);
}
/* Cách 2: dùng kiểu có dấu, dễ đọc nhất cho người mới */
for (int i = (int)n - 1; i >= 0; --i) {
printf("%d ", i);
}
/* Cách 3: đổi biến, chạy xuôi rồi tính ngược */
for (size_t k = 0; k < n; ++k) {
size_t i = n - 1 - k;
printf("%zu ", i);
}#Chọn for hay while
| Tình huống | Chọn | Vì sao |
|---|---|---|
| Biết trước số lần lặp | for | Ba thành phần gom một chỗ, không quên được |
| Duyệt mảng theo chỉ số | for | Biến chạy chỉ sống trong vòng lặp |
| Đọc dữ liệu tới khi hết | while | Không có biến đếm, số lần phụ thuộc dữ liệu |
| Lặp tới khi đạt điều kiện toán học | while | Không đếm được trước |
| Phải chạy ít nhất một lần | do while | Điều kiện kiểm tra sau thân |
| Duyệt danh sách liên kết | for | Vẫn hợp lý: for (p = head; p; p = p->next) |
Tự làm thử
- In bảng cửu chương từ 2 tới 9, các cột căn thẳng hàng, mỗi ô rộng 4 cột.
- Tính giai thừa của n bằng
forvới kiểuunsigned long long. Tìm giá trị n lớn nhất mà kết quả chưa bị tràn. - Đảo ngược một mảng tại chỗ bằng vòng lặp hai biến chạy ngược nhau.
- Viết vòng lặp đếm ngược từ
n - 1về 0 với kiểusize_t, thử cả cách sai và cả ba cách đúng. - Biên dịch vòng lặp sai đó với
-Wextra, chép lại nguyên văn cảnh báo. - In dãy các lũy thừa của 2 nhỏ hơn một triệu, dùng phần cập nhật
i *= 2. Đếm xem vòng lặp chạy bao nhiêu lượt. - Viết lại một vòng lặp
whilebất kỳ từ Bài 6.1 thànhforvà ngược lại.
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
- Phần khởi tạo chạy một lần, phần điều kiện và phần cập nhật lặp lại mỗi lượt.
- Ba phần trong ngoặc đều được phép bỏ trống, bỏ cả ba thì thành vòng lặp vô hạn.
- Khai báo biến chạy ngay trong for để thu hẹp phạm vi và không quên khởi tạo.
- Đếm ngược với
size_tbằngi >= 0là vòng lặp vô hạn, phải dùngi-- > 0. - Cờ
-Wtype-limitstrong-Wextrabắt được phép so sánh luôn đúng này. - Dùng for khi biết trước số lần lặp, kể cả khi biến chạy là con trỏ.