İşlev Yükleme
Aynı isimde birden fazla işlev tanımlamaya işlev yükleme denir. İsimleri aynı olan bu işlevlerin ayırt edilebilmeleri için parametrelerinin birbirlerinden farklı olmaları gerekir.
Bu kullanımda "yükleme" sözcüğünün "aynı isme yeni görev yükleme" anlamına geldiğini düşünebilirsiniz.
Aşağıda aynı isimde ama parametreleri farklı işlevler görüyorsunuz:
import std.stdio; void bilgiVer(double sayı) { writeln("Kesirli sayı: ", sayı); } void bilgiVer(int sayı) { writeln("Tamsayı : ", sayı); } void bilgiVer(string dizgi) { writeln("Dizgi : ", dizgi); } void main() { bilgiVer(1.2); bilgiVer(3); bilgiVer("merhaba"); }
İşlevlerin hepsinin de ismi bilgiVer
olduğu halde, derleyici parametrenin türüne uygun olan işlevi seçer ve onun çağrılmasını sağlar. Örneğin 1.2
hazır değerinin türü double
olduğu için, onun kullanıldığı durumda o işlevler arasından double
parametre alanı çağrılır.
Hangi işlevin çağrılacağı derleme zamanında seçilir. Bu seçim her zaman kolay veya açık olmayabilir. Örneğin şu kodda kullanılan int
değer hem real
hem de double
türüne uyduğu için derleyici hangisini seçeceğine karar veremez:
real yediKatı(real değer) { return 7 * değer; } double yediKatı(double değer) { return 7 * değer; } void main() { int sayı = 5; auto sonuç = yediKatı(sayı); // ← derleme HATASI }
Not: Normalde aynı işi yapan böyle iki işlevin yazılması gereksizdir. Tek işlev tanımının nasıl birden fazla tür için kullanılabileceğini daha sonra Şablonlar bölümünde göreceğiz.
Öte yandan, bu işlevlerin long
türünde parametre alan bir üçüncüsü tanımlansa derleme hatası ortadan kalkar çünkü yüklenen işlev seçimi konusunda int
değerler long
türüne kesirli türlerden daha uyumludurlar:
long yediKatı(long değer) { return 7 * değer; } // ... auto sonuç = yediKatı(sayı); // şimdi derlenir
Parametre uyum kuralları
Aynı isimde birden fazla işlev bulunması, derleyicinin bir seçim yapmasını gerektirir. Yüklenen işlevler arasından, kullanılan parametrelere daha çok uyan işlev seçilir.
Bu seçim çoğu durumda kolay ve beklendiği gibi olur; ama hangi işlevin daha çok uyduğu konusu bazen çok karışıktır. Bu yüzden uyum kuralları geliştirilmiştir.
Parametreler için uyum konusunda dört durum vardır:
- uyumsuzluk
- otomatik tür dönüşümü yoluyla uyum
const
'a dönüştürerek uyum- tam uyum
Derleyici, yüklenmiş olan işlevlerden hangisini çağıracağına karar vermek için işlevleri gözden geçirir. Her işlevin parametrelerine teker teker bakar ve her parametrenin yukarıdaki dört uyum durumundan hangisinde olduğunu belirler. Bütün parametreler içindeki en az uyum, bu işlevin de uyumu olarak kabul edilir.
Bu şekilde bütün işlevlerin uyum durumları belirlendikten sonra; eğer varsa, en çok uyan işlev seçilir.
Eğer birden fazla işlev aynı derecede uymuşsa, onlardan hangisinin seçileceğine daha da karışık başka kurallar yoluyla karar verilir.
Ben burada bu kuralların daha derinine inmeyeceğim; çünkü eğer bu kadar karışık kurallarla yüz yüze kalmışsanız, aslında programınızın tasarımında değişiklik yapma zamanı gelmiş demektir. Belki de işlev şablonlarını kullanmak daha doğru olacaktır. Hatta belki de aynı isimde işlev tanımlamak yerine, daha açıklayıcı isimler kullanarak hangisini çağırmak istediğinizi açıkça belirtmek bütün karışıklığı ortadan kaldıracaktır: yediKatı_real
ve yediKatı_double
gibi...
Yapılar için işlev yükleme
İşlev yükleme, yapılarda ve sınıflarda çok yararlıdır; üstelik o türlerde işlev seçimi konusunda uyum sorunları da çok daha azdır. Yukarıdaki bilgiVer
işlevini Yapılar bölümünde kullandığımız bazı türler için yükleyelim:
struct GününSaati { int saat; int dakika; } void bilgiVer(GününSaati zaman) { writef("%02s:%02s", zaman.saat, zaman.dakika); }
O tanım sayesinde artık GününSaati
nesnelerini de bilgiVer
işlevine gönderebiliriz. Böylece programımızda her tür nesneyi aynı isimle yazdırabileceğimiz alışılmış bir yöntemimiz olur:
auto kahvaltıZamanı = GününSaati(7, 0);
bilgiVer(kahvaltıZamanı);
Temel türlerde olduğu gibi, artık GününSaati
nesneleri de kendilerine özgü çıktı düzenleri ile yazdırılmış olurlar:
07:00
bilgiVer
işlevini yapılar bölümünde değinilen Toplantı
yapısı için de yükleyelim:
struct Toplantı { string konu; int katılımcıSayısı; GününSaati başlangıç; GününSaati bitiş; } void bilgiVer(Toplantı toplantı) { bilgiVer(toplantı.başlangıç); write('-'); bilgiVer(toplantı.bitiş); writef(" \"%s\" toplantısı (%s katılımcı)", toplantı.konu, toplantı.katılımcıSayısı); }
Gördüğünüz gibi; bilgiVer
'in GününSaati
için yüklenmiş olanı, Toplantı
'yı yazdıran işlev tarafından kullanılmaktadır. Artık Toplantı
nesnelerini de alıştığımız isimdeki işlevle yazdırabiliriz:
auto geziToplantısı = Toplantı("Bisikletle gezilecek yerler", 3, GününSaati(9, 0), GününSaati(9, 10)); bilgiVer(geziToplantısı);
Çıktısı:
09:00-09:10 "Bisikletle gezilecek yerler" toplantısı (3 katılımcı)
Eksiklikler
Yukarıdaki bilgiVer
işlevi her ne kadar kullanım kolaylığı getirse de, bu yöntemin bazı eksiklikleri vardır:
bilgiVer
işlevi yalnızcastdout
'a yazdığı için fazla kullanışlı değildir. Oysa örneğinFile
türünden bir dosyaya da yazabiliyor olsa, kullanışlılığı artardı. Bunu sağlamanın yolu, çıktının yazdırılacağı akımı da işleve bir parametre olarak vermektir:void bilgiVer(File akım, GününSaati zaman) { akım.writef("%02s:%02s", zaman.saat, zaman.dakika); }
O sayede
GününSaati
nesnelerini istersekstdout
'a, istersek de bir dosyaya yazdırabiliriz:bilgiVer(stdout, kahvaltıZamanı); auto dosya = File("bir_dosya", "w"); bilgiVer(dosya, kahvaltıZamanı);
Not:
stdin
,stdout
, vestderr
nesnelerinin türleri de aslındaFile
'dır.- Daha önemlisi,
bilgiVer
gibi bir işlev, yapı nesnelerini temel türler kadar rahatça kullanabilmemiz için yeterli değildir. Temel türlerden alışık olduğumuz rahatlık yoktur:writeln(kahvaltıZamanı); // Kullanışsız: Genel düzende yazar
O kod çalıştırıldığında
GününSaati
türünün ismi ve üyelerinin değerleri programa uygun biçimde değil, genel bir düzende yazdırılır:GününSaati(7, 0)
Bunun yerine, yapı nesnesinin değerini örneğin
"12:34"
biçiminde birstring
'e dönüştürebilen bir işlev olması çok daha yararlı olur. Yapı nesnelerinin de otomatik olarakstring
'e dönüştürülebileceklerini bundan sonraki bölümde göstereceğim.
Problem
bilgiVer
işlevini şu iki yapı için de yükleyin:
struct Yemek { GününSaati zaman; string adres; } struct GünlükPlan { Toplantı sabahToplantısı; Yemek öğleYemeği; Toplantı akşamToplantısı; }
Yemek
yapısı yalnızca başlangıç zamanını barındırdığı için; onun bitiş zamanını başlangıç zamanından bir buçuk saat sonrası olarak belirleyin. Bu işlem için yapılar bölümünde tanımladığımız zamanEkle
işlevi yararlı olabilir:
GününSaati zamanEkle(GününSaati başlangıç,
GününSaati eklenecek) {
GününSaati sonuç;
sonuç.dakika = başlangıç.dakika + eklenecek.dakika;
sonuç.saat = başlangıç.saat + eklenecek.saat;
sonuç.saat += sonuç.dakika / 60;
sonuç.dakika %= 60;
sonuç.saat %= 24;
return sonuç;
}
Yemek bitiş zamanları o işlev yardımıyla hesaplanınca GünlükPlan
nesneleri çıkışa şuna benzer şekilde yazdırılabilirler:
10:30-11:45 "Bisiklet gezisi" toplantısı (4 katılımcı) 12:30-14:00 Yemek, Yer: Taksim 15:30-17:30 "Bütçe" toplantısı (8 katılımcı)