Vòng lặp For
Vòng lặp này được thực hiện bởi một câu lệnh với từ khóa for
, do đó có tên gọi như vậy. Ở dạng tổng quát, nó có thể được mô tả như sau:
for ([initialization]; [condition]; [expression])
loop body
2
Trong tiêu đề, sau từ for
, các thành phần sau được chỉ ra trong dấu ngoặc đơn:
- Khởi tạo (Initialization): Một câu lệnh để khởi tạo một lần trước khi vòng lặp bắt đầu;
- Điều kiện (Condition): Một điều kiện kiểu boolean được kiểm tra ở đầu mỗi lần lặp, và vòng lặp chạy miễn là điều kiện này đúng (
true
); - Biểu thức (Expression): Công thức tính toán được thực hiện vào cuối mỗi lần lặp, khi tất cả các câu lệnh trong thân vòng lặp đã được thực thi.
Thân vòng lặp là một câu lệnh đơn giản hoặc phức hợp.
Cả ba thành phần trong tiêu đề đều là tùy chọn và có thể được bỏ qua ở bất kỳ tổ hợp nào, bao gồm cả việc không có thành phần nào.
Khởi tạo có thể bao gồm khai báo biến (cùng với việc đặt giá trị ban đầu) hoặc gán giá trị cho các biến đã tồn tại. Những biến như vậy được gọi là biến vòng lặp. Nếu chúng được khai báo trong tiêu đề, thì phạm vi và thời gian sống của chúng bị giới hạn trong vòng lặp.
Vòng lặp bắt đầu thực thi nếu sau khi khởi tạo, điều kiện là true
, và tiếp tục thực thi miễn là điều kiện này đúng ở đầu mỗi lần lặp tiếp theo. Nếu trong lần kiểm tra tiếp theo, điều kiện bị vi phạm, vòng lặp sẽ thoát ra, tức là quyền điều khiển được chuyển đến câu lệnh được viết sau vòng lặp và thân của nó. Nếu điều kiện là false
trước khi vòng lặp bắt đầu (sau khi khởi tạo), nó sẽ không bao giờ được thực thi.
Điều kiện và biểu thức thường bao gồm các biến vòng lặp.
Thực thi một vòng lặp có nghĩa là thực thi thân của nó.
Dạng phổ biến nhất của vòng lặp for
có một biến vòng lặp duy nhất kiểm soát số lần lặp. Trong ví dụ sau, chúng ta tính bình phương của các số trong mảng a
.
int a[] = {1, 2, 3, 4, 5, 6, 7};
const int n = ArraySize(a);
for(int i = 0; i < n; ++i)
a[i] = a[i] * a[i];
ArrayPrint(a); // 1 4 9 16 25 36 49
// Print(i); // lỗi: 'i' - chưa được khai báo
2
3
4
5
6
Vòng lặp này được thực thi theo các bước sau:
- Một biến
i
với giá trị ban đầu là 0 được tạo ra. - Điều kiện được kiểm tra xem biến
i
có nhỏ hơn kích thước của vòng lặpn
hay không. Miễn là điều kiện đúng, vòng lặp tiếp tục. Nếu sai, chúng ta nhảy đến câu lệnh gọi hàmArrayPrint
. - Nếu điều kiện đúng, các câu lệnh trong thân vòng lặp được thực thi. Trong trường hợp này, phần tử thứ
i
của mảng nhận giá trị tích của giá trị ban đầu của phần tử này với chính nó, tức là giá trị của mỗi phần tử được thay thế bằng bình phương của nó. - Biến
i
được tăng lên 1.
Sau đó, mọi thứ lặp lại, bắt đầu từ bước 2. Sau khi thoát khỏi vòng lặp, biến của nó i
bị hủy, và việc cố gắng truy cập nó sẽ gây ra lỗi.
Biểu thức cho bước 4 có thể có độ phức tạp tùy ý, không chỉ là tăng biến vòng lặp. Ví dụ, để lặp qua các phần tử chẵn hoặc lẻ, ta có thể viết i += 2
.
Bất kể thân vòng lặp gồm bao nhiêu câu lệnh, nên viết nó trên một dòng (hoặc các dòng) riêng biệt với tiêu đề. Điều này giúp quá trình gỡ lỗi từng bước dễ dàng hơn.
Khởi tạo có thể bao gồm nhiều khai báo biến, nhưng chúng phải cùng loại vì chúng là một câu lệnh. Ví dụ, để sắp xếp lại các phần tử theo thứ tự ngược lại, bạn có thể viết vòng lặp như sau (đây chỉ là minh họa cho vòng lặp, có hàm tích hợp sẵn ArrayReverse
để đảo ngược thứ tự trong mảng, xem Sao chép và chỉnh sửa mảng):
for(int i = 0, j = n - 1; i < n / 2; ++i, --j)
{
int temp = a[i];
a[i] = a[j];
a[j] = temp;
}
ArrayPrint(a); // 49 36 25 16 9 4 1
2
3
4
5
6
7
Biến phụ trợ temp
được tạo và hủy trong mỗi lần lặp của vòng lặp, nhưng trình biên dịch chỉ cấp phát bộ nhớ cho nó một lần, như đối với tất cả các biến cục bộ, khi vào hàm. Tối ưu hóa này hoạt động tốt với các loại tích hợp sẵn. Tuy nhiên, nếu một đối tượng lớp tùy chỉnh được mô tả trong vòng lặp, thì hàm khởi tạo và hàm hủy của nó sẽ được gọi ở mỗi lần lặp.
Có thể thay đổi biến vòng lặp trong thân vòng lặp, nhưng kỹ thuật này chỉ được sử dụng trong những trường hợp rất đặc biệt. Không nên làm điều này, vì có thể gây ra lỗi (đặc biệt, các phần tử đã xử lý có thể bị bỏ qua hoặc thực thi có thể rơi vào vòng lặp vô hạn).
Để minh họa khả năng bỏ qua các thành phần tiêu đề, hãy tưởng tượng bài toán sau: Chúng ta cần tìm số lượng phần tử của cùng một mảng có tổng nhỏ hơn 100. Để làm điều này, chúng ta cần một biến đếm k
được định nghĩa trước vòng lặp vì nó phải tiếp tục tồn tại sau khi vòng lặp hoàn thành. Chúng ta cũng sẽ tạo biến sum
để tính tổng theo cách tích lũy.
int k = 0, sum = 0;
for( ; sum < 100; )
{
sum += a[k++];
}
Print(k - 1, " ", sum - a[k - 1]); // 2 85
2
3
4
5
6
Do đó, không cần thực hiện khởi tạo trong tiêu đề. Ngoài ra, bộ đếm k
được tăng bằng cách sử dụng tăng hậu tố trực tiếp trong biểu thức tính tổng (khi truy cập phần tử mảng). Vì vậy, chúng ta không cần biểu thức trong tiêu đề.
Khi vòng lặp kết thúc, chúng ta in ra k
và tổng trừ đi phần tử cuối cùng được thêm vào, vì đó là phần tử vượt quá giới hạn 100 của chúng ta.
Lưu ý rằng chúng ta sử dụng khối phức hợp mặc dù chỉ có một câu lệnh trong thân vòng lặp. Điều này hữu ích vì khi chương trình phát triển, mọi thứ đã sẵn sàng để thêm các câu lệnh bổ sung bên trong dấu ngoặc. Ngoài ra, cách tiếp cận này đảm bảo phong cách thống nhất cho tất cả các vòng lặp. Nhưng lựa chọn, trong mọi trường hợp, thuộc về lập trình viên.
Trong phiên bản rõ ràng, được rút ngắn tối đa, tiêu đề vòng lặp có thể trông như sau:
for( ; ; )
{
// ... // các hành động định kỳ
Sleep(1000); // tạm dừng chương trình trong 1 giây
}
2
3
4
5
Nếu không có câu lệnh nào trong thân của vòng lặp này làm gián đoạn vòng lặp do một số điều kiện, nó sẽ được thực thi vô thời hạn. Chúng ta sẽ tìm hiểu cách ngắt và kiểm tra điều kiện trong Break jump và If selection tương ứng.
Các thuật toán lặp như vậy thường được sử dụng trong các dịch vụ (được thiết kế để làm việc nền liên tục) để theo dõi trạng thái của terminal hoặc tài nguyên mạng bên ngoài. Chúng thường chứa các câu lệnh tạm dừng chương trình theo khoảng thời gian được chỉ định, ví dụ, sử dụng hàm tích hợp sẵn Sleep
. Nếu không có biện pháp phòng ngừa này, một vòng lặp vô hạn sẽ tải 100% một lõi xử lý.
Script StmtLoopsFor.mq5
chứa một vòng lặp vô hạn ở cuối, nhưng nó chỉ nhằm mục đích minh họa.
for( ; ; )
{
Comment(GetTickCount());
Sleep(1000); // 1000 ms
// vòng lặp chỉ có thể thoát bằng cách xóa script theo lệnh của người dùng
// sau 3 giây chờ đợi, chúng ta sẽ nhận được thông báo 'Abnormal termination'
}
Comment(""); // dòng này sẽ không bao giờ được thực thi
2
3
4
5
6
7
8
9
Trong vòng lặp, cứ mỗi giây, bộ đếm thời gian nội bộ của máy tính (GetTickCount
) được hiển thị bằng hàm Comment
: giá trị được hiển thị ở góc trên bên trái của biểu đồ. Chỉ người dùng mới có thể ngắt vòng lặp bằng cách xóa toàn bộ script khỏi biểu đồ (nút "Delete" trong hộp thoại Experts). Mã này không kiểm tra các yêu cầu dừng của người dùng bên trong vòng lặp, mặc dù có hàm tích hợp sẵn IsStopped
cho mục đích này. Nó trả về true
nếu người dùng đã ra lệnh dừng. Trong chương trình, đặc biệt nếu có vòng lặp và các phép tính dài hạn, nên kiểm tra giá trị của hàm này và tự nguyện chấm dứt vòng lặp cũng như toàn bộ chương trình khi nhận được true
. Nếu không, terminal sẽ buộc chấm dứt script sau 3 giây chờ đợi (với đầu ra vào nhật ký "Abnormal termination"), điều này sẽ xảy ra trong ví dụ này.
Phiên bản tốt hơn của vòng lặp này nên là:
for( ; !IsStopped(); ) // tiếp tục cho đến khi người dùng ngắt
{
Comment(GetTickCount());
Sleep(1000); // 1000 ms
}
Comment(""); // sẽ xóa bình luận
2
3
4
5
6
Tuy nhiên, vòng lặp này sẽ được triển khai tốt hơn bằng một câu lệnh lặp khác while
. Theo nguyên tắc chung, vòng lặp for
chỉ nên được sử dụng khi có một biến vòng lặp rõ ràng và/hoặc số lần lặp được xác định trước. Trong trường hợp này, các điều kiện đó không được đáp ứng.
Các biến vòng lặp thường là số nguyên, mặc dù các loại khác cũng được phép, chẳng hạn như double
. Điều này là do logic hoạt động của vòng lặp ngụ ý việc đánh số các lần lặp. Ngoài ra, luôn có thể tính toán các số thực cần thiết từ một chỉ số nguyên, với độ chính xác cao hơn. Ví dụ, vòng lặp sau lặp qua các giá trị từ 0.0 đến 1.0 với bước tăng 0.01:
for(double x = 0.0; x < 1.0; x += 0.01) { ... }
Nó có thể được thay thế bằng một vòng lặp tương tự với biến nguyên:
for(int i = 0; i < 100; ++i) { double x = i * 0.01; ... }
Trong trường hợp đầu tiên, khi cộng x += 0.01
, sai số của phép tính dấu phẩy động dần tích lũy. Trong trường hợp thứ hai, mỗi giá trị x
được tính trong một phép toán i * 0.01
, với độ chính xác tối đa có sẵn.
Thông thường, các biến vòng lặp được đặt tên bằng các chữ cái đơn như i, j, k, m, p, q
. Nhiều tên được yêu cầu khi các vòng lặp lồng nhau hoặc khi tính toán cả chỉ số tăng dần và giảm dần trong cùng một vòng lặp.
Nhân tiện, đây là một ví dụ về vòng lặp lồng nhau. Mã sau tính toán và lưu trữ bảng cửu chương trong một mảng hai chiều.
int table[10][10] = {0};
for(int i = 1; i <= 10; ++i)
{
for(int j = 1; j <= 10; ++j)
{
table[i - 1][j - 1] = i * j;
}
}
ArrayPrint(table);
2
3
4
5
6
7
8
9