Con trỏ hàm (typedef)
MQL5 có từ khóa typedef
, cho phép bạn mô tả một kiểu đặc biệt của con trỏ hàm.
Không giống như C++, nơi
typedef
có ứng dụng rộng hơn nhiều, trong MQL5typedef
chỉ được sử dụng cho con trỏ hàm.
Cú pháp để khai báo một kiểu mới là:
typedef function_result_type (*function_type)([list_of_input_parameters]);
Định danh function_type
xác định một tên kiểu trở thành biệt danh (alias) cho một con trỏ tới bất kỳ hàm nào trả về giá trị của kiểu đã cho function_result_type
và chấp nhận một danh sách các tham số đầu vào (list_of_input_parameters
).
Ví dụ, chúng ta có thể có 2 hàm với cùng nguyên mẫu (hai tham số đầu vào kiểu double
và kiểu kết quả cũng là double
) thực hiện các phép toán số học khác nhau: cộng và trừ (FuncTypedef.mq5
).
double plus(double v1, double v2)
{
return v1 + v2;
}
double minus(double v1, double v2)
{
return v1 - v2;
}
2
3
4
5
6
7
8
9
Nguyên mẫu chung của chúng dễ dàng được mô tả để sử dụng như một con trỏ:
typedef double (*Calc)(double, double);
Mục này đưa kiểu Calc
vào chương trình, với kiểu này bạn có thể định nghĩa một biến/tham số để lưu trữ/truyền một tham chiếu tới bất kỳ hàm nào có nguyên mẫu như vậy, bao gồm cả hai hàm plus
và minus
. Kiểu này là một con trỏ vì ký tự '*' (*Calc
) được sử dụng trong mô tả. Chúng ta sẽ tìm hiểu thêm về các đặc điểm của dấu sao khi áp dụng cho con trỏ khi nghiên cứu OOP.
Việc sử dụng lớp con trỏ như vậy rất thuận tiện để tạo các thuật toán tùy chỉnh có thể "ngay lập tức" gọi các hàm khác nhau tương ứng với biệt danh, tùy thuộc vào dữ liệu đầu vào.
Cụ thể, chúng ta có thể giới thiệu một hàm tính toán tổng quát:
double calculator(Calc ptr, double v1, double v2)
{
if(ptr == NULL) return 0;
return ptr(v1, v2);
}
2
3
4
5
Tham số đầu tiên của nó được khai báo với kiểu Calc
. Nhờ đó, chúng ta có thể truyền một hàm bất kỳ có nguyên mẫu phù hợp vào nó và kết quả là thực hiện một số phép toán, mà bản thân hàm calculator
không biết về bản chất của phép toán đó. Nó thực hiện điều này bằng cách ủy thác lời gọi cho một con trỏ: ptr(v1, v2)
. Vì ptr
là một con trỏ hàm, cú pháp này không chỉ giống một lời gọi hàm mà thực sự gọi hàm mà con trỏ đang giữ.
Lưu ý rằng chúng ta kiểm tra trước tham số ptr
với giá trị đặc biệt NULL (NULL là tương đương của số không cho con trỏ). Sự thật là con trỏ có thể không trỏ đến đâu, tức là nó có thể không được khởi tạo. Vì vậy, trong script, chúng ta có một biến toàn cục được mô tả:
Calc calc;
Nó không có con trỏ. Nếu không có "bảo vệ" chống lại NULL, việc gọi calculator
với một con trỏ "rỗng" calc
sẽ dẫn đến lỗi thời gian chạy "Invalid function pointer call" (Lời gọi con trỏ hàm không hợp lệ).
Các lời gọi đến hàm calculator
với các con trỏ khác nhau trong tham số đầu tiên sẽ cho các kết quả sau (được hiển thị trong các bình luận):
void OnStart()
{
Print(calculator(plus, 1, 2)); // 3
Print(calculator(minus, 1, 2)); // -1
Print(calculator(calc, 1, 2)); // 0
}
2
3
4
5
6
Lưu ý rằng nếu không có khởi tạo rõ ràng, tất cả các con trỏ hàm đều được điền với giá trị bằng không. Điều này áp dụng cho cả biến toàn cục và biến cục bộ của kiểu đã cho.
Một kiểu con trỏ được định nghĩa bằng typedef
có thể được trả về từ các hàm, ví dụ:
Calc generator(ushort type)
{
switch(type)
{
case '+': return plus;
case '-': return minus;
}
return NULL;
}
2
3
4
5
6
7
8
9
Ngoài ra, kiểu con trỏ hàm thường được sử dụng cho các hàm gọi lại (callback
, xem FuncCallback.mq5
). Giả sử chúng ta có một hàm DoMath
thực hiện các tính toán dài (có lẽ nó được triển khai trong một thư viện riêng). Về mặt tiện lợi và thân thiện với giao diện người dùng, sẽ rất tuyệt nếu hiển thị cho người dùng chỉ báo tiến trình. Để làm điều này, bạn có thể định nghĩa một kiểu con trỏ hàm đặc biệt cho các thông báo về phần trăm công việc đã hoàn thành (ProgressCallback
), và thêm một tham số của kiểu này vào hàm DoMath
. Trong mã của DoMath
, bạn nên định kỳ gọi hàm được truyền vào:
typedef void (*ProgressCallback)(const float percent);
void DoMath(double &bigdata[], ProgressCallback callback)
{
const int N = 1000000;
for(int i = 0; i < N; ++i)
{
if(i % 10000 == 0 && callback != NULL)
{
callback(i * 100.0f / N);
}
// tính toán dài
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Sau đó, mã gọi có thể định nghĩa hàm gọi lại cần thiết, truyền một con trỏ tới nó cho DoMath
và nhận các cập nhật khi quá trình tính toán diễn ra.
void MyCallback(const float percent)
{
Print(percent);
}
void OnStart()
{
double data[] = {0};
DoMath(data, MyCallback);
}
2
3
4
5
6
7
8
9
10
Con trỏ hàm chỉ hoạt động với các hàm tùy chỉnh được định nghĩa trong MQL5. Chúng không thể trỏ đến các hàm tích hợp của API MQL5.