İfadeler ve atama: öncelik, yan etki, short-circuit, coercion
Operatör önceliği ve birleşme yönü, operand değerlendirme sırası ve functional side effect, short-circuit evaluation, mixed-mode ifadeler ve tip dönüşümleri, atama biçimleri. Sınavdaki “What is printed?” sorularının kaynağı.
İfadeler (expressions) bir programda hesaplamanın temel aracı. Bu konunun sınav karşılığı tek cümle: “What is printed?”. Öncelik, birleşme yönü, değerlendirme sırası, kısa devre ve tip dönüşümü kurallarını bilen biri kodu kafasında çalıştırır. Bilmeyen tahmin eder ve kaybeder.
Altın kurallar:
- Önce öncelik, sonra birleşme yönü. Aynı seviyedekiler genelde soldan sağa;
**,?:ve=sağdan sola. - Java operandları soldan sağa hesaplar. C ve C++ bunu garanti etmez.
&&ve||gerekmedikçe sağ tarafı hesaplamaz.&ve|her zaman hesaplar.x++eski değeri,++xyeni değeri verir.- Narrowing dönüşüm Java’da sadece cast ile olur.
Aritmetik ifadeler: öncelik ve birleşme yönü
Operatör önceliği (precedence), farklı operatörlerin hangi sırayla uygulanacağını belirler. Birleşme yönü (associativity), aynı önceliktekilerin sırasını belirler.
| Seviye (yüksek → düşük) | Operatörler | Yön |
|---|---|---|
| 1 | ** (Ruby, Python, Fortran) | sağa |
| 2 | unary + -, ++ --, ! | sağa (önek) |
| 3 | * / % | sola |
| 4 | binary + - | sola |
| 5 | < <= > >=, sonra == != | sola |
| 6 | &&, sonra || | sola |
| 7 | ?: | sağa |
| 8 | = += -= ... | sağa |
System.out.println(8 - 3 * 2 + 10 / 4); // 8 - 6 + 2
System.out.println(100 / 5 / 2 * 3); // ((100/5)/2)*3
int a = 4, b = 9;
System.out.println(a > b ? a : b > 5 ? b * 2 : b);
4
30
18
Python’da 2 ** 3 ** 2 = 2 ** 9 = 512 (sağa birleşir) ve -3 ** 2 = -9 (** unary eksiden yüksek). JavaScript’te de 2 ** 3 ** 2 512 verir. APL’de öncelik yoktur, her şey sağdan sola: a + b * c - d = a + (b * (c - d)).
Operand değerlendirme sırası ve side effect
Bir operand değişken ise değeri bellekten okunur; fonksiyon çağrısıysa fonksiyon çalışır. Eğer fonksiyon başka bir operandı değiştiriyorsa sıra önemlidir. Buna functional side effect denir: fonksiyonun bir parametreyi ya da nonlocal (global) bir değişkeni değiştirmesi.
static int g = 4;
static int bump() { g = g + 3; return 2; }
int x = g * bump(); // g önce okunur: 4 * 2
g = 4;
int y = bump() * g; // bump önce: g = 7, sonra 2 * 7
System.out.println(x + " " + y + " " + g);
8 14 7
Matematikte çarpma değişmeli; burada değil. Java ve C# soldan sağa garanti ettiği için sonuç belli. C ve C++ sırayı belirtmez: aynı kod farklı derleyicilerde farklı sonuç verebilir.
Dil tasarımcısının iki seçeneği var:
- Functional side effect’i yasakla: fonksiyonlarda iki yönlü parametre ve nonlocal erişim olmasın. Esneklik ölür.
- Değerlendirme sırasını sabitle (Java’nın yaptığı). Bazı derleyici optimizasyonları (operandları yeniden sıralama) imkânsızlaşır.
Referential transparency: aynı değere sahip iki ifade programda birbirinin yerine konabiliyorsa program referentially transparent’tır. Saf fonksiyonel dillerde (Haskell) değişken ve yan etki olmadığı için bu doğal olarak sağlanır; programı anlamak ve doğrulamak kolaylaşır.
Artırma operatörleri izleme
int c = 3;
int r = c++ * 2 + --c; // 3*2 + 3
System.out.println(r + " " + c);
int k = 10;
k -= k++ - --k; // k = 10 - (10 - 10)
System.out.println(k);
int p = 1;
p += p++ + ++p; // p = 1 + (1 + 3)
System.out.println(p);
9 3
10
5
Tarif: soldan sağa her operand için “şu an değer ne, sonra değişken ne oldu?” diye yaz. Compound assignment’ta (k -= e) soldaki değişkenin değeri e’den önce alınır, bu yüzden e’nin içindeki değişiklikler son atamayla ezilir.
int[] arr = {5, 6, 7};
int idx = 0;
arr[idx++] = arr[idx] + 10;
System.out.println(arr[0] + " " + arr[1] + " " + idx);
16 6 1
Java önce sol taraftaki hedefi (arr[0]) belirler, idx 1 olur; sonra sağ taraf arr[1] + 10 = 16.
Overloaded operatörler
Aynı sembolün birden fazla anlamda kullanılması: + Java’da hem toplama hem String birleştirme. C’de & hem bit düzeyi AND hem adres alma; - hem binary çıkarma hem unary eksi. Kullanıcı tanımlı overloading (C++, C#, Python) okunabilirliği artırabilir (a + b matris toplama) ya da öldürebilir (+ ile çıkarma yapan bir sınıf). Java kullanıcı tanımlı overloading’e izin vermez.
Tip dönüşümleri
- Narrowing: hedef tip kaynağın tüm değerlerini tutamaz (double → int, int → byte). Bilgi kaybı olabilir.
- Widening: hedef tip tüm değerleri (en azından yaklaşık) tutar (int → double). Genelde güvenli; ama int → float büyük sayılarda hassasiyet kaybettirebilir.
- Mixed-mode ifade: farklı tipte operandlar. Dil ya coercion (örtük dönüşüm) yapar ya da hata verir.
- Cast: programcının yazdığı açık dönüşüm.
int n = 5;
System.out.println(n / 2 + n / 2.0);
System.out.println((byte) 200);
System.out.println((int) -7.9 + " " + (int) 7.9);
char ch = 'a'; ch += 2;
System.out.println(ch + " " + ('a' + 2));
4.5
-56
-7 7
c 99
Coercion yazmayı kolaylaştırır ama tip hatalarını gizler (reliability düşer). ML ve F# ifadelerde hiç coercion yapmaz. Java atamada narrowing coercion yapmaz: int g = 3.5; derlenmez.
İlişkisel ve boolean ifadeler
İlişkisel operatörler iki operandı karşılaştırıp boolean üretir. C89’da boolean tip yoktur; 0 false, diğerleri true. Bu yüzden a < b < c C’de geçerlidir ama düşündüğün şeyi yapmaz:
console.log(1 < 2 < 3, 3 > 2 > 1);
console.log("5" == 5, "5" === 5, "5" + 1, "5" - 1);
true false
true false 51 4
3 > 2 > 1: (3 > 2) = true → true > 1 → 1 > 1 → false. JavaScript’in == operatörü coercion yapar, === yapmaz. + String görünce birleştirir, - sayıya çevirir. Java’da 3 > 2 > 1 derlenmez.
Short-circuit evaluation
Sonuç ilk operanddan belli oluyorsa ikincisi hesaplanmaz. false && X her zaman false, true || X her zaman true.
int i = 2, j = 7;
boolean r1 = (i > 5) && (++j > 0); // atlandı
boolean r2 = (i < 5) || (j++ > 0); // atlandı
boolean r3 = (i < 5) | (j++ > 100); // çalıştı: j = 8
boolean r4 = (i > 5) & (++j > 0); // çalıştı: j = 9
System.out.println(r1 + " " + r2 + " " + r3 + " " + r4 + " " + j);
false true true false 9
Kısa devre sadece hız için değil, doğruluk için de gerekli:
while (k < n && list[k] != key) k++;
k == n olduğunda list[k] dizinin dışında. Kısa devre sayesinde hiç okunmaz. Koşulların sırasını ters yazarsan koruma kaybolur.
| Dil | Kısa devre |
|---|---|
| C, C++, Java, C# | && || evet; & | hayır |
| Python, Ruby, Perl, JavaScript | Tüm mantıksal operatörler |
| Ada | Programcı seçer: and then, or else |
Atama biçimleri
| Biçim | Örnek | Not |
|---|---|---|
| Basit | a = b | C’de = atama, == eşitlik; Ada ve Pascal := |
| Koşullu hedef | (flag ? a : b) = 0 (Perl) | |
| Compound | a += b | ALGOL 68’den C’ye |
| Unary | sum = ++count | Önce artır, sonra ata. count++ önce ata |
| İfade olarak atama | while ((ch = getchar()) != EOF) | Kısa ama okunması zor, yan etki |
| Multiple | (x, y) = (y, x) (Perl, Python) | Swap |
my ($x, $y) = (3, 8);
($x, $y) = ($y, $x);
print "$x $y\n";
8 3
TUZAK · C’de
if (x = 0). Atama bir ifadedir ve değeri 0’dır. C koşul olarak her aritmetik ifadeyi kabul eder: derlenir, koşul hep false olur ve x sıfırlanır. Java’da koşul boolean olmak zorunda, int boolean’a dönüşmez: derleme hatası. Java burada reliability’yi seçmiş.
Sık yapılan hatalar
| Yanlış | Doğrusu |
|---|---|
100 / 5 / 2 * 3 sağdan | Sola birleşir: 30 |
?: sola birleşir | Sağa: a ? b : c ? d : e = a ? b : (c ? d : e) |
g * bump() = bump() * g | Yan etki varsa farklı |
| C de soldan sağa garanti eder | Etmez (unspecified) |
& kısa devre yapar | Yapmaz, sağdaki j++ çalışır |
k -= k++ - --k → içteki değişiklik kalır | k’nin eski değeri önce alındı, sonuç 10 |
(int) -7.9 = -8 | -7, sıfıra doğru kesilir |
3 > 2 > 1 true | JavaScript’te false, Java’da derleme hatası |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
What is printed in Java?
System.out.println(8 - 3 * 2 + 10 / 4);Cevabı göster
Önce * ve / soldan sağa: 3 * 2 = 6, 10 / 4 = 2 (int bölme). Sonra - ve + soldan sağa: 8 - 6 = 2, 2 + 2 = 4.
What is printed in Java?
System.out.println(100 / 5 / 2 * 3);Cevabı göster
/ ve * aynı seviye, sola birleşir: ((100 / 5) / 2) * 3 = (20 / 2) * 3 = 10 * 3 = 30. Sağdan yapsaydın 100 / (5 / (2 * 3)) = 100 / 0 olurdu: birleşme yönü sonucu tamamen değiştirir.
What is the value of m?
int a = 4, b = 9;
int m = a > b ? a : b > 5 ? b * 2 : b;Cevabı göster
Koşullu operatör (?:) sağa birleşir: a > b ? a : (b > 5 ? b * 2 : b). a > b false → ikinci kısım. b > 5 true → b * 2 = 18.
What is printed in Java?
int i = 2, j = 7;
boolean r1 = (i > 5) && (++j > 0);
boolean r2 = (i < 5) || (j++ > 0);
boolean r3 = (i < 5) | (j++ > 100);
boolean r4 = (i > 5) & (++j > 0);
System.out.println(r1 + " " + r2 + " " + r3 + " " + r4 + " " + j);Cevabı göster
r1: sol false, && kısa devre yapar, ++j çalışmaz (j = 7). r2: sol true, || sağı atlar (j = 7). r3: | kısa devre yapmaz, j++ çalışır: 7 > 100 false, j = 8; true | false = true. r4: & de iki tarafı hesaplar: ++j → 9; false & true = false. Son j = 9.
What is printed in Java?
static int g = 4;
static int bump() { g = g + 3; return 2; }
// in main:
int x = g * bump();
g = 4;
int y = bump() * g;
System.out.println(x + " " + y + " " + g);Cevabı göster
Java operandları soldan sağa hesaplar. x: önce g okunur (4), sonra bump() g'yi 7 yapar ve 2 döner: 4 * 2 = 8. y: önce bump(): g = 7, döner 2; sonra g okunur: 7 → 2 * 7 = 14. g sonunda 7. Matematikte g * bump() ile bump() * g aynı olmalıydı; functional side effect bunu bozar.
What is printed in Java?
int c = 3;
int r = c++ * 2 + --c;
System.out.println(r + " " + c);Cevabı göster
Soldan sağa: c++ eski değeri verir (3) ve c'yi 4 yapar. 3 * 2 = 6. --c önce azaltır: c = 3, değer 3. 6 + 3 = 9. Son c = 3.
What is printed in Java?
int[] arr = {5, 6, 7};
int idx = 0;
arr[idx++] = arr[idx] + 10;
System.out.println(arr[0] + " " + arr[1] + " " + idx);Cevabı göster
Java atamada önce sol taraftaki dizi elemanının yerini belirler: idx++ indis 0'ı verir, idx = 1 olur. Sonra sağ taraf: arr[idx] = arr[1] = 6, + 10 = 16. arr[0] = 16. C'de bu satırın sonucu tanımsızdır (unspecified order).
What is the value of d?
int n = 5;
double d = n / 2 + n / 2.0;Cevabı göster
n / 2: iki int, tam sayı bölmesi: 2. n / 2.0: mixed-mode, n double'a coercion edilir: 2.5. 2 + 2.5 = 4.5 (int, double'a genişletilir).
What is printed in Java?
System.out.println((byte) 200);
System.out.println((int) -7.9);Cevabı göster
(byte) 200: narrowing dönüşüm, byte -128..127. 200 - 256 = -56. (int) -7.9: ondalık kısım atılır, sıfıra doğru: -7 (yuvarlama yok). Narrowing dönüşümler bilgi kaybettirebilir, bu yüzden Java onları sadece açık cast ile yapar.
What does 3 > 2 > 1 evaluate to in JavaScript, and why? What happens in Java?
Cevabı göster
Sola birleşir: (3 > 2) > 1 → true > 1. true sayıya coercion edilir: 1 > 1 → false. C'de de benzer (1 > 1 = 0). Java'da boolean ile int karşılaştırılamaz: derleme hatası. Ayrıca 1 < 2 < 3 JavaScript'te true çıkar ama "doğru" olduğu için değil.
What is printed in Java?
int k = 10;
k -= k++ - --k;
System.out.println(k);Cevabı göster
Compound assignment k -= e = k = k - (e), soldaki k'nin değeri ÖNCE alınır: 10. e: k++ → 10 (k = 11), --k → 10 (k = 10); 10 - 10 = 0. k = 10 - 0 = 10. Aradaki artırma/azaltmalar son atamayla ezilir.
What does this Perl code print?
my ($x, $y) = (3, 8);
($x, $y) = ($y, $x);
print "$x $y\n";Cevabı göster
Multiple assignment: sağ taraftaki liste önce tümüyle hesaplanır (8, 3), sonra sırayla sola atanır. Geçici değişken olmadan swap. Python ve Ruby'de de aynı: x, y = y, x.
Doğrulama: Tüm Java cevapları JDK ile çalıştırıldı (java verify/SEN2001/W06.java); JavaScript karşılaştırma/coercion örnekleri node ile (verify/SEN2001/06.mjs), Perl çoklu atama ve mantıksal operatörler perl ile (verify/SEN2001/06.pl) çalıştırıldı. 2 ** 3 ** 2 = 512 node ile çalıştırıldı; Python kurulu olmadığı için -3 ** 2 = -9 bilgisi Python dil belgesindeki öncelik tablosuna dayanır. C'deki belirsiz değerlendirme sırası iddiası C standardına dayanır, çalıştırılarak gösterilemez.