İçeriğe geç
academia.sh

Ders 05 / 14

Denetim Akışı

PHP'de karşılıksız kalan dalların davranışı: switch ile match arasındaki fark, gevşek ve katı karşılaştırma, döngü değişkenlerinin ömrü, foreach ve referans tuzağı ve match içinde default kolunun durdurmayı sessize çevirmesi.

Yayın Güncelleme

Bu derste

  • Karşılıksız kalan bir switch ile match dalının sonucunu ölçerek ayırt eder
  • switch'in gevşek, match'in katı karşılaştırma yaptığını aynı girdilerle gösterir
  • İç içe döngülerde break ve continue düzeylerinin sonucu nasıl değiştirdiğini doğrular
  • foreach ile referans kullanımının dizide bıraktığı kalıntıyı ölçer ve nasıl temizlendiğini gösterir
İçindekiler

İşleçler dersi, tipleri buluşturan işleçlerin neyi bildirdiğini ve neyi susturduğunu ölçtü. Bu ders aynı soruyu dallara soruyor: bir koşulun, bir durumun ya da bir match kolunun hiçbiri eşleşmediğinde, yani karşılıksız bir dal kaldığında, dil ne yapıyor? Cevap iki deyimde farklıdır ve bu dersin ölçü sınıfı durdurur: en az bir deyim, eşleşme yoksa yürütmeyi keser.

PHP’de koşullu dallanma için if, elseif ve else, çoklu seçim için switch ve match, tekrar için for, while, do while ve foreach vardır. Programlama Temelleri kursunda (M01/K02) bu yapıların mantığı kurulmuştu; burada yalnız PHP’nin eşleşme ve çıkış davranışı ölçülüyor. Her dosya bir hata işleyicisi kurar ve işleyici, sinyal gelirse onu görünür kılar.

Karşılıksız Dal: switch ve match

switch bir değeri sırayla case etiketleriyle karşılaştırır ve hiçbiri eşleşmezse, default yoksa, hiçbir şey yapmadan geçer. match aynı işi bir ifade olarak yapar ve bir değer döndürür; hiçbir kol eşleşmezse bir UnhandledMatchError fırlatır. Aşağıdaki dosya üç değeri iki deyimden geçirir. $sonuc değişkeni her turda önceki bir değerle başlar; karşılıksız dalda bu değerin ne olduğu ölçülür.

<?php
// karsiliksiz.php — switch sessizce gecer, match durdurur
$bildirim = 0;
set_error_handler(function (int $seviye) use (&$bildirim): bool {
    $bildirim++;
    return true;
});

foreach ([1, 2, 3] as $kod) {
    $sonuc = 'onceki';
    switch ($kod) {
        case 1:
            $sonuc = 'bir';
            break;
        case 2:
            $sonuc = 'iki';
            break;
    }
    echo "switch($kod): $sonuc\n";

    $sonuc = 'onceki';
    try {
        $sonuc = match ($kod) {
            1 => 'bir',
            2 => 'iki',
        };
        echo "match($kod):  $sonuc\n";
    } catch (UnhandledMatchError $hata) {
        echo "match($kod):  ", get_class($hata), ' (sonuc: ', $sonuc, ")\n";
    }
}
echo 'bildirim: ', $bildirim, "\n";
switch(1): bir
match(1):  bir
switch(2): iki
match(2):  iki
switch(3): onceki
match(3):  UnhandledMatchError (sonuc: onceki)
bildirim: 0

KN34. Üçüncü turda switch hiçbir şey yapmadı: değişken önceki değerinde kaldı ve bildirim sayacı artmadı. match aynı girdide UnhandledMatchError fırlattı ve atama gerçekleşmediği için $sonuc yine önceki değerinde kaldı; fark, dilin bu durumda artık sessiz kalmamasıdır. match ifadesi, eşleşmeyen bir değeri bir hata olarak tanımlar; switch ise bunu normal bir yürütme yolu sayar.

Gevşek ve Katı Karşılaştırma

İkisi arasındaki ikinci fark, karşılaştırmanın sıkılığıdır. switch etiketleri == ile, yani gevşek eşitlikle karşılaştırır; match kolları === ile, yani katı eşitlikle. Aynı girdi iki deyimde farklı kola gidebilir. Aşağıdaki dosya beş girdiyi iki deyime verir; etiket ve kol listeleri aynıdır.

<?php
// karsilastirma.php — switch gevsek, match kati karsilastirir
$girdiler = [
    ["'1'", '1'],
    ['null', null],
    ["''", ''],
    ['0', 0],
    ["'a'", 'a'],
];
printf("%-6s%-13s%s\n", 'girdi', 'switch', 'match');
foreach ($girdiler as [$ad, $girdi]) {
    switch ($girdi) {
        case 1:
            $s = 'case 1';
            break;
        case false:
            $s = 'case false';
            break;
        case 'a':
            $s = "case 'a'";
            break;
        default:
            $s = 'default';
    }
    $m = match ($girdi) {
        1 => 'kol 1',
        false => 'kol false',
        'a' => "kol 'a'",
        default => 'default',
    };
    printf("%-6s%-13s%s\n", $ad, $s, $m);
}
girdi switch       match
'1'   case 1       default
null  case false   default
''    case false   default
0     case false   default
'a'   case 'a'     kol 'a'

KN35. '1' dizgisi switch’te case 1’e gitti, match’te ise 1 => koluna uymayıp default’a düştü. null, boş dizgi ve 0 switch’te case false’e gitti, çünkü gevşek eşitlikte false’la eşittirler; match’te hiçbiri false => koluna uymadı. Yalnız 'a' iki deyimde aynı yere gitti. Bu, İşleçler dersindeki eşitlik tablosunun denetim akışındaki yansımasıdır: switch bildirim üretmeden gevşek eşitliğin bütün sürprizlerini devralır.

İç İçe Döngülerde Çıkış

break bir döngüyü bitirir, continue bir sonraki tura geçer ve ikisi de bir sayı alabilir: break 2 iki iç içe döngüden birden çıkar, continue 2 dıştaki döngünün sonraki turuna geçer. Dil bu sayıyı geçerli bir düzey sınırı dışında kabul etmez, ama sayının neyi atlattığı programcının okuması gereken bir ayrıntıdır. Aşağıdaki dosya üç iç içe durumu çalıştırır ve döngü değişkenlerinin döngü bittikten sonraki değerini de basar.

<?php
// cikis.php — break 2 ve continue 2 dis donguyu etkiler
$satirlar = [];
for ($i = 1; $i <= 3; $i++) {
    for ($j = 1; $j <= 3; $j++) {
        if ($j === 2) {
            continue;
        }
        if ($i === 2) {
            continue 2;
        }
        if ($i === 3 && $j === 3) {
            break 2;
        }
        $satirlar[] = $i . $j;
    }
}
echo implode(',', $satirlar), "\n";
echo "i: $i j: $j\n";
11,13,31
i: 3 j: 3

KN36. Çıktıdaki üç çift, her çıkışın etkisini gösterir: j ikiye eşit olduğunda iç tur atlandı, i ikiye eşit olduğunda dış turun geri kalanı atlandı, i üç ve j üç olduğunda iki döngü birden bitti. Son satır, PHP’de bir döngü değişkeninin döngü bittikten sonra da yaşadığını gösterir: i ve j hâlâ tanımlıdır ve çıkış anındaki değerlerini korur. Döngü değişkeninin kapsamı döngü değil, onu içeren fonksiyondur; bu, aynı adı sonradan kullanan koda kalıntı bırakabilir.

foreach ve Referans Tuzağı

foreach bir dizinin elemanlarını sırayla bir değişkene kopyalar; kopya değiştirilirse dizi etkilenmez. Değişken & ile bir referans olarak bağlanırsa dizi değişir ve döngü bittikten sonra bu referans yaşamaya devam eder. Referansı temizlemeden aynı adı yeniden kullanan ikinci bir foreach, dizinin son elemanının üzerine yazar. Aşağıdaki dosya üç durumu ölçer.

<?php
// referans.php — foreach referansi donguden sonra da yasar
$a = [1, 2, 3];
foreach ($a as $v) {
    $v *= 10;
}
echo 'kopya:         ', implode(',', $a), "\n";

$b = [1, 2, 3];
foreach ($b as &$w) {
    $w *= 10;
}
foreach ($b as $w) {
}
echo 'referans:      ', implode(',', $b), "\n";

$c = [1, 2, 3];
foreach ($c as &$x) {
    $x *= 10;
}
unset($x);
foreach ($c as $x) {
}
echo 'unset ile:     ', implode(',', $c), "\n";
kopya:         1,2,3
referans:      10,20,20
unset ile:     10,20,30

KN37. Kopya ile dönen foreach diziyi değiştirmedi. Referansla dönen, her elemanı on katına çıkardı. Ardından aynı adı bir kopya olarak kullanan ikinci döngü, referans hâlâ son elemana bağlı olduğu için dizinin son elemanını sessizce ezdi ve 10,20,20 çıktı. unset($x) referansı koparttı ve 10,20,30 kaldı. Bu davranış sessizdir: bildirim yok, istisna yok, yalnız yanlış bir dizi.

Koşulun İçinde Atama

Tek bir karakter farkı, bir koşulu bir atamaya çevirir: = ile ==. PHP bunu sözdizim hatası saymaz, çünkü atama bir ifadedir ve değeri atanan değerdir. Koşul o değere göre karar verir. Aşağıdaki dosya aynı koşulu üç biçimde yazar; hata işleyicisi bir sayaç tutar.

<?php
// kosul_atama.php — if icinde = atama yapar ve degeri kosul sayilir
$bildirim = 0;
set_error_handler(function (int $seviye) use (&$bildirim): bool {
    $bildirim++;
    return true;
});

$durum = 'bos';
if ($durum = 'dolu') {
    echo "atama: dal dogru, durum: $durum\n";
}

$durum = 'bos';
if ($durum == 'dolu') {
    echo "karsilastirma: dal dogru\n";
} else {
    echo "karsilastirma: dal yanlis, durum: $durum\n";
}

$deger = 5;
if ($deger = 0) {
    echo "sifir atama: dal dogru\n";
} else {
    echo "sifir atama: dal yanlis, deger: $deger\n";
}
echo 'bildirim: ', $bildirim, "\n";
atama: dal dogru, durum: dolu
karsilastirma: dal yanlis, durum: bos
sifir atama: dal yanlis, deger: 0
bildirim: 0

KN38. 'dolu' atandığında koşul doğru çıktı ve $durum değişti; karşılaştırma ise değeri korudu. 0 atanınca koşul yanlış çıktı ve $deger beşten sıfıra düştü. Üç durumda da sayaç sıfır kaldı: dil atamayı koşul içinde meşru sayar ve bir sinyal vermez. Hata, yalnız değişkenin değerinin koşuldan sonra değişmiş olmasından anlaşılır.

Döngü Kaç Kez Döner

Bir döngünün kaç tur attığı koşuldan çıkarılabilir, ama üç durumda beklenti ile gerçek ayrışır. do while koşulu turdan sonra sınar, bu yüzden yanlış bir koşulda bile bir kez döner. Kayan noktalı bir sayaç, 0.1 adımlarla 1’e ulaştığında beklenenden bir tur fazla atabilir. Döngü sırasında diziye eleman eklemek, foreach’in dönüş sayısını değiştirmez, çünkü foreach başlangıç anındaki diziyi dolaşır. Aşağıdaki dosya üçünü sayar.

<?php
// tur_sayisi.php — do while bir kez doner, kayan nokta sayaci fazla doner
$say = 0;
do {
    $say++;
} while (false);
echo "do while:    $say\n";

$say = 0;
for ($x = 0; $x < 1; $x += 0.1) {
    $say++;
}
echo "for 0.1 adim: $say\n";

$dizi = [1, 2];
$say = 0;
foreach ($dizi as $eleman) {
    $dizi[] = $eleman * 10;
    $say++;
}
echo "foreach:      $say tur, dizi: ", count($dizi), " eleman\n";
do while:    1
for 0.1 adim: 11
foreach:      2 tur, dizi: 4 eleman

KN39. do while yanlış koşulda bile bir tur attı. Kayan noktalı sayaç on değil on bir tur attı, çünkü onuncu toplamda sayaç tam 1 olmadı, 1’in hemen altında kaldı; bu, Değişkenler dersindeki 0.1 + 0.2 ölçümünün döngü tarafıdır. foreach iki tur attı ve dizi dört elemana çıktı: dönülen dizi ilk andaki kopyadır. Üçü de sessizdir.

foreach ve Dizi Olmayan Değer

foreach bir dizi ya da bir nesne bekler. Başka bir değer verilirse döngü hiç dönmez ve dil bir E_WARNING bildirir; yani bu durum, bu dersin ölçü sınıfından farklı olarak, durdurmaz ama sessiz de değildir. Aşağıdaki dosya null, bir dizgi ve boş bir diziyi aynı döngüye verir.

<?php
// dizi_degil.php — foreach dizi olmayan degerde uyarir ve donmez
set_error_handler(function (int $seviye): bool {
    echo $seviye === E_WARNING ? "  [E_WARNING]\n" : "  [baska]\n";
    return true;
});

$girdiler = [
    'null'      => null,
    "'abc'"     => 'abc',
    '[]'        => [],
    '[1, 2]'    => [1, 2],
];
foreach ($girdiler as $ad => $girdi) {
    $say = 0;
    foreach ($girdi as $eleman) {
        $say++;
    }
    echo "$ad: $say tur\n";
}
  [E_WARNING]
null: 0 tur
  [E_WARNING]
'abc': 0 tur
[]: 0 tur
[1, 2]: 2 tur

KN40. null ve dizgi döngüye girmedi ve her biri bir E_WARNING bildirdi; boş dizi ve iki elemanlı dizi bildirimsiz döndü. Boş dizi ile null arasındaki bu fark, bir fonksiyonun “sonuç yok” için hangisini döndürdüğünü bir sözleşme hâline getirir: null döndürüp foreach’e vermek her çağrıda bir uyarı üretir.

Döngü Sırasında Değişen Dizi

foreach değerle çalışırken diziyi başlangıçtaki hâliyle dolaşır; döngü içinde dizi büyütülse bile tur sayısı değişmez. for ise koşulu her turda yeniden hesaplar; koşul count() ise dizi büyüdükçe tur da uzar. Aşağıdaki dosya aynı büyütmeyi iki döngüde sayar.

<?php
// buyume.php — foreach baslangic dizisini dolasir, for kosulu her turda yeniden hesaplar
$a = [1, 2, 3];
$tur_a = 0;
foreach ($a as $v) {
    if ($tur_a < 5) {
        $a[] = $v;
    }
    $tur_a++;
}
echo 'foreach: ', $tur_a, ' tur, ', count($a), " eleman\n";

$b = [1, 2, 3];
$tur_b = 0;
for ($i = 0; $i < count($b); $i++) {
    if ($tur_b < 3) {
        $b[] = 9;
    }
    $tur_b++;
}
echo 'for:     ', $tur_b, ' tur, ', count($b), " eleman\n";
foreach: 3 tur, 6 eleman
for:     6 tur, 6 eleman

KN41. foreach üç tur döndü ve dizi altı elemana çıktı: dolaşılan, başlangıçtaki üç elemanlı kopyaydı. for altı tur döndü, çünkü count($b) her turda yeni uzunluğu verdi. Biri büyümeyi görmez, öbürü görür; ikisi de bildirim üretmez. Döngünün döndüğü dizi döngü içinde değişiyorsa tur sayısı, hangi döngü yapısının seçildiğine bağlıdır.

Sınırlayıcı Ölçüm

match karşılıksız girdide durdurduğu için, bir kolun default taşıması durdurmayı sessize çevirir. Bu ders sınıfının sınırı budur: aynı deyim, tek bir kol eklenince durdurur sınıfından sessiz sınıfa geçer. Aşağıdaki dosya, default kolu olan ve olmayan iki fonksiyonu aynı girdilerle çalıştırır. Sonuç önce hesaplanır, sonra yazdırılır.

<?php
// varsayilan.php — default kolu match'in durdurmasini sessize cevirir
function ad_katisi(int $kod): string
{
    return match ($kod) {
        1 => 'bir',
        2 => 'iki',
    };
}

function ad_varsayilan(int $kod): string
{
    return match ($kod) {
        1 => 'bir',
        2 => 'iki',
        default => 'bilinmiyor',
    };
}

foreach ([1, 7] as $kod) {
    try {
        $katisi = ad_katisi($kod);
    } catch (UnhandledMatchError $hata) {
        $katisi = get_class($hata);
    }
    $varsayilan = ad_varsayilan($kod);
    printf("%d  katisi: %-20s varsayilan: %s\n", $kod, $katisi, $varsayilan);
}
1  katisi: bir                  varsayilan: bir
7  katisi: UnhandledMatchError  varsayilan: bilinmiyor

KN42. 1 girdisinde iki fonksiyon de 'bir' döndürdü. 7 girdisinde default kolu olmayan fonksiyon UnhandledMatchError fırlattı, olan fonksiyon ise 'bilinmiyor' döndürdü ve hiçbir şey bildirmedi. Bu durumda default iki şeyi birden yapar: eşleşmeyen girdiyi sessizce kabul eder ve girdinin eşleşmediğini programdan saklar. Hangi seçimin doğru olduğu, eşleşmeyen girdinin beklenen mi yoksa bir hata mı olduğuna bağlıdır; ölçüm yalnız seçimin sonucunu gösterir.

Özet

  • Denetim akışının ölçü sınıfı durdurur: match karşılıksız kolda UnhandledMatchError fırlatır, switch ise sessizce geçer ve değişkeni önceki değerinde bırakır.
  • switch etiketleri gevşek, match kolları katı eşitlikle karşılaştırır: '1' switch’te case 1’e gider, match’te default’a düşer.
  • break 2 ve continue 2 iç içe döngülerde dış döngüyü etkiler; döngü değişkenleri döngüden sonra da tanımlı kalır.
  • Referansla çalışan bir foreach döngüden sonra referansı bırakır; unset olmadan aynı adla ikinci bir döngü dizinin son elemanını ezer.
  • match’e bir default kolu eklemek durdurmayı sessize çevirir ve eşleşmeyen girdiyi gizler.

Sonraki Adım

Karşılıksız bir dalın ne yaptığı görüldü: switch geçer, match durdurur. Fonksiyonlar dersi aynı soruyu çağrılara soruyor: bir fonksiyona beklenenden fazla argüman verildiğinde, fazlalık nereye gidiyor?

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ ↵ Esc gezin · aç · kapat