Alt programlar I: parametre geçirme yöntemleri
Formal/actual parametre, in/out/inout modelleri ve beş uygulama: pass-by-value, result, value-result, reference, name. Aynı programın her yöntemde ne bastığını izlemek; ayrıca Java'nın kuralı, closure ve default parametreler.
Bu hafta parametre geçirme yöntemleri: sınavın en “mekanik” ve en çok puan getiren konularından biri. Soru şablonu hep aynı: küçük bir prosedür, bir çağrı, “her yöntemde değişkenler ne olur?” Tarif: her yöntem için formal parametrenin ne aldığını (değer mi, adres mi, ifade mi) ve ne zaman geri yazdığını yaz, sonra satır satır izle.
Altın kurallar:
- Value: kopya. Reference: adres. Biri dışarıyı değiştirmez, diğeri anında değiştirir.
- Value-result dışarıyı sadece dönüşte değiştirir, ve sonuç kopya sırasına bağlı olabilir.
- Name: her kullanımda ifadeyi yeniden hesapla. İndis değişkeni değişirse başka elemana gidersin.
- Aynı değişken iki parametreye ya da parametre + global olarak geçerse alias var. En çok soru buradan çıkar.
- Java’da her şey pass-by-value, referanslar dahil.
Temel kavramlar
Alt program (subprogram) tanımı bir header (ad, parametreler) ve gövdeden oluşur. Tanımdaki parametreler formal parameter, çağrıdaki ifadeler actual parameter.
- Positional: actual’lar sırayla eşleşir. Kısa, ama uzun listelerde hata yaptırır.
- Keyword:
area(width = 3, height = 5)(Python, Ada). Sıra serbest, ama formal adları bilinmeli. - Default: actual verilmezse kullanılan değer.
function f(a, b = a * 2) { return a + b; }
console.log(f(3), f(3, 1));
9 4
Alt programlar ikiye ayrılır: değer döndüren function ve döndürmeyen procedure. Yerel değişkenler genelde stack-dynamic’tir (özyineleme mümkün); C’de static ile static yapılabilir (geçmişi hatırlar).
Anlamsal modeller: in, out, inout
| Model | Veri akışı | Uygulama yöntemleri |
|---|---|---|
| In mode | Çağırandan çağrılana | Pass-by-value |
| Out mode | Çağrılandan çağırana | Pass-by-result |
| Inout mode | İki yönlü | Pass-by-value-result, pass-by-reference, pass-by-name |
Uygulama iki yolla yapılır: değeri fiziksel olarak kopyalamak (value, result, value-result) ya da bir erişim yolu (adres) aktarmak (reference).
Beş yöntem
Pass-by-value
Actual’ın değeri formal’ı başlatır, formal yerel bir değişken gibi davranır. Dışarıya hiçbir etki yok. Maliyet: büyük nesneler kopyalanır.
Pass-by-result
Giriş değeri aktarılmaz. Formal çağrılanda başlatılır, dönüşte değeri actual’a kopyalanır. Sorunlar: g(a, a) gibi çağrılarda hangi kopyanın kazanacağı (actual parameter collision) ve actual’ın adresinin çağrıda mı dönüşte mi hesaplanacağı.
void g(out int x, out int y) { x = 5; y = 7; }
g(a, a); soldan sağa kopya: a = 5, sonra a = 7 → 7
Pass-by-value-result
Value + result: girişte kopya içeri, dönüşte kopya dışarı. Reference’a benzer ama alias etkileri dönüşe kadar görünmez.
Pass-by-reference
Actual’ın adresi aktarılır, formal onun takma adı (alias) olur. Kopya yok, hızlı. Ama alias’lar okunabilirliği ve güvenilirliği düşürür; her erişimde bir dolaylı adresleme (indirection) gerekir.
Pass-by-name
Formal’ın her kullanımı, actual’ın metniyle değiştirilmiş gibi davranır ve o anda çağıranın ortamında hesaplanır. ALGOL 60’ta var, bugün pek kullanılmaz. Uygulaması: her parametre için bir thunk (çağrıldığında adres ya da değer hesaplayan küçük fonksiyon). Basit bir değişken geçince reference ile aynı davranır; fark dizi elemanı ya da ifade geçince çıkar.
Çözümlü örnek 1: aynı değişken iki kez
void f(int x, int y) { x = x + 1; y = y * x; }
a = 2; f(a, a);
| Yöntem | İzleme | Son a |
|---|---|---|
| Value | x = 2, y = 2 kopya; a’ya dokunulmaz | 2 |
| Reference | x ve y = a’nın hücresi. a = 3, a = 3 · 3 | 9 |
| Value-result (soldan sağa) | x = 3, y = 2 · 3 = 6; dönüş: a = 3, sonra a = 6 | 6 |
| Value-result (sağdan sola) | aynı, dönüş: a = 6, sonra a = 3 | 3 |
| Name | x ve y her kullanımda a: reference ile aynı | 9 |
Çözümlü örnek 2: global ile alias
int g = 3;
void fun(int x) { x = x * 2; g = g + 1; }
fun(g);
value: x = 3 → 6 (yerel) g = 3 + 1 → 4
reference: x ≡ g: g = 6 g = 6 + 1 → 7
value-result: x = 3 → 6 (yerel) g = 4; dönüşte g = x → 6
name: x ≡ g (basit değişken) → 7
Çözümlü örnek 3: dizi elemanı ve değişen indis
int i = 0; int A[3] = {1, 2, 3};
void h(int x) { i = i + 1; x = x + 10; }
h(A[i]);
value: dizi değişmez {1, 2, 3}
reference: adres çağrıda: x ≡ A[0] {11, 2, 3}
i değişmesi önemsiz
name: x her kullanımda A[i]; i artık 1 {1, 12, 3}
x = x + 10 → A[1] = A[1] + 10
Value-result’ta da {11, 2, 3} çıkar, çünkü dönüş adresi çağrı anında hesaplanır. Bazı tanımlarda adres dönüşte hesaplanır; o zaman {1, 11, 3} olur. Sınavda soruda belirtilmemişse çağrı anını varsay ve yaz.
Çözümlü örnek 4: klasik swap(i, A[i])
void swap(int x, int y) { int t = x; x = y; y = t; }
i = 1; A = {5, 0, 7};
swap(i, A[i]);
reference (adresler çağrıda: x ≡ i, y ≡ A[1]):
t = i = 1; i = A[1] = 0; A[1] = t = 1 → i = 0, A = {5, 1, 7} doğru takas
name (her kullanımda yeniden hesapla):
t = i → 1
i = A[i] → i = A[1] = 0
A[i] = t → A[0] = 1 ← i artık 0
→ i = 0, A = {1, 0, 7} takas BOZUK
Pass-by-name ile doğru çalışan genel bir swap yazılamaz. Bu yöntemin terk edilme nedenlerinden biri.
TUZAK · name ile reference aynı sanmak. Basit bir değişken (
a) geçince ikisi aynı sonucu verir. Dizi elemanı geçerken indis gövdede değişiyorsa ayrılırlar: reference eski elemana, name yeni elemana yazar.
Dillerde durum
| Dil | Kural |
|---|---|
| C | Pass-by-value. Reference etkisi için pointer geçilir (swap(&x, &y)) |
| C++ | Value + reference tipleri (int&); const int& büyük nesneleri kopyasız ama değiştirmeden geçirir |
| Java | Hepsi pass-by-value. Nesne değişkenleri referans tuttuğu için referansın kopyası geçer |
| C# | Varsayılan value; ref ile reference, out ile result benzeri |
| Ada | in, out, in out modları açıkça yazılır |
| Python, Ruby | Pass-by-assignment: nesneye referans kopyalanır (Java’ya benzer) |
static void swap(int a, int b) { int t = a; a = b; b = t; }
static void change(int[] arr) { arr[0] = 99; arr = new int[]{7, 7}; arr[1] = 55; }
static void grow(StringBuilder sb) { sb.append("!"); sb = new StringBuilder("new"); sb.append("?"); }
int x = 1, y = 2; swap(x, y);
System.out.println(x + " " + y);
int[] a = {1, 2}; change(a);
System.out.println(a[0] + " " + a[1]);
StringBuilder s = new StringBuilder("hi"); grow(s);
System.out.println(s);
1 2
99 2
hi!
Kural: metot nesnenin içini değiştirebilir (çünkü aynı nesneyi gösteriyor), ama çağıranın değişkenini başka nesneye yöneltemez.
Closure ve diğer konular
Closure, bir alt program ile tanımlandığı referencing environment’ın birleşimidir. Fonksiyon bir değer olarak döndürülüp başka yerde çağrılabiliyorsa, yakaladığı değişkenlerin ömrü uzamalıdır (unlimited extent).
function makeCounter() { let c = 0; return () => ++c; }
const c1 = makeCounter(), c2 = makeCounter();
console.log(c1(), c1(), c2(), c1());
const fs = []; for (var i = 0; i < 3; i++) fs.push(() => i);
console.log(fs.map((g) => g()).join(","));
const gs = []; for (let j = 0; j < 3; j++) gs.push(() => j);
console.log(gs.map((g) => g()).join(","));
1 2 1 3
3,3,3
0,1,2
- Overloaded subprogram: aynı ad, farklı parametre profili (Java, C++). Hangisinin çağrılacağı derlemede belirlenir.
- Generic subprogram: farklı tiplerle çalışan tek tanım (Java generics, C++ template). Parametrik polimorfizm.
- Değişken sayıda parametre: Java
int... xs, Cprintf.
Sık yapılan hatalar
| Yanlış | Doğrusu |
|---|---|
| Value-result = reference | Alias etkisi dönüşe kadar görünmez; f(a, a) farklı sonuç |
| Value-result’ta kopya sırası önemsiz | f(a, a)‘da önemli: soldan sağa 6, sağdan sola 3 |
| Name = reference | Değişen indisle farklılaşır |
| Reference’ta adres her erişimde hesaplanır | Çağrı anında bir kez |
| Java nesneleri pass-by-reference | Referansın kopyası; arr = new dışarıyı değiştirmez |
| Result modunda giriş değeri kullanılır | Formal başlatılmamıştır |
| Global değişkeni değiştiren satır value’da etkisiz | Global doğrudan değişir; sadece formal kopya |
Kendini test et
Sınavdaki gibi İngilizce sorular. Önce kâğıt kalemle çöz, sonra cevabı aç.
Given the procedure below and a global a = 2, what is the value of a after the call f(a, a) under (a) pass-by-value, (b) pass-by-reference?
void f(int x, int y) {
x = x + 1;
y = y * x;
}Cevabı göster
Value: x ve y a'nın kopyaları, a'ya dokunulmaz: 2. Reference: x ve y ikisi de a'nın adresine bağlı (aynı hücre, alias). x = x + 1 → a = 3. y = y * x → a = 3 * 3 = 9.
Same procedure and call f(a, a) with a = 2, but pass-by-value-result, copying back from left to right. What is a? What if copying is right to left?
Cevabı göster
Girişte x = 2, y = 2 (kopyalar). x = 3. y = 2 * 3 = 6. Dönüşte kopyala: önce x → a (a = 3), sonra y → a (a = 6). Sağdan sola kopyalarsan önce y (6), sonra x (3): a = 3. Aynı değişken iki kez geçince sonuç kopya sırasına bağlı; dil bunu tanımlamalı.
Using pass-by-result with g(a, a), where void g(out int x, out int y) { x = 5; y = 7; }, and copying back left to right, what is a?
Cevabı göster
Pass-by-result'ta giriş değeri aktarılmaz, x ve y çağrılanda başlatılır. Dönüşte x → a (5), sonra y → a (7). Son kopya kazanır. Bu duruma actual parameter collision denir: sonuç kopyalama sırasına bağlıdır ve dil bu sırayı tanımlamazsa program taşınabilir olmaz.
With i = 1 and A = {5, 0, 7} (0-based), what are i and A after swap(i, A[i]) using pass-by-name?
void swap(int x, int y) {
int t = x;
x = y;
y = t;
}Cevabı göster
Pass-by-name'de her x ve y kullanımı metinsel olarak i ve A[i] ile değiştirilir ve o anda hesaplanır.
t = i t = 1
i = A[i] i = A[1] = 0
A[i] = t A[0] = 1 (i artık 0!)Takas başarısız: istenen i = 0, A[1] = 1 idi. Reference ile aynı çağrı doğru takas yapar: i = 0, A = {5, 1, 7}.Global i = 0, A = {1, 2, 3}. What is A after h(A[i]) under (a) reference, (b) name?
void h(int x) {
i = i + 1;
x = x + 10;
}Cevabı göster
Reference: adres çağrı anında hesaplanır, x = A[0]'ın adresi. i'nin değişmesi x'i etkilemez: A[0] = 1 + 10 = 11. Name: x her kullanımda A[i] olarak yeniden hesaplanır; i artık 1: A[1] = A[1] + 10 = 12. Value ile ise dizi değişmez.
Global g = 3. What is g after fun(g) under value, reference and value-result?
void fun(int x) {
x = x * 2;
g = g + 1;
}Cevabı göster
Value: x kopya, sadece g = g + 1 etkili: 4. Reference: x ile g aynı hücre (alias): x = 6 → g = 6, sonra g = 7. Value-result: x = 3 kopya, x = 6; g = 4; dönüşte x g'ye kopyalanır ve g = 4'ü ezer: 6.
What is printed in Java?
static void swap(int a, int b) { int t = a; a = b; b = t; }
// in main:
int x = 1, y = 2;
swap(x, y);
System.out.println(x + " " + y);Cevabı göster
Java'da tüm parametreler pass-by-value. a ve b, x ve y'nin kopyaları; takas kopyalar arasında olur. Java'da iki int'i takas eden bir metot yazılamaz.
What is printed in Java?
static void change(int[] arr) {
arr[0] = 99;
arr = new int[]{7, 7};
arr[1] = 55;
}
// in main:
int[] a = {1, 2};
change(a);
System.out.println(a[0] + " " + a[1]);Cevabı göster
Referansın kendisi değerle geçer. arr[0] = 99 aynı nesneyi değiştirir, a'dan görünür. arr = new ... sadece yerel kopyayı başka nesneye yöneltir; a hâlâ eski diziyi gösterir. arr[1] = 55 yeni diziye yazılır. Sonuç 99 2.
What does this JavaScript code print?
function makeCounter() { let c = 0; return () => ++c; }
const c1 = makeCounter(), c2 = makeCounter();
console.log(c1(), c1(), c2(), c1());Cevabı göster
Closure: dönen fonksiyon, yaratıldığı ortamın c değişkenini yanında taşır. c'nin lifetime'ı makeCounter dönünce bitmez (unlimited extent). Her makeCounter çağrısı ayrı bir c yaratır: c1'in sayacı 1, 2, 3; c2'ninki 1.
What does this JavaScript code print?
function f(a, b = a * 2) { return a + b; }
console.log(f(3), f(3, 1));Cevabı göster
Default parametre: actual verilmezse b = a * 2 = 6, 3 + 6 = 9. Verilirse kullanılır: 3 + 1 = 4. Default parametreler Python, C++, Ada, JavaScript'te var; Java'da yok (overloading ile taklit edilir).
Which parameter passing method is described: (a) the actual's value initializes the formal, no value is sent back; (b) the formal is an alias of the actual; (c) the actual is textually substituted and re-evaluated at every reference; (d) value copied in at the call and copied out at the return.
Cevabı göster
Value = in mode, fiziksel kopya. Reference = inout, erişim yolu (adres) aktarılır. Name = inout, geç bağlama (late binding), thunk ile uygulanır. Value-result = inout, iki yönlü kopya; bazen pass-by-copy denir. Result = out mode.
What is printed in JavaScript?
const fs = [];
for (var i = 0; i < 3; i++) fs.push(() => i);
console.log(fs.map(g => g()).join(","));Cevabı göster
Üç closure da aynı var i'yi yakalar (var fonksiyon scope'lu, tek değişken). Çağrıldıklarında döngü bitmiş, i = 3. let j ile yazılsaydı her tur ayrı bir değişken olurdu ve 0,1,2 basılırdı.
Doğrulama: Beş parametre geçirme yöntemi node'da gerçek bir bellek modeliyle (hücreler, adresler, by-name için her erişimde yeniden hesaplanan thunk'lar) benzetildi ve her sorudaki program bu benzetimde çalıştırıldı (verify/SEN2001/08.mjs); copy-back sırası ve adresin çağrıda mı dönüşte mi hesaplandığı ayrıca denendi. Java'nın pass-by-value davranışı JDK ile (java verify/SEN2001/W08.java), closure ve default parametreler node ile (verify/SEN2001/08b.mjs) çalıştırıldı.