Nạp chồng hàm
MQL5 cho phép định nghĩa các hàm có cùng tên nhưng với số lượng hoặc kiểu tham số khác nhau trong cùng một mã nguồn. Cách tiếp cận này được gọi là nạp chồng hàm. Nó thường được áp dụng khi cùng một hành động có thể được kích hoạt bởi các đầu vào khác nhau. Sự khác biệt trong chữ ký cho phép trình biên dịch tự động xác định hàm nào sẽ được gọi dựa trên các đối số được truyền vào. Tuy nhiên, có một số đặc điểm cụ thể.
Các hàm không thể chỉ khác nhau về kiểu trả về. Trong trường hợp này, cơ chế nạp chồng không được kích hoạt và lỗi "function already defined and has different type" (hàm đã được định nghĩa và có kiểu khác) sẽ được trả về.
Nếu các hàm có cùng tên nhưng số lượng tham số khác nhau và các tham số "thừa" được khai báo là tùy chọn, thì trình biên dịch sẽ không thể xác định được hàm nào để gọi. Điều này sẽ tạo ra lỗi "ambiguous call to overloaded function with the same parameters" (lời gọi hàm nạp chồng không rõ ràng với các tham số giống nhau).
Khi một hàm nạp chồng được gọi, trình biên dịch khớp các đối số và tham số trong các phiên bản nạp chồng có sẵn. Nếu không tìm thấy sự khớp chính xác, trình biên dịch sẽ cố gắng thêm/bỏ bộ điều chỉnh const
và thực hiện mở rộng kiểu số cũng như chuyển đổi số học. Trong trường hợp con trỏ đối tượng, các quy tắc kế thừa lớp được sử dụng.
Với số lượng tham số khác nhau hoặc các kiểu tham số không liên quan ở cùng vị trí (chẳng hạn như số và chuỗi), lựa chọn thường rõ ràng. Tuy nhiên, nếu các kiểu tham số cần được chuyển đổi ngầm từ kiểu này sang kiểu khác, có thể xảy ra sự mơ hồ.
Ví dụ, chúng ta có hai hàm tính tổng:
double sum(double v1, double v2)
{
return v1 + v2;
}
int sum(int v1, int v2)
{
return v1 + v2;
}
2
3
4
5
6
7
8
9
Sau đó, lời gọi sau sẽ dẫn đến lỗi:
sum(1, 3.14); // lời gọi hàm nạp chồng không rõ ràng
Ở đây, trình biên dịch không thoải mái với mỗi phiên bản nạp chồng: đối với hàm double sum(double v1, double v2)
, cần chuyển đổi ngầm đối số đầu tiên thành double
, và đối với int sum(int v1, int v2)
, đối số thứ hai cần được chuyển đổi thành int
.
Thuật ngữ "nạp chồng" nên được hiểu theo nghĩa rằng một tên được tái sử dụng được "nạp" với "nhiệm vụ" nặng nề hơn nhiều lần so với một tên thông thường chỉ được sử dụng cho một hàm.
Hãy thử nạp chồng hàm cho việc hoán vị ma trận. Chúng ta đã có một ví dụ cho mảng 2x2 (xem Tham số giá trị và tham số tham chiếu). Hãy triển khai cùng thao tác cho mảng 3x3. Kích thước của tham số mảng đa chiều ở các chiều cao hơn (khác không) thay đổi kiểu, tức là double [][2]
khác với double [][3]
. Do đó, chúng ta sẽ nạp chồng phiên bản cũ của hàm:
void Transpose(double &m[][2]);
bằng cách thêm một phiên bản mới (FuncOverload.mq5
):
void Transpose(double &m[][3]);
Trong việc triển khai phiên bản mới, thuận tiện khi sử dụng hàm trợ giúp Swap
để hoán đổi hai phần tử ma trận tại các chỉ số đã cho.
void Transpose(double &m[][3])
{
Swap(m, 0, 1);
Swap(m, 0, 2);
Swap(m, 1, 2);
}
void Swap(double &m[][3], const int i, const int j)
{
static double temp;
temp = m[i][j];
m[i][j] = m[j][i];
m[j][i] = temp;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
Bây giờ chúng ta có thể gọi cả hai hàm từ OnStart
bằng cùng ký hiệu cho các mảng có kích thước khác nhau. Trình biên dịch sẽ tự tạo ra lời gọi đến các phiên bản đúng.
double a[2][2] = {{1, 2}, {3, 4}};
Transpose(a);
...
double b[3][3] = {{1, 2, 3}, {4, 5, 6}, {7, 8, 9}};
Transpose(b);
2
3
4
5
Điều quan trọng cần lưu ý là bộ điều chỉnh const
trên tham số, mặc dù thay đổi nguyên mẫu của hàm, không phải lúc nào cũng là sự khác biệt đủ để nạp chồng. Hai hàm cùng tên, chỉ khác nhau ở sự hiện diện và vắng mặt của const
cho một số tham số, có thể được coi là giống nhau. Điều này sẽ dẫn đến lỗi "function already defined and has body" (hàm đã được định nghĩa và có phần thân). Hành vi này xảy ra vì, đối với các tham số giá trị, bộ điều chỉnh const
bị loại bỏ khi đối số được gán (vì tham số giá trị, theo định nghĩa, không thể thay đổi đối số trong mã gọi), và điều này không cho phép chọn một trong số các hàm nạp chồng dựa trên nó.
Để chứng minh điều này, chỉ cần thêm một hàm trong script:
void Swap(double &m[][3], int i, int j);
Nó là một nạp chồng không thành công cho hàm hiện có:
void Swap(double &m[][3], const int i, const int j);
Sự khác biệt duy nhất giữa hai hàm là các bộ điều chỉnh const
cho các tham số i
và j
. Do đó, cả hai đều phù hợp để gọi với các đối số kiểu int
và truyền theo giá trị.
Khi các tham số được truyền theo tham chiếu, việc nạp chồng với sự khác biệt chỉ ở thuộc tính const
/không-const
thành công vì, đối với tham chiếu, bộ điều chỉnh const
rất quan trọng (nó thay đổi kiểu và loại bỏ khả năng chuyển đổi ngầm). Điều này được thể hiện trong script với một cặp hàm:
void SwapByReference(double &m[][3], int &i, int &j)
{
Print(__FUNCSIG__);
}
void SwapByReference(double &m[][3], const int &i, const int &j)
{
Print(__FUNCSIG__);
}
void OnStart()
{
// ...
{
int i = 0, j = 1;
SwapByReference(b, i, j);
}
{
const int i = 0, j = 1;
SwapByReference(b, i, j);
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
Chúng được để lại dưới dạng các bản sơ khai gần như trống, trong đó chữ ký của mỗi hàm được in bằng lời gọi Print(__FUNCSIG__)
. Điều này cho phép đảm bảo rằng phiên bản phù hợp của hàm được gọi tùy thuộc vào thuộc tính const
của các đối số.