Chuyển tới nội dung chính

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à breakcontinue. 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

Cộng dồn 1 + 2 + 3 bằng for C++
1int main() {
2 int tong = 0;
3 for (int i = 1; i <= 3; i++) {
4 tong += i;
5 }
6 cout << tong;
7}
Ngăn xếp stack
main()
tong= 0
Bộ nhớ động heap
(trống)
Khởi tạo tong = 0 trước vòng lặp.
1/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 whiledo-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.

Ghi nhớ

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: breakcontinue

Đô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 đó.

  • break thoát ngay khỏi vòng lặp đang chạy, bỏ hết các vòng còn lại.
  • continue bỏ 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ĩaDù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épphần tử lớn, chỉ đọc
for (int& x : v)tham chiếu, sửa được phần tử gốcmuố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 <<=. 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.

Vượt biên mảng không phải lúc nào cũng báo lỗi

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ề intsize() trả về kiểu không dấu.

Những nhầm lẫn hay gặp
  • Đặ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 for thì 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ừa i++ 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 vector hay string, 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

Kiểm tra nhanh: vòng lặp0/4 đúngchưa trả lời
  1. 1Vòng for (int i = 0; i < n; i++) chạy thân bao nhiêu lần?
  2. 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?
  3. 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?
  4. 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.
  • for có ba phần khởi tạo, điều kiện, cập nhật gói chung một dòng.
  • while kiểm tra điều kiện ở đầu, do-while kiểm tra ở cuối nên thân chạy ít nhất một lần.
  • break thoát hẳn vòng lặp, continue bỏ 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ạy b - a lần, i <= b chạy b - a + 1 lầ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.