D Programlama Dili – Programlama dersleri ve D referansı
Ali Çehreli

aralık: [range], belirli biçimde erişilen bir grup eleman
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ı
döngü: [loop], tekrarlanan program yapısı
eleman: [element], topluluktaki verilerin her biri
nesne: [object], belirli bir sınıf veya yapı türünden olan değişken
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ı
söz dizimi: [syntax], dilin yazım ile ilgili olan kuralları
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 kendi tanımladığı işlemleri
yapı: [struct], başka verileri bir araya getiren veri yapısı
... bütün sözlük



İngilizce Kaynaklar


Diğer




foreach Döngüsü

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

Topluluk elemanlarının tümüyle yapılan işlemler programcılıkta çok yaygındır. 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 yapılmak yerine foreach ile çok daha basit olarak şöyle ifade edilir:

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

foreach'in güçlü yanlarından birisi, eşleme tabloları ile de aynı biçimde kullanılabilmesidir. for döngüsünde ise, örneğin bir eşleme tablosunun bütün elemanlarına erişmek için tablo'nun .values niteliği çağrılır:

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

foreach eşleme tabloları için özel bir kullanım gerektirmez; eşleme tabloları da dizilerle aynı biçimde kullanı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

Bu anahtar sözcüklerin ikisi de burada da aynı anlama gelirler: continue döngünün erkenden ilerletilmesini, break de 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 otomatik bir sayaçtır, ikincisi yine elemanı ifade eder:

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

Sayacın değeri foreach tarafından otomatik olarak arttırılır. Sayaç değişkeninin ismi programcıya kalmış olsa da isim olarak i de çok yaygındır.

Dizgilerle kullanımı ve std.range.stride

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);
    }

char ve wchar türlerinin Unicode karakterlerini barındırmaya genel olarak uygun olmadıklarını hatırlayın. foreach bu türlerle kullanıldığında karakterlere değil, kod birimlerine erişilir:

    foreach (sayaç, kod; "abcçd") {
        writeln(sayaç, ": ", kod);
    }

Örneğin ç'yi oluşturan kodlara ayrı ayrı erişilir:

0: a
1: b
2: c
3: 
4: �
5: d

UTF kodlamasından bağımsız olarak her tür dizginin foreach ile karakter karakter erişilmesini sağlayan olanak, std.range modülündeki stride'dır. stride "adım" anlamına gelir ve karakterlerin kaçar kaçar atlanacağı bilgisini de alır:

import std.range;

// ...

    foreach (harf; stride("abcçd", 1)) {
        writeln(harf);
    }

stride kullanıldığında UTF kodlarına değil Unicode karakterlerine erişilir:

a
b
c
ç
d

Bu kodda neden sayaç kullanılamadığını biraz aşağıda açıklayacağım.

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 bu döngüde sayaç yazmadım.

Eşleme tabloları indekslerini ve elemanlarını aralıklar olarak da sunabilirler. Aralıkları daha ilerideki bir bölümde göreceğiz. Eşleme tablolarının .byKey, .byValue, ve .byKeyValue nitelikleri foreach döngülerinden başka ortamlarda da kullanılabilen hızlı aralık nesneleri döndürürler.

.byValue, foreach döngülerinde yukarıdaki elemanlı döngü ile karşılaştırıldığında fazla bir yarar sağlamaz. .byKey ise bir eşleme tablosunun yalnızca indeksleri üzerinde ilerlemenin en hızlı yoludur:

    foreach (indeks; tablo.byKey) {
        writeln(indeks);
    }

.byKeyValue çokuzlu gibi kullanılan bir değişken döndürür. İndeks ve eleman değerleri o değişkenin .key ve .value nitelikleri ile elde edilir:

    foreach (eleman; tablo.byKeyValue) {
        writefln("%s indeksinin değeri: %s",
                 eleman.key, eleman.value);
    }
Sayı aralıklarıyla kullanımı

Sayı aralıklarını Başka Dizi Olanakları bölümünde 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, sınıflarla, ve aralıklarla kullanımı

foreach, bu desteği veren yapı, sınıf, ve aralık 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. foreach'in nasıl işlediğini ancak söz konusu yapının, sınıfın, veya aralığın belgesinden öğrenebiliriz.

Yapılar ve sınıflar foreach desteğini ya opApply() isimli üye işlevleri ya da aralık (range) üye işlevleri aracılığıyla verirler; aralıklar ise bu iş için aralık üye işlevleri tanımlarlar. Bu olanakları daha sonraki bölümlerde göreceğiz.

Sayaç yalnızca dizilerde otomatiktir

Otomatik sayaç olanağı yalnızca dizilerde bulunur. foreach başka çeşit türlerle kullanılırken sayaç gerektiğinde iki seçenek vardır:

Böyle bir değişken sayacın döngünün her ilerletilişinde değil, belirli bir koşul sağlandığında arttırılması gerektiğinde de yararlı olur. Örneğin, aşağıdaki döngü yalnızca 10'a tam olarak bölünen sayıları sayar:

import std.stdio;

void main() {
    auto dizi = [ 1, 0, 15, 10, 3, 5, 20, 30 ];

    size_t sayaç = 0;
    foreach (sayı; dizi) {
        if ((sayı % 10) == 0) {
            ++sayaç;
            write(sayaç);

        } else {
            write(' ');
        }

        writeln(": ", sayı);
    }
}

Çıktısı:

 : 1
1: 0
 : 15
2: 10
 : 3
 : 5
3: 20
4: 30
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ı'yı asıl elemanın bir takma ismi olarak tanımlar. sayı'da yapılan değişiklik artık elemanın kendisini etkilemektedir.

Topluluğun kendisi değiştirilmemelidir

Topluluk elemanlarını ref olarak tanımlanmış olan değişkenler aracılığıyla 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 veya diziye eleman eklenmemelidir.

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

Ters sırada ilerlemek için foreach_reverse

foreach_reverse foreach ile aynı biçimde işler ama aralığı ters sırada ilerler:

    auto elemanlar = [ 1, 2, 3 ];

    foreach_reverse (eleman; elemanlar) {
        writefln("%s ", eleman);
    }

Çıktısı:

3 
2 
1 

foreach_reverse'ün kullanımı yaygın değildir. Çoğunlukla onun yerine daha sonra göreceğimiz retro() isimli aralık işlevi kullanılır.

Problem

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.

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

    writeln(rakamla["yirmi"]);

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

20