Vòng lặp: for / while
Hãy tưởng tượng bạn phải in ra các số từ 1 tới 100, hoặc cộng điểm của 50 sinh
viên, hoặc kiểm tra từng ký tự trong một chuỗi. Nếu chép tay từng dòng lệnh thì
chương trình dài vô tận và không ai đọc nổi. Máy tính sinh ra là để làm những việc
lặp đi lặp lại, và vòng lặp chính là công cụ ra lệnh cho nó lặp. Thay vì viết
một trăm dòng cout, ta viết một vòng lặp gọn vài dòng và để máy tự chạy đủ số lần.
Có một điều đáng suy nghĩ ở đây. Vòng lặp không chỉ giúp bạn gõ ít phím hơn, nó thay đổi
hẳn thứ bạn viết được. Chương trình chép tay một trăm dòng cout chỉ in được đúng một
trăm số, và bạn phải biết trước con số một trăm từ lúc ngồi viết. Còn vòng lặp
for (int i = 1; i <= n; i++) in được n số với n là bất cứ gì người dùng nhập lúc
chạy, kể cả một triệu. Nói cách khác, một đoạn mã có độ dài cố định lại làm được lượng
việc không cố định. Đó là bước nhảy đầu tiên từ chỗ ra lệnh cho máy sang chỗ mô tả cho máy
một quy luật, và mọi thứ về sau như duyệt mảng, tìm kiếm, sắp xếp đều dựng trên bước nhảy
này.
Mọi vòng lặp, dù viết kiểu gì, đều trả lời đúng ba câu hỏi: bắt đầu ở đâu, dừng khi nào, và mỗi lần thì tiến lên ra sao. Nếu bạn trả lời được ba câu này bằng lời trước khi gõ, việc viết mã chỉ còn là chép lại câu trả lời. Ngược lại, gần như mọi lỗi vòng lặp mà bạn sẽ gặp trong đời đều truy về việc một trong ba câu bị trả lời sai: bắt đầu lệch một, dừng sớm hoặc muộn một nhịp, hoặc quên hẳn phần tiến lên nên vòng lặp chạy mãi không dứt.
Trong bài này ta học ba loại vòng lặp của C++ là for, while, do-while, thêm vòng lặp
phạm vi của C++ hiện đại, cùng hai lệnh điều khiển dòng chảy là break và continue. Ta
cũng nhìn kỹ những chỗ người mới hay vấp: vòng lặp vô hạn, lỗi lệch một đơn vị
(off-by-one), và cách chọn đúng loại vòng lặp cho từng tình huống. for hợp khi ta
biết trước số lần cần lặp; while hợp khi ta lặp đến khi một điều kiện không còn
đúng mà chưa biết trước bao nhiêu lần.
Ý tưởng cốt lõi
for (khởi_tạo; điều_kiện; cập_nhật) { thân }: chạy thân trong khi điều kiện còn đúng.- Mỗi vòng: kiểm tra điều kiện → chạy thân → cập nhật biến đếm → lặp lại.
- Biến cộng dồn (như
tong) phải khởi tạo trước vòng lặp.
Ta gọi mẫu dùng một biến để gom kết quả dần dần là mẫu tích lũy. Biến tích lũy
đặt giá trị ban đầu ở ngoài vòng lặp rồi được cập nhật trong thân: tổng khởi tạo
0, tích khởi tạo 1, biến đếm khởi tạo 0. Nếu quên khởi tạo, biến cục bộ trong
C++ mang một giá trị rác bất kỳ và kết quả sẽ sai hoàn toàn.
Thử ngay: xem vòng lặp chạy từng bước
1int main() {2 int tong = 0;3 for (int i = 1; i <= 3; i++) {4 tong += i;5 }6 cout << tong;7}
Ba phần của vòng for
Câu lệnh for gói gọn ba việc vào một dòng, ngăn cách bằng dấu chấm phẩy.
- Khởi tạo chạy đúng một lần trước khi vào vòng, thường dùng để đặt biến đếm, ví dụ
int i = 1. - Điều kiện được kiểm tra ở đầu mỗi vòng. Còn đúng thì chạy thân, sai thì thoát ngay.
- Cập nhật chạy sau mỗi lần chạy thân, thường là
i++để đưa biến đếm tiến một bước.
Nhờ ba phần nằm chung một chỗ, người đọc nhìn dòng for là biết ngay vòng lặp bắt
đầu từ đâu, dừng khi nào, và bước nhảy bao nhiêu. Bước nhảy không nhất thiết là +1.
#include <iostream>
using namespace std;
int main() {
for (int i = 0; i < 5; i++) cout << i << " "; // 0 1 2 3 4
cout << "\n";
for (int i = 1; i <= 5; i++) cout << i << " "; // 1 2 3 4 5
cout << "\n";
for (int i = 10; i > 0; i -= 2) cout << i << " "; // 10 8 6 4 2
}
Vòng thứ nhất đếm từ 0, dừng khi i đạt 5, in ra 0 1 2 3 4. Vòng thứ hai đếm
từ 1 tới 5 vì dùng điều kiện i <= 5. Vòng thứ ba đi lùi với bước i -= 2, cho
thấy vòng for chạy xuôi hay ngược đều được, miễn phần cập nhật đưa biến đếm về
phía làm điều kiện dần sai.
Muốn nhớ chắc thứ tự chạy của for, hãy nhìn nó như một vòng while được viết gọn lại.
Hai đoạn dưới đây làm y hệt nhau:
for (int i = 0; i < n; i++) {
// than
}
// tuong duong voi:
{
int i = 0; // khoi tao, chay dung mot lan
while (i < n) { // dieu kien, kiem tra o dau moi vong
// than
i++; // cap nhat, chay sau than
}
}
Cách viết bung ra này làm rõ hai điều dễ nhầm. Thứ nhất, phần cập nhật i++ chạy sau
thân chứ không phải trước, nên trong lần lặp đầu tiên thân nhìn thấy i bằng đúng giá trị
khởi tạo. Thứ hai, điều kiện được kiểm tra trước cả lần chạy thân đầu tiên, nên nếu điều
kiện sai ngay từ đầu (ví dụ n bằng 0) thì thân không chạy lần nào. Đây là điểm phân
biệt then chốt giữa for với do-while mà ta sẽ gặp ngay sau đây.
Cặp ngoặc nhọn bao ngoài trong đoạn tương đương cũng nói lên một chuyện thật: biến i khai
báo trong phần khởi tạo chỉ sống trong phạm vi của vòng lặp. Ra khỏi vòng là nó biến mất,
nên cout << i; đặt sau vòng sẽ không dịch được. Nếu bạn cần biết giá trị cuối của biến
đếm, hãy khai báo nó ở ngoài: int i; for (i = 0; i < n; i++) { ... }. Đa số trường hợp
thì không cần, và giữ i bên trong là thói quen tốt vì nó ngăn bạn vô tình dùng lại một
biến đếm đã cũ.
Cuối cùng, ba phần của for đều có thể bỏ trống, chỉ giữ lại hai dấu chấm phẩy. Viết
for (;;) là một vòng lặp không có điều kiện dừng, tức vòng lặp vô hạn có chủ ý, thường
dùng chung với break để thoát ở giữa thân. Bạn sẽ thấy nó ở phần sau.
Vòng while và do-while
while (điều_kiện) { thân } lặp khi ta không biết trước số vòng, chỉ biết điều
kiện dừng. Máy kiểm tra điều kiện trước, còn đúng mới chạy thân. Thân bắt buộc phải
có một lệnh làm điều kiện dần sai, nếu không vòng lặp chạy mãi.
#include <iostream>
using namespace std;
int main() {
long long n = 2025;
int tong = 0;
while (n > 0) {
tong += n % 10; // cộng chữ số hàng đơn vị
n /= 10; // bỏ chữ số vừa cộng, n nhỏ dần
}
cout << tong; // 2 + 0 + 2 + 5 = 9
}
Đoạn trên cộng các chữ số của một số. Ta không biết trước số có mấy chữ số, nên
while hợp hơn for. Điểm mấu chốt là dòng n /= 10 làm n nhỏ dần về 0, bảo
đảm vòng lặp có ngày dừng.
do-while giống while nhưng kiểm tra điều kiện ở cuối, nên thân luôn chạy ít
nhất một lần. Loại này hợp khi cần làm việc gì đó trước rồi mới xét có lặp tiếp không,
kinh điển là đọc và kiểm tra dữ liệu nhập vào.
#include <iostream>
using namespace std;
int main() {
int tuoi;
do {
cout << "Nhap tuoi (1..120): ";
cin >> tuoi;
} while (tuoi < 1 || tuoi > 120); // hỏi lại khi nhập sai
cout << "Tuoi hop le: " << tuoi;
}
Vì lời nhắc phải hiện ra một lần trước khi biết người dùng gõ gì, do-while gọn hơn
while ở tình huống này. Ba loại vòng lặp thực ra có thể thay nhau, nhưng chọn đúng
loại làm code sáng ý hơn.
Mã nguồn
#include <iostream>
using namespace std;
int main() {
int tong = 0;
for (int i = 1; i <= 3; i++) {
tong += i; // cộng dồn 1, 2, 3
}
cout << tong; // 6
}
Đây chính là chương trình mà trình mô phỏng ở đầu bài đã chạy. Giờ đã biết ba phần của
for, ta giảng lại đoạn này bằng lời, từng nhịp một, vì nó là bộ khung của hàng trăm bài
tập sau này. Trước khi vào vòng, tong được đặt bằng 0. Đây không phải chi tiết thừa: tong là
biến tích lũy, và 0 là phần tử trung tính của phép cộng, nghĩa là cộng 0 với bất cứ số
nào cũng không làm sai lệch kết quả. Nếu bài toán là tính tích thì giá trị khởi tạo phải là
1 theo đúng logic đó.
Vào vòng, phần khởi tạo tạo ra i bằng 1. Máy kiểm tra 1 <= 3, đúng, nên chạy thân:
tong từ 0 thành 1. Xong thân, phần cập nhật đưa i lên 2. Kiểm tra 2 <= 3, vẫn
đúng, chạy thân: tong thành 3. Cập nhật, i thành 3. Kiểm tra 3 <= 3, vẫn đúng vì
đây là dấu bé hơn hoặc bằng, chạy thân: tong thành 6. Cập nhật, i thành 4. Kiểm
tra 4 <= 3, lần này sai, vòng lặp kết thúc và máy nhảy tới dòng cout.
Chú ý kỹ điều này: thân chạy ba lần, nhưng i kết thúc ở 4 chứ không phải 3. Lý do là
phần cập nhật vẫn chạy sau lần lặp cuối, rồi mới tới lượt điều kiện phát hiện ra đã quá
đà. Giá trị 4 chính là giá trị đầu tiên làm điều kiện sai. Hiểu được nhịp thừa này là
bạn đã nắm được gốc rễ của mọi lỗi lệch một đơn vị.
Tự kiểm tra
Vòng lặp chạy bao nhiêu lần, và vì sao i kết thúc ở 4?
Chạy 3 lần (i = 1, 2, 3). Sau lần i = 3, lệnh i++ vẫn chạy đưa i lên 4, rồi
mới kiểm tra 4 <= 3 thấy sai và thoát. Vậy i = 4 là giá trị làm điều kiện dừng.
for lặp theo trình tự: kiểm tra điều kiện → thân → cập nhật. Biến cộng dồn khởi tạo
trước vòng lặp. while (đk) { ... } tương đương khi không cần biến đếm cố định.
Điều khiển dòng chảy: break và continue
Đôi khi ta muốn ngắt vòng lặp giữa chừng thay vì chờ điều kiện tự sai. Hai lệnh giúp làm việc đó.
breakthoát ngay khỏi vòng lặp đang chạy, bỏ hết các vòng còn lại.continuebỏ qua phần còn lại của thân vòng hiện tại và nhảy sang vòng kế tiếp.
#include <iostream>
using namespace std;
int main() {
// Tìm số đầu tiên chia hết cho 7 trong khoảng 20..40
for (int i = 20; i <= 40; i++) {
if (i % 7 == 0) {
cout << "Tim thay: " << i; // 21
break; // thấy rồi thì dừng, khỏi duyệt tiếp
}
}
}
Ở đây break cần thiết: tìm được đáp án đầu tiên là đủ, duyệt tiếp chỉ tốn công.
continue thì khác, nó không thoát mà nhảy tới vòng sau.
#include <iostream>
using namespace std;
int main() {
int tong_le = 0;
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) continue; // số chẵn thì bỏ qua, không cộng
tong_le += i; // chỉ cộng số lẻ
}
cout << tong_le; // 1+3+5+7+9 = 25
}
Khi gặp số chẵn, continue khiến máy nhảy thẳng tới i++ mà không chạy tong_le += i.
Lưu ý một bẫy: nếu dùng continue trong vòng while mà lệnh cập nhật biến đếm đặt ở
cuối thân, continue sẽ nhảy qua luôn lệnh cập nhật đó và gây vòng lặp vô hạn. Trong
vòng for thì an toàn hơn vì phần cập nhật luôn chạy.
break cũng là bạn đồng hành của vòng lặp vô hạn có chủ ý. Khi điều kiện dừng chỉ lộ ra ở
giữa thân, ví dụ đọc dữ liệu tới khi gặp số 0, viết while (true) rồi break lại rõ ý
hơn là bóp méo điều kiện đầu vòng cho vừa.
#include <iostream>
using namespace std;
int main() {
long long tong = 0;
while (true) { // vong lap vo han co chu y
int x;
cin >> x;
if (x == 0) break; // gap 0 thi dung, khong cong 0 vao tong
tong += x;
}
cout << tong << "\n";
return 0;
}
Một lưu ý về break trong vòng lặp lồng nhau: nó chỉ thoát khỏi một vòng, cụ thể là
vòng gần nhất bao quanh nó, chứ không thoát hết mọi vòng. Muốn thoát cả hai tầng, cách
sạch sẽ nhất là đưa hai vòng đó vào một hàm rồi dùng return.
Vòng lặp phạm vi
Khi bạn chỉ muốn đi qua từng phần tử của một vector hay string mà không quan tâm chỉ
số của chúng, C++ có một cách viết gọn hơn hẳn gọi là vòng lặp phạm vi
(range-based for). Cú pháp đọc gần như tiếng Anh: với mỗi x trong v, làm gì đó.
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {3, 1, 4, 1, 5};
long long tong = 0;
for (int x : v) { // voi moi phan tu x trong v
tong += x;
}
cout << tong << "\n"; // 14
return 0;
}
So với for (int i = 0; i < (int)v.size(); i++) tong += v[i];, cách viết này bỏ hẳn biến
đếm, bỏ hẳn điều kiện dừng, và vì thế bỏ luôn khả năng viết sai chúng. Không còn chỗ nào
để lệch một đơn vị, không còn chỗ nào để truy cập vượt biên. Đó là lợi ích thật sự của nó,
lớn hơn nhiều so với chuyện gõ ít ký tự.
Có ba biến thể cần phân biệt, và chọn nhầm là nguồn của những lỗi rất khó thấy:
| Cách viết | Ý nghĩa | Dùng khi nào |
|---|---|---|
for (int x : v) | x là một bản sao của phần tử | kiểu nhỏ, chỉ đọc |
for (const string& s : v) | tham chiếu hằng, không sao chép | phần tử lớn, chỉ đọc |
for (int& x : v) | tham chiếu, sửa được phần tử gốc | muốn thay đổi v |
Điểm dễ sập bẫy nhất nằm ở dòng đầu: for (int x : v) x *= 2; không nhân đôi các phần
tử của v, vì x chỉ là bản sao, sửa bản sao xong nó bị vứt đi ngay. Muốn sửa thật, phải
thêm dấu & để x thành tham chiếu tới phần tử gốc:
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {1, 2, 3};
for (int& x : v) x *= 2; // dau & moi sua duoc phan tu goc
for (int x : v) cout << x << " "; // 2 4 6
cout << "\n";
return 0;
}
Vòng lặp phạm vi cũng có giới hạn. Nó không cho bạn biết mình đang ở phần tử thứ mấy, nên
khi cần in kèm chỉ số, cần so sánh phần tử này với phần tử kề, hoặc cần duyệt ngược, bạn
vẫn phải quay về for có biến đếm. Và tuyệt đối đừng thêm hay xóa phần tử của v ngay
trong lúc đang duyệt nó bằng vòng lặp phạm vi: vector có thể phải dời toàn bộ dữ liệu
sang vùng nhớ mới, khiến vòng lặp đi tiếp trên vùng nhớ đã chết.
Vòng lặp lồng nhau
Đặt một vòng lặp bên trong thân của vòng lặp khác gọi là lồng nhau. Vòng ngoài chạy một lần thì vòng trong chạy trọn vẹn từ đầu tới cuối. Đây là cách tự nhiên để duyệt bảng hai chiều hay sinh mọi cặp phần tử.
#include <iostream>
using namespace std;
int main() {
// In bảng cửu chương 1..3
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
cout << i << "x" << j << "=" << i * j << " ";
}
cout << "\n"; // xuống dòng sau mỗi hàng
}
}
Vòng ngoài chạy i từ 1 tới 3. Với mỗi giá trị i, vòng trong chạy j đủ ba
lần. Tổng cộng thân trong chạy 3 x 3 = 9 lần. Đây cũng là lý do vòng lặp lồng nhau
tốn thời gian: hai vòng lồng nhau trên n phần tử chạy khoảng n * n lần, ba vòng
lồng nhau chạy khoảng n * n * n lần. Với n lớn con số này phình rất nhanh.
Vòng lặp vô hạn và lỗi off-by-one
Hai lỗi phổ biến nhất với người mới đều liên quan tới điều kiện dừng.
Vòng lặp vô hạn xảy ra khi điều kiện không bao giờ sai. Thường do quên cập nhật
biến điều kiện trong while, hoặc viết sai chiều so sánh.
int i = 0;
while (i < 5) {
cout << i << " ";
// quên i++ ở đây → i luôn bằng 0 → chạy mãi mãi
}
Nguyên nhân gốc của vòng lặp vô hạn luôn là một trong ba thứ: điều kiện không hề nhắc tới
biến nào bị thân vòng thay đổi; biến điều kiện có bị thay đổi nhưng theo chiều đi xa dần
chỗ dừng; hoặc lệnh cập nhật bị nhánh continue nhảy qua. Mỗi lần viết while, hãy tự
hỏi thành lời: "cái gì trong thân này làm điều kiện tiến dần tới sai?". Nếu không chỉ ra
được, vòng lặp của bạn sẽ chạy mãi.
Lỗi off-by-one (lệch một đơn vị) xảy ra khi vòng lặp chạy thừa hoặc thiếu một
lần, do lẫn lộn giữa < và <=. Muốn duyệt mảng n phần tử với chỉ số 0..n-1,
điều kiện đúng là i < n. Muốn đếm từ 1 tới n thì viết i <= n. Chỉ số vượt
biên mảng còn gây lỗi truy cập bộ nhớ ngoài vùng cho phép.
Có một mẹo đếm giúp bạn kiểm tra nhanh mà không cần chạy thử. Vòng
for (int i = a; i < b; i++) chạy đúng b - a lần, còn for (int i = a; i <= b; i++)
chạy b - a + 1 lần. Áp vào ví dụ quen thuộc: for (int i = 0; i < n; i++) chạy
n - 0 bằng n lần, đúng bằng số phần tử của mảng. Còn for (int i = 1; i <= n; i++)
chạy n - 1 + 1 cũng bằng n lần. Hai cách viết này cùng chạy n lần nhưng dãy giá trị
của i khác nhau, và chính chuyện lẫn lộn hai dãy đó sinh ra lỗi. Quy ước dễ nhớ: khi làm
việc với chỉ số mảng thì dùng i < n bắt đầu từ 0; khi đếm theo cách của con người, ví
dụ in bảng cửu chương từ 1 tới 10, thì dùng i <= n bắt đầu từ 1.
Viết for (int i = 0; i <= n; i++) tong += v[i]; là chạy thừa một vòng, và ở vòng thừa đó
bạn đọc v[n], một ô nằm ngoài mảng. C++ không kiểm tra biên: nó cứ đọc ô nhớ ngay
sau mảng và trả về bất cứ thứ gì nằm ở đó. Chương trình thường vẫn chạy trơn tru, chỉ cho
ra kết quả sai lệch một cách khó hiểu, và có thể sập ở một lần chạy khác trên một máy khác.
Đây là hành vi không xác định, thứ lỗi tệ nhất vì nó không tự lộ diện. Hai cách phòng vệ
đơn giản: dùng vòng lặp phạm vi khi không cần chỉ số, và khi cần chỉ số thì luôn viết
i < (int)v.size(), nhớ ép về int vì size() trả về kiểu không dấu.
- Đặt dấu chấm phẩy ngay sau
for (...), ví dụfor (int i = 0; i < n; i++);khiến thân vòng rỗng, khối lệnh phía sau chỉ chạy đúng một lần. - Khai báo biến đếm bên trong vòng
forthì biến đó không dùng được sau khi thoát vòng, vì nó chỉ sống trong phạm vi của vòng. - Sửa giá trị biến đếm ngay trong thân vòng
for(vừa cói++ở phần cập nhật vừai++trong thân) làm số vòng chạy khác với dự định. - Quên dấu ngoặc nhọn khi thân có nhiều lệnh: chỉ lệnh đầu tiên thuộc vòng lặp, các lệnh sau nằm ngoài.
Khi nào chọn loại nào
- Biết trước số lần lặp, hoặc cần một biến đếm chạy đều: chọn
for. - Chỉ cần đi qua từng phần tử của
vectorhaystring, không cần chỉ số: chọn vòng lặp phạm vi. - Lặp tới khi một điều kiện thay đổi mà chưa biết trước số lần: chọn
while. - Cần chạy thân ít nhất một lần rồi mới xét lặp tiếp: chọn
do-while. - Ba loại đều thay nhau được, nên tiêu chí thật sự là loại nào làm ý đồ rõ nhất cho người đọc.
Nói kỹ hơn về tiêu chí cuối, vì nó dễ bị coi là lời khuyên sáo rỗng. Về mặt máy móc, bạn
viết mọi thứ bằng while cũng được, chương trình chạy y hệt. Nhưng người đọc mã, kể cả
chính bạn sau ba tháng, sẽ suy luận dựa vào hình dạng mã. Nhìn thấy for là người ta ngầm
hiểu số vòng lặp đã được định trước và có thể đọc ra ngay từ dòng đầu. Nhìn thấy while
là người ta ngầm hiểu số vòng phụ thuộc dữ liệu, và lập tức đi tìm xem thân vòng làm gì để
điều kiện dừng lại. Chọn đúng loại là bạn đang gửi đi một tín hiệu đúng, giúp người đọc
tiết kiệm công sức. Chọn sai, ví dụ nhét một biến đếm vào while trong khi rõ ràng biết
trước số lần, thì mã vẫn chạy nhưng gửi đi tín hiệu sai và làm người đọc mất thời gian
kiểm tra một chuyện vốn không cần kiểm tra.
Một tiêu chí phụ nữa là chọn loại nào ít cho bạn cơ hội sai nhất. Vòng lặp phạm vi không có
chỗ để viết sai điều kiện dừng, nên nó an toàn hơn for có chỉ số; for gom cả ba phần
vào một dòng nên khó quên cập nhật hơn while. Khi hai lựa chọn đều diễn đạt ý tốt như
nhau, hãy lấy cái ít bẫy hơn.
Bài tập
Bài 1. In ra tổng các số chẵn từ 1 tới 100 bằng vòng for.
#include <iostream>
using namespace std;
int main() {
int tong = 0;
for (int i = 2; i <= 100; i += 2) { // chỉ duyệt số chẵn
tong += i;
}
cout << tong; // 2550
}
Bằng cách cho biến đếm bước +2 và bắt đầu từ 2, ta chỉ chạm đúng các số chẵn,
khỏi cần lệnh if kiểm tra chẵn lẻ.
Bài 2. Đếm xem số nguyên dương n có bao nhiêu chữ số, dùng while.
#include <iostream>
using namespace std;
int main() {
int n = 40725;
int dem = 0;
while (n > 0) {
dem++; // đếm thêm một chữ số
n /= 10; // bỏ chữ số cuối
}
cout << dem; // 5
}
Mỗi lần chia cho 10 ta bỏ đi một chữ số, nên số lần lặp bằng số chữ số. Với n = 0
cần xét riêng vì vòng lặp không chạy lần nào, đáp án đúng phải là 1.
Bài 3. In một tam giác vuông cạnh 4 bằng dấu sao, dùng vòng lặp lồng nhau.
#include <iostream>
using namespace std;
int main() {
for (int i = 1; i <= 4; i++) { // 4 hàng
for (int j = 1; j <= i; j++) { // hàng thứ i có i dấu sao
cout << "*";
}
cout << "\n";
}
}
Vòng ngoài quyết định số hàng, vòng trong in số dấu sao đúng bằng chỉ số hàng. Điều
kiện j <= i gắn số dấu sao vào từng hàng, cho ra hình bậc thang.
Bài 4. Đếm số phần tử dương trong một vector<int>, dùng vòng lặp phạm vi.
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {3, -1, 0, 7, -5, 2};
int dem = 0; // bien dem khoi tao 0 truoc vong lap
for (int x : v) { // duyet tung phan tu, khong can chi so
if (x > 0) dem++;
}
cout << dem << "\n"; // 3
return 0;
}
Bài này cố ý chọn vòng lặp phạm vi vì ta chỉ cần giá trị của từng phần tử, không cần biết
nó nằm ở vị trí nào. Nhờ vậy không có biến đếm chỉ số, không có điều kiện dừng, và cũng
không còn chỗ nào để mắc lỗi lệch một đơn vị hay đọc vượt biên. Chú ý x ở đây là bản sao,
nhưng ta chỉ đọc nó nên hoàn toàn ổn; nếu bài yêu cầu đổi dấu mọi số âm ngay trong v thì
phải viết for (int& x : v) với dấu &.
Bài 5. Tìm chỉ số của lần xuất hiện đầu tiên của giá trị k trong vector<int>, in -1 nếu không có.
#include <iostream>
#include <vector>
using namespace std;
int main() {
vector<int> v = {4, 8, 15, 8, 23};
int k = 8;
int viTri = -1; // gia tri mac dinh: chua tim thay
for (int i = 0; i < (int)v.size(); i++) { // ep size() ve int, dieu kien dung la i < size
if (v[i] == k) {
viTri = i;
break; // tim thay lan dau tien la du, dung ngay
}
}
cout << viTri << "\n"; // 1
return 0;
}
Đây là bài buộc phải dùng for có chỉ số chứ không dùng được vòng lặp phạm vi, vì đáp án
cần chính là chỉ số. Ba chi tiết đáng học. Thứ nhất, viTri khởi tạo bằng -1 để nếu vòng
lặp chạy hết mà không thấy gì thì giá trị đó chính là câu trả lời, khỏi cần thêm biến cờ.
Thứ hai, break là bắt buộc theo yêu cầu đề bài: nếu bỏ nó đi, vòng lặp chạy tiếp và
viTri sẽ bị ghi đè bởi lần xuất hiện cuối cùng thay vì lần đầu, và đây đúng là số 8
thứ hai ở chỉ số 3. Thứ ba, điều kiện là i < (int)v.size() chứ không phải
i <= v.size(): dấu bé hơn hoặc bằng sẽ đọc v[5] nằm ngoài mảng, còn phép ép về int
tránh chuyện so sánh giữa số có dấu và số không dấu.
Câu hỏi tự kiểm
- 1Vòng for (int i = 0; i < n; i++) chạy thân bao nhiêu lần?
- 2Sau for (int i = 1; i <= 3; i++), thân chạy 3 lần nhưng i kết thúc ở giá trị nào?
- 3Muốn nhân đôi mọi phần tử của vector v ngay trong danh sách gốc, viết thế nào?
- 4Vì sao continue trong vòng while dễ gây lặp vô hạn hơn trong for?
Tóm tắt
- Vòng lặp cho máy làm lại một việc nhiều lần mà không phải chép code.
forcó ba phần khởi tạo, điều kiện, cập nhật gói chung một dòng.whilekiểm tra điều kiện ở đầu,do-whilekiểm tra ở cuối nên thân chạy ít nhất một lần.breakthoát hẳn vòng lặp,continuebỏ qua phần còn lại của vòng hiện tại.- Vòng lặp lồng nhau dùng để duyệt bảng, nhưng số lần chạy nhân lên rất nhanh.
- Vòng lặp phạm vi
for (int x : v)duyệt từng phần tử mà không cần chỉ số, nên loại bỏ hẳn lỗi vượt biên; thêm&khi muốn sửa phần tử gốc. for (int i = a; i < b; i++)chạyb - alần,i <= bchạyb - a + 1lần; nhớ công thức này để tự kiểm tra số vòng.- Biến tích lũy khởi tạo trước vòng; canh kỹ
<với<=để tránh lỗi off-by-one; luôn bảo đảm điều kiện có ngày sai để tránh lặp vô hạn.