Biến bên ngoài
WARNING
Tài liệu trong phần này vừa phức tạp vừa tùy chọn. Nó đòi hỏi kiến thức về các khái niệm dựa trên phép loại suy với C++
và các khái niệm được xem xét dưới đây. Đồng thời, hiệu ứng của cấu trúc ngôn ngữ được mô tả có thể đạt được theo cách khác, trong khi tính linh hoạt của nó là một nguồn tiềm ẩn của lỗi.
MQL5 cho phép mô tả các biến như các biến bên ngoài. Điều này được thực hiện bằng cách sử dụng từ khóa extern
và chỉ được phép trong ngữ cảnh toàn cục.
Đối với biến bên ngoài, cú pháp về cơ bản lặp lại mô tả bình thường nhưng nó cũng có từ khóa extern
trong khi việc khởi tạo bị cấm:
extern type identifier;
Việc mô tả một biến là biến bên ngoài có nghĩa là mô tả của biến đó bị trì hoãn và phải diễn ra sau trong mã nguồn, thường là trong một tệp khác (việc kết nối các tệp bằng lệnh #include
sẽ được xem xét trong chương nói về bộ tiền xử lý). Một số tệp nguồn khác nhau có thể có mô tả về cùng một biến bên ngoài, tức là các tệp có kiểu và định danh giống hệt nhau. Tất cả các mô tả như vậy đều tham chiếu đến cùng một biến.
Giả sử biến này sẽ được mô tả đầy đủ trong một trong các tệp. Nếu biến không được định nghĩa ở bất kỳ đâu trong mã mà không có từ khóa extern
, lỗi biên dịch "unresolved extern variable-biến extern chưa được giải quyết" sẽ được trả về (tương tự như lỗi liên kết trong C++ trong những trường hợp như vậy).
Mô tả một biến bên ngoài cho phép sử dụng nó một cách hiệu quả trong mã nguồn của một tệp cụ thể. Nói cách khác, nó cho phép biên dịch một mô-đun nhất định, mặc dù biến không được tạo trong mô-đun này.
Sử dụng extern
trong MQL5 không quá cấp thiết như trong C++ và trong hầu hết các trường hợp, có thể thay thế bằng cách cho phép tệp tiêu đề có mô tả chung về các biến được khai báo là extern
. Chỉ cần thực hiện các định nghĩa này theo cách thông thường là đủ. Trình biên dịch đảm bảo thêm mỗi tệp đính kèm vào mã nguồn chỉ một lần. Xem xét rằng trong MQL5, một chương trình luôn bao gồm một đơn vị có thể biên dịch được mq5, không có vấn đề C++ nào ở đây, với lỗi tiềm ẩn của nhiều định nghĩa của cùng một biến do cho phép tiêu đề ở các đơn vị khác nhau.
Ngay cả khi tệp mq5 bổ sung (không phải mqh
) được đính kèm trong chỉ thị #include
, nó cũng không cạnh tranh bình đẳng với đơn vị chính, đơn vị mà quá trình biên dịch được khởi chạy; thay vào đó, nó được coi là một trong các tiêu đề.
Không giống như C++, MQL5 không cho phép chỉ định giá trị ban đầu cho biến bên ngoài (khởi tạo trong C++ dẫn đến bỏ qua từ extern
). Nếu bạn cố gắng đặt giá trị ban đầu, bạn sẽ nhận được lỗi biên dịch "extern variable initialization is not allowed".
Nhìn chung, việc mô tả một biến là bên ngoài có thể được coi là một dạng mô tả "mềm": Nó đảm bảo giao diện của biến và loại trừ lỗi ghi đè có thể xảy ra nếu biến được mô tả trong nhiều tệp mà không có trình sửa đổi extern .
Tuy nhiên, đây có thể là nguồn lỗi. Nếu trong các tệp tiêu đề khác nhau, do trùng hợp ngẫu nhiên, các biến giống hệt nhau được mô tả cho các mục đích khác nhau, thì không có từ khóa extern
nào cho phép xác định xung đột, trong khi với extern
, các biến sẽ trở thành một và logic hoạt động của chương trình rất có thể sẽ bị hỏng.
Là bên ngoài, cả biến và hàm đều có thể được mô tả (chúng sẽ được xem xét bên dưới). Đối với các hàm, việc mô tả chúng bằng thuộc tính là bên ngoài là một điều cơ bản (tức là nó được biên dịch, nhưng không thực hiện bất kỳ thay đổi nào). Hai khai báo sau đây của một hàm là tương đương:
extern return_type name([parameters]);
return_type name([parameters]);
2
Theo nghĩa này, sự có mặt/vắng mặt của extern
chỉ có thể được sử dụng để phân biệt về mặt phong cách giữa mô tả về phía trước của một chức năng từ đơn vị hiện tại (không có extern
) hoặc từ mô tả bên ngoài ( có extern
).
Bạn có thể sử dụng extern
trong cả đơn vị mq5 cần biên dịch và tệp tiêu đề cần đính kèm.
Hãy xem xét một số tùy chọn để sử dụng extern
: Chúng được nhập vào các tệp khác nhau, tức là tập lệnh chính ExternMain.mq5
và 3 tệp đính kèm: ExternHeader1.mqh
, ExternHeader2.mqh
và ExternCommon.mqh
.
Trong tệp chính, chỉ có ExternHeader1.mqh
và ExternHeader2.mqh
được đính kèm, trong khi chúng ta sẽ cần ExternCommon.mqh
sau một chút.
// mã nguồn từ các tệp mqh sẽ được thay thế ngầm định
// trong tệp mq5 chính, thay vì các chỉ thị này
#include "ExternHeader1.mqh"
#include "ExternHeader2.mqh"
2
3
4
Trong các tệp tiêu đề, hai hàm hữu ích có điều kiện được định nghĩa: Trong tệp đầu tiên, hàm inc
cho biến x
tăng, trong khi trong tệp thứ hai, hàm dec
cho biến x
giảm. Biến x
được mô tả trong cả hai tệp là bên ngoài:
// ExternHeader1.mqh
extern int x;
void inc()
{
x++;
}
// -----------------
// ExternHeader2.mqh
extern int x;
void dec()
{
x--;
}
2
3
4
5
6
7
8
9
10
11
12
13
Nhờ mô tả này, mỗi tệp mqh
được biên dịch theo cách thông thường. Khi chúng được đưa vào tệp mq5
cùng nhau, toàn bộ chương trình cũng được biên dịch.
Nếu biến được định nghĩa trong mỗi tệp mà không có từ extern
, lỗi định nghĩa lại sẽ xảy ra khi biên dịch toàn bộ chương trình. Nếu chúng ta chuyển định nghĩa của x
từ các tệp tiêu đề vào đơn vị chính, các tệp tiêu đề sẽ ngừng được biên dịch (có lẽ không phải là vấn đề đối với ai đó; tuy nhiên, trong các chương trình lớn hơn, các nhà phát triển thích kiểm tra khả năng biên dịch của các bản sửa lỗi ngay lập tức mà không cần biên dịch toàn bộ dự án).
Trong tập lệnh chính, chúng ta định nghĩa một biến (trong trường hợp này, với giá trị ban đầu là 2, trong khi nếu chúng ta không chỉ định giá trị, giá trị mặc định là 0 sẽ được sử dụng) và gọi các hàm hữu ích có điều kiện, cũng như in giá trị x
.
int x = 2;
void OnStart()
{
inc(); // uses x
dec(); // uses x
Print(x); // 2
...
}
2
3
4
5
6
7
8
9
Trong tệp ExternHeader1.mqh
, có mô tả về biến short z
(không có extern
). Một mô tả tương tự được bình luận trong tập lệnh chính. Nếu chúng ta kích hoạt chuỗi này, chúng ta sẽ nhận được lỗi đã đề cập trước đó ("biến đã được định nghĩa"). Điều này được thực hiện để minh họa cho vấn đề tiềm ẩn.
Trong ExternHeader1.mqh
, extern long y
cũng được mô tả. Đồng thời, trong tệp ExternHeader2.mqh
, biến ngoài đồng âm có một kiểu khác: extern short y
. Nếu mô tả sau không được "di chuyển" vào bình luận trước, lỗi không tương thích kiểu ("biến 'y
' đã được định nghĩa với kiểu khác") sẽ xảy ra ở đây. Tóm tắt: Kiểu phải trùng nhau hoặc biến không được là biến ngoài. Nếu cả hai tùy chọn đều không tốt, điều đó có nghĩa là có lỗi trong tên của một trong các biến.
Hơn nữa, cần lưu ý rằng biến y
không được khởi tạo rõ ràng. Tuy nhiên, tập lệnh chính gọi nó thành công và in 0 trong nhật ký:
long y;
void OnStart()
{
...
Print(y); // 0
}
2
3
4
5
6
7
Cuối cùng, có một khả năng được cung cấp trong tập lệnh để thử một phương án thay thế cho các biến đôi bên ngoài, được minh họa bằng biến x
đã biết. Thay vì mô tả extern int x
, mỗi tệp ExternHeader1.mqh
và ExternHeader2.mqh
có thể bao gồm một tiêu đề chung khác, ExternCommon.mqh
, trong đó có mô tả về int x
(không có extern
). Nó trở thành mô tả duy nhất về x
trong dự án.
Chế độ thay thế này để lắp ráp chương trình được bật khi kích hoạt macro USE_INCLUDE_WORKAROUND
: Nó nằm trong phần bình luận ở đầu tập lệnh:
#define USE_INCLUDE_WORKAROUND // this string was in the comment
#include "ExternHeader1.mqh"
#include "ExternHeader2.mqh"
2
3
Trong cấu hình này, các tệp include cụ thể vẫn có thể biên dịch được, cũng như toàn bộ dự án. Trong một dự án thực tế, nếu không sử dụng phương pháp này, tệp mqh chung sẽ được bao gồm trong ExternHeader1.mqh
và ExternHeader2.mqh
vô điều kiện (không có điều kiện USE_INCLUDE_WORKAROUND
). Trong ví dụ này, việc chuyển đổi giữa hai luồng lệnh dựa trên USE_INCLUDE_WORKAROUND
chỉ cần thiết để chứng minh cả hai chế độ. Ví dụ, phiên bản đơn giản hóa của ExternHeader2.mqh
sẽ xuất hiện như sau:
// ExternHeader2.mqh
#include "ExternCommon.mqh" // int x; now here
void dec()
{
x--;
}
2
3
4
5
6
7
Chúng ta có thể kiểm tra trong nhật ký MetaEditor rằng tệp ExternCommon.mqh
chỉ được tải một lần, mặc dù nó được tham chiếu trong cả ExternHeader1.mqh
và ExternHeader2.mqh
.
'ExternMain.mq5'
'ExternHeader1.mqh'
'ExternCommon.mqh'
'ExternHeader2.mqh'
code generated
2
3
4
5
Nếu biến x
được "đăng ký" trong ExternCommon.mqh
, chúng ta sẽ không định nghĩa lại biến này (không có extern
) trong đơn vị chính vì điều này sẽ gây ra lỗi biên dịch, nhưng chúng ta có thể chỉ cần gán cho nó giá trị mong muốn khi bắt đầu thuật toán.