Biến tĩnh
Đôi khi cần phải mô tả một biến bên trong một hàm, đảm bảo sự tồn tại của nó trong toàn bộ thời gian thực thi chương trình. Ví dụ, chúng ta muốn đếm số lần hàm này được gọi.
Một biến như vậy không thể là biến cục bộ, vì khi đó nó sẽ mất "bộ nhớ dài-long" vì nó sẽ được tạo ra mỗi lần gọi hàm và bị xóa khi thoát khỏi hàm. Về mặt kỹ thuật, nó có thể được mô tả toàn cục; tuy nhiên, nếu biến chỉ được sử dụng trong hàm này, thì cách tiếp cận này là sai về mặt thiết kế chương trình.
Đầu tiên, biến toàn cục có thể vô tình bị thay đổi ở bất kỳ vị trí nào trong chương trình.
Thứ hai, hãy tưởng tượng xem "sở thú" biến nào sẽ được tạo ra trong vùng toàn cục của chương trình nếu chúng ta khai báo một biến toàn cục với lý do nhỏ nhất. Thay vào đó, nên khai báo các biến trong khối nhỏ nhất (nếu có nhiều khối lồng nhau) mà chúng được sử dụng.
Do đó, bộ đếm thực thi hàm phải được mô tả bên trong hàm. Đây là nơi thuộc tính mới của biến giúp ích, bản chất tĩnh của chúng.
Một từ khóa đặc biệt (modifier), static
, được đặt trước kiểu biến trong khai báo của nó cho phép kéo dài thời gian tồn tại của nó lên đến toàn bộ thời gian thực thi chương trình, tức là làm cho nó giống với các biến toàn cục. Theo quy tắc, một biến tĩnh chỉ được định nghĩa cục bộ, trong một trong các hàm. Do đó, khả năng hiển thị của nó bị giới hạn bởi khối mã có liên quan, như trong một biến cục bộ thông thường.
Biến tĩnh cũng có thể được mô tả ở cấp độ toàn cục, nhưng không khác gì so với biến toàn cục thông thường theo bất kỳ cách nào (ít nhất là tính đến thời điểm viết cuốn sách này). Nó khác với hành vi của chúng trong C++: Ở đó, khả năng hiển thị của chúng bị giới hạn bởi tệp mà chúng được mô tả. Trong MQL5, một chương trình được biên dịch dựa trên một tệp mq5 chính và có thể là một số tệp tiêu đề (xem chỉ thị #include); do đó, cả biến toàn cục tĩnh và biến toàn cục thông thường đều có sẵn trong tất cả các tệp nguồn của chương trình.
Biến tĩnh cục bộ chỉ được tạo một lần — tại thời điểm chương trình lần đầu tiên bước vào hàm nơi biến này được mô tả. Biến như vậy sẽ chỉ bị xóa khi dỡ chương trình. Nếu một hàm chưa bao giờ được gọi, các biến tĩnh cục bộ được mô tả trong hàm đó, nếu có, sẽ không bao giờ được tạo.
Ví dụ, hãy sửa đổi hàm Greeting
từ Phần 1 để nó đưa ra lời chào khác nhau ở mỗi lần gọi. Hãy đặt tên cho tập lệnh mới là GoodTimes.mq5
.
Chúng ta sẽ xóa đầu vào của tập lệnh GreetingHour
và tham số của hàm Greeting
. Bên trong hàm Greeting
, chúng ta sẽ mô tả một biến tĩnh mới, counter
, kiểu số nguyên, với giá trị khởi tạo là 0
. Cần lưu ý rằng đây chính xác là khởi tạo và nó sẽ chỉ được thực thi một lần vì biến là tĩnh.
string Greeting()
{
static int counter = 0;
static string messages[3] =
{
"Good morning", "Good day", "Good evening"
};
return messages[counter++ % 3];
}
2
3
4
5
6
7
8
9
Vì chúng ta biết trình sửa đổi static
bây giờ, nên cũng hợp lý khi sử dụng nó cho mảng messages
. Vấn đề là nó đã được khai báo là cục bộ trước đó và nó sẽ được tạo lại mỗi lần tại nhiều lần gọi hàm Greeting
(và bị xóa khi thoát). Điều này không hiệu quả.
Cần lưu ý rằng mảng là một tập hợp được đặt tên gồm một số giá trị cùng loại, có sẵn theo chỉ mục được chỉ định trong dấu ngoặc vuông sau tên. Phần lớn những gì đã nói về biến áp dụng trực tiếp cho mảng. Các sắc thái khác khi làm việc với mảng sẽ được đề cập trong phần Mảng
Nhưng hãy quay lại vấn đề hiện tại của chúng ta. Một tùy chọn được chọn từ mảng dựa trên giá trị của biến đếm trong câu lệnh return
và cho đến nay có vẻ khá là huyền bí:
return messages[counter++ % 3];
Chúng ta đã đề cập một cách thông thường về phép toán lấy phần dư được thực hiện bằng ký tự %
trong Phần 1. Với phép toán này, chúng ta đảm bảo rằng chỉ số phần tử sẽ không thể vượt quá kích thước mảng: Dù là số nào đi nữa, phép chia lấy phần dư cho 3 sẽ bằng 0 hoặc 1, hoặc 2.
Điều tương tự cũng áp dụng cho cấu trúc counter++
, nghĩa là thêm 1 vào giá trị biến (tăng đơn).
Điều quan trọng cần lưu ý là, trong ký hiệu này, việc tăng dần sẽ diễn ra sau khi tính toán toàn bộ biểu thức, trong trường hợp này, sau khi chia counter % 3
. Điều này có nghĩa là việc đếm sẽ bắt đầu từ số không, tức là giá trị ban đầu. Có khả năng thực hiện một phép tăng dần trước khi tính toán biểu thức, sau khi viết: ++counter % 3
. Khi đó, việc đếm sẽ bắt đầu từ 1. Chúng ta sẽ xem xét các phép toán thuộc loại này trong phần Tăng dần và Giảm dần.
Hãy gọi hàm Greeting từ OnStart
3 lần liên tiếp.
void OnStart()
{
Print(Greeting(), ", ", Symbol());
Print(Greeting(), ", ", Symbol());
Print(Greeting(), ", ", Symbol());
// Print(counter); // lỗi: 'counter' - mã số nhận dạng chưa khai báo
}
2
3
4
5
6
7
Kết quả là, chúng ta sẽ thấy ba chuỗi ký tự dự kiến với tất cả lời chào lần lượt xuất hiện trong nhật ký.
GoodTimes (EURUSD,H1) Good morning, EURUSD
GoodTimes (EURUSD,H1) Good afternoon, EURUSD
GoodTimes (EURUSD,H1) Good evening, EURUSD
2
3
Nếu chúng ta tiếp tục gọi hàm, bộ đếm sẽ tăng lên và các tin nhắn sẽ xoay vòng.
Cố gắng tham chiếu đến biến đếm ở cuối OnStart
(đã chú thích) sẽ không cho phép biên dịch mã, vì biến tĩnh, mặc dù vẫn tồn tại, chỉ khả dụng bên trong hàm Greeting
.
WARNING
Xin lưu ý rằng dấu ngoặc nhọn {}
được sử dụng cho cả việc tạo khối mã và khởi tạo mảng. Bạn nên phân biệt giữa các ứng dụng của chúng. Mảng sẽ được xem xét chi tiết trong phần có liên quan. Tuy nhiên, đây không phải là tất cả các ứng dụng của dấu ngoặc nhọn: Sử dụng chúng, sau này chúng ta sẽ tìm hiểu cách định nghĩa các kiểu, cấu trúc và lớp tùy chỉnh. Biến tĩnh cũng có thể được định nghĩa bên trong các cấu trúc và lớp.