Üye İşlevler
Bu bölümde her ne kadar yapıları kullanıyor olsak da buradaki bilgilerin çoğu daha sonra göreceğimiz sınıflar için de geçerlidir.
Bu bölümde yapıların ve sınıfların üye işlevlerini tanıyacağız, ve bunların içerisinden özel olarak, nesneleri string
türüne dönüştürmede kullanılan toString
üye işlevini göreceğiz.
Bir yapının tanımlandığı çoğu durumda, o yapıyı kullanan bir grup işlev de onunla birlikte tanımlanır. Bunun örneklerini önceki bölümlerde zamanEkle
ve bilgiVer
işlevlerinde gördük. O işlevler bir anlamda GününSaati
yapısı ile birlikte sunulan ve o yapının arayüzünü oluşturan işlevlerdir.
Hatırlarsanız; zamanEkle
ve bilgiVer
işlevlerinin ilk parametresi, üzerinde işlem yaptıkları nesneyi belirliyordu. Şimdiye kadar tanımladığımız bütün diğer işlevler gibi, onlar da bağımsız olarak, tek başlarına, ve modül kapsamında tanımlanmışlardı.
Bir yapının arayüzünü oluşturan işlevler çok karşılaşılan bir kavram olduğu için; o işlevler yapının içinde, yapının üye işlevleri olarak da tanımlanabilirler.
Üye işlev
Bir yapının veya sınıfın küme parantezlerinin içinde tanımlanan işlevlere üye işlev denir:
struct BirYapı { void üye_işlev(/* parametreleri */) { // ... işlevin tanımı ... } // ... yapının üyeleri ve diğer işlevleri ... }
Üye işlevlere yapının diğer üyelerinde olduğu gibi nesne isminden sonraki nokta karakteri ve ardından yazılan işlev ismi ile erişilir:
nesne.üye_işlev(parametreleri);
Üye işlevleri aslında daha önce de kullandık; örneğin standart giriş ve çıkış işlemlerinde stdin
ve stdout
nesnelerini açıkça yazabiliyorduk:
stdin.readf(" %s", &numara);
stdout.writeln(numara);
O satırlardaki readf
ve writeln
üye işlevlerdir.
İlk örneğimiz olarak GününSaati
yapısını yazdıran bilgiVer
işlevini bir üye işlev olarak tanımlayalım. O işlevi daha önce serbest olarak şöyle tanımlamıştık:
void bilgiVer(GününSaati zaman) { writef("%02s:%02s", zaman.saat, zaman.dakika); }
Üye işlev olarak yapının içinde tanımlanırken bazı değişiklikler gerekir:
struct GününSaati { int saat; int dakika; void bilgiVer() { writef("%02s:%02s", saat, dakika); } }
Daha önce yapı dışında serbest olarak tanımlanmış olan bilgiVer
işlevi ile bu üye işlev arasında iki fark vardır:
- Üye işlev yazdırdığı nesneyi parametre olarak almaz
- O yüzden üyelere
zaman.saat
vezaman.dakika
diye değil,saat
vedakika
diye erişir
Bunun nedeni, üye işlevlerin zaten her zaman için bir nesne üzerinde çağrılıyor olmalarıdır:
auto zaman = GününSaati(10, 30); zaman.bilgiVer();
Orada, bilgiVer
işlevi zaman
nesnesini yazdıracak şekilde çağrılmaktadır. Üye işlevin tanımı içinde noktasız olarak yazılan saat
ve dakika
, zaman
nesnesinin üyeleridir; ve sırasıyla zaman.saat
ve zaman.dakika
üyelerini temsil ederler.
O üye işlev çağrısı, daha önceden serbest olarak yazılmış olan bilgiVer
'in şu şekilde çağrıldığı durumla eşdeğerdir:
zaman.bilgiVer(); // üye işlev bilgiVer(zaman); // serbest işlev (önceki tanım)
Üye işlev her çağrıldığında, üzerinde çağrıldığı nesnenin üyelerine erişir:
auto sabah = GününSaati(10, 0); auto akşam = GününSaati(22, 0); sabah.bilgiVer(); write('-'); akşam.bilgiVer(); writeln();
bilgiVer
, sabah
üzerinde çağrıldığında sabah
'ın değerini, akşam
üzerinde çağrıldığında da akşam
'ın değerini yazdırır:
10:00-22:00
Nesneyi string
olarak ifade eden toString
Bir önceki bölümde bilgiVer
işlevinin eksikliklerinden söz etmiştim. Rahatsız edici bir diğer eksikliğini burada göstermek istiyorum: Her ne kadar zamanı okunaklı bir düzende çıktıya gönderiyor olsa da, genel çıktı düzeni açısından '-'
karakterini yazdırmayı ve satırın sonlandırılmasını kendimiz ayrıca halletmek zorunda kalıyoruz.
Oysa, nesnelerin diğer türler gibi kullanışlı olabilmeleri için örneğin şu şekilde yazabilmemiz çok yararlı olurdu:
writefln("%s-%s", sabah, akşam);
Öyle yazabilseydik; daha önceki 4 satırı böyle tek satıra indirgemiş olmanın yanında, nesneleri stdout
'tan başka akımlara da, örneğin bir dosyaya da aynı şekilde yazdırabilirdik:
auto dosya = File("zaman_bilgisi", "w"); dosya.writefln("%s-%s", sabah, akşam);
Yapıların toString
ismindeki üye işlevleri özeldir ve nesneleri string
türüne dönüştürmek için kullanılır. Bunun doğru olarak çalışabilmesi için, ismi "string'e dönüştür"den gelen bu işlev o nesneyi ifade eden bir string
döndürmelidir.
Bu işlevin içeriğini sonraya bırakalım, ve önce yapı içinde nasıl tanımlandığına bakalım:
import std.stdio; struct GününSaati { int saat; int dakika; string toString() { return "deneme"; } } void main() { auto sabah = GününSaati(10, 0); auto akşam = GününSaati(22, 0); writefln("%s-%s", sabah, akşam); }
Nesneleri dizgi olarak kullanabilen kütüphane işlevleri onların toString
işlevlerini çağırırlar ve döndürülen dizgiyi kendi amaçlarına uygun biçimde kullanırlar.
Bu örnekte henüz anlamlı bir dizgi üretmediğimiz için çıktı da şimdilik anlamsız oluyor:
deneme-deneme
Ayrıca bilgiVer
'i artık emekliye ayırmakta olduğumuza da dikkat edin; toString
'in tanımını tamamlayınca ona ihtiyacımız kalmayacak.
toString
işlevini yazmanın en kolay yolu, std.string
modülünde tanımlanmış olan format
işlevini kullanmaktır. Bu işlev, çıktı düzeni için kullandığımız bütün olanaklara sahiptir ve örneğin writef
ile aynı şekilde çalışır. Tek farkı, ürettiği sonucu bir akıma göndermek yerine, bir string
olarak döndürmesidir.
toString
'in de zaten bir string
döndürmesi gerektiği için, format
'ın döndürdüğü değeri olduğu gibi döndürebilir:
import std.string; // ... struct GününSaati { // ... string toString() { return format("%02s:%02s", saat, dakika); } }
toString
'in yalnızca bu nesneyi string
'e dönüştürdüğüne dikkat edin. Çıktının geri kalanı, writefln
çağrısı tarafından halledilmektedir. writefln
, "%s"
düzen bilgilerine karşılık olarak toString
'i otomatik olarak iki nesne için ayrı ayrı çağırır, aralarına '-'
karakterini yerleştirir, ve en sonunda da satırı sonlandırır:
10:00-22:00
Görüldüğü gibi, burada anlatılan toString
işlevi parametre almamaktadır. toString
'in parametre olarak delegate
alan bir tanımı daha vardır. O tanımını daha ilerideki İşlev Göstergeleri, İsimsiz İşlevler, ve Temsilciler bölümünde göreceğiz.
Örnek: ekle
üye işlevi
Bu sefer de GününSaati
nesnelerine zaman ekleyen bir üye işlev tanımlayalım.
Ama ona geçmeden önce, önceki bölümlerde yaptığımız bir yanlışı gidermek istiyorum. Yapılar bölümünde tanımladığımız zamanEkle
işlevinin, GününSaati
nesnelerini toplamasının normal bir işlem olmadığını görmüş, ama yine de o şekilde kullanmıştık:
GününSaati zamanEkle(GününSaati başlangıç, GününSaati eklenecek) { // anlamsız // ... }
Gün içindeki iki zamanı birbirine eklemek doğal bir işlem değildir. Örneğin yola çıkma zamanına sinemaya varma zamanını ekleyemeyiz. Gün içindeki bir zamana eklenmesi normal olan, bir süredir. Örneğin yola çıkma zamanına yol süresini ekleyerek sinemaya varış zamanını buluruz.
Öte yandan, gün içindeki iki zamanın birbirlerinden çıkartılmaları normal bir işlem olarak görülebilir. O işlemin sonucu da örneğin Süre
türünden olmalıdır.
Bu bakış açısı ile, dakika duyarlığıyla çalışan bir Süre
yapısını ve onu kullanan zamanEkle
işlevini şöyle yazabiliriz:
struct Süre { int dakika; } GününSaati zamanEkle(GününSaati başlangıç, Süre süre) { // başlangıç'ın kopyasıyla başlıyoruz GününSaati sonuç = başlangıç; // Süreyi ekliyoruz sonuç.dakika += süre.dakika; // Taşmaları ayarlıyoruz sonuç.saat += sonuç.dakika / 60; sonuç.dakika %= 60; sonuç.saat %= 24; return sonuç; } unittest { // Basit bir test assert(zamanEkle(GününSaati(10, 30), Süre(10)) == GününSaati(10, 40)); // Gece yarısı testi assert(zamanEkle(GününSaati(23, 9), Süre(51)) == GününSaati(0, 0)); // Sonraki güne taşma testi assert(zamanEkle(GününSaati(17, 45), Süre(8 * 60)) == GününSaati(1, 45)); }
Şimdi aynı işlevi bir üye işlev olarak tanımlayalım. Üye işlev zaten bir nesne üzerinde çalışacağı için GününSaati
parametresine gerek kalmaz ve parametre olarak yalnızca süreyi geçirmek yeter:
struct Süre { int dakika; } struct GününSaati { int saat; int dakika; string toString() { return format("%02s:%02s", saat, dakika); } void ekle(Süre süre) { dakika += süre.dakika; saat += dakika / 60; dakika %= 60; saat %= 24; } unittest { auto zaman = GününSaati(10, 30); // Basit bir test zaman.ekle(Süre(10)); assert(zaman == GününSaati(10, 40)); // 15 saat sonra bir sonraki güne taşmalı zaman.ekle(Süre(15 * 60)); assert(zaman == GününSaati(1, 40)); // 22 saat ve 20 dakika sonra gece yarısı olmalı zaman.ekle(Süre(22 * 60 + 20)); assert(zaman == GününSaati(0, 0)); } }
ekle
, nesnenin zamanını belirtilen süre kadar ilerletir. Daha sonraki bölümlerde göreceğimiz işleç yükleme olanağı sayesinde bu konuda biraz daha kolaylık kazanacağız. Örneğin +=
işlecini yükleyerek yapı nesnelerini de temel türler gibi kullanabileceğiz:
zaman += Süre(10); // bunu daha sonra öğreneceğiz
Ayrıca gördüğünüz gibi, üye işlevler için de unittest
blokları yazılabilir. O blokların yapı tanımını kalabalıklaştırdığını düşünüyorsanız, bloğu bütünüyle yapının dışında da tanımlayabilirsiniz:
struct GününSaati { // ... yapı tanımı ... } unittest { // ... yapı testleri ... }
Bunun nedeni, unittest
bloklarının aslında belirli bir noktada tanımlanmalarının gerekmemesidir. Denetledikleri kodlarla bir arada bulunmaları daha doğal olsa da, onları uygun bulduğunuz başka yerlerde de tanımlayabilirsiniz.
Problemler
GününSaati
yapısına nesnelerin değeriniSüre
kadar azaltan bir üye işlev ekleyin.ekle
işlevinde olduğu gibi, süre azaltıldığında bir önceki güne taşsın. Örneğin 00:05'ten 10 dakika azaltınca 23:55 olsun.Başka bir deyişle,
azalt
işlevini şu birim testlerini geçecek biçimde gerçekleştirin:struct GününSaati { // ... void azalt(Süre süre) { // ... burasını siz yazın ... } unittest { auto zaman = GününSaati(10, 30); // Basit bir test zaman.azalt(Süre(12)); assert(zaman == GününSaati(10, 18)); // 3 gün ve 11 saat önce zaman.azalt(Süre(3 * 24 * 60 + 11 * 60)); assert(zaman == GününSaati(23, 18)); // 23 saat ve 18 dakika önce gece yarısı olmalı zaman.azalt(Süre(23 * 60 + 18)); assert(zaman == GününSaati(0, 0)); // 1 dakika öncesi zaman.azalt(Süre(1)); assert(zaman == GününSaati(23, 59)); } }
- Daha önce İşlev Yükleme bölümünün çözümünde kullanılan diğer bütün
bilgiVer
işlevlerinin yerineToplantı
,Yemek
, veGünlükPlan
yapıları içintoString
üye işlevlerini tanımlayın.Çok daha kullanışlı olmalarının yanında, her birisinin tek satırda yazılabildiğini göreceksiniz.