D.ershane D Programlama Dili Dersleri

aralık: [range], topluluk elemanlarının bir bölümü
blok: [block], küme parantezleriyle gruplanmış ifadelerin tümü
deyim: [statement], ifadelerin işletilmelerini ve sıralarını etkileyen program yapısı
dilim: [slice], başka bir dizinin bir bölümüne erişim sağlayan yapı
eleman: [element], topluluktaki verilerin her biri
nesne: [object], belirli bir sınıf veya yapı türünden olan değer
referans: [reference], asıl nesneye, onun takma ismi gibi erişim sağlayan program yapısı
sınıf: [class], kendi üzerinde kullanılan işlevleri de tanımlayan veri yapısı
topluluk: [container], aynı türden birden fazla veriyi bir araya getiren veri yapısı
üye işlev: [member function], yapı veya sınıfın özel işlemleri
yapı: [struct], başka verileri bir araya getiren veri yapısı
... bütün sözlük

Bölümler
İngilizce Kaynaklar
Diğer



foreach Döngüsü

foreach, D'nin en kullanışlı deyimlerinden birisidir. "Her birisi" anlamına gelir. Belirli işlemleri bir topluluktaki (veya bir aralıktaki) elemanların her birisi ile yapmayı sağlar.

Programcılıkta, topluluk elemanlarının tümüyle yapılan işlemlerle çok sık karşılaşılır. Bu yüzden for döngüsünün bir dizinin bütün elemanlarına erişmek için nasıl kullanıldığını görmüştük:

    for (int i = 0; i != dizi.length; ++i) {
        writeln(dizi[i]);
    }

Bu iş için gereken adımları şöyle özetleyebiliriz:

Bu adımları ayrı ayrı elle yapmak yerine; aynı işi foreach ile çok daha temiz bir şekilde şöyle ifade ederiz:

    foreach (eleman; dizi) {
        writeln(eleman);
    }

Bunun önemini vurgulamak için, for döngüsünün her çeşit topluluk için aynı şekilde yazılmadığını hatırlatırım. Örneğin bir eşleme tablosunun bütün elemanlarına erişmek için for döngüsünü tablo'nun .values niteliği üzerinde kurmak gerekir:

    auto elemanlar = tablo.values;
    for (int i = 0; i != elemanlar.length; ++i) {
        writeln(elemanlar[i]);
    }

Oysa aynı işlem foreach ile eşleme tabloları için de aynı şekilde yazılır:

    foreach (eleman; tablo) {
        writeln(eleman);
    }
Söz dizimi

foreach üç bölümden oluşur:

    foreach (isimler; topluluk_veya_aralık) {
        işlem_bloğu
    }
continue ve break aynı anlamdadır

Bu anahtar sözcüklerin ikisi de burada da aynı anlama gelirler: continue döngünün ilerletilmesini, break döngünün sonlandırılmasını bildirir.

Dizilerle kullanımı

isimler bölümüne yazılan tek isim, dizinin elemanını ifade eder:

    foreach (eleman; dizi) {
        writeln(eleman);
    }

Eğer iki isim yazılırsa; birincisi sayaçtır, ikincisi yine elemanı ifade eder:

    foreach (sayaç, eleman; dizi) {
        writeln(sayaç, ": ", eleman);
    }

O örnekteki sayaç isminin seçimi de programcıya bağlıdır; örneğin alışılmış olan i de yazılabilir.

Dizgilerle kullanımı

Dizilerle aynı şekilde kullanılır. Tek isim yazılırsa dizginin karakterini ifade eder, çift isim yazılırsa sayaç ve karakterdir:

    foreach (karakter; "merhaba") {
        writeln(karakter);
    }

    foreach (sayaç, karakter; "merhaba") {
        writeln(sayaç, ": ", karakter);
    }
Eşleme tablolarıyla kullanımı

Tek isim yazılırsa eleman değerini, iki isim yazılırsa indeks ve eleman değerini ifade eder:

    foreach (eleman; tablo) {
        writeln(eleman);
    }

    foreach (indeks, eleman; tablo) {
        writeln(indeks, ": ", eleman);
    }

Not: Eşleme tablolarında indeksin de herhangi bir türden olabileceğini hatırlayın. O yüzden son döngüde sayaç yazmadım.

Aralıklarla kullanımı

Aralıkları dilimler dersinde görmüştük. foreach'in topluluk_veya_aralık bölümüne bir sayı aralığı da yazılabilir:

    foreach (sayı; 10..15) {
        writeln(sayı);
    }

Hatırlarsanız; yukarıdaki kullanımda 10 aralığa dahildir, 15 değildir.

Yapılarla ve sınıflarla kullanımı

foreach, bu desteği veren yapı ve sınıf nesneleriyle de kullanılabilir. Nasıl kullanıldığı hakkında burada genel bir şey söylemek olanaksızdır, çünkü tamamen o tür tarafından belirlenir.

Yapılar ve sınıflar foreach kullanımı desteğini opApply() isimli üye işlevleri aracılığıyla verirler. Onun nasıl işlediğini ancak söz konusu yapı veya sınıfın belgesinden öğrenebiliriz.

Örneğin şimdiye kadar gördüğümüz sınıflar içinden File, foreach ile kullanıldığında, dosyaya satır satır erişme olanağı sunar. Bir dosyanın boş olmayan satırlarını yazdıran bir döngü şöyle yazılabilir:

    foreach (char[] satır; dosya) {
        if (satır.length != 0) {
            dout.writefln(satır);
        }
    }

Not: satır isminden önceki char[]'ın neden gerektiğini biraz aşağıda anlatıyorum.

Dizilere benzer şekilde, isimler bölümüne iki isim yazıldığında, birincisi satır numarasıdır (onun türünün de ulong olarak yazılması gerekir):

    foreach (ulong satırNumarası, char[] satır; dosya) {
        if (satır.length != 0) {
            dout.writefln(
                "Satır ", satırNumarası, ": ", satır);
        }
    }
İsimlerin türleri

Çoğu durumda isimlerin türlerini belirtmek gerekmez: auto anahtar sözcüğünde olduğu gibi, elemanın ve varsa diğer nesnelerin türleri otomatik olarak anlaşılır.

Ama yukarıdaki File örneğinde de olduğu gibi, derleyicinin karar veremediği durumlarda türün açıkça yazılması gerekir. Örneğin File satır erişimini hem char[], hem de wchar[] türünde sunduğu için, hangisini istediğimizi bizim bildirmemiz gerekir.

Örneğin UTF-16 kodlamasıyla yazılmış olan bir dosyayı wchar[] türünde satır satır okumak ve çıkışa yazdırmak için:

    auto dosya = new EndianStream(
        new File("deneme_dosyasi", FileMode.In));

    dosya.readBOM();

    foreach (wchar[] satır; dosya) {
        dout.writefln(satır);
    }
Elemanın kopyası, kendisi değil

foreach döngüsü; normalde elemanın kendisine değil, bir kopyasına erişim sağlar. Topluluk elemanlarının yanlışlıkla değiştirilmelerini önlemek amacıyla böyle tasarlandığını düşünebilirsiniz.

Bir dizinin elemanlarının her birisini iki katına çıkartmaya çalışan şu koda bakalım:

import std.stdio;

void main()
{
    double[] sayılar = [ 1.2, 3.4, 5.6 ];

    writefln("Önce : %s", sayılar);

    foreach (sayı; sayılar) {
        sayı *= 2;
    }

    writefln("Sonra: %s", sayılar);
}

Programın çıktısı, foreach kapsamında sayı'ya yapılan atamanın etkisi olmadığını gösteriyor:

Önce : 1.2 3.4 5.6
Sonra: 1.2 3.4 5.6

Bunun nedeni, sayı'nın dizi elemanının kendisi değil, onun bir kopyası olmasıdır. Dizi elemanının kendisinin ifade edilmesi istendiğinde, isim bir referans olarak tanımlanır:

    foreach (ref sayı; sayılar) {
        sayı *= 2;
    }

Yeni çıktıda görüldüğü gibi, ref anahtar sözcüğü, dizideki asıl elemanın etkilenmesini sağlamıştır:

Önce : 1.2 3.4 5.6
Sonra: 2.4 6.8 11.2

Oradaki ref anahtar sözcüğü, sayı'nın, asıl elemanın bir takma ismi gibi kullanılmasını sağlamaktadır. sayı'da yapılan değişiklik, artık elemanın kendisini etkilemektedir.

Topluluğun kendisi değiştirilemez

Topluluk elemanlarını ref kullanarak değiştirmekte bir sakınca yoktur. Ancak, foreach döngüsü kapsamında topluluğun kendi yapısını etkileyecek hiçbir işlem yapılmamalıdır. Örneğin diziden eleman silinmemeli, ve diziye eleman eklenmemelidir.

Bu tür işlemler dizinin yapısını değiştirdikleri için, ilerlemekte olan foreach döngüsünün işini bozarlar. O noktadan sonra programın davranışının ne olacağı bilinemez.

Problemler
  1. Eşleme tablolarının indeks değerleri ile eleman değerlerini eşlediklerini görmüştük. Bu tek yönlüdür: indeks verildiğinde eleman değerini elde ederiz, ama eleman değeri verildiğinde indeks değerini elde edemeyiz.
  2. Elinizde hazırda şöyle bir eşleme tablosu olsun:

        string[int] isimle = [ 1:"bir", 7:"yedi", 20:"yirmi" ];
    

    O tablodan ve tek bir foreach döngüsünden yararlanarak, rakamla isminde başka bir eşleme tablosu oluşturun. Bu yeni tablo, isimle tablosunun tersi olarak çalışsın: isime karşılık rakam elde edebilelim. Örneğin

        dout.writefln(rakamla["yirmi"]);
    

    yazdığımızda çıktı şöyle olsun:

    20
    
... çözümler