SEN2001 · 6. hafta · Expressions and Assignment Statements

İ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ğı.

arithmetic expressionoperator precedenceoperator associativityoperand evaluation orderfunctional side effectreferential transparencyoperator overloadingmixed-mode expressioncoerciontype conversionwideningnarrowingcastrelational expressionshort-circuit evaluationconditional expressioncompound assignmentunary assignmentmultiple assignment

İ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:

  1. Önce öncelik, sonra birleşme yönü. Aynı seviyedekiler genelde soldan sağa; **, ?: ve = sağdan sola.
  2. Java operandları soldan sağa hesaplar. C ve C++ bunu garanti etmez.
  3. && ve || gerekmedikçe sağ tarafı hesaplamaz. & ve | her zaman hesaplar.
  4. x++ eski değeri, ++x yeni değeri verir.
  5. 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örlerYön
1** (Ruby, Python, Fortran)sağa
2unary + -, ++ --, !sağa (önek)
3* / %sola
4binary + -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:

  1. Functional side effect’i yasakla: fonksiyonlarda iki yönlü parametre ve nonlocal erişim olmasın. Esneklik ölür.
  2. 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.

DilKısa devre
C, C++, Java, C#&& || evet; & | hayır
Python, Ruby, Perl, JavaScriptTüm mantıksal operatörler
AdaProgramcı seçer: and then, or else

Atama biçimleri

BiçimÖrnekNot
Basita = bC’de = atama, == eşitlik; Ada ve Pascal :=
Koşullu hedef(flag ? a : b) = 0 (Perl)
Compounda += bALGOL 68’den C’ye
Unarysum = ++countÖnce artır, sonra ata. count++ önce ata
İfade olarak atamawhile ((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ğdanSola birleşir: 30
?: sola birleşirSağa: a ? b : c ? d : e = a ? b : (c ? d : e)
g * bump() = bump() * gYan etki varsa farklı
C de soldan sağa garanti ederEtmez (unspecified)
& kısa devre yaparYapmaz, sağdaki j++ çalışır
k -= k++ - --k → içteki değişiklik kalırk’nin eski değeri önce alındı, sonuç 10
(int) -7.9 = -8-7, sıfıra doğru kesilir
3 > 2 > 1 trueJavaScript’te false, Java’da derleme hatası

Kendini test et

Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.

Soru 1kolay

What is printed in Java?

System.out.println(8 - 3 * 2 + 10 / 4);
Cevabı göster
4

Ö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.

Soru 2kolay

What is printed in Java?

System.out.println(100 / 5 / 2 * 3);
Cevabı göster
30

/ 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.

Soru 3orta

What is the value of m?

int a = 4, b = 9;
int m = a > b ? a : b > 5 ? b * 2 : b;
Cevabı göster
18

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.

Soru 4orta

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
false true true false 9

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.

Soru 5zor

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
8 14 7

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.

Soru 6zor

What is printed in Java?

int c = 3;
int r = c++ * 2 + --c;
System.out.println(r + " " + c);
Cevabı göster
9 3

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.

Soru 7zor

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
16 6 1

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).

Soru 8orta

What is the value of d?

int n = 5;
double d = n / 2 + n / 2.0;
Cevabı göster
4.5

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).

Soru 9orta

What is printed in Java?

System.out.println((byte) 200);
System.out.println((int) -7.9);
Cevabı göster
-56 then -7

(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.

Soru 10orta

What does 3 > 2 > 1 evaluate to in JavaScript, and why? What happens in Java?

Cevabı göster
false in JavaScript; compile error in Java

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.

Soru 11zor

What is printed in Java?

int k = 10;
k -= k++ - --k;
System.out.println(k);
Cevabı göster
10

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.

Soru 12kolay

What does this Perl code print?

my ($x, $y) = (3, 8);
($x, $y) = ($y, $x);
print "$x $y\n";
Cevabı göster
8 3

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.

Deftere not kartı

öncelik: ** > unary > * / % > + - > karşılaştırma > && > || > ?: > = * / % aynı seviye, sola: 100/5/2*3 = 30 ; ** ve ?: ve = SAĞA APL: öncelik yok, sağdan sola. Parantez her şeyi ezer Java/C# operandları SOLDAN SAĞA hesaplar; C/C++ belirtmez (unspecified) functional side effect: fonksiyon global/parametre değiştirir → g*f() != f()*g çözüm: yan etkiyi yasakla (esneklik kaybı) VEYA sırayı sabitle (optimizasyon kaybı) referential transparency: aynı değerli ifadeler yer değiştirebilir (saf fonksiyonel) && || kısa devre, & | (boolean) değil → sağdaki x++ çalışır x++ eski değer, ++x yeni değer. k -= e → k'nin eski değeri ÖNCE alınır widening (int→double) güvenli; narrowing (double→int, int→byte) cast ister (int)-7.9 = -7 (byte)200 = -56 n/2 + n/2.0 = 2 + 2.5 coercion = örtük, cast = açık. coercion reliability düşürür (ML/F# yok) JS: 3 > 2 > 1 false, "5" == 5 true, "5" === 5 false, "5"+1 "51", "5"-1 4 Perl/Python çoklu atama: (x, y) = (y, x) swap