Bài 6.1: Vòng lặp for lồng nhau trong C++

Bài 6.1: Vòng lặp for lồng nhau trong C++
Chào mừng các bạn quay trở lại với chuỗi bài viết về C++ của FullhouseDev!
Trong các bài trước, chúng ta đã làm quen và thành thạo với vòng lặp for
cơ bản, một công cụ không thể thiếu để tự động hóa các tác vụ lặp đi lặp lại. Nhưng cuộc sống (và cả lập trình) không phải lúc nào cũng chỉ đơn giản là lặp một thứ duy nhất. Đôi khi, chúng ta cần lặp các tác vụ mà bản thân chúng lại chứa đựng các tác vụ lặp khác. Nghe hơi "xoắn" đúng không? Đó chính là lúc vòng lặp for
lồng nhau tỏa sáng!
Vòng lặp for
lồng nhau là gì?
Đúng như tên gọi, vòng lặp for
lồng nhau đơn giản là việc đặt một (hoặc nhiều) vòng lặp for
vào bên trong thân của một vòng lặp for
khác.
- Vòng lặp bên ngoài được gọi là vòng lặp ngoài (outer loop).
- Vòng lặp bên trong được gọi là vòng lặp trong (inner loop).
Cấu trúc cơ bản sẽ trông như thế này:
for (khoi_tao_ngoai; dieu_kien_lap_ngoai; cap_nhat_ngoai) {
// Code trước vòng lặp trong (tùy chọn)
for (khoi_tao_trong; dieu_kien_lap_trong; cap_nhat_trong) {
// Code của vòng lặp trong
// Đoạn code này sẽ được thực thi LẦN LƯỢT cho MỌI lần lặp của vòng lặp ngoài
}
// Code sau vòng lặp trong (tùy chọn)
}
Cách thức hoạt động
Đây là điểm quan trọng nhất cần nắm vững:
- Vòng lặp ngoài bắt đầu thực hiện lần lặp đầu tiên.
- Khi đến phần thân của vòng lặp ngoài, chương trình gặp vòng lặp trong.
- Vòng lặp trong sẽ thực hiện TOÀN BỘ các lần lặp của nó, từ đầu đến cuối điều kiện lặp của nó.
- Sau khi vòng lặp trong kết thúc, chương trình tiếp tục các lệnh còn lại trong thân vòng lặp ngoài (nếu có).
- Vòng lặp ngoài kết thúc lần lặp hiện tại và chuyển sang lần lặp tiếp theo (nếu điều kiện còn đúng).
- Quá trình lặp lại từ bước 2: vòng lặp trong lại được thực thi TOÀN BỘ một lần nữa cho lần lặp mới của vòng lặp ngoài.
- Cứ thế tiếp diễn cho đến khi vòng lặp ngoài kết thúc.
Nói cách khác, nếu vòng lặp ngoài lặp N
lần và vòng lặp trong lặp M
lần, thì tổng cộng các lệnh bên trong vòng lặp trong sẽ được thực thi N * M
lần. Đây chính là sức mạnh (và đôi khi là điểm cần lưu ý về hiệu suất) của vòng lặp lồng nhau.
Ví dụ minh họa: Vẽ hình chữ nhật bằng ký tự
Một trong những ứng dụng kinh điển và dễ hiểu nhất của vòng lặp for
lồng nhau là vẽ các hình dạng 2D trên console. Hãy cùng vẽ một hình chữ nhật đơn giản bằng ký tự '*'
.
Chúng ta cần:
- Một vòng lặp ngoài để điều khiển số hàng (rows).
- Một vòng lặp trong để điều khiển số cột (columns) trên mỗi hàng.
#include <iostream> // Để dùng cout, endl
int main() {
int soHang = 5; // Số hàng của hình chữ nhật
int soCot = 10; // Số cột của hình chữ nhật
cout << "Ve hinh chu nhat:" << endl;
// Vòng lặp ngoài: Duyệt qua từng hàng
for (int i = 0; i < soHang; ++i) {
// Vòng lặp trong: Duyệt qua từng cột trên hàng hiện tại
for (int j = 0; j < soCot; ++j) {
cout << "* "; // In một ký tự và khoảng trắng
}
cout << endl; // Kết thúc một hàng, xuống dòng
}
return 0;
}
Giải thích:
- Vòng lặp ngoài với biến
i
chạy từ 0 đếnsoHang - 1
(tức là 5 lần). Mỗi lần lặp củai
tương ứng với việc chúng ta đang xử lý một hàng mới. - Bên trong vòng lặp ngoài, vòng lặp với biến
j
chạy từ 0 đếnsoCot - 1
(tức là 10 lần). Vòng lặp này in ra ký tự'* '
cho mỗi cột trên hàng hiện tại. - Sau khi vòng lặp trong (duyệt cột) kết thúc (nghĩa là đã in xong tất cả các ký tự trên một hàng), lệnh
cout << endl;
sẽ đưa con trỏ xuống dòng mới, sẵn sàng để vòng lặp ngoài bắt đầu in hàng tiếp theo.
Kết quả sẽ là một hình chữ nhật 5x10 các ký tự *
. Đơn giản nhưng hiệu quả!
Ví dụ minh họa: Vẽ hình tam giác vuông
Bây giờ, hãy thử một hình dạng phức tạp hơn một chút: tam giác vuông. Điểm khác biệt ở đây là số ký tự trên mỗi hàng không cố định mà phụ thuộc vào số hàng.
#include <iostream>
int main() {
int kichThuoc = 5; // Kích thước cạnh của tam giác (số hàng)
cout << "Ve hinh tam giac vuong:" << endl;
// Vòng lặp ngoài: Duyệt qua từng hàng
for (int i = 0; i < kichThuoc; ++i) {
// Vòng lặp trong: Duyệt qua số cột bằng với số hàng hiện tại + 1
// (Hàng 0 có 1 ký tự, hàng 1 có 2 ký tự, ..., hàng i có i+1 ký tự)
for (int j = 0; j <= i; ++j) {
cout << "* ";
}
cout << endl; // Xuống dòng sau khi in xong một hàng
}
return 0;
}
Giải thích:
- Vòng lặp ngoài với
i
vẫn chạy từ 0 đếnkichThuoc - 1
. Biếni
lúc này không chỉ là số hàng mà còn đại diện cho "độ cao" hiện tại. - Vòng lặp trong với
j
chạy từ 0 đếni
. Điều này có nghĩa là:- Khi
i
= 0 (hàng đầu tiên),j
chạy từ 0 đến 0 (1 lần). In 1 ký tự. - Khi
i
= 1 (hàng thứ hai),j
chạy từ 0 đến 1 (2 lần). In 2 ký tự. - Khi
i
= 4 (hàng cuối cùng vớikichThuoc
=5),j
chạy từ 0 đến 4 (5 lần). In 5 ký tự.
- Khi
- Kết quả là một tam giác vuông với cạnh bằng
kichThuoc
.
Bạn thấy đấy, chỉ cần thay đổi điều kiện lặp hoặc số lần lặp của vòng lặp trong dựa trên biến đếm của vòng lặp ngoài, chúng ta có thể tạo ra nhiều hình dạng khác nhau!
Ví dụ minh họa: Bảng cửu chương
Một ví dụ thực tế hơn là tạo ra bảng cửu chương. Chúng ta cần lặp qua các số nhân (1 đến 9) và cho mỗi số nhân, lặp qua các số bị nhân (1 đến 9) để tính tích.
#include <iostream>
#include <iomanip> // Để dùng setw
int main() {
cout << "Bang cuu chuong 1-9:" << endl;
// Vòng lặp ngoài: Số nhân (từ 1 đến 9)
for (int i = 1; i <= 9; ++i) {
// Vòng lặp trong: Số bị nhân (từ 1 đến 9)
for (int j = 1; j <= 9; ++j) {
// In kết quả: i * j
// setw(4) giúp căn chỉnh các số, làm bảng dễ đọc hơn
cout << setw(4) << i * j;
}
cout << endl; // Xuống dòng sau mỗi "dãy" cửu chương (ví dụ: hết 1x1 -> 1x9)
}
return 0;
}
Giải thích:
- Vòng lặp ngoài (
i
) chạy từ 1 đến 9, đại diện cho các số ở cột đầu tiên của phép nhân (ví dụ: 1xJ, 2xJ, ...). - Vòng lặp trong (
j
) chạy từ 1 đến 9, đại diện cho các số ở hàng thứ hai của phép nhân (ví dụ: Ix1, Ix2, ...). - Bên trong vòng lặp trong, chúng ta tính
i * j
và in ra. setw(4)
là một tiện ích từ thư viện<iomanip>
giúp định dạng đầu ra, đảm bảo mỗi số chiếm ít nhất 4 ký tự, giúp các cột trong bảng được căn thẳng hàng.- Sau khi vòng lặp trong kết thúc (đã in xong một dãy cửu chương, ví dụ từ 2x1 đến 2x9), chúng ta xuống dòng bằng
endl
.
Kết quả là một bảng 9x9 hiển thị kết quả của phép nhân từ 1x1 đến 9x9. Rất hữu ích phải không?
Ví dụ minh họa: Duyệt mảng 2 chiều (Matrix)
Một ứng dụng cực kỳ phổ biến khác của vòng lặp lồng nhau là làm việc với các cấu trúc dữ liệu hai chiều, chẳng hạn như mảng hai chiều (hoặc ma trận). Mảng 2 chiều có cấu trúc hàng và cột, rất phù hợp để duyệt bằng hai vòng lặp lồng nhau.
#include <iostream>
int main() {
// Khai báo và khởi tạo một mảng 2 chiều 3x4
int matrix[3][4] = {
{1, 2, 3, 4},
{5, 6, 7, 8},
{9, 10, 11, 12}
};
int soHang = 3; // Số hàng
int soCot = 4; // Số cột
cout << "Duyet mang 2 chieu:" << endl;
// Vòng lặp ngoài: Duyệt qua các hàng (chỉ số hàng)
for (int i = 0; i < soHang; ++i) {
// Vòng lặp trong: Duyệt qua các cột trong hàng hiện tại (chỉ số cột)
for (int j = 0; j < soCot; ++j) {
// Truy cập và in phần tử tại hàng i, cột j
cout << matrix[i][j] << "\t"; // In phần tử và tab để căn chỉnh
}
cout << endl; // Xuống dòng sau khi in xong một hàng
}
return 0;
}
Giải thích:
- Vòng lặp ngoài với
i
chạy từ 0 đếnsoHang - 1
, đại diện cho chỉ số của từng hàng. - Vòng lặp trong với
j
chạy từ 0 đếnsoCot - 1
, đại diện cho chỉ số của từng cột trong hàng hiện tại (i
). - Bên trong vòng lặp trong, chúng ta sử dụng
matrix[i][j]
để truy cập đến từng phần tử của mảng 2 chiều dựa trên chỉ số hàngi
và chỉ số cộtj
hiện tại.
Đây là cách tiêu chuẩn để duyệt qua tất cả các phần tử trong một mảng 2 chiều trong C++.
Một vài lưu ý khi sử dụng vòng lặp lồng nhau
- Hiệu suất: Vòng lặp lồng nhau có độ phức tạp thường là O(NM). Nếu N và M rất lớn, tổng số lần thực thi bên trong vòng lặp trong sẽ tăng lên rất nhanh, có thể ảnh hưởng đến hiệu suất chương trình. Hãy cẩn trọng* khi sử dụng chúng với dữ liệu lớn và cân nhắc các giải thuật hiệu quả hơn nếu cần.
- Độ rõ ràng (Readability): Với nhiều lớp lồng nhau (ví dụ: 3 hoặc 4 vòng lặp lồng nhau), code có thể trở nên khó đọc và khó hiểu. Cố gắng giữ số lớp lồng nhau ở mức tối thiểu và sử dụng tên biến có ý nghĩa (
i
,j
thường dùng cho chỉ số hàng/cột nhưng hãy đặt tên rõ ràng hơn nếu ngữ cảnh phức tạp). break
vàcontinue
: Lệnhbreak
vàcontinue
bên trong vòng lặp lồng nhau chỉ ảnh hưởng đến vòng lặp gần nhất mà chúng nằm trong. Ví dụ, mộtbreak
bên trong vòng lặp trong sẽ chỉ thoát khỏi vòng lặp trong đó, không ảnh hưởng đến vòng lặp ngoài. Để thoát khỏi cả hai vòng lặp, bạn thường cần sử dụng thêm biến cờ hoặc lệnhgoto
(mặc dùgoto
nên hạn chế sử dụng).
Tóm lại
Vòng lặp for
lồng nhau là một công cụ mạnh mẽ và linh hoạt trong C++. Chúng cho phép chúng ta xử lý các vấn đề có cấu trúc lặp đa chiều, từ việc in hình đơn giản đến duyệt qua các ma trận phức tạp. Nắm vững cách chúng hoạt động và thực hành với các ví dụ sẽ giúp bạn mở rộng khả năng giải quyết vấn đề của mình đáng kể.
Comments