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:
- İsmi geleneksel olarak
iolan bir sayaç tanımlamak (aslında biz önceki örneklerde hepsayaçdedik) - Döngüyü topluluğun
.lengthniteliğine kadar ilerletmek i'yi arttırmak- Elemana erişmek
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
}
- 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
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:
- Daha sonra Yapı ve Sınıflarda
foreachbölümünde göreceğimizstd.range.enumerate'ten yararlanmak. - Bir sayaç değişkeni tanımlamak ve elle arttırmak.
size_t sayaç = 0; foreach (eleman; topluluk) { // ... ++sayaç; }
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
Kitaplar
Forum
Tanıtım
İletişim
Hakları