Đề cho số nguyên và bảo in các số từ 1 đến . Viết 1000 lệnh cout thì vừa dài vừa vô ích, vì chỉ được biết khi chương trình chạy. Vòng lặp (loop) giải quyết việc này: ta viết một đoạn lệnh một lần rồi bảo máy chạy nó nhiều lần, số lần tùy theo dữ liệu. Đọc số, thử từng ứng viên, lặp cho tới khi một điều kiện xảy ra: hầu như chương trình thi nào cũng có vòng lặp. Bài này dạy for và while, cách chạy tay chúng, và những chỗ người mới hay vấp.
Vòng for: lặp một số lần biết trước
Chương trình dưới đọc rồi in các số từ 1 đến .
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
cout << i << ' ';
}
cout << '\n';
}
5
1 2 3 4 5
Điều cần học nằm ở dòng for (int i = 1; i <= n; i++). Trong ngoặc có ba phần, ngăn nhau bởi hai dấu ;:
int i = 1là phần khởi tạo: tạo biến đếmi(loop variable) và gán cho nó giá trị 1. Phần này chạy đúng một lần, trước lượt đầu tiên.i <= nlà điều kiện, được kiểm tra trước mỗi lượt. Đúng thì chạy thân vòng, sai thì thoát khỏi vòng lặp.i++là phần cập nhật, chạy sau mỗi lượt. Lệnhi++viết gọn choi = i + 1.
Phần nằm trong cặp ngoặc nhọn { } là thân vòng. Mỗi lần thân chạy xong gọi là một lượt lặp (iteration).
Ta chạy tay với . Cột i ghi giá trị của i ngay lúc kiểm tra điều kiện.
| Lần kiểm tra | i |
i <= 3? |
Việc xảy ra |
|---|---|---|---|
| 1 | 1 | đúng | in 1, rồi i++ đưa i lên 2 |
| 2 | 2 | đúng | in 2, rồi i++ đưa i lên 3 |
| 3 | 3 | đúng | in 3, rồi i++ đưa i lên 4 |
| 4 | 4 | sai | thoát khỏi vòng |
Khi vòng dừng, i bằng 4 chứ không phải 3: vòng chỉ dừng sau khi i đã vượt qua n. Biến i khai báo trong ngoặc của for chỉ tồn tại bên trong vòng; dùng nó sau dấu } đóng vòng sẽ bị báo lỗi biên dịch.
Dòng for có đúng hai dấu ; trong ngoặc và không có dấu ; nào sau dấu ). Thiếu một dấu trong ngoặc thì trình biên dịch báo lỗi ngay; thừa một dấu sau ) lại biên dịch được nhưng chạy sai, ta sẽ thấy ở mục sau.
Hỏi nhanh
Vòng for (int i = 3; i <= 10; i++) chạy thân vòng bao nhiêu lượt?
Cả hai đầu mút 3 và 10 đều chạy, nên số lượt là . Hiệu chỉ đếm số bước nhảy giữa các giá trị chứ không đếm số giá trị; nhầm hai thứ này là lỗi lệch một kinh điển. Lần kiểm tra với có xảy ra nhưng cho kết quả sai, nên không có lượt thứ chín. Con số 10 chỉ là giá trị cuối của i.
Ví dụBảng nhân
Đề. Cho số nguyên (). In mười tích , mỗi tích trên một dòng.
Lời giải. Số lượt biết trước là 10 và không phụ thuộc , nên biến đếm i chạy từ 1 tới 10, mỗi lượt in n * i rồi xuống dòng. chỉ được đọc một lần, trước vòng. Tích lớn nhất là , vừa int.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
for (int i = 1; i <= 10; i++) {
cout << n * i << '\n';
}
}
3
3
6
9
12
15
18
21
24
27
30
Đếm xuống, nhảy bước và lỗi lệch một
Cả ba phần trong ngoặc for đều đổi được. Chương trình dưới in các số từ xuống 1, rồi in các số 1, 4, 7, ... không vượt quá .
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
for (int i = n; i >= 1; i--) {
cout << i << ' ';
}
cout << '\n';
for (int i = 1; i <= n; i += 3) {
cout << i << ' ';
}
cout << '\n';
}
10
10 9 8 7 6 5 4 3 2 1
1 4 7 10
Lệnh i-- là i = i - 1, còn i += 3 là i = i + 3. Đếm xuống thì điều kiện cũng phải đổi chiều thành i >= 1; giữ nguyên i <= n thì i cứ giảm mà điều kiện vẫn đúng, vòng không dừng ở 1. Lệnh cout << i << ' ' để lại một dấu cách thừa cuối dòng; trình chấm của các bài ở đây bỏ qua nó.
Lỗi người mới gặp nhiều nhất với vòng lặp là lệch một (off-by-one): vòng chạy nhiều hơn hoặc ít hơn đúng một lượt. Có hai cách viết đáng thuộc lòng:
- Duyệt các số từ đến , gồm cả hai đầu:
for (int i = a; i <= b; i++). Vòng chạy lượt. - Làm đúng lần, đếm từ 0:
for (int i = 0; i < n; i++). Vòng chạy lượt, với từ 0 tới .
Trộn hai cách vào nhau là gây lỗi: đếm từ 1 mà viết i < n thì mất giá trị , với chỉ in 1 2 3 4. Cách kiểm nhanh là thử và hỏi vòng có chạy đúng một lượt không.
Lỗi thứ hai là dấu ; thừa ngay sau dấu ):
for (int i = 1; i <= 3; i++);
cout << "x";
Dấu ; đó là một lệnh rỗng, nên thân vòng là “không làm gì” và được lặp 3 lần. Lệnh cout nằm ngoài vòng, chạy đúng một lần, nên chỉ in ra một chữ x. Trình biên dịch không đọc thụt lề: chỉ cặp { } và dấu ; quyết định lệnh nào thuộc vòng.
Ví dụIn các số chẵn
Đề. Cho số nguyên (). In các số chẵn từ 0 đến theo thứ tự tăng dần trên một dòng.
Lời giải. Các số chẵn cách nhau 2, nên bắt đầu từ 0 và nhảy bước i += 2 thay vì duyệt mọi số rồi hỏi i % 2 == 0; vòng chạy nửa số lượt. Điều kiện là i <= n để chẵn cũng được in. Với vòng in 0, 2, 4, 6 rồi i lên 8, vượt 7 nên dừng. Với vòng vẫn chạy một lượt và in số 0, đúng như đề.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
for (int i = 0; i <= n; i += 2) {
cout << i << ' ';
}
cout << '\n';
}
7
0 2 4 6
Đọc dữ liệu và cộng dồn trong vòng lặp
Phần lớn vòng lặp trong đề thi làm một việc: đọc số, mỗi lượt xử lý một số rồi gom kết quả vào vài biến. Khuôn mẫu gồm ba bước: gán giá trị đầu cho các biến trước vòng, cập nhật chúng trong vòng, in kết quả sau vòng. Chương trình dưới tính tổng, số phần tử dương và phần tử lớn nhất của số.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
long long sum = 0;
int pos = 0, mx = INT_MIN;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
sum += x;
if (x > 0) {
pos++;
}
if (x > mx) {
mx = x;
}
}
cout << sum << ' ' << pos << ' ' << mx << '\n';
}
5
3 -4 8 0 -1
6 2 8
Lệnh cin >> x nằm trong thân vòng nên mỗi lượt đọc một số, và lượt thì đọc đủ số. Biến x khai báo trong thân, mỗi lượt là một biến mới. Ba biến sum (tổng), pos (số phần tử dương), mx (lớn nhất) khai báo ngoài vòng để giữ giá trị từ lượt này sang lượt sau; đặt long long sum = 0; vào trong thân thì mỗi lượt tổng lại về 0.
Bảng dưới cho giá trị các biến sau mỗi lượt của ví dụ. INT_MIN là số int nhỏ nhất, bằng .
| Sau lượt | x |
sum |
pos |
mx |
|---|---|---|---|---|
| (trước vòng) | 0 | 0 | −2147483648 | |
| 1 | 3 | 3 | 1 | 3 |
| 2 | −4 | −1 | 1 | 3 |
| 3 | 8 | 7 | 2 | 8 |
| 4 | 0 | 7 | 2 | 8 |
| 5 | −1 | 6 | 2 | 8 |
Giá trị đầu quyết định chương trình đúng hay sai. Tổng và biến đếm bắt đầu từ 0. Tích bắt đầu từ 1, vì bắt đầu từ 0 thì tích mãi bằng 0. Số lớn nhất phải bắt đầu từ một giá trị nhỏ hơn mọi dữ liệu: đặt mx = 0 thì với dãy chương trình in 0, một số không có trong dãy. Còn khai báo mà không gán (long long sum;) thì biến chứa giá trị rác, mỗi lần chạy có thể ra một kết quả khác.
Tổng của nhiều số nhỏ rất dễ vượt int: , trong khi int chỉ tới . Giai thừa lớn còn nhanh hơn: còn vừa int, thì không, và long long đủ cho tới .
Hỏi nhanh
Ta cộng các số với . Mỗi số hạng đều rất nhỏ so với giới hạn của int. Biến chứa tổng nên khai báo kiểu gì?
Tổng bằng , vượt . Số hạng nhỏ không bảo đảm tổng nhỏ: tràn xảy ra ngay ở phép cộng. C++ không tự đổi kiểu của biến đã khai báo, nên phép cộng vượt phạm vi int cho ra một số sai mà không báo lỗi. Gán 0 từ đầu là cần, nhưng nó chỉ chống giá trị rác, không chống tràn.
Ví dụĐợt mưa liên tiếp
Đề. Ghi chép ngày (), mỗi ngày là 1 (có mưa) hoặc 0 (không mưa), ngày đánh số từ 1. In độ dài đợt mưa liên tiếp dài nhất và ngày bắt đầu của nó; nhiều đợt dài bằng nhau thì chọn đợt sớm nhất; không có ngày mưa thì in 0 0.
Lời giải. Không cần lưu cả ngày. Gọi cur là số ngày mưa liên tiếp kết thúc đúng tại ngày đang đọc: ngày mưa thì cur tăng 1, ngày khô thì cur về 0. Tại ngày cuối của mỗi đợt, cur bằng đúng độ dài cả đợt, nên đợt dài nhất là giá trị lớn nhất cur từng đạt. Mỗi khi cur > best, ghi lại best = cur và ngày bắt đầu st = i - cur + 1, vì đợt gồm cur ngày kết thúc ở ngày i.
| Ngày | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 |
|---|---|---|---|---|---|---|---|---|
| dữ liệu | 1 | 1 | 0 | 1 | 1 | 1 | 0 | 1 |
cur |
1 | 2 | 0 | 1 | 2 | 3 | 0 | 1 |
best |
1 | 2 | 2 | 2 | 2 | 3 | 3 | 3 |
st |
1 | 1 | 1 | 1 | 1 | 4 | 4 | 4 |
So sánh chặt > giữ lại đợt sớm hơn khi hai đợt dài bằng nhau: đợt sau chỉ bằng chứ không lớn hơn. Viết >= sẽ chọn nhầm đợt muộn. Không có ngày mưa thì cur luôn bằng 0, best và st giữ giá trị đầu 0 0, đúng quy ước của đề.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int cur = 0, best = 0, st = 0;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
if (x == 1) {
cur++;
} else {
cur = 0;
}
if (cur > best) {
best = cur;
st = i - cur + 1;
}
}
cout << best << ' ' << st << '\n';
}
8
1 1 0 1 1 1 0 1
3 4
Vòng while: lặp khi điều kiện còn đúng
Vòng while có dạng while (điều kiện) { thân vòng }. Máy kiểm tra điều kiện trước; đúng thì chạy thân rồi quay lại kiểm tra, sai thì dừng. Nếu điều kiện sai ngay từ đầu, thân vòng chạy 0 lần. while không có chỗ riêng cho khởi tạo và cập nhật: ta tự viết khởi tạo trước vòng và cập nhật trong thân. Vậy for chỉ là cách gom ba phần ấy vào một dòng; hai đoạn dưới làm cùng một việc:
for (int i = 1; i <= n; i++) {
cout << i << ' ';
}
int i = 1;
while (i <= n) {
cout << i << ' ';
i++;
}
Cách chọn: biết trước biến đếm chạy từ đâu tới đâu thì dùng for; chỉ biết điều kiện để dừng thì dùng while. Ví dụ: chia đôi số liên tục, bỏ phần dư, cho tới khi về 0; cần mấy lần chia? Số lần không đoán ngay được từ , nhưng điều kiện dừng thì rõ: .
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int cnt = 0;
while (n > 0) {
n /= 2;
cnt++;
}
cout << cnt << '\n';
}
100
7
n /= 2 là n = n / 2, chia nguyên nên phần dư bị bỏ: 25 / 2 là 12. Với , giá trị của qua các lượt là : bảy phép chia, nên in 7. Ý “chia đôi liên tục” sẽ quay lại ở chặt nhị phân (bài 05).
Mỗi lượt của while phải đưa điều kiện tiến gần tới chỗ sai. Quên dòng n /= 2 thì n không bao giờ đổi, điều kiện đúng mãi và chương trình không dừng: đó là vòng lặp vô hạn (infinite loop). Trên máy mình, bấm Ctrl+C để dừng; trên máy chấm, kết quả là “quá thời gian”. Trước khi viết while, hãy hỏi: biến nào đổi trong thân, và nó đổi theo hướng làm điều kiện sai? Dấu ; thừa sau while (n > 0) cũng gây vô hạn, vì thân vòng thành lệnh rỗng và n không đổi.
Khi đề không cho trước số lượng số, ta đọc tới hết dữ liệu. Biểu thức cin >> x vừa đọc một số vào x, vừa có giá trị đúng nếu đọc được và sai nếu hết dữ liệu, nên while (cin >> x) lặp tới khi đọc hết:
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
long long x, sum = 0;
int cnt = 0;
while (cin >> x) {
sum += x;
cnt++;
}
cout << cnt << ' ' << sum << '\n';
}
4 -2 9
5 1
5 17
Hỏi nhanh
Bạn viết đoạn dưới và thụt lề như thể cả hai lệnh đều thuộc vòng lặp:
int i = 1;
while (i <= 3)
cout << i << ' ';
i++;
Chạy chương trình thì chuyện gì xảy ra?
Khi không có { }, thân vòng chỉ gồm lệnh ngay sau while, tức là cout. Lệnh i++ nằm ngoài vòng và chỉ chạy sau khi vòng kết thúc, nhưng vòng không bao giờ kết thúc vì i mãi bằng 1. Đoạn code đúng cú pháp nên vẫn biên dịch được; thụt lề không có tác dụng gì với trình biên dịch. Vì vậy ta luôn viết { } cho thân vòng có nhiều hơn một lệnh.
Ví dụGấp đôi đến đích
Đề. Bắt đầu với giá trị 1, mỗi lần nhân đôi giá trị hiện tại. Cho (), in số lần nhân đôi ít nhất để giá trị đạt hoặc vượt .
Lời giải. Không biết trước số lần, chỉ biết điều kiện dừng: giá trị . Vậy while (x < n) nhân đôi x và đếm. Với : đi , bốn lần nhân. Với điều kiện sai ngay từ đầu, vòng chạy 0 lượt và in 0, đúng. Lượt cuối có thể đưa lên gần , sát giới hạn int, nên khai báo x là long long.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
long long n, x = 1;
cin >> n;
int cnt = 0;
while (x < n) {
x *= 2;
cnt++;
}
cout << cnt << '\n';
}
9
4
break và continue
Đôi khi ta muốn dừng vòng lặp sớm hơn điều kiện của nó. Lệnh break thoát ngay khỏi vòng lặp chứa nó. Lệnh continue bỏ qua phần còn lại của lượt hiện tại và sang lượt kế tiếp.
Ví dụ kinh điển cho break là kiểm tra số nguyên tố: số là số nguyên tố nếu nó không có ước nào trong khoảng từ 2 đến . Ta thử lần lượt từng và dừng ngay khi gặp ước đầu tiên.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
bool ok = n >= 2;
for (int d = 2; d < n; d++) {
if (n % d == 0) {
ok = false;
break;
}
}
if (ok) {
cout << "YES\n";
} else {
cout << "NO\n";
}
}
91
NO
Kiểu bool chỉ có hai giá trị true và false; biến ok ghi nhớ câu “ vẫn có thể là số nguyên tố”, và nó bắt đầu là false với hay . Với , các đều không chia hết, tới thì 91 % 7 == 0 và break chạy: vòng dừng sau 6 lượt thay vì 89. Với số nguyên tố như 97 thì không có ước nào, vòng chạy đủ và ok giữ nguyên true. Thật ra chỉ cần thử tới , ta sẽ làm vậy ở bài 08.
Ví dụ cho continue: cộng các số từ 1 tới , trừ các bội của 3.
for (int i = 1; i <= n; i++) {
if (i % 3 == 0) {
continue;
}
sum += i;
}
Trong for, continue nhảy tới phần cập nhật i++ rồi mới kiểm tra điều kiện, nên vòng vẫn tiến. Trong while, continue nhảy thẳng về phép kiểm tra: nếu lệnh cập nhật nằm sau continue thì nó bị bỏ qua và vòng có thể không bao giờ dừng.
Ví dụMốc đóng góp
Đề. Có lượt đóng góp (), lượt thứ góp ; cần ít nhất . In lượt đầu tiên mà tổng đạt hoặc vượt và tổng ngay sau lượt đó; không bao giờ đạt thì in -1 0.
Lời giải. Giữ tổng chạy sum. Đọc từng lượt, cộng vào, và ngay khi sum >= t thì ghi lại lượt i, tổng sum, rồi break: các lượt sau không đổi được đáp án. Gán sẵn đáp án -1 0 trước vòng để trường hợp không đạt tự đúng. Với ví dụ, tổng đi và dừng ở lượt 4.
break khi còn dữ liệu chưa đọc chỉ an toàn vì sau vòng chương trình không đọc gì nữa. Nếu phía sau còn phải đọc tiếp (chẳng hạn một dòng dữ liệu khác), các số chưa đọc của dãy này sẽ bị đọc nhầm vào đó; khi ấy thay break bằng một biến cờ và đọc hết dãy.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n, t;
cin >> n >> t;
int sum = 0, pos = -1, at = 0;
for (int i = 1; i <= n; i++) {
int x;
cin >> x;
sum += x;
if (sum >= t) {
pos = i;
at = sum;
break;
}
}
cout << pos << ' ' << at << '\n';
}
5 10
2 0 5 4 1
4 11
Tách chữ số bằng % và /
Một số nguyên không có sẵn “chữ số thứ ”, nhưng hai phép toán cho ta tách được từng chữ số. n % 10 là phần dư khi chia cho 10, chính là chữ số hàng đơn vị: 5093 % 10 bằng 3. n / 10 là thương nguyên, tức là bỏ chữ số cuối: 5093 / 10 bằng 509. Lặp hai bước này cho tới khi , ta lấy ra mọi chữ số từ phải sang trái.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
while (n > 0) {
cout << n % 10 << ' ';
n /= 10;
}
cout << '\n';
}
5093
3 9 0 5
Số lượt bằng số chữ số, nên với vòng chạy tối đa 10 lượt; chữ số 0 ở giữa vẫn tính một lượt. Vòng lặp làm thay đổi , nên muốn giữ giá trị gốc thì sao chép trước, ví dụ int m = n;. Với số âm, % cho kết quả âm (-7 % 10 bằng -7), vì thế ở đây ta chỉ làm việc với .
Có một chỗ hở: khi , điều kiện n > 0 sai ngay từ đầu, vòng không chạy và chương trình không in chữ số nào, trong khi số 0 có một chữ số. Cách sửa gọn là vòng do ... while: nó chạy thân trước rồi mới kiểm tra điều kiện, nên thân luôn chạy ít nhất một lượt. Dấu ; sau while (n > 0) ở cuối là bắt buộc.
do {
cout << n % 10 << ' ';
n /= 10;
} while (n > 0);
Hỏi nhanh
Bạn đếm số chữ số của bằng đoạn cnt = 0; while (n > 0) { cnt++; n /= 10; }. Với đầu vào nào thì kết quả sai?
Với , điều kiện n > 0 sai ngay nên cnt giữ nguyên 0, nhưng số 0 có đúng một chữ số. Các đầu vào còn lại đều đúng: 7 cho 1 lượt, 10 cho 2 lượt (), 999999999 cho 9 lượt và vừa trong int. Số 0 là trường hợp biên, luôn nên thử riêng; ở đây có thể dùng do ... while hoặc xét if (n == 0) trước.
Ví dụTổng chữ số
Đề. Cho số nguyên (). In tổng các chữ số của .
Lời giải. Chữ số cuối của là n % 10, và các chữ số còn lại chính là các chữ số của n / 10. Vậy mỗi lượt cộng n % 10 vào sum rồi thay bằng n / 10, tới khi . Mỗi lượt chuyển đúng một chữ số từ sang sum, nên khi về 0 thì sum là tổng mọi chữ số. Với 4027 các chữ số lấy ra là 7, 2, 0, 4, tổng 13. Khác với bài đếm chữ số, ở đây không cần xét riêng: vòng không chạy và chương trình in 0, đúng với đề.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
int sum = 0;
while (n > 0) {
sum += n % 10;
n /= 10;
}
cout << sum << '\n';
}
4027
13
Vòng lặp lồng nhau
Thân của một vòng lặp có thể chứa một vòng lặp khác. Với mỗi giá trị của vòng ngoài, vòng trong chạy trọn từ đầu tới cuối, rồi vòng ngoài mới sang giá trị kế. Chương trình dưới in tam giác dòng, dòng thứ có dấu *.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int n;
cin >> n;
for (int i = 1; i <= n; i++) {
for (int j = 1; j <= i; j++) {
cout << '*';
}
cout << '\n';
}
}
4
*
**
***
****
Vòng ngoài chọn dòng i, vòng trong in i dấu *, hết một dòng thì xuống dòng. Hai biến đếm nên khác tên (i và j); đặt trùng thì vòng trong che mất biến của vòng ngoài. Vị trí của lệnh xuống dòng cũng quan trọng: đặt cout << '\n' trong vòng trong thì mỗi dấu * chiếm một dòng, đặt sau cả hai vòng thì mọi dấu * dính thành một hàng.
Để đếm số lượt của vòng trong, ta cộng qua các giá trị của vòng ngoài: , bằng 15 khi . Nếu vòng trong luôn chạy lượt thì tổng là . Con số này cũng là thước đo thời gian: mốc tới thao tác đơn giản mỗi giây chỉ là ước lượng thô. Hai vòng đầy đủ với chạy lượt, rất thoải mái. Với chúng chạy lượt, quá xa giới hạn; khi đó cần một ý tưởng khác chứ viết nhanh hơn cũng không cứu được.
break chỉ thoát vòng trong cùng chứa nó. Muốn thoát cả hai vòng thì cần thêm một biến cờ bool, hoặc đưa hai vòng vào một hàm rồi dùng return (bài B4).
Hỏi nhanh
Hai vòng lồng nhau for (int i = 1; i <= n; i++) và for (int j = 1; j <= n; j++) với , thân vòng trong chỉ làm một phép cộng. Chương trình có chạy kịp trong một giây không?
Với vòng lồng nhau, số lượt nhân lên chứ không cộng lại. Nếu giả sử xử lý được lượt mỗi giây thì lượt đã cần khoảng 100 giây; đó chỉ là phép ước lượng, không phải thời gian đo. Đếm số lần thân vòng trong cùng chạy là cách ước lượng thời gian đáng tin nhất.
Ví dụKhung bảng
Đề. In một hình chữ nhật dòng, cột (): ô thuộc biên in *, ô bên trong in ., không chèn dấu cách. Chỉ có một dòng hoặc một cột thì mọi ô đều thuộc biên.
Lời giải. Vòng ngoài duyệt dòng i từ 1 tới , vòng trong duyệt cột j từ 1 tới , và mỗi ô tự hỏi mình có nằm ở biên không: dòng đầu, dòng cuối, cột đầu hoặc cột cuối, tức i == 1 || i == h || j == 1 || j == w. Trường hợp một dòng hay một cột không cần xét riêng: khi thì mọi ô đều có i == 1. Hết một dòng thì xuống dòng, đặt sau vòng trong.
#include <bits/stdc++.h>
using namespace std;
int main() {
ios::sync_with_stdio(false);
cin.tie(0);
int h, w;
cin >> h >> w;
for (int i = 1; i <= h; i++) {
for (int j = 1; j <= w; j++) {
if (i == 1 || i == h || j == 1 || j == w) {
cout << '*';
} else {
cout << '.';
}
}
cout << '\n';
}
}
3 5
*****
*...*
*****
Khi đi thi
- Trước khi gõ một vòng lặp, ghi ra ba điều: biến đếm chạy từ đâu tới đâu (đóng hay mở), mỗi lượt làm gì, và cái gì khiến vòng dừng. Rồi chạy tay với , và một giá trị nhỏ.
- Đếm số lần thân vòng trong cùng chạy để ước lượng thời gian, với mốc thô tới thao tác đơn giản mỗi giây. Tổng hoặc tích có thể vượt thì khai báo
long long. - Mọi biến cộng dồn phải có giá trị đầu đúng trước vòng: 0 cho tổng và biến đếm, 1 cho tích, một số rất nhỏ cho số lớn nhất. Khi kết quả sai mà không rõ vì sao, in các biến ra sau mỗi lượt và so với bảng chạy tay.
Tóm tắt
for (khởi tạo; điều kiện; cập nhật): khởi tạo một lần, rồi mỗi lượt kiểm tra điều kiện, chạy thân, cập nhật. Đoạn gồm hai đầu thì viếti = a; i <= b; làm lần đếm từ 0 thì viếti = 0; i < n.while (điều kiện)kiểm tra trước nên có thể chạy 0 lần. Thân vòng phải làm điều kiện tiến tới sai, nếu không là vòng lặp vô hạn. Biết trước phạm vi thì dùngfor, chỉ biết điều kiện dừng thì dùngwhile.- Khuôn cộng dồn: gán giá trị đầu trước vòng, cập nhật trong vòng, in sau vòng; tổng và tích lớn thì dùng
long long. breakthoát vòng trong cùng,continuesang lượt kế;breakgiữa lúc đọc dãy chỉ an toàn khi sau đó không đọc gì nữa.- Với ,
n % 10lấy chữ số cuối,n / 10bỏ nó đi. Khi cần in hoặc đếm chữ số,while (n > 0)bỏ sót trường hợp ;do ... whilethì không. - Với vòng lồng nhau, số lượt nhân lên. Không đặt dấu
;saufor (...)haywhile (...).
Bài tập
Đọc thêm
- 循环OI Wiki
- While statementslearncpp.com
- For statementslearncpp.com
- Break and continuelearncpp.com
- for loopcppreference
- Off-by-one errorWikipedia