SEN2001 · 8. hafta · Subprograms I

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.

subprogramformal parameteractual parameterpositional parameterkeyword parameterdefault parameterin modeout modeinout modepass-by-valuepass-by-resultpass-by-value-resultpass-by-referencepass-by-namealiasingthunkclosureoverloaded subprogramgeneric subprogram

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:

  1. Value: kopya. Reference: adres. Biri dışarıyı değiştirmez, diğeri anında değiştirir.
  2. Value-result dışarıyı sadece dönüşte değiştirir, ve sonuç kopya sırasına bağlı olabilir.
  3. Name: her kullanımda ifadeyi yeniden hesapla. İndis değişkeni değişirse başka elemana gidersin.
  4. Aynı değişken iki parametreye ya da parametre + global olarak geçerse alias var. En çok soru buradan çıkar.
  5. 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

ModelVeri akışıUygulama yöntemleri
In modeÇağırandan çağrılanaPass-by-value
Out modeÇağrılandan çağıranaPass-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İzlemeSon a
Valuex = 2, y = 2 kopya; a’ya dokunulmaz2
Referencex ve y = a’nın hücresi. a = 3, a = 3 · 39
Value-result (soldan sağa)x = 3, y = 2 · 3 = 6; dönüş: a = 3, sonra a = 66
Value-result (sağdan sola)aynı, dönüş: a = 6, sonra a = 33
Namex 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

DilKural
CPass-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
JavaHepsi 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
Adain, out, in out modları açıkça yazılır
Python, RubyPass-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, C printf.

Sık yapılan hatalar

YanlışDoğrusu
Value-result = referenceAlias etkisi dönüşe kadar görünmez; f(a, a) farklı sonuç
Value-result’ta kopya sırası önemsizf(a, a)‘da önemli: soldan sağa 6, sağdan sola 3
Name = referenceDeğişen indisle farklılaşır
Reference’ta adres her erişimde hesaplanırÇağrı anında bir kez
Java nesneleri pass-by-referenceReferansın kopyası; arr = new dışarıyı değiştirmez
Result modunda giriş değeri kullanılırFormal başlatılmamıştır
Global değişkeni değiştiren satır value’da etkisizGlobal doğrudan değişir; sadece formal kopya

Kendini test et

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

Soru 1orta

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
(a) 2 (b) 9

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.

Soru 2zor

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
6; right to left gives 3

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

Soru 3orta

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
7

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.

Soru 4zor

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
i = 0, A = {1, 0, 7}

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

Soru 5zor

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
(a) {11, 2, 3} (b) {1, 12, 3}

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.

Soru 6zor

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: 4, reference: 7, value-result: 6

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.

Soru 7kolay

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
1 2

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.

Soru 8orta

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
99 2

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.

Soru 9orta

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
1 2 1 3

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.

Soru 10kolay

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
9 4

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

Soru 11kolay

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
(a) value (b) reference (c) name (d) value-result

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.

Soru 12zor

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
3,3,3

Üç 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ı.

Deftere not kartı

formal = tanımdaki ad, actual = çağrıdaki ifade. positional / keyword / default in: value | out: result | inout: value-result, reference, name VALUE: kopya içeri. dışarı etkisi yok RESULT: içeri değer yok, dönüşte kopya dışarı. f(a,a) → son kopya kazanır VALUE-RESULT: kopya içeri + dönüşte dışarı. f(a,a) sonucu kopya SIRASINA bağlı REFERENCE: adres geçer, alias. adres ÇAĞRI anında hesaplanır NAME: metinsel yerine koyma, HER KULLANIMDA yeniden hesapla (thunk) swap(i, A[i]) by name bozuk: i önce değişir, A[i] başka elemanı gösterir global + parametre aynı değişken → alias: value 4, reference 7, value-result 6 tipi sorular Java: HER ŞEY pass-by-value. referans kopyalanır: nesneyi değiştirebilir, a = new ... dışarıyı değiştirmez C: value (pointer ile reference taklidi). C++: & reference. C#: ref / out closure: fonksiyon + yaratıldığı ortam. yakalanan değişkenin lifetime'ı uzar var i ile closure → hepsi son değeri görür; let ile her tur ayrı