Bỏ qua điều hướng, tới nội dung chính
Học C
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 */
}
Phần khởi tạo chạy đúng một lần. Hai phần còn lại lặp lại mỗi lượt.

Thứ tự chạy đầy đủ, viết ra cho rõ:

BướcViệcLặp lại?
1Chạy phần khởi tạoKhông, chỉ một lần
2Kiểm tra điều kiệnCó, trước mỗi lượt
3Nếu sai thì thoát khỏi vòng lặp
4Nếu đúng thì chạy thân
5Chạy phần cập nhậtCó, sau mỗi lượt
6Quay lại bước 2

#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ốngChọnVì sao
Biết trước số lần lặpforBa thành phần gom một chỗ, không quên được
Duyệt mảng theo chỉ sốforBiến chạy chỉ sống trong vòng lặp
Đọc dữ liệu tới khi hếtwhileKhô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ọcwhileKhông đếm được trước
Phải chạy ít nhất một lầndo whileĐiều kiện kiểm tra sau thân
Duyệt danh sách liên kếtforVẫn hợp lý: for (p = head; p; p = p->next)

Tự làm thử

  1. 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.
  2. Tính giai thừa của n bằng for với kiểu unsigned long long. Tìm giá trị n lớn nhất mà kết quả chưa bị tràn.
  3. Đảo ngược một mảng tại chỗ bằng vòng lặp hai biến chạy ngược nhau.
  4. Viết vòng lặp đếm ngược từ n - 1 về 0 với kiểu size_t, thử cả cách sai và cả ba cách đúng.
  5. Biên dịch vòng lặp sai đó với -Wextra, chép lại nguyên văn cảnh báo.
  6. 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.
  7. Viết lại một vòng lặp while bất kỳ từ Bài 6.1 thành for và 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_t bằng i >= 0 là vòng lặp vô hạn, phải dùng i-- > 0.
  • Cờ -Wtype-limits trong -Wextra bắ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ỏ.