Dạng đơn giản của #define
Dạng đơn giản của chỉ thị #define
đăng ký một định danh và chuỗi ký tự mà định danh đó sẽ thay thế ở mọi nơi trong mã nguồn sau chỉ thị, cho đến cuối chương trình hoặc trước chỉ thị #undef
với cùng định danh.
Cú pháp của nó là:
#define macro_identifier [text]
Văn bản bắt đầu sau định danh và tiếp tục đến cuối dòng hiện tại. Định danh và văn bản phải được phân cách bằng một số lượng khoảng trắng hoặc tab bất kỳ. Nếu chuỗi ký tự cần thiết quá dài, để dễ đọc, bạn có thể chia nó thành nhiều dòng bằng cách đặt ký tự dấu gạch chéo ngược '' ở cuối dòng.
#define macro_identifier text_beginning \
text_continued \
text_ending
2
3
Văn bản có thể bao gồm bất kỳ cấu trúc ngôn ngữ nào: hằng số, toán tử, định danh và dấu câu. Nếu bạn thay thế macro_identifier
thay vì các cấu trúc được tìm thấy trong mã nguồn, tất cả chúng sẽ được bao gồm trong quá trình biên dịch.
Dạng đơn giản truyền thống được sử dụng cho một số mục đích:
- Khai báo cờ (flag), sau đó được sử dụng để kiểm tra biên dịch có điều kiện;
- Khai báo hằng số có tên;
- Ký hiệu rút gọn của các câu lệnh phổ biến.
Điểm đầu tiên đặc trưng ở chỗ không cần chỉ định gì sau định danh - sự hiện diện của chỉ thị với tên đã đủ để định danh tương ứng được đăng ký và có thể được sử dụng trong các chỉ thị có điều kiện #ifdef/ifndef
. Đối với chúng, chỉ quan trọng là định danh có tồn tại hay không, tức là nó hoạt động ở chế độ cờ: được khai báo / không được khai báo. Ví dụ, chỉ thị sau định nghĩa cờ DEMO:
#define DEMO
Nó sau đó có thể được sử dụng, chẳng hạn, để xây dựng phiên bản demo của chương trình, trong đó một số chức năng nhất định bị loại bỏ (xem ví dụ trong phần biên dịch có điều kiện).
Cách thứ hai để sử dụng chỉ thị đơn giản cho phép bạn thay thế các "số ma thuật" trong mã nguồn bằng các tên thân thiện. "Số ma thuật" là các hằng số được chèn vào văn bản nguồn, ý nghĩa của chúng không phải lúc nào cũng rõ ràng (vì một con số chỉ là một con số: ít nhất nên giải thích nó trong một bình luận). Ngoài ra, cùng một giá trị có thể rải rác ở nhiều phần khác nhau của mã, và nếu lập trình viên quyết định thay đổi nó thành giá trị khác, thì anh ta sẽ phải làm điều này ở tất cả các nơi (và hy vọng rằng không bỏ sót gì).
Với một macro có tên, hai vấn đề này dễ dàng được giải quyết. Ví dụ, một script có thể chuẩn bị một mảng với các số Fibonacci đến một độ sâu tối đa nhất định. Khi đó, việc định nghĩa một macro với kích thước mảng được xác định trước và sử dụng nó trong mô tả của chính mảng là hợp lý (Preprocessor.mq5
).
#define MAX_FIBO 10
int fibo[MAX_FIBO]; // 10
void FillFibo()
{
int prev = 0;
int result = 1;
for(int i = 0; i < MAX_FIBO; ++i) // i < 10
{
int temp = result;
result = result + prev;
fibo[i] = result;
prev = temp;
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Nếu sau đó lập trình viên quyết định rằng kích thước mảng cần được tăng lên, anh ta chỉ cần làm điều này ở một nơi - trong chỉ thị #define
. Do đó, chỉ thị thực sự định nghĩa một tham số nhất định của thuật toán, được "cố định" vào mã nguồn và không khả dụng để người dùng cấu hình. Nhu cầu này xảy ra khá thường xuyên.
Câu hỏi có thể đặt ra là việc định nghĩa qua #define
khác với biến hằng trong ngữ cảnh toàn cục như thế nào. Thực tế, chúng ta có thể khai báo một biến với cùng tên và mục đích, và thậm chí giữ nguyên chữ cái in hoa:
const int MAX_FIBO = 10;
Tuy nhiên, trong trường hợp này, MQL5 sẽ không cho phép định nghĩa một mảng với kích thước đã chỉ định, vì chỉ các hằng số được phép trong dấu ngoặc vuông, tức là các giá trị nguyên văn (và một biến hằng, mặc dù có tên tương tự, không phải là hằng số). Để giải quyết vấn đề này, chúng ta có thể định nghĩa một mảng dưới dạng động (không chỉ định kích thước trước) và sau đó cấp phát bộ nhớ cho nó bằng hàm ArrayResize
- việc truyền một biến làm kích thước ở đây không khó.
Một cách thay thế để định nghĩa hằng số có tên được cung cấp bởi enum, nhưng bị giới hạn ở các giá trị nguyên duy nhất. Ví dụ:
enum
{
MAX_FIBO = 10
};
2
3
4
Nhưng macro có thể chứa giá trị của bất kỳ kiểu nào.
#define TIME_LIMIT D'2023.01.01'
#define MIN_GRID_STEP 0.005
2
Việc tìm kiếm tên macro trong văn bản nguồn để thay thế được thực hiện có tính đến cú pháp của ngôn ngữ, tức là các thành phần không thể chia cắt, như định danh biến hoặc chuỗi ký tự nguyên văn, sẽ giữ nguyên, ngay cả khi chúng bao gồm một chuỗi con khớp với một trong các macro. Ví dụ, với macro XYZ bên dưới, biến XYZAXES sẽ được giữ nguyên như cũ, và tên XYZ (vì nó hoàn toàn giống với macro) sẽ được thay đổi thành ABC.
#define XYZ ABC
int XYZAXES = 3; // int XYZAXES = 3
int XYZ = 0; // int ABC = 0
2
3
Các thay thế macro cho phép bạn nhúng mã của mình vào mã nguồn của các chương trình khác. Kỹ thuật này thường được sử dụng bởi các thư viện được phân phối dưới dạng tệp tiêu đề .mqh
và kết nối với các chương trình bằng các chỉ thị #include
.
Cụ thể, đối với các script, chúng ta có thể định nghĩa triển khai thư viện của riêng mình cho hàm OnStart
, hàm này phải thực hiện một số hành động bổ sung mà không ảnh hưởng đến chức năng ban đầu của chương trình.
void OnStart()
{
Print("OnStart wrapper started");
// ... hành động bổ sung
_OnStart();
// ... hành động bổ sung
Print("OnStart wrapper stopped");
}
#define OnStart _OnStart
2
3
4
5
6
7
8
9
10
Giả sử phần này nằm trong tệp tiêu đề được bao gồm (Preprocessor.mqh
).
Khi đó, hàm OnStart
ban đầu (trong Preprocessor.mq5
) sẽ được tiền xử lý đổi tên trong mã nguồn thành _OnStart
(hiểu rằng định danh này không được sử dụng ở nơi nào khác cho mục đích khác). Và phiên bản mới của OnStart
từ tệp tiêu đề gọi _OnStart
, "bao bọc" nó vào các câu lệnh bổ sung.
Cách sử dụng phổ biến thứ ba của #define
đơn giản là để rút ngắn ký hiệu của các cấu trúc ngôn ngữ. Ví dụ, tiêu đề của một vòng lặp vô hạn có thể được biểu thị bằng một từ LOOP:
#define LOOP for( ; !IsStopped() ; )
Và sau đó áp dụng trong mã:
LOOP
{
// ...
Sleep(1000);
}
2
3
4
5
Phương pháp này cũng là kỹ thuật chính để sử dụng chỉ thị #define
với tham số (xem bên dưới).