Biraz Java metni okuyorum ve şu kodu aldım:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
Metinde yazar net bir açıklama yapmadı ve son satırın etkisi: a[1] = 0;
Anladığımdan pek emin değilim: değerlendirme nasıl gerçekleşti?
Biraz Java metni okuyorum ve şu kodu aldım:
int[] a = {4,4};
int b = 1;
a[b] = b = 0;
Metinde yazar net bir açıklama yapmadı ve son satırın etkisi: a[1] = 0;
Anladığımdan pek emin değilim: değerlendirme nasıl gerçekleşti?
Yanıtlar:
Bunu çok net bir şekilde söyleyeyim, çünkü insanlar bunu her zaman yanlış anlıyor:
Alt ifadelerin değerlendirme sırası, hem ilişkilendirilebilirlik hem de öncelikten bağımsızdır . Birleşim ve öncelik hangi sırayla belirlemek operatörleri yürütür ama yok hangi sırayla belirlemek alt ifadelere değerlendirilir. Sorunuz, alt ifadelerin değerlendirildiği sırayla ilgilidir .
Düşünün A() + B() + C() * D()
. Çarpma, toplamadan daha yüksek önceliğe sahiptir ve toplama, sola ilişkiseldir, bu nedenle bu, (A() + B()) + (C() * D())
Butun size yalnızca ilk toplamanın ikinci toplamadan önce olacağını ve çarpmanın ikinci toplamadan önce olacağını söyleyen Bilmek ile eşdeğerdir . A (), B (), C () ve D () 'nin hangi sırayla çağrılacağını size söylemez! (Ayrıca çarpmanın ilk toplamadan önce mi sonra mı olduğunu da söylemez.) Öncelik ve çağrışım kurallarına uymak şu şekilde derleyerek mükemmel bir şekilde mümkündür :
d = D() // these four computations can happen in any order
b = B()
c = C()
a = A()
sum = a + b // these two computations can happen in any order
product = c * d
result = sum + product // this has to happen last
Tüm öncelik ve ilişkisellik kuralları orada takip edilir - ilk toplama ikinci toplamadan önce olur ve çarpma ikinci toplamadan önce gerçekleşir. Açıkçası, A (), B (), C () ve D () çağrılarını herhangi bir sırayla yapabiliriz ve yine de öncelik ve çağrışım kurallarına uyabiliriz!
Alt ifadelerin değerlendirilme sırasını açıklamak için öncelik ve ilişkilendirilebilirlik kurallarıyla ilgisi olmayan bir kurala ihtiyacımız var . Java'da (ve C #) ilgili kural "alt ifadeler soldan sağa değerlendirilir" dir. A (), C () 'nin solunda göründüğünden, C () bir çarpmaya ve A () yalnızca bir toplamaya dahil olmasına bakılmaksızın, önce A () değerlendirilir .
Artık sorunuzu cevaplamak için yeterli bilgiye sahipsiniz. Gelen a[b] = b = 0
çağrışımsallık söz sahibi kurallarına bu olduğunu a[b] = (b = 0);
ama bu demek değildir b=0
ilk çalışır! Öncelik kuralları, indekslemenin atamadan daha yüksek önceliğe sahip olduğunu söyler, ancak bu, indeksleyicinin en sağdaki atamadan önce çalıştığı anlamına gelmez .
(GÜNCELLEME: Bu cevabın önceki bir sürümünde, aşağıdaki bölümde düzelttiğim bazı küçük ve pratik olarak önemsiz eksiklikler vardı. Ayrıca bu kuralların Java ve C # 'da neden mantıklı olduğunu açıklayan bir blog makalesi de burada yazdım: https: // ericlippert.com/2019/01/18/indexer-error-cases/ )
O Öncelik ve birleşme sadece bize sıfır atama için b
gerçekleşmesi gerekir önce için atama a[b]
, çünkü sıfır Hesaplamalar indeksleme operasyonda atanan değer atama. Öncelik ve konusunda çağrışımsallık yalnız diyelim hiçbir şey a[b]
değerlendirilir önce veya sonrab=0
.
Yine, bu şununla aynıdır: A()[B()] = C()
- Tek bildiğimiz, indekslemenin atamadan önce yapılması gerektiğidir. A (), B () veya C () 'nin öncelik ve ilişkiselliğe göre mi çalıştığını bilmiyoruz . Bunu bize söylemek için başka bir kurala ihtiyacımız var.
Kural yine, "ilk olarak ne yapacağına dair bir seçim yaptığında, her zaman soldan sağa git" dir. Ancak, bu özel senaryoda ilginç bir kırışıklık var. Boş bir koleksiyon veya aralık dışı dizinin neden olduğu bir istisnanın yan etkisi, atamanın sol tarafının hesaplanmasının bir parçası mı yoksa atamanın kendisinin hesaplanmasının bir parçası mı? Java ikincisini seçer. (Elbette, bu, yalnızca kod zaten yanlışsa önemli olan bir ayrımdır , çünkü doğru kod ilk etapta boş referansta bulunmaz veya kötü bir dizini geçmez.)
Peki ne olur?
a[b]
Solunda b=0
yüzden, a[b]
ishal ilk , neden a[1]
. Ancak, bu indeksleme işleminin geçerliliğinin kontrol edilmesi gecikir.b=0
olur.a
geçerli ve a[1]
aralık dahilinde olan doğrulama gerçekleşira[1]
son gerçekleşir.Dolayısıyla, bu özel durumda, ilk başta doğru kodda meydana gelmemesi gereken nadir hata durumları için dikkate alınması gereken bazı incelikler olsa da , genel olarak şu sonuca varabilirsiniz: soldaki şeyler sağdaki şeylerden önce gerçekleşir . Aradığınız kural bu. Öncelik ve çağrışımdan bahsetmek hem kafa karıştırıcı hem de alakasızdır.
İnsanlar bu şeyleri her zaman yanlış anlar , daha iyi bilmesi gerekenler bile. Kuralları yanlış ifade eden çok fazla programlama kitabını düzenledim , bu nedenle pek çok insanın öncelik / ilişkilendirilebilirlik ve değerlendirme sıralaması arasındaki ilişki hakkında tamamen yanlış inançlara sahip olması şaşırtıcı değil - yani, gerçekte böyle bir ilişki yok ; bağımsızdırlar.
Bu konu ilginizi çekiyorsa, daha fazla okumak için konuyla ilgili makalelerime bakın:
http://blogs.msdn.com/b/ericlippert/archive/tags/precedence/
Bunlar C # hakkındadır, ancak bunların çoğu Java için de aynı derecede geçerlidir.
Eric Lippert'in ustaca cevabı, farklı bir dilden bahsettiği için yine de pek yardımcı olmuyor. Bu, Java Dil Belirtiminin anlambilimin kesin açıklaması olduğu Java'dır. Özellikle, §15.26.1 , =
operatör için değerlendirme sırasını açıkladığı için önemlidir (hepimiz bunun doğru ilişkisel olduğunu biliyoruz, evet?). Bu soruda önemsediğimiz parçalara biraz kısaltın:
Sol taraftaki işlenen ifadesi bir dizi erişim ifadesiyse ( §15.13 ), birçok adım gereklidir:
- İlk olarak, sol taraftaki işlenen dizi erişim ifadesinin dizi başvurusu alt ifadesi değerlendirilir. Bu değerlendirme aniden tamamlanırsa, ödev ifadesi aynı nedenle birdenbire tamamlanır; dizin alt ifadesi (sol taraftaki işlenen dizi erişim ifadesinin) ve sağ taraftaki işlenen değerlendirilmez ve atama gerçekleşmez.
- Aksi takdirde, sol el işlenen dizisi erişim ifadesinin dizin alt ifadesi değerlendirilir. Bu değerlendirme aniden tamamlanırsa, atama ifadesi aynı nedenle aniden tamamlanır ve sağ taraftaki işlenen değerlendirilmez ve herhangi bir atama gerçekleşmez.
- Aksi takdirde, sağ taraftaki işlenen değerlendirilir. Bu değerlendirme aniden tamamlanırsa, atama ifadesi aynı nedenle aniden tamamlanır ve herhangi bir atama olmaz.
[… Daha sonra, burada kısaca göz ardı edebileceğimiz, ödevin kendisinin gerçek anlamını açıklamaya devam eder…]
Kısacası, Java, herhangi bir operatör veya yöntem çağrısı için argümanlarda hemen hemen tam olarak soldan sağa olan çok yakından tanımlanmış bir değerlendirme sırasına sahiptir. Dizi atamaları daha karmaşık durumlardan biridir, ancak orada bile hala L2R'dir. (JLS, bu tür karmaşık anlamsal kısıtlamalara ihtiyaç duyan bir kod yazmamanızı önerir ve ben de öyle: her ifade başına yalnızca bir atama ile gereğinden fazla sorunla karşılaşabilirsiniz!)
C ve C ++, bu alanda Java'dan kesinlikle farklıdır: dil tanımları, daha fazla optimizasyon sağlamak için kasıtlı olarak değerlendirme sırasını tanımsız bırakır. Görünüşe göre C # Java'ya benziyor, ancak literatürünü biçimsel tanıma işaret edecek kadar iyi bilmiyorum. (Bu gerçekten dile göre değişir, Ruby kesinlikle L2R'dir, tıpkı Tcl'de olduğu gibi - burada ilgili olmayan nedenlerden dolayı kendi başına bir atama operatörü eksiktir - ve Python, L2R'dir, ancak atama açısından R2L'dir , ben tuhaf buluyorum ama işte oraya gidiyorsunuz .)
a[-1]=c
, c
değerlendirilir, önce -1
geçersiz olarak kabul edilir.
a[b] = b = 0;
1) dizi indeksleme operatörü, atama operatöründen daha yüksek önceliğe sahiptir ( bu cevaba bakınız ):
(a[b]) = b = 0;
2) 15.26. JLS'nin Atama Operatörleri
12 atama operatörü vardır; tümü sözdizimsel olarak sağa ilişkilidir (sağdan sola gruplanırlar). Bu nedenle, a = b = c, c'nin değerini b'ye atayan ve sonra b'nin değerini a'ya atayan a = (b = c) anlamına gelir.
(a[b]) = (b=0);
3) 15.7'ye göre. JLS'nin Değerlendirme Sırası
Java programlama dili, operatörlerin işlenenlerinin belirli bir değerlendirme sırasına göre, yani soldan sağa, değerlendirildiğini garanti eder.
ve
Bir ikili operatörün sol taraf operandı, sağ taraf operandının herhangi bir kısmı değerlendirilmeden önce tam olarak değerlendirilmiş gibi görünmektedir.
Yani:
a) (a[b])
ilk olarak değerlendirilira[1]
b) daha sonra (b=0)
değerlendirildi0
c) (a[1] = 0)
en son değerlendirildi
Kodunuz şuna eşdeğerdir:
int[] a = {4,4};
int b = 1;
c = b;
b = 0;
a[c] = b;
bu sonucu açıklıyor.
Aşağıdaki daha derinlemesine başka bir örneği ele alalım.
Bu soruları çözerken okunabilecek Öncelik Sırası Kuralları ve İlişkilendirilebilirlik tablosuna sahip olmak en iyisidir, örneğin http://introcs.cs.princeton.edu/java/11precedence/
İşte güzel bir örnek:
System.out.println(3+100/10*2-13);
Soru: Yukarıdaki Satırın Çıktısı Nedir?
Cevap: Öncelik ve İlişkilendirme Kurallarını Uygulayın
Adım 1: Öncelik kurallarına göre: / ve * operatörleri + - operatörlerine göre önceliklidir. Bu nedenle, bu denklemi yürütmek için başlangıç noktası daraltılacaktır:
100/10*2
Adım 2: Kurallara ve önceliğe göre: / ve *, öncelik bakımından eşittir.
/ Ve * operatörleri öncelik bakımından eşit olduklarından, bu operatörler arasındaki ilişkiye bakmamız gerekir.
Bu iki belirli operatörün DERNEĞİ KURALLARI'na göre, denklemi Soldan sağa doğru çalıştırmaya başlarız, yani önce 100/10 çalıştırılır:
100/10*2
=100/10
=10*2
=20
Adım 3: Denklem şu anda aşağıdaki yürütme durumundadır:
=3+20-13
Kurallara ve önceliğe göre: + ve - öncelikte eşittir.
Şimdi + ve - operatörleri arasındaki ilişkiye bakmamız gerekiyor. Bu iki belirli operatörün ilişkilendirilebilirliğine göre, denklemi soldan sağa doğru çalıştırmaya başlarız, yani 3 + 20 önce yürütülür:
=3+20
=23
=23-13
=10
10 derlendiğinde doğru çıktıdır
Yine, bu soruları çözerken yanınızda Öncelik Sırası Kuralları ve İlişkilendirme tablosu bulundurmanız önemlidir, örneğin http://introcs.cs.princeton.edu/java/11precedence/
10 - 4 - 3
.
+
tekli operatör (sağdan sola ilişkilendirilebilirlik vardır) olmasından kaynaklandığından şüpheleniyorum , ancak + ve - aynı çarpımsal * /% sol doğru çağrışımsallığa.