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:
- ismi geleneksel olarak
iolan bir sayaç tanımlamak (aslında biz önceki örneklerde hep sayaç dedik) - döngüyü topluluğun
.lengthniteliğine kadar ilerletmek i'yi arttırmak- elemana erişmek
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
}
- topluluk_veya_aralık: döngünün işletileceği elemanları belirler
- işlem_bloğu: her elemanla yapılacak işlemleri belirler
- isimler: erişilen elemanın ve varsa başka nesnelerin isimlerini belirler; seçilen isimler programcıya bağlı olsa da, bunların anlamı ve adedi topluluk çeşidine göre değişir
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
- 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
dout.writefln(rakamla["yirmi"]);
yazdığımızda çıktı şöyle olsun:
20
D.ershane
Forum
Wiki
Projeler
Tanıtım
İletişim
Hakları