Bài 5.1: Vòng lặp for và các biến thể trong C++

Bài 5.1: Vòng lặp for và các biến thể trong C++
Chào mừng quay trở lại với chuỗi bài viết về C++! Trong lập trình, rất nhiều lần chúng ta cần thực hiện cùng một tác vụ lặp đi lặp lại. Thay vì viết cùng một đoạn code nhiều lần, chúng ta sử dụng các cấu trúc điều khiển lặp, hay còn gọi là vòng lặp. C++ cung cấp nhiều loại vòng lặp khác nhau, và trong bài viết này, chúng ta sẽ đi sâu vào một trong những loại phổ biến và mạnh mẽ nhất: vòng lặp for
cùng với các biến thể và cách sử dụng hiệu quả của nó.
Vòng lặp for
cực kỳ linh hoạt và thường được sử dụng khi bạn biết trước số lần lặp hoặc khi bạn cần kiểm soát chặt chẽ quá trình lặp dựa trên một biến đếm. Hãy cùng tìm hiểu kỹ nhé!
1. Cú pháp cơ bản của vòng lặp for
Vòng lặp for
truyền thống trong C++ có cấu trúc ba phần rất rõ ràng:
for (khởi tạo; điều kiện; cập nhật) {
// Khối lệnh sẽ được thực thi lặp đi lặp lại
}
Hãy phân tích từng phần:
khởi tạo
(initialization): Phần này chỉ được thực thi một lần duy nhất trước khi vòng lặp bắt đầu. Nó thường dùng để khai báo và khởi tạo biến điều khiển vòng lặp (biến đếm). Bạn có thể khai báo biến ngay tại đây.điều kiện
(condition): Đây là một biểu thức boolean được kiểm tra trước mỗi lần lặp. Nếu biểu thức này cho kết quảtrue
, khối lệnh bên trong vòng lặp sẽ được thực thi. Nếu kết quả làfalse
, vòng lặp sẽ dừng lại.cập nhật
(update): Phần này được thực thi sau mỗi lần hoàn thành khối lệnh bên trong vòng lặp. Nó thường dùng để thay đổi giá trị của biến điều khiển vòng lặp (ví dụ: tăng hoặc giảm biến đếm).
Lưu ý quan trọng: Vòng lặp for
sẽ tiếp tục chạy miễn là điều kiện
còn đúng (true
).
Ví dụ cơ bản: Đếm từ 1 đến 5
Hãy xem một ví dụ kinh điển về vòng lặp for
để in ra các số từ 1 đến 5:
#include <iostream>
int main() {
for (int i = 1; i <= 5; ++i) {
cout << "Số: " << i << endl;
}
return 0;
}
Giải thích code:
#include <iostream>
: Bao gồm thư viện nhập xuất để sử dụngcout
vàendl
.int main()
: Hàm chính của chương trình.for (int i = 1; i <= 5; ++i)
: Đây là vòng lặpfor
.int i = 1;
: Phần khởi tạo. Một biếnint
tên lài
được khai báo và gán giá trị ban đầu là 1. Biếni
này chỉ tồn tại trong phạm vi của vòng lặpfor
.i <= 5;
: Phần điều kiện. Trước mỗi lần lặp, chương trình kiểm tra xemi
có nhỏ hơn hoặc bằng 5 không.++i;
: Phần cập nhật. Sau khi thực hiện khối lệnh{ ... }
, giá trị củai
được tăng lên 1.
cout << "Số: " << i << endl;
: Khối lệnh bên trong vòng lặp. Nó sẽ in ra chuỗi "Số: " tiếp theo là giá trị hiện tại củai
và xuống dòng.
Quá trình chạy của vòng lặp:
- Khởi tạo:
i
được đặt là 1. - Kiểm tra điều kiện:
1 <= 5
làtrue
. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "Số: 1".
- Cập nhật:
i
trở thành 2. - Kiểm tra điều kiện:
2 <= 5
làtrue
. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "Số: 2".
- Cập nhật:
i
trở thành 3. - Kiểm tra điều kiện:
3 <= 5
làtrue
. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "Số: 3".
- Cập nhật:
i
trở thành 4. - Kiểm tra điều kiện:
4 <= 5
làtrue
. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "Số: 4".
- Cập nhật:
i
trở thành 5. - Kiểm tra điều kiện:
5 <= 5
làtrue
. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "Số: 5".
- Cập nhật:
i
trở thành 6. - Kiểm tra điều kiện:
6 <= 5
làfalse
. Vòng lặp kết thúc.
2. Các biến thể và sự linh hoạt của vòng lặp for
Sức mạnh của for
nằm ở sự linh hoạt của ba phần trong dấu ngoặc đơn ()
. Bạn có thể tùy chỉnh chúng theo nhiều cách khác nhau.
2.1. Bỏ qua các phần (hoặc tất cả!)
Bạn hoàn toàn có thể bỏ trống một hoặc nhiều phần trong cú pháp for
. Tuy nhiên, bạn vẫn cần giữ dấu chấm phẩy ;
để phân cách các phần.
Bỏ qua phần khởi tạo: Khi biến đếm đã được khởi tạo trước vòng lặp.
#include <iostream> int main() { int i = 1; // Khởi tạo biến i bên ngoài for (; i <= 5; ++i) { // Phần khởi tạo bị bỏ trống cout << "Số: " << i << endl; } return 0; }
Bỏ qua phần cập nhật: Khi việc cập nhật biến đếm được thực hiện bên trong khối lệnh của vòng lặp.
#include <iostream> int main() { for (int i = 1; i <= 5; ) { // Phần cập nhật bị bỏ trống cout << "Số: " << i << endl; i++; // Cập nhật biến i bên trong khối lệnh } return 0; }
Bỏ qua điều kiện: Nếu bạn bỏ qua điều kiện, vòng lặp sẽ được coi là luôn đúng (
true
), dẫn đến một vòng lặp vô hạn trừ khi bạn sử dụngbreak
để thoát. Cú phápfor (;;)
là cách phổ biến để tạo vòng lặp vô hạn.#include <iostream> int main() { int count = 0; for (;;) { // Vòng lặp vô hạn cout << "Đang lặp..." << endl; count++; if (count == 3) { break; // Thoát khỏi vòng lặp khi count đạt 3 } } cout << "Vòng lặp đã dừng." << endl; return 0; }
Giải thích: Vòng lặp này sẽ chạy mãi mãi nếu không có câu lệnh
break
. Chúng ta sử dụng biếncount
để đếm số lần lặp và thoát khi cần thiết.
2.2. Sử dụng nhiều biến trong khởi tạo và cập nhật
Bạn có thể khai báo và khởi tạo nhiều biến, cũng như thực hiện nhiều thao tác cập nhật trong các phần tương ứng, bằng cách phân cách chúng bằng dấu phẩy ,
.
#include <iostream>
int main() {
for (int i = 0, j = 10; i < 5 && j > 5; ++i, --j) {
cout << "i: " << i << ", j: " << j << endl;
}
return 0;
}
Giải thích: Vòng lặp này khởi tạo hai biến i
và j
, chạy khi cả i < 5
và j > 5
cùng đúng, đồng thời tăng i
và giảm j
sau mỗi lần lặp.
2.3. Lặp ngược hoặc bước nhảy tùy ý
Bạn không nhất thiết phải lặp theo chiều tăng dần từng bước 1.
Lặp ngược (Đếm lùi):
#include <iostream> int main() { for (int i = 5; i >= 1; --i) { cout << "Đếm lùi: " << i << endl; } return 0; }
Giải thích: Khởi tạo
i = 5
, điều kiện lài >= 1
, và cập nhật là giảmi
đi 1 (--i
).Bước nhảy tùy ý: Tăng hoặc giảm biến đếm với một giá trị khác 1.
#include <iostream> int main() { // Lặp các số chẵn từ 0 đến 10 for (int i = 0; i <= 10; i += 2) { cout << "Số chẵn: " << i << endl; } return 0; }
Giải thích: Phần cập nhật sử dụng
i += 2
để tăngi
lên 2 sau mỗi lần lặp.
3. Vòng lặp for
lồng nhau (Nested for loops)
Bạn có thể đặt một vòng lặp for
bên trong khối lệnh của một vòng lặp for
khác. Điều này thường được sử dụng để làm việc với các cấu trúc dữ liệu hai chiều (như ma trận) hoặc in ra các mẫu hình.
#include <iostream>
int main() {
// In ra một hình vuông 3x3 dấu *
for (int i = 0; i < 3; ++i) { // Vòng lặp ngoài (điều khiển hàng)
for (int j = 0; j < 3; ++j) { // Vòng lặp trong (điều khiển cột)
cout << "* "; // In dấu * và khoảng trắng
}
cout << endl; // Xuống dòng sau khi hoàn thành một hàng
}
return 0;
}
Giải thích:
- Vòng lặp ngoài chạy 3 lần (
i
từ 0 đến 2). Mỗi lần vòng lặp ngoài chạy, nó sẽ thực thi toàn bộ vòng lặp bên trong. - Vòng lặp trong chạy 3 lần (
j
từ 0 đến 2) cho mỗi lần vòng lặp ngoài chạy. - Kết quả là khối lệnh
cout << "* ";
được thực thi tổng cộng 3 * 3 = 9 lần. cout << endl;
được thực thi sau mỗi khi vòng lặp trong hoàn thành, tức là sau mỗi hàng.
Output sẽ là:
* * *
* * *
* * *
4. Vòng lặp range-based for
(C++11 trở lên)
Từ C++11, một biến thể mới của vòng lặp for
được giới thiệu, gọi là range-based for
loop (vòng lặp for dựa trên phạm vi). Vòng lặp này được thiết kế để duyệt qua tất cả các phần tử trong một dãy (range), chẳng hạn như mảng, vector, chuỗi (string
), và các container khác một cách dễ dàng và an toàn hơn.
Cú pháp rất đơn giản:
for (kiểu_dữ_liệu tên_biến : dãy) {
// Khối lệnh thực thi cho mỗi phần tử
}
kiểu_dữ_liệu tên_biến
: Khai báo một biến sẽ nhận giá trị của từng phần tử trong dãy trong mỗi lần lặp.kiểu_dữ_liệu
nên khớp với kiểu dữ liệu của các phần tử trongdãy
. Thường dùngauto
để trình biên dịch tự suy luận kiểu hoặcconst auto&
để tránh sao chép không cần thiết và đảm bảo không làm thay đổi giá trị của phần tử gốc (trừ khi bạn muốn thay đổi, khi đó dùngauto&
).dãy
: Đây là container hoặc mảng mà bạn muốn duyệt qua.
Ví dụ với range-based for
#include <iostream>
#include <vector> // Cần cho vector
#include <string> // Cần cho string
int main() {
// Ví dụ với mảng
int mang_so[] = {10, 20, 30, 40, 50};
cout << "Duyệt mảng:" << endl;
for (int so : mang_so) {
cout << so << " ";
}
cout << endl << endl;
// Ví dụ với vector
vector<string> danh_sach_ten = {"Alice", "Bob", "Charlie"};
cout << "Duyệt vector:" << endl;
for (const string& ten : danh_sach_ten) { // Sử dụng const auto& để hiệu quả hơn
cout << ten << " ";
}
cout << endl << endl;
// Ví dụ với string (chuỗi ký tự)
string loi_chao = "Xin chao!";
cout << "Duyệt chuỗi:" << endl;
for (char ky_tu : loi_chao) {
cout << ky_tu << "-";
}
cout << endl;
return 0;
}
Giải thích:
- Vòng lặp đầu tiên duyệt qua từng phần tử kiểu
int
trong mảngmang_so
. Biếnso
lần lượt nhận giá trị 10, 20, 30, 40, 50. - Vòng lặp thứ hai duyệt qua từng phần tử kiểu
string
trong vectordanh_sach_ten
. Việc sử dụngconst string&
(hoặcconst auto&
) là cách làm tốt để tránh sao chép toàn bộ chuỗi trong mỗi lần lặp, đặc biệt với các phần tử lớn. - Vòng lặp thứ ba duyệt qua từng ký tự kiểu
char
trong chuỗiloi_chao
.
Ưu điểm của range-based for
:
- Đơn giản hơn: Cú pháp gọn gàng, ít boilerplate code hơn so với
for
truyền thống khi chỉ cần duyệt qua tất cả phần tử. - Ít lỗi hơn: Giảm thiểu lỗi liên quan đến việc quản lý chỉ số (index), điều kiện dừng, hoặc cập nhật biến đếm sai.
- Dễ đọc hơn: Thể hiện rõ ý định của bạn là "thực hiện điều gì đó cho mọi phần tử trong dãy".
Nhược điểm (hoặc giới hạn):
- Bạn không có trực tiếp chỉ số (index) của phần tử hiện tại. Nếu bạn cần chỉ số, bạn phải dùng vòng lặp
for
truyền thống hoặc tự thêm một biến đếm vào vòng lặprange-based for
. - Không phù hợp khi bạn cần lặp với bước nhảy tùy ý, lặp ngược (một cách tự nhiên), hoặc khi điều kiện dừng không liên quan trực tiếp đến việc duyệt hết container.
5. Sử dụng break
và continue
trong vòng lặp for
Hai câu lệnh break
và continue
cung cấp khả năng kiểm soát luồng thực thi bên trong vòng lặp.
break
: Ngay lập tức thoát khỏi vòng lặp chứa nó gần nhất. Chương trình sẽ tiếp tục thực thi câu lệnh ngay sau vòng lặp.#include <iostream> int main() { cout << "Ví dụ với break:" << endl; for (int i = 1; i <= 10; ++i) { if (i == 6) { cout << "Gặp số 6, thoát vòng lặp." << endl; break; // Thoát khỏi vòng lặp for } cout << i << " "; } cout << endl << "Đã thoát vòng lặp." << endl; return 0; }
Giải thích: Vòng lặp này được thiết lập để chạy từ 1 đến 10. Tuy nhiên, khi
i
đạt giá trị 6, câu lệnhbreak
được thực thi, khiến vòng lặp kết thúc ngay lập tức. Output sẽ chỉ in các số từ 1 đến 5, sau đó là thông báo "Gặp số 6, thoát vòng lặp." và cuối cùng là "Đã thoát vòng lặp.".continue
: Bỏ qua phần còn lại của khối lệnh trong lần lặp hiện tại và chuyển sang lần lặp tiếp theo (thực hiện phần cập nhật và kiểm tra điều kiện).#include <iostream> int main() { cout << "Ví dụ với continue:" << endl; for (int i = 1; i <= 10; ++i) { if (i == 5) { cout << "Bỏ qua số 5..." << endl; continue; // Bỏ qua phần còn lại của lần lặp này } cout << i << " "; } cout << endl << "Kết thúc vòng lặp." << endl; return 0; }
Giải thích: Vòng lặp này cũng chạy từ 1 đến 10. Khi
i
đạt giá trị 5, câu lệnhcontinue
được thực thi. Điều này làm cho phầncout << i << " ";
bị bỏ qua chỉ trong lần lặp đó. Vòng lặp sẽ tiếp tục vớii = 6
. Output sẽ in các số từ 1 đến 10, nhưng thiếu số 5.
Cả break
và continue
đều hữu ích để tinh chỉnh hành vi của vòng lặp dựa trên các điều kiện đặc biệt.
6. Một vài lưu ý và mẹo nhỏ
- Phạm vi biến: Biến được khai báo trong phần khởi tạo của vòng lặp
for
(ví dụ:int i = 0;
) chỉ tồn tại trong phạm vi của chính vòng lặp đó. Sau khi vòng lặp kết thúc, bạn không thể truy cập biếni
nữa. - Vòng lặp vô hạn: Cẩn thận với các điều kiện luôn đúng hoặc phần cập nhật không làm cho điều kiện sai đi. Ví dụ:
for (int i = 0; i < 10; ) { cout << i; }
là vòng lặp vô hạn vìi
không bao giờ thay đổi. - Lỗi off-by-one (lệch 1 đơn vị): Đây là lỗi phổ biến khi sử dụng vòng lặp
for
truyền thống. Cẩn thận với việc sử dụng<
so với<=
trong điều kiện lặp. Ví dụ: lặp 5 lần, bắt đầu từ 0:for (int i = 0; i < 5; ++i)
(i sẽ là 0, 1, 2, 3, 4 - 5 giá trị). Lặp 5 lần, bắt đầu từ 1:for (int i = 1; i <= 5; ++i)
(i sẽ là 1, 2, 3, 4, 5 - 5 giá trị). - Chọn loại vòng lặp:
- Sử dụng
for
truyền thống khi bạn cần kiểm soát chính xác số lần lặp, cần truy cập chỉ mục, lặp với bước nhảy không phải 1, hoặc lặp ngược. - Sử dụng
range-based for
khi bạn chỉ đơn giản muốn duyệt qua tất cả các phần tử trong một container/dãy từ đầu đến cuối mà không cần quan tâm đến chỉ mục.range-based for
thường an toàn và dễ đọc hơn trong trường hợp này.
- Sử dụng
Vòng lặp for
là một công cụ cơ bản nhưng vô cùng mạnh mẽ trong lập trình C++. Nắm vững cách sử dụng cả cú pháp truyền thống và range-based for
, cùng với break
và continue
, sẽ giúp bạn giải quyết hiệu quả nhiều bài toán lặp trong chương trình của mình.
Comments