Menu

Vòng lặp for trong C++: cú pháp, ví dụ và lỗi thường gặp

Cách lặp lại code bằng vòng lặp for trong C++: phần đầu ba thành phần, đếm lên và đếm xuống, duyệt mảng, lồng vòng lặp, break và continue, cùng lỗi lệch một và lỗi unsigned mà ai cũng gặp.

Trang này có trình soạn thảo chạy trực tiếp. Sửa, chạy và xem đầu ra ngay lập tức.

Vì sao cần vòng lặp for

switch chọn một nhánh để chạy một lần. Nhưng chương trình thật cần làm một việc lặp đi lặp lại: in mọi điểm số, cộng một danh sách số, vẽ 10 hàng của một lưới. Vòng lặp (loop) for là công cụ chủ lực của C++ để lặp lại code một số lần biết trước, với một biến đếm có sẵn do bạn điều khiển.

Mọi thứ vòng lặp for cần đều nằm trong một phần đầu gọn gàng, nên toàn bộ câu chuyện "lặp bao nhiêu lần và lặp thế nào" hiện ra chỉ trong một cái nhìn.

Phần đầu ba thành phần

Phần đầu của vòng lặp for có ba phần ngăn cách bằng dấu chấm phẩy: khởi tạo, điều kiện và cập nhật.

for (initializer; condition; update) {
    // body - runs while condition is true
}

Chúng chạy theo một thứ tự cụ thể: phần khởi tạo chạy một lần lúc bắt đầu; sau đó điều kiện được kiểm tra trước mỗi lần lặp; thân vòng lặp chỉ chạy nếu điều kiện là true; và phần cập nhật chạy ở cuối mỗi lần lặp, ngay trước khi điều kiện được kiểm tra lại.

Ở đây int i = 0 chạy một lần. Rồi i < 5 được kiểm tra: khi điều kiện còn đúng, thân vòng lặp in ra và i++ tăng biến đếm. Khi i đạt 5, điều kiện là false, nên vòng lặp thoát và done được in ra. Thân vòng lặp chạy đúng 5 lần, với i lần lượt nhận các giá trị từ 0 đến 4.

Khai báo biến đếm ngay trong phần đầu (int i = 0) giữ i trong phạm vi của vòng lặp: nó không tồn tại sau dấu ngoặc nhọn đóng, đúng như điều bạn muốn.

Đếm lên, đếm xuống và đếm theo bước

Phần cập nhật không chỉ giới hạn ở i++. Bạn có thể đếm xuống, bước một khoảng bất kỳ, hoặc lặp qua chỉ số của mảng.

Vòng lặp đầu chạy khi i > 0, mỗi lần giảm đi một, nên in ra 5 4 3 2 1. Vòng lặp thứ hai cộng 2 mỗi lượt và dùng <= 10 vì 10 là giá trị ta muốn tính vào. Hãy cho điều kiện khớp với phần cập nhật: đếm xuống đi với > hoặc >=, đếm lên đi với < hoặc <=.

Duyệt mảng

Cách dùng phổ biến nhất của vòng lặp đếm là duyệt mảng theo chỉ số. Biến đếm cũng chính là vị trí bạn đọc.

Để ý điều kiện là i < n, không phải i <= n. Một mảng 5 phần tử có chỉ số hợp lệ từ 0 đến 4; chỉ số 5 đã vượt quá cuối mảng. Đọc scores[5] là undefined behavior: nó có thể in ra giá trị rác, làm chương trình sập, hoặc trông như vẫn chạy nhưng âm thầm làm hỏng bộ nhớ. Mẫu i < n là lựa chọn an toàn mặc định cho mọi mảng đánh số từ không.

Nếu bạn chỉ cần giá trị mà không cần chỉ số, vòng lặp for theo phạm vi sẽ gọn hơn. Hãy dùng vòng lặp có chỉ số cổ điển khi bạn thật sự cần vị trí.

break và continue

Hai từ khóa cho phép bạn thay đổi luồng chạy giữa vòng lặp. break thoát khỏi vòng lặp ngay lập tức; continue bỏ qua phần còn lại của lần lặp hiện tại và nhảy tới phần cập nhật.

Vòng lặp đầu dừng ngay khi tìm thấy 7 và không kiểm tra phần còn lại. Vòng lặp thứ hai dùng continue để bỏ qua lệnh in trong thân mỗi khi i chẵn; phần cập nhật i++ vẫn chạy, nên vòng lặp tiếp tục tiến lên. Một điểm dễ nhầm tinh tế: continue nhảy tới phần cập nhật, nên nếu bạn dùng continue trong một vòng lặp mà biến đếm được cập nhật bên trong thân thay vì ở phần đầu, bạn có thể vô tình bỏ qua lần cập nhật đó và lặp mãi không dừng.

Vòng lặp lồng nhau

Đặt một vòng for vào trong vòng khác để làm việc với lưới, bảng hoặc các cặp giá trị. Vòng lặp bên trong chạy trọn vẹn cho mỗi bước của vòng lặp bên ngoài.

Đoạn này in ra bảng cửu chương 3x3. Vòng lặp ngoài cố định một row, vòng lặp trong quét qua mọi col của hàng đó, rồi một ký tự xuống dòng kết thúc hàng. Hãy đặt tên khác nhau cho các biến đếm (row/col, không phải i/i): dùng lại một tên sẽ che khuất biến bên ngoài và gây ra những bug khó hiểu. Cũng để ý chi phí: lồng một vòng n lần vào trong một vòng n lần sẽ chạy thân vòng lặp n * n lần, con số tăng rất nhanh.

Lỗi thường gặp

Vài cái bẫy chiếm phần lớn các bug vòng lặp for trong C++:

  • Lệch một (off-by-one): i <= n với kích thước đánh số từ không sẽ đọc vượt quá cuối mảng một phần tử. Dùng i < n.
  • Tràn dưới với unsigned: đếm xuống bằng kiểu unsigned không bao giờ âm được. for (size_t i = n - 1; i >= 0; i--) lặp mãi mãi, vì i >= 0 luôn đúng với giá trị unsigned: khi i là 0, i-- quay vòng thành một số dương khổng lồ. Hãy dùng int có dấu để đếm xuống, hoặc viết lại điều kiện.
  • Sửa biến đếm bên trong thân: thay đổi i cả trong thân lẫn ở phần đầu khiến số lần lặp không đoán trước được. Hãy chọn một chỗ duy nhất.
// BUG: infinite loop - unsigned i is never < 0
for (size_t i = n - 1; i >= 0; i--) {
    process(arr[i]);
}

Biến đếm kiểu số thực là một mối nguy âm thầm khác: for (double x = 0.0; x != 1.0; x += 0.1) có thể không bao giờ chạm đúng 1.0 vì 0.1 không được lưu chính xác. Hãy lặp với một biến đếm số nguyên và tính giá trị bên trong, hoặc dùng < thay cho !=.

Tiếp theo: vòng lặp while

Vòng lặp for tỏa sáng khi bạn biết trước số lần lặp. Nhưng đôi khi bạn cần lặp cho tới khi một điều kiện thay đổi, như đọc đầu vào cho tới cuối tệp hay thử lại cho tới khi thành công, mà không có số bước cố định. Đó là việc của vòng lặp while, vốn rút gọn phần đầu chỉ còn mỗi điều kiện. Đó là nội dung trang tiếp theo.

Câu hỏi thường gặp

Viết vòng lặp for trong C++ như thế nào?

Đặt ba phần trong phần đầu vòng lặp, ngăn cách bằng dấu chấm phẩy: khởi tạo, điều kiện và cập nhật. for (int i = 0; i < 5; i++) { cout << i; } chạy thân vòng lặp với i lần lượt là 0, 1, 2, 3, 4. Vòng lặp dừng ngay khi điều kiện trở thành false.

Vòng lặp for và vòng lặp for theo phạm vi trong C++ khác nhau thế nào?

for cổ điển cho bạn một biến đếm chỉ số do bạn điều khiển (for (int i = 0; i < n; i++)), cần thiết khi bạn muốn biết vị trí hoặc muốn bước theo cách riêng. Vòng lặp for theo phạm vi (for (int x : v)) giấu chỉ số đi và chỉ đưa cho bạn từng phần tử, gọn hơn khi bạn chỉ cần giá trị.

Vì sao vòng lặp for trong C++ của tôi chạy thừa hoặc thiếu một lần?

Đó là bug lệch một (off-by-one) kinh điển. Dùng <= thay cho < với kích thước đánh số từ không sẽ chạy thêm một lần lặp và đọc vượt quá cuối mảng; dùng < khi bạn muốn tính cả giá trị cuối thì lại chạy thiếu một lần. Với mảng kích thước n, mẫu an toàn là for (int i = 0; i < n; i++).

Hình minh họa các ngôn ngữ lập trình của Coddy

Học lập trình cùng Coddy

Bắt đầu