Hãy để tôi nói điều này rất rõ ràng, bởi vì mọi người luôn hiểu sai điều này:
Thứ tự đánh giá biểu thị phụ độc lập với cả tính liên kết và mức độ ưu tiên . Tính liên kết và mức độ ưu tiên xác định thứ tự các toán tử được thực hiện nhưng không xác định thứ tự các biểu thức con được đánh giá. Câu hỏi của bạn là về thứ tự mà các biểu thức phụ được đánh giá.
Hãy cân nhắc A() + B() + C() * D()
. Phép nhân được ưu tiên cao hơn phép cộng và phép cộng có tính chất kết hợp trái, vì vậy điều này tương đương với (A() + B()) + (C() * D())
Nhưng việc biết điều đó chỉ cho bạn biết rằng phép cộng đầu tiên sẽ xảy ra trước phép cộng thứ hai và phép nhân sẽ xảy ra trước phép cộng thứ hai. Nó không cho bạn biết A (), B (), C () và D () sẽ được gọi theo thứ tự nào! (Nó cũng không cho bạn biết liệu phép nhân xảy ra trước hay sau phép cộng đầu tiên.) Bạn hoàn toàn có thể tuân theo các quy tắc ưu tiên và kết hợp bằng cách biên dịch như sau:
d = D()
b = B()
c = C()
a = A()
sum = a + b
product = c * d
result = sum + product
Tất cả các quy tắc ưu tiên và liên kết đều được tuân theo ở đó - phép cộng đầu tiên xảy ra trước phép cộng thứ hai và phép nhân xảy ra trước phép cộng thứ hai. Rõ ràng là chúng ta có thể thực hiện các lệnh gọi đến A (), B (), C () và D () theo bất kỳ thứ tự nào mà vẫn tuân theo các quy tắc ưu tiên và kết hợp!
Chúng ta cần một quy tắc không liên quan đến quy tắc ưu tiên và kết hợp để giải thích thứ tự mà các biểu thức con được đánh giá. Quy tắc liên quan trong Java (và C #) là "biểu thức con được đánh giá từ trái sang phải". Vì A () xuất hiện bên trái C (), A () được đánh giá đầu tiên, bất kể thực tế là C () tham gia vào một phép nhân và A () chỉ tham gia vào một phép cộng.
Vì vậy, bây giờ bạn có đủ thông tin để trả lời câu hỏi của bạn. Trong a[b] = b = 0
các quy tắc của thuyết kết hợp nói rằng điều này là a[b] = (b = 0);
vậy nhưng điều đó không có nghĩa là b=0
chạy trước! Các quy tắc ưu tiên nói rằng lập chỉ mục có mức ưu tiên cao hơn so với gán, nhưng điều đó không có nghĩa là trình lập chỉ mục chạy trước chỉ định ngoài cùng bên phải .
(CẬP NHẬT: Phiên bản trước của câu trả lời này có một số thiếu sót nhỏ và thực tế không quan trọng trong phần mà sau đó tôi đã sửa. Tôi cũng đã viết một bài blog mô tả lý do tại sao các quy tắc này hợp lý trong Java và C # tại đây: https: // ericlippert.com/2019/01/18/indexer-error-case/ )
Tính ưu tiên và tính liên kết chỉ cho chúng ta biết rằng việc gán số 0 cho b
phải xảy ra trước khi phép gán cho a[b]
, bởi vì việc gán số 0 tính giá trị được gán trong thao tác lập chỉ mục. Ưu tiên và associativity mình nói gì về việc liệu a[b]
được đánh giá trước khi hoặc sau khi sự b=0
.
Một lần nữa, điều này cũng giống như: A()[B()] = C()
- Tất cả những gì chúng ta biết là việc lập chỉ mục phải xảy ra trước khi gán. Chúng tôi không biết liệu A (), B () hay C () chạy trước dựa trên mức độ ưu tiên và tính kết hợp . Chúng tôi cần một quy tắc khác để cho chúng tôi biết điều đó.
Một lần nữa, quy tắc là "khi bạn có lựa chọn về việc phải làm đầu tiên, hãy luôn đi từ trái sang phải". Tuy nhiên, có một nếp nhăn thú vị trong kịch bản cụ thể này. Tác dụng phụ của một ngoại lệ được ném gây ra bởi tập hợp rỗng hoặc chỉ mục nằm ngoài phạm vi được coi là một phần của tính toán phía bên trái của bài tập hay một phần của chính việc tính toán bài tập đó? Java chọn cái sau. (Tất nhiên, đây là sự phân biệt chỉ quan trọng nếu mã đã sai , bởi vì mã đúng không bỏ tham chiếu rỗng hoặc chuyển chỉ mục xấu ngay từ đầu.)
Vậy điều gì xảy ra?
- Là
a[b]
bên trái của b=0
, do đó, a[b]
chạy trước , dẫn đến a[1]
. Tuy nhiên, việc kiểm tra tính hợp lệ của hoạt động lập chỉ mục này bị trì hoãn.
- Sau đó, điều
b=0
xảy ra.
- Sau đó, xác minh
a
hợp lệ và a[1]
nằm trong phạm vi sẽ xảy ra
- Việc gán giá trị
a[1]
sẽ xảy ra sau cùng.
Vì vậy, mặc dù trong trường hợp cụ thể này, có một số điều tinh tế cần xem xét đối với những trường hợp lỗi hiếm gặp không nên xảy ra trong mã chính xác ngay từ đầu, nhưng nói chung, bạn có thể suy luận: những thứ bên trái xảy ra trước những thứ bên phải . Đó là quy tắc bạn đang tìm kiếm. Nói về mức độ ưu tiên và tính liên kết vừa khó hiểu vừa không liên quan.
Người có được công cụ này sai mọi lúc , ngay cả những người nên biết tốt hơn. Tôi đã chỉnh sửa quá nhiều sách lập trình nêu các quy tắc không chính xác, vì vậy không có gì ngạc nhiên khi rất nhiều người có niềm tin hoàn toàn không đúng về mối quan hệ giữa mức độ ưu tiên / tính liên kết và thứ tự đánh giá - cụ thể là trong thực tế không có mối quan hệ này ; chúng độc lập.
Nếu chủ đề này khiến bạn quan tâm, hãy xem các bài viết của tôi về chủ đề này để đọc thêm:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
Chúng là về C #, nhưng hầu hết những thứ này đều áp dụng tốt cho Java.