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>
using namespace std;
int main() {
for (int i = 1; i <= 5; ++i) {
cout << "So: " << i << endl;
}
}
Output:
So: 1
So: 2
So: 3
So: 4
So: 5
Giải thích code:
#include <iostream>: Bao gồm thư viện nhập xuất để sử dụngcoutvà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ếninttên làiđược khai báo và gán giá trị ban đầu là 1. Biếninà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 xemicó 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 << "So: " << i << endl;: Khối lệnh bên trong vòng lặp. Nó sẽ in ra chuỗi "So: " tiếp theo là giá trị hiện tại củaivà 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 <= 5làtrue. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "So: 1".
- Cập nhật:
itrở thành 2. - Kiểm tra điều kiện:
2 <= 5làtrue. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "So: 2".
- Cập nhật:
itrở thành 3. - Kiểm tra điều kiện:
3 <= 5làtrue. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "So: 3".
- Cập nhật:
itrở thành 4. - Kiểm tra điều kiện:
4 <= 5làtrue. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "So: 4".
- Cập nhật:
itrở thành 5. - Kiểm tra điều kiện:
5 <= 5làtrue. Thực thi khối lệnh. - Thực thi khối lệnh: In ra "So: 5".
- Cập nhật:
itrở thành 6. - Kiểm tra điều kiện:
6 <= 5là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> using namespace std; int main() { int i = 1; for (; i <= 5; ++i) { cout << "So: " << i << endl; } }Output:
So: 1 So: 2 So: 3 So: 4 So: 5Bỏ 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> using namespace std; int main() { for (int i = 1; i <= 5; ) { cout << "So: " << i << endl; i++; } }Output:
So: 1 So: 2 So: 3 So: 4 So: 5Bỏ 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> using namespace std; int main() { int c = 0; for (;;) { cout << "Lap..." << endl; c++; if (c == 3) { break; } } cout << "Dung." << endl; }Output:
Lap... Lap... Lap... Dung.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ếncđể đế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>
using namespace std;
int main() {
for (int i = 0, j = 10; i < 5 && j > 5; ++i, --j) {
cout << "i: " << i << ", j: " << j << endl;
}
}
Output:
i: 0, j: 10
i: 1, j: 9
i: 2, j: 8
i: 3, j: 7
i: 4, j: 6
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> using namespace std; int main() { for (int i = 5; i >= 1; --i) { cout << "Dem: " << i << endl; } }Output:
Dem: 5 Dem: 4 Dem: 3 Dem: 2 Dem: 1Giả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> using namespace std; int main() { for (int i = 0; i <= 10; i += 2) { cout << "Chan: " << i << endl; } }Output:
Chan: 0 Chan: 2 Chan: 4 Chan: 6 Chan: 8 Chan: 10Giải thích: Phần cập nhật sử dụng
i += 2để tăngilê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>
using namespace std;
int main() {
for (int i = 0; i < 3; ++i) {
for (int j = 0; j < 3; ++j) {
cout << "* ";
}
cout << endl;
}
}
Output:
* * *
* * *
* * *
Giải thích:
- Vòng lặp ngoài chạy 3 lần (
itừ 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 (
jtừ 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.
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ệunê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>
#include <string>
using namespace std;
int main() {
int a[] = {10, 20, 30, 40, 50};
cout << "Mang:" << endl;
for (int x : a) {
cout << x << " ";
}
cout << endl << endl;
vector<string> ds_ten = {"Alice", "Bob", "Charlie"};
cout << "Vector:" << endl;
for (const auto& s : ds_ten) {
cout << s << " ";
}
cout << endl << endl;
string chuoi = "Xin chao!";
cout << "Chuoi:" << endl;
for (char c : chuoi) {
cout << c << "-";
}
cout << endl;
}
Output:
Mang:
10 20 30 40 50
Vector:
Alice Bob Charlie
Chuoi:
X-i-n- -c-h-a-o-!-
Giải thích:
- Vòng lặp đầu tiên duyệt qua từng phần tử kiểu
inttrong mảnga. Biếnxlầ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
stringtrong vectords_ten. Việc sử dụngconst 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
chartrong chuỗichuoi.
Ư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
fortruyề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
fortruyề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> using namespace std; int main() { cout << "Vd break:" << endl; for (int i = 1; i <= 10; ++i) { if (i == 6) { cout << "Gap 6, thoat." << endl; break; } cout << i << " "; } cout << endl << "Da thoat." << endl; }Output:
Vd break: 1 2 3 4 5 Gap 6, thoat. Da thoat.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 "Gap 6, thoat." và cuối cùng là "Da thoat.".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> using namespace std; int main() { cout << "Vd continue:" << endl; for (int i = 1; i <= 10; ++i) { if (i == 5) { cout << "Bo qua 5..." << endl; continue; } cout << i << " "; } cout << endl << "Ket thuc." << endl; }Output:
Vd continue: 1 2 3 4 Bo qua 5... 6 7 8 9 10 Ket thuc.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ếninữ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ìikhô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
fortruyề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
fortruyề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 forkhi 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 forthườ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