Tham số giá trị và Tham số tham chiếu
Đối số có thể được truyền vào hàm theo hai cách: theo giá trị và theo tham chiếu.
Tất cả các trường hợp chúng ta đã xem xét cho đến nay đều là truyền theo giá trị. Tùy chọn này có nghĩa là giá trị của đối số được chuẩn bị bởi đoạn mã gọi sẽ được sao chép vào một biến mới, tức là biến đầu vào tương ứng của hàm. Nếu không, đối số và biến đầu vào không liên quan đến nhau. Tất cả các thao tác tiếp theo với biến bên trong hàm không ảnh hưởng đến đối số theo bất kỳ cách nào.
Để mô tả một tham số tham chiếu, hãy thêm dấu và &
vào bên phải của kiểu. Nhiều lập trình viên thích gắn dấu và vào tên tham số, qua đó nhấn mạnh rằng tham số là một tham chiếu đến kiểu đã cho. Ví dụ, các mục sau là tương đương:
void func(int ¶meter);
void func(int & parameter);
void func(int& parameter);
2
3
Khi một hàm được gọi, một biến cục bộ tương ứng không được tạo cho tham số tham chiếu. Thay vào đó, đối số được chỉ định cho tham số này trở nên khả dụng bên trong hàm dưới tên (bí danh) của tham số đầu vào. Do đó, giá trị không được sao chép, mà được sử dụng tại cùng một địa chỉ trong bộ nhớ. Vì vậy, các sửa đổi đối với tham số trong hàm sẽ được phản ánh trong trạng thái của đối số liên quan. Một tính năng quan trọng xuất phát từ điều này.
Bạn chỉ có thể chỉ định một biến (LValue, xem Toán tử gán) làm đối số cho tham số tham chiếu. Nếu không, chúng ta sẽ nhận được lỗi "parameter passed as reference, variable expected" (tham số được truyền dưới dạng tham chiếu, cần một biến).
Truyền theo tham chiếu được sử dụng trong một số trường hợp:
- Để cải thiện hiệu suất của chương trình bằng cách loại bỏ việc sao chép giá trị;
- Để truyền dữ liệu đã sửa đổi từ hàm đến mã gọi khi việc trả về một giá trị duy nhất với
return
là không đủ.
Điểm đầu tiên đặc biệt quan trọng đối với các biến có khả năng lớn như chuỗi hoặc mảng.
Để phân biệt giữa mục đích đầu tiên và thứ hai của tham số tham chiếu, tác giả của hàm được khuyến khích thêm bộ điều chỉnh const
khi tham số bên trong hàm không dự kiến thay đổi. Điều này sẽ nhắc nhở bạn và làm rõ cho các nhà phát triển khác rằng việc truyền một biến vào trong hàm sẽ không dẫn đến các tác dụng phụ.
Việc không áp dụng bộ điều chỉnh const
cho các tham số tham chiếu ở những nơi có thể sẽ gây ra vấn đề trong toàn bộ hệ thống phân cấp lời gọi hàm. Sự thật là việc gọi các hàm như vậy sẽ yêu cầu các đối số không phải hằng số. Nếu không, sẽ xảy ra lỗi "constant variable cannot be passed as reference" (biến hằng không thể được truyền dưới dạng tham chiếu). Kết quả là, dần dần có thể thấy rằng tất cả các tham số trong tất cả các hàm nên bị loại bỏ bộ điều chỉnh const
để mã có thể biên dịch được. Thực tế, điều này mở rộng phạm vi cho các lỗi tiềm ẩn với việc vô tình làm hỏng biến. Tình huống nên được sửa chữa theo cách ngược lại: đặt const
ở bất cứ nơi nào không yêu cầu trả về và sửa đổi giá trị.
Để so sánh các cách truyền tham số trong script FuncDeclaration.mq5
, một số hàm được triển khai: FuncByValue
– truyền theo giá trị, FuncByReference
– truyền theo tham chiếu, FuncByConstReference
– truyền theo tham chiếu hằng.
void FuncByValue(int v)
{
++v;
// chúng ta đang làm gì đó khác với v
}
void FuncByReference(int &v)
{
++v;
}
void FuncByConstReference(const int &v)
{
// lỗi
// ++v; // 'v' - hằng số không thể sửa đổi
Print(v);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Trong hàm OnStart
, chúng ta gọi tất cả các hàm này và quan sát hiệu ứng của chúng trên biến i
được sử dụng làm đối số. Lưu ý rằng việc truyền tham số theo tham chiếu không thay đổi cú pháp gọi hàm.
void OnStart()
{
int i = 0;
FuncByValue(i); // i không thể thay đổi
Print(i); // 0
FuncByReference(i); // i đang thay đổi
Print(i); // 1
FuncByConstReference(i); // i không thể thay đổi, 1
const int j = 1;
// lỗi
// 'j' - biến hằng không thể được truyền dưới dạng tham chiếu
// FuncByReference(j);
FuncByValue(10); // ok
// lỗi: '10' - tham số được truyền dưới dạng tham chiếu, cần một biến
// FuncByReference(10);
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
Hằng số chỉ có thể được truyền vào hàm FuncByValue
, vì các hàm khác yêu cầu một tham chiếu, tức là một biến, làm đối số.
Hàm FuncByReference
không thể được gọi với biến j
, vì biến này được khai báo là hằng số, và hàm này tuyên bố khả năng (hoặc ý định) thay đổi tham số của nó vì nó không được trang bị bộ điều chỉnh const
. Điều này tạo ra lỗi "constant variable cannot be passed as reference".
Script cũng mô tả hàm Transpose
: nó hoán vị một ma trận 2x2 được truyền dưới dạng mảng hai chiều theo tham chiếu.
void Transpose(double &m[][2])
{
double temp = m[1][0];
m[1][0] = m[0][1];
m[0][1] = temp;
}
2
3
4
5
6
Lời gọi của nó từ OnStart
thể hiện sự thay đổi mong đợi trong nội dung của mảng cục bộ a
.
double a[2][2] = {{-1, 2}, {3, 0}};
Transpose(a);
ArrayPrint(a);
2
3
Trong MQL5, các tham số mảng luôn được truyền dưới dạng cấu trúc nội bộ của một mảng động (xem phần Đặc điểm của mảng). Do đó, mô tả của tham số như vậy phải có kích thước mở ở chiều đầu tiên, tức là để trống bên trong cặp dấu ngoặc vuông đầu tiên.
Điều này không ngăn cản, nếu cần, truyền vào hàm một đối số thực tế là một mảng có kích thước cố định (như trong ví dụ của chúng ta). Tuy nhiên, các hàm như ArrayResize
sẽ không thể thay đổi kích thước hoặc tổ chức lại một mảng cố định được ngụy trang như vậy.
Kích thước của mảng trong tất cả các chiều trừ chiều đầu tiên phải khớp cho cả tham số và đối số. Nếu không, chúng ta sẽ nhận được lỗi "parameter conversion not allowed" (chuyển đổi tham số không được phép). Đặc biệt, hàm TransposeVector
được định nghĩa trong ví dụ:
void TransposeVector(double &v[])
{
}
2
3
Việc cố gắng gọi nó trên một mảng hai chiều a
đã được bình luận trong OnStart
vì nó tạo ra lỗi trên: kích thước mảng không khớp.
Ngoài việc truyền tham số theo giá trị hoặc theo tham chiếu, còn có một tùy chọn khác: truyền con trỏ. Không giống như C++, MQL5 chỉ hỗ trợ con trỏ cho các kiểu đối tượng (lớp). Chúng ta sẽ xem xét tính năng này trong Phần thứ ba.