Dạng #define như một hàm giả
Cú pháp của dạng tham số #define
tương tự như một hàm.
#define macro_identifier(parameter,...) text_with_parameters
Macro như vậy có một hoặc nhiều tham số trong dấu ngoặc đơn. Các tham số được phân cách bằng dấu phẩy. Mỗi tham số là một định danh đơn giản (thường là một chữ cái đơn). Hơn nữa, tất cả các tham số của một macro phải có định danh khác nhau.
Điều quan trọng là không có khoảng trắng giữa định danh và dấu ngoặc đơn mở, nếu không macro sẽ được coi là dạng đơn giản mà văn bản thay thế bắt đầu bằng dấu ngoặc đơn mở.
Sau khi chỉ thị này được đăng ký, tiền xử lý sẽ tìm kiếm trong mã nguồn các dòng có dạng:
macro_identifier(expression,...)
Các biểu thức bất kỳ có thể được chỉ định thay cho các tham số. Số lượng đối số phải khớp với số lượng tham số của macro. Tất cả các lần xuất hiện được tìm thấy sẽ được thay thế bằng text_with_parameters
, trong đó, đến lượt nó, các tham số sẽ được thay thế bằng các biểu thức được truyền vào. Mỗi tham số có thể xuất hiện nhiều lần, theo bất kỳ thứ tự nào.
Ví dụ, macro sau tìm giá trị lớn nhất của hai giá trị:
#define MAX(A,B) ((A) > (B) ? (A) : (B))
Nếu mã chứa câu lệnh:
int z = MAX(x, y);
nó sẽ được tiền xử lý "mở rộng" thành:
int z = ((x) > (y) ? (x) : (y));
Thay thế macro sẽ hoạt động với bất kỳ kiểu dữ liệu nào (mà các phép toán áp dụng bên trong macro là hợp lệ).
Tuy nhiên, thay thế cũng có thể gây ra tác dụng phụ. Ví dụ, nếu tham số thực tế là một lời gọi hàm hoặc câu lệnh thay đổi biến (chẳng hạn, ++x
), thì hành động tương ứng có thể được thực hiện nhiều lần (thay vì chỉ một lần như dự định). Trong trường hợp của MAX, điều này sẽ xảy ra hai lần: trong quá trình so sánh và khi lấy giá trị trong một trong các nhánh của toán tử '?:'. Về vấn đề này, nên chuyển đổi các macro như vậy thành hàm bất cứ khi nào có thể (đặc biệt là khi trong MQL5 các hàm được tự động nội tuyến).
Có các dấu ngoặc đơn bao quanh các tham số và toàn bộ định nghĩa macro. Chúng được sử dụng để đảm bảo rằng việc thay thế các biểu thức làm tham số hoặc chính macro bên trong các biểu thức khác không làm sai lệch thứ tự tính toán do các mức độ ưu tiên khác nhau. Giả sử macro định nghĩa phép nhân của hai tham số (chưa được bao trong dấu ngoặc):
#define MUL(A,B) A * B
Thì việc sử dụng macro với các biểu thức sau sẽ tạo ra kết quả không mong đợi:
int x = MUL(1 + 2, 3 + 4); // 1 + 2 * 3 + 4
Thay vì phép nhân (1 + 2) * (3 + 4)
cho ra 21, chúng ta có 1 + 2 * 3 + 4
, tức là 11. Định nghĩa macro phù hợp nên như sau:
#define MUL(A,B) ((A) * (B))
Bạn có thể chỉ định một macro khác làm tham số macro. Ngoài ra, bạn cũng có thể chèn các macro khác vào định nghĩa macro. Tất cả các macro như vậy sẽ được thay thế tuần tự. Ví dụ:
#define SQ3(X) (X * X * X)
#define ABS(X) MathAbs(SQ3(X))
#define INC(Y) (++(Y))
2
3
Sau đó, mã sau sẽ in ra 504 (MathAbs
là hàm tích hợp trả về mô-đun của một số, tức là không có dấu):
int x = -10;
Print(ABS(INC(x)));
// -> ABS(++(Y))
// -> MathAbs(SQ3(++(Y)))
// -> MathAbs((++(Y))*(++(Y))*(++(Y)))
// -> MathAbs(-9*-8*-7)
// -> 504
2
3
4
5
6
7
Trong biến x
, giá trị -7 sẽ còn lại (do tăng ba lần).
Định nghĩa macro có thể chứa các dấu ngoặc không khớp. Kỹ thuật này thường được sử dụng trong một cặp macro, trong đó một macro mở một đoạn mã nhất định, và macro kia đóng nó lại. Trong trường hợp này, các dấu ngoặc không khớp trong mỗi macro sẽ trở thành khớp nhau. Cụ thể, trong các tệp thư viện chuẩn có sẵn trong gói phân phối MetaTrader 5, trong Controls/Defines.mqh
, các macro EVENT_MAP_BEGIN
và EVENT_MAP_END
được định nghĩa. Chúng được sử dụng để tạo hàm xử lý sự kiện trong các đối tượng đồ họa.
Tiền xử lý đọc toàn bộ văn bản nguồn của chương trình từng dòng một, bắt đầu từ tệp .mq5
chính và chèn các văn bản từ các tệp tiêu đề gặp phải vào đúng vị trí. Đến thời điểm bất kỳ dòng mã nào được đọc, một tập hợp các macro đã được định nghĩa sẽ được hình thành. Thứ tự định nghĩa các macro không quan trọng: hoàn toàn có thể một macro tham chiếu trong định nghĩa của nó đến một macro khác, được mô tả cả ở trên lẫn dưới trong văn bản. Điều quan trọng là trong dòng mã nguồn nơi tên macro được sử dụng, các định nghĩa của tất cả các macro được tham chiếu phải đã biết.
Xem xét một ví dụ.
#define NEG(x) (-SQN(x))*TEN
#define SQN(x) ((x)*(x))
#define TEN 10
...
Print(NEG(2)); // -40
2
3
4
5
Ở đây, macro NEG sử dụng các macro SQN và TEN, được mô tả bên dưới nó. Và điều này không ngăn chúng ta sử dụng thành công nó trong mã sau cả ba #define
.
Tuy nhiên, nếu chúng ta thay đổi vị trí tương đối của các dòng thành như sau:
#define NEG(x) (-SQN(x))*TEN
#define SQN(x) ((x)*(x))
...
Print(NEG(2)); // error: 'TEN' - undeclared identifier
...
#define TEN 10
2
3
4
5
6
chúng ta sẽ nhận được lỗi biên dịch "undeclared identifier" (định danh chưa được khai báo).