Điều khiển vòng lặp
Sau bài này bạn sẽ làm được
- Dùng break và continue đúng ngữ cảnh vòng lặp
- Thoát nhiều tầng vòng lặp bằng ba cách khác nhau
- Giải thích vì sao continue trong while dễ gây vòng lặp vô hạn
- Biết khi nào nên đưa điều kiện dừng vào phần điều kiện thay vì dùng break
Bài 5.6 và 5.7 đã giới thiệu break và continue khi bạn chưa học vòng lặp. Giờ đã có đủ nền, bài này gom lại đầy đủ bốn công cụ điều khiển vòng lặp, kèm ba cách thoát nhiều tầng và một lời cảnh báo về việc lạm dụng.
#Bốn công cụ
| Lệnh | Tác dụng | Phạm vi ảnh hưởng |
|---|---|---|
| break | Thoát hẳn khỏi vòng lặp | Một tầng gần nhất |
| continue | Bỏ qua phần còn lại, sang lượt tiếp theo | Một tầng gần nhất |
| goto nhan | Nhảy tới nhãn bất kỳ trong cùng hàm | Bao nhiêu tầng cũng được |
| return | Thoát khỏi cả hàm | Toàn bộ, kể cả mọi tầng vòng lặp |
#break trong vòng lặp
/* Tìm phần tử, dừng ngay khi thấy */
long vi_tri = -1;
for (size_t i = 0; i < n; ++i) {
if (a[i] == muc_tieu) {
vi_tri = (long)i;
break;
}
}Lợi ích không chỉ là gọn. Với mảng một triệu phần tử mà phần tử cần tìm nằm ở vị trí thứ mười, có break thì dừng sau 10 lượt, không có thì chạy đủ một triệu lượt.
#continue và bẫy của while
continue nhảy tới cuối thân vòng lặp, bỏ qua mọi câu lệnh còn lại. Nhưng chuyện gì xảy ra tiếp theo thì khác nhau giữa for và while, và đó chính là cạm bẫy.
| Vòng lặp | Sau continue thì làm gì | Rủi ro |
|---|---|---|
| for | Chạy phần cập nhật rồi kiểm tra điều kiện | Không có, biến chạy vẫn tiến |
| while | Kiểm tra điều kiện ngay | Cao, nếu phần tăng biến nằm sau continue |
| do while | Kiểm tra điều kiện ở cuối ngay | Cao, tương tự while |
int i = 0;
while (i < 10) {
if (i % 2 == 0) {
continue; /* nhảy thẳng về điều kiện, i KHÔNG tăng */
}
printf("%d ", i);
i++;
}
/* Vòng lặp vô hạn ngay từ i bằng 0. */for (int i = 0; i < 10; ++i) {
if (i % 2 == 0) {
continue; /* an toàn, phần ++i vẫn chạy */
}
printf("%d ", i);
}Ứng dụng chính là lọc dữ liệu
char dong[256];
while (fgets(dong, sizeof dong, f) != NULL) {
dong[strcspn(dong, "\n")] = '\0';
if (dong[0] == '\0') {
continue; /* bỏ qua dòng trống */
}
if (dong[0] == '#') {
continue; /* bỏ qua dòng chú thích */
}
/* Phần xử lý chính không bị thụt lề thêm tầng nào. */
phan_tich(dong);
}Ở đây while an toàn vì không có biến đếm nào cần tăng. Việc tiến tới dòng tiếp theo do fgets ở phần điều kiện lo, mà phần điều kiện thì luôn được chạy.
#Thoát nhiều tầng
C không có break kèm nhãn như Java hay Go. Có ba cách thay thế, và cách thứ ba thường tốt nhất.
Cách 1: dùng cờ
int xong = 0;
for (int i = 0; i < n && !xong; ++i) {
for (int j = 0; j < m; ++j) {
if (a[i][j] == muc_tieu) {
xong = 1;
break;
}
}
}Dễ hiểu, nhưng phải kiểm tra cờ ở điều kiện của mọi tầng bên ngoài. Với ba tầng trở lên thì khá rườm rà.
Cách 2: dùng goto
for (int i = 0; i < n; ++i) {
for (int j = 0; j < m; ++j) {
if (a[i][j] == muc_tieu) {
goto tim_thay;
}
}
}
printf("Không tìm thấy\n");
goto ket_thuc;
tim_thay:
printf("Tìm thấy\n");
ket_thuc: ;Cách 3: tách thành hàm riêng
int tim_trong_ma_tran(int a[][M], int n, int muc_tieu,
int *hang, int *cot)
{
for (int i = 0; i < n; ++i) {
for (int j = 0; j < M; ++j) {
if (a[i][j] == muc_tieu) {
*hang = i;
*cot = j;
return 1; /* return thoát khỏi MỌI tầng cùng lúc */
}
}
}
return 0;
}#Khi nào không nên dùng
break và continue rất tiện, nhưng dùng nhiều làm vòng lặp khó theo dõi, vì người đọc phải tìm hết mọi lối thoát mới hiểu được vòng lặp kết thúc khi nào.
size_t i = 0;
while (1) {
if (i >= n) {
break;
}
if (a[i] == x) {
break;
}
++i;
}size_t i = 0;
while (i < n && a[i] != x) {
++i;
}
if (i < n) {
printf("Tìm thấy tại %zu\n", i);
}Trường hợp break là lựa chọn đúng
while (1) {
char dong[256];
if (fgets(dong, sizeof dong, stdin) == NULL) {
break; /* hết dữ liệu, chỉ biết sau khi đọc */
}
dong[strcspn(dong, "\n")] = '\0';
if (strcmp(dong, "thoat") == 0) {
break; /* người dùng yêu cầu dừng */
}
xu_ly(dong);
}Ở đây hai điều kiện dừng đều chỉ xác định được sau khi đã đọc xong một dòng, nên không thể đưa lên phần điều kiện. Dùng break là hợp lý và rõ ràng.
Tự làm thử
- Tìm số nguyên tố đầu tiên lớn hơn một triệu, dùng
breakđể dừng ngay khi thấy. Đếm số lượt lặp thực tế. - Tái hiện vòng lặp vô hạn bằng
continuetrongwhile, rồi sửa bằng hai cách khác nhau. - Tìm một giá trị trong ma trận hai chiều bằng cả ba cách thoát nhiều tầng, so sánh độ dài và độ dễ đọc của ba bản.
- Đọc file, đếm số dòng có nội dung, bỏ qua dòng trống và dòng bắt đầu bằng dấu thăng.
- Viết lại một vòng lặp có
breakthành vòng lặp có điều kiện dừng nằm trong phần điều kiện. Chỉ ra trường hợp nào không viết lại được. - Đặt
breaktrongswitchnằm trongwhile, quan sát vòng lặp vô hạn, rồi sửa bằng cờ.
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
- Bốn công cụ điều khiển vòng lặp là break, continue, goto và return.
breakvàcontinuechỉ tác động một tầng gần nhất.- Trong
for,continuevẫn chạy phần cập nhật. Trongwhilethì không, nên dễ gây vòng lặp vô hạn. breaktrongswitchnằm trong vòng lặp chỉ thoátswitch.- Thoát nhiều tầng bằng cờ, bằng goto tiến xuống, hoặc tách thành hàm rồi return.
- Ưu tiên đưa điều kiện dừng vào phần điều kiện của vòng lặp thay vì rải break trong thân.