Các khái niệm cơ bản
Trước khi đi sâu vào các nhóm toán tử cụ thể, chúng ta nên giới thiệu một số khái niệm cơ bản vốn có trong tất cả các toán tử và ảnh hưởng đến khả năng áp dụng cũng như hành vi của chúng trong một bối cảnh cụ thể.
Trước hết, theo số lượng toán hạng cần thiết, toán tử có thể là toán hạng một ngôi và toán hạng hai ngôi. Như tên gọi đã thấy rõ, toán hạng một ngôi xử lý một toán hạng, trong khi toán hạng hai ngôi xử lý hai toán hạng. Trong trường hợp toán hạng hai ngôi, toán tử luôn được đặt giữa các toán hạng. Trong số toán hạng một ngôi, có những toán tử phải được đặt trước toán hạng và những toán tử được đặt sau toán hạng. Ví dụ, toán tử trừ một ngôi (-
) cho phép đảo ngược dấu của giá trị:
int x = 10;
int y = -x; // -10
2
Đồng thời, có một toán tử hai ngôi để trừ sử dụng cùng một ký tự, -
.
int z = x - y; // 10 - -10 -> 20
Việc trình biên dịch lựa chọn toán tử (phép toán) đúng trong một ngữ cảnh cụ thể được xác định bởi ngữ cảnh sử dụng toán tử đó trong biểu thức.
Mỗi toán tử được gán mức độ ưu tiên. Nó xác định thứ tự mà các toán tử sẽ được tính toán trong các biểu thức phức tạp có nhiều toán tử. Các toán tử có mức độ ưu tiên cao hơn được tính toán đầu tiên, trong khi các toán tử có mức độ ưu tiên thấp hơn được tính toán cuối cùng. Ví dụ, trong biểu thức 1 + 2 * 3
có hai phép toán (cộng và nhân) và ba toán hạng. Vì phép nhân có mức độ ưu tiên cao hơn phép cộng, nên tích của 2 * 3
sẽ được tìm thấy đầu tiên, sau đó sẽ được cộng với một.
Sau đó chúng ta sẽ cung cấp bảng hoạt động đầy đủ cùng với thứ tự ưu tiên.
Ngoài ra, mỗi toán tử được đặc trưng bởi tính kết hợp. Nó có thể là trái hoặc phải và xác định thứ tự, trong đó các toán tử liên tiếp có cùng mức độ ưu tiên được thực hiện. Ví dụ, biểu thức 10 - 7 - 1
có thể được tính toán hoàn toàn theo lý thuyết theo hai cách:
- Trừ 7 khỏi 10 rồi trừ 1 khỏi 3 kết quả, ta được 2; hoặc
- Trừ 1 khỏi 7, ta được 6, sau đó trừ 6 khỏi 10, ta được 4.
Trong trường hợp đầu tiên, các phép tính được thực hiện từ trái sang phải, tương ứng với phép kết hợp trái; vì phép trừ có tính kết hợp trái nên câu trả lời đầu tiên là đúng.
Tùy chọn tính toán thứ hai tương ứng với tính kết hợp đúng và sẽ không được sử dụng.
Hãy xem xét một ví dụ khác trong đó có sự tham gia của tính ưu tiên và tính kết hợp đồng thời:
11 + 5 * 4 / 2 + 3
. Cả hai loại phép toán, tức là phép cộng và phép nhân, đều được thực hiện từ trái sang phải. Nếu các tính ưu tiên không khác nhau, chúng ta sẽ nhận được 35, mặc dù 24 là câu trả lời đúng. Thay đổi tính kết hợp cho bên phải sẽ cho chúng ta 14.Để xác định lại rõ ràng các ưu tiên trong biểu thức, có thể sử dụng dấu ngoặc đơn, ví dụ:
(11 + 5) * 4 / (2 + 3)
. Những gì được bao quanh trong dấu ngoặc đơn được tính toán trước đó và kết quả trung gian được thay thế trong biểu thức để sử dụng trong các phép toán khác. Các nhóm trong dấu ngoặc đơn có thể được lồng vào nhau. Để biết thêm chi tiết, vui lòng xem phần Nhóm với dấu ngoặc đơn.
Một toán tử liên kết phải có thể được minh họa bằng toán tử một ngôi của phép phủ định logic, !
. Về cơ bản, nhiệm vụ của nó là biến thành đúng từ sai và ngược lại. Giống như các toán tử một ngôi khác, tính liên kết có nghĩa là trong ngữ cảnh này, toán hạng phải được đặt ở phía nào của toán tử. Ký hiệu !
được đặt trước toán hạng, tức là toán hạng ở bên phải.
int x = 10;
int on_off = !!x; // 1
2
Trong trường hợp này, phủ định logic được thực hiện hai lần: lần đầu tiên liên quan đến biến x
(!
phải) và lần thứ hai liên quan đến kết quả của phủ định trước đó (!
trái). Phủ định kép như vậy cho phép chuyển đổi bất kỳ giá trị khác không nào thành 1 do chuyển đổi thành bool
và ngược lại.
Bảng phép tính cuối cùng cũng sẽ thể hiện tính kết hợp.
Cuối cùng, điểm tinh tế cuối cùng nhưng không kém phần quan trọng trong quá trình xử lý biểu thức là thứ tự tính toán các toán hạng. Cần phân biệt thứ tự này với thứ tự ưu tiên thuộc về phép toán, không phải toán hạng. Thứ tự tính toán các toán hạng của phép toán nhị phân không được xác định rõ ràng, điều này cung cấp cho trình biên dịch không gian để tối ưu hóa mã và nâng cao hiệu quả của nó. Trình biên dịch chỉ đảm bảo rằng các toán hạng sẽ được tính toán trước khi thực hiện phép toán.
Có một tập hợp các phép toán giới hạn, trong đó thứ tự đánh giá toán hạng được xác định. Đặc biệt, đối với logic AND
(&&
) và OR
(||
), thứ tự là từ trái sang phải và phần bên phải có thể bị bỏ qua nếu nó không ảnh hưởng đến bất kỳ điều gì do giá trị của phần bên trái. Nhưng đối với toán tử điều kiện ba ngôi ?:
thứ tự thậm chí còn phức tạp hơn, vì một hoặc một nhánh khác sẽ được tính toán khi tính toán các điều kiện đầu tiên, tùy thuộc vào tính đúng đắn của nó. Xem các phần tiếp theo để biết thêm chi tiết.
Thứ tự đánh giá toán hạng được minh họa bằng tình huống có nhiều lệnh gọi hàm trong biểu thức. Ví dụ, hãy sử dụng 4 hàm trong biểu thức:
a() + b() * c() - d()
Các quy tắc về mức độ ưu tiên và kết hợp sẽ chỉ được sử dụng cho các kết quả trung gian của việc gọi các hàm này, trong khi các lệnh gọi có thể được trình biên dịch tạo ra theo bất kỳ thứ tự nào mà nó "cho là cần thiết" dựa trên các tính năng mã nguồn và cài đặt trình biên dịch. Ví dụ, các hàm b
và c
liên quan đến phép nhân có thể được gọi theo thứ tự [ b(), c() ]
hoặc ngược lại, [ c(), b() ]
. Nếu các hàm trong quá trình thực thi có thể ảnh hưởng đến cùng một dữ liệu, trạng thái của chúng sẽ không rõ ràng khi tính toán biểu thức.
Một vấn đề tương tự có thể thấy khi làm việc với mảng và toán tử tăng (xem phần Tăng và Giảm).
int i = 0;
int a[5] = {0, 1, 2, 3, 4};
int w = a[++i] - a[++i];
2
3
Tùy thuộc vào việc toán hạng sai phân bên trái hay bên phải sẽ được tính là toán hạng đầu tiên, chúng ta có thể nhận được -1 ( a[1] - a[2] ) hoặc +1 ( a[2] - a[1] )
. Vì trình biên dịch MQL5 không ngừng cải tiến nên không có gì đảm bảo rằng kết quả hiện tại (-1) sẽ được giữ nguyên trong tương lai.
Để tránh các vấn đề tiềm ẩn, khuyến cáo không nên sử dụng một toán hạng nhiều lần nếu nó đã được sửa đổi trong cùng một biểu thức.
Trong tất cả các biểu thức, thường có thể có các toán hạng có các kiểu khác nhau. Điều này dẫn đến nhu cầu ép kiểu chúng thành một kiểu chung nhất định, trước khi thực hiện bất kỳ hành động nào với chúng. Nếu không có ép kiểu rõ ràng, MQL5 sẽ thực hiện chuyển đổi ngầm định khi cần thiết. Bên cạnh đó, các quy tắc chuyển đổi khác nhau đối với các kết hợp kiểu khác nhau. Việc ép kiểu rõ ràng và ngầm định được thảo luận trong phần có liên quan .