Object
Açıkça başka bir sınıftan türetilmeyen sınıflar otomatik olarak Object
adlı sınıftan türerler.
Sıradüzenin en üstündeki sınıf Object
'ten otomatik olarak türer:
class Çalgı : Object { // ": Object" yazılmaz; otomatiktir // ... } class TelliÇalgı : Çalgı { // dolaylı olarak Object'ten türer // ... }
En üstteki sınıf Object
'ten türediği için, altındaki bütün sınıflar da dolaylı olarak Object
'ten türerler. Bu anlamda "her sınıf, Object
türündendir".
Bu türeme sonucunda her sınıf Object
'in bazı üye işlevlerini edinir:
toString
: Nesnenin dizgi olarak ifadesi.opEquals
: Eşitlik karşılaştırması.opCmp
: Sıra karşılaştırması.toHash
: Eşleme tablosu indeks değeri.
Bu işlevlerden son üçü sınıf nesnelerinin değerlerini ön plana çıkartmak ve onları eşleme tablolarında indeks türü olarak kullanmak için gereklidir.
Türeme yoluyla edinildikleri için bu işlevlerin türeyen tür için override
anahtar sözcüğü ile tanımlanmaları gerekir.
Not: Object
'ten edinilen başka üyeler de vardır. Bu bölümde yalnızca bu dört işlevini göreceğiz.
typeid
ve TypeInfo
Object
sınıfı, std
pakedinin parçası olmayan object
modülünde tanımlanmıştır. Bu modül türler hakkında bilgi taşıyan TypeInfo
sınıfını da tanımlar. Programın çalışması sırasında her farklı tür için farklı bir TypeInfo
nesnesi vardır. typeid
ifadesi belirli bir türe karşılık gelen TypeInfo
nesnesine erişim sağlar. Biraz aşağıda göreceğimiz gibi, TypeInfo
sınıfı türlerin eşit olup olmadıklarını belirlemek yanında türlerin özel işlevlerine (toHash
, postblit
, vs.) de erişim sağlar.
TypeInfo
her zaman için çalışma zamanındaki asıl tür ile ilgilidir. Örneğin, aşağıdaki Kemençe
ve Saz
doğrudan TelliÇalgı
sınıfından ve dolaylı olarak Çalgı
sınıfından türemiş olsalar da ikisinin TypeInfo
nesneleri farklıdır:
class Çalgı { } class TelliÇalgı : Çalgı { } class Kemençe : TelliÇalgı { } class Saz : TelliÇalgı { } void main() { TypeInfo k = typeid(Kemençe); TypeInfo s = typeid(Saz); assert(k != s); // ← türler aynı değil }
Yukarıdaki kullanımlarda typeid
'ye türün kendisi verilmektedir (Kemençe
gibi). typeid
diğer kullanımında ifade alır ve o ifadenin değerinin türü ile ilgili olan TypeInfo
nesnesini döndürür. Örneğin, aşağıdaki işlev birbirleriyle ilgili olsalar da farklı türden olan iki parametre almaktadır:
import std.stdio; // ... void foo(Çalgı ç, TelliÇalgı t) { const aynı_mı = (typeid(ç) == typeid(t)); writefln("Türleri %s.", aynı_mı ? "aynı" : "aynı değil"); } // ... auto a = new Kemençe(); auto b = new Kemençe(); foo(a, b);
Yukarıdaki foo
çağrısı için gönderilen asıl parametre değerlerinin ikisi de Kemençe
türünden olduklarından foo
onların aynı türden olduklarını belirler:
Türleri aynı.
Kendilerine verilen ifadeleri işletmeyen .sizeof
ve typeof
'un aksine, typeid
verilen ifadeyi işletmek zorundadır:
import std.stdio; int foo(string bilgi) { writefln("'%s' sırasında çağrıldı.", bilgi); return 0; } void main() { const s = foo("sizeof").sizeof; // foo() çağrılmaz alias T = typeof(foo("typeof")); // foo() çağrılmaz auto ti = typeid(foo("typeid")); // foo() çağrılır }
Programın çıktısında görüldüğü gibi, yalnızca typeid
'nin ifadesi işletilmiştir:
'typeid' sırasında çağrıldı.
Bu farkın nedeni, bazı ifadelerin asıl türlerinin ancak o ifadeler işletildikten sonra bilinebilmesidir. Örneğin, aşağıdaki işlevin asıl dönüş türü aldığı parametre değerine bağlı olarak her çağrıda ya Kemençe
ya da Saz
olacaktır:
Çalgı foo(int i) { return (i % 2) ? new Kemençe() : new Saz(); }
TypeInfo
'nun dizi, yapı, sınıf, vs. gibi çeşitli türler için farklı alt sınıfları vardır. Bunlar arasından TypeInfo_Class
özellikle yararlıdır. Örneğin, bir sınıf nesnesinin asıl türü TypeInfo_Class.name
niteliğinden string
olarak öğrenilebilir. Bir sınıf nesnesinin .classinfo
niteliği o nesnenin türüne özgü TypeInfo_Class
nesnesini verir:
TypeInfo_Class bilgi = a.classinfo; string asılTürİsmi = bilgi.name;
toString
Yapılarda olduğu gibi, toString
sınıf nesnelerinin dizgi olarak kullanılmalarını sağlar:
const saat = new Saat(20, 30, 0); writeln(saat); // saat.toString()'i çağırır
Sınıfın Object
'ten kalıtım yoluyla edindiği toString
işlevi fazla kullanışlı değildir; döndürdüğü string
yalnızca türün ismini içerir:
deneme.Saat
Sınıfın isminden önceki bölüm, yani yukarıdaki deneme
, o sınıfı içeren modülün ismini belirtir. Ona bakarak, Saat
sınıfının deneme.d
isimli bir kaynak dosya içinde tanımlandığını anlayabiliriz.
Önceki bölümde olduğu gibi, anlamlı bir string
üretmesi için bu işlev hemen hemen her zaman için özel olarak tanımlanır:
import std.string; class Saat { override string toString() const { return format("%02s:%02s:%02s", saat, dakika, saniye); } // ... } class ÇalarSaat : Saat { override string toString() const { return format("%s ♫%02s:%02s", super.toString(), alarmSaati, alarmDakikası); } // ... } // ... auto başucuSaati = new ÇalarSaat(20, 30, 0, 7, 0); writeln(başucuSaati);
Çıktısı:
20:30:00 ♫07:00
opEquals
İşleç Yükleme bölümünde gördüğümüz gibi, bu üye işlev ==
işlecinin tanımını belirler (ve dolaylı olarak !=
işlecinin tanımını). İşlevin dönüş değeri nesneler eşitlerse true
, değillerse false
olmalıdır.
Uyarı: Bu işlevin opCmp
ile tutarlı olması gerekir; true
döndürdüğü durumda opCmp
da sıfır döndürmelidir.
Yapıların aksine, derleyici a == b
gibi bir ifadeyi otomatik olarak a.opEquals(b)
ifadesine dönüştürmez. İki sınıf nesnesi karşılaştırıldıklarında aşağıdaki dört adımlık algoritma uygulanır:
bool opEquals(Object a, Object b) { if (a is b) return true; // (1) if (a is null || b is null) return false; // (2) if (typeid(a) == typeid(b)) return a.opEquals(b); // (3) return a.opEquals(b) && b.opEquals(a); // (4) }
- İki değişken de aynı nesneye erişim sağlıyorlarsa (veya ikisi de
null
iseler) eşittirler. - Yalnızca birisi
null
ise eşit değildirler. - Her iki nesne de aynı türden iseler ve o türün
opEquals
işlevi tanımlanmışsaa.opEquals(b)
işletilir. - Aksi taktirde, eşit olarak kabul edilebilmeleri için eğer tanımlanmışlarsa hem
a.opEquals(b)
'nin hem deb.opEquals(a)
'nıntrue
üretmeleri gerekir.
Dolayısıyla, opEquals
programcı tarafından özellikle tanımlanmamışsa o sınıfın nesnelerinin değerlerine bakılmaz; iki sınıf değişkeninin aynı nesneye erişim sağlayıp sağlamadıklarına bakılır:
auto değişken0 = new Saat(6, 7, 8); auto değişken1 = new Saat(6, 7, 8); assert(değişken0 != değişken1); // eşit değiller // (çünkü farklı nesneler)
Yukarıdaki koddaki iki nesne aynı parametre değerleriyle kuruldukları halde, new
ile ayrı ayrı kurulmuş oldukları için iki farklı nesnedir. Bu yüzden, onlara erişim sağlayan değişken0
ve değişken1
değişkenleri Object
'in gözünde eşit değillerdir.
Öte yandan, aynı nesneye erişim sağladıkları için şu iki değişken eşittir:
auto ortak0 = new Saat(9, 10, 11); auto ortak1 = ortak0; assert(ortak0 == ortak1); // eşitler // (çünkü aynı nesne)
Bazen nesneleri böyle kimliklerine göre değil, değerlerine göre karşılaştırmak isteriz. Örneğin değişken0
'ın ve değişken1
'in erişim sağladıkları nesnelerin değerlerinin eşit olmalarına bakarak, ==
işlecinin true
üretmesini bekleyebiliriz.
Yapılardan farklı olarak, ve Object
'ten kalıtımla edinildiği için, opEquals
işlevinin parametresi Object
'tir. O yüzden, bu işlevi kendi sınıfımız için tanımlarken parametresini Object
olarak yazmamız gerekir:
class Saat { override bool opEquals(Object o) const { // ... } // ... }
Kendimiz Object
olarak doğrudan kullanmayacağımız için bu parametrenin ismini kullanışsız olarak o
diye seçmekte bir sakınca görmüyorum. İlk ve çoğu durumda da tek işimiz, onu bir tür dönüşümünde kullanmak olacak.
opEquals
'a parametre olarak gelen nesne, kod içinde ==
işlecinin sağ tarafında yazılan nesnedir. Örneğin şu iki satır birbirinin eşdeğeridir:
değişken0 == değişken1; // o, değişken1'i temsil eder
Bu işleçteki amaç bu sınıftan iki nesneyi karşılaştırmak olduğu için, işlevi tanımlarken yapılması gereken ilk şey, parametre olarak gelen Object
'in türünü kendi sınıfımızın türüne dönüştürmektir. Sağdaki nesneyi değiştirmek gibi bir niyetimiz de olmadığı için, tür dönüşümünde const
belirtecini kullanmak da uygun olur:
override bool opEquals(Object o) const { const sağdaki = cast(const Saat)o; // ... }
Hatırlayacağınız gibi, tür dönüşümü için std.conv.to
işlevi de kullanılabilir:
import std.conv; // ... const sağdaki = to!(const Saat)(o);
Yukarıdaki tür dönüşümü işlemi ya sağdaki
'nin türünü bu şekilde const Saat
olarak belirler ya da dönüşüm uyumsuzsa null
üretir.
Burada karar verilmesi gereken önemli bir konu, sağdaki nesnenin türünün bu nesnenin türü ile aynı olmadığında ne olacağıdır. Sağdaki nesnenin tür dönüşümü sonucunda null
üretmesi, sağdaki nesnenin aslında bu türe dönüştürülemediği anlamına gelir.
Ben nesnelerin eşit kabul edilebilmeleri için bu dönüşümün başarılı olması gerektiğini varsayacağım. Bu yüzden eşitlik karşılaştırmalarında öncelikle sağdaki
'nin null
olmadığına bakacağım. Zaten null
olduğu durumda sağdaki
'nin üyelerine erişmek hatalıdır:
class Saat { int saat; int dakika; int saniye; override bool opEquals(Object o) const { const sağdaki = cast(const Saat)o; return (sağdaki && (saat == sağdaki.saat) && (dakika == sağdaki.dakika) && (saniye == sağdaki.saniye)); } // ... }
İşlevin bu tanımı sayesinde, ==
işleci Saat
nesnelerini artık değerlerine göre karşılaştırır:
auto değişken0 = new Saat(6, 7, 8); auto değişken1 = new Saat(6, 7, 8); assert(değişken0 == değişken1); // artık eşitler // (çünkü değerleri aynı)
opEquals
'ı tanımlarken, eğer varsa ve nesnelerin eşit kabul edilmeleri için gerekliyse, üst sınıfın üyelerini de unutmamak gerekir. Örneğin alt sınıf olan ÇalarSaat
'in nesnelerini karşılaştırırken, Saat
'ten kalıtımla edindiği parçaları da karşılaştırmak anlamlı olur:
class ÇalarSaat : Saat { int alarmSaati; int alarmDakikası; override bool opEquals(Object o) const { const sağdaki = cast(const ÇalarSaat)o; return (sağdaki && (alarmSaati == sağdaki.alarmSaati) && (alarmDakikası == sağdaki.alarmDakikası) && super.opEquals(o)); } // ... }
Oradaki ifade super
'in opEquals
işlevini çağırır ve eşitlik kararında onun da sonucunu kullanmış olur. Onun yerine daha kısaca super == o
da yazılabilir. Ancak, öyle yazıldığında yukarıdaki dört adımlı algoritma tekrar işletileceğinden kod biraz daha yavaş olabilir.
opCmp
Sınıf nesnelerini sıralamak için kullanılır. <
, <=
, >
, ve >=
işleçlerinin tanımı için perde arkasında bu işlev kullanılır.
Bu işlevin dönüş değerini <
işleci üzerinde düşünebilirsiniz: Soldaki nesne önce olduğunda eksi bir değer, sağdaki nesne önce olduğunda artı bir değer, ikisi eşit olduklarında sıfır döndürmelidir.
Uyarı: Bu işlevin opEquals
ile tutarlı olması gerekir; sıfır döndürdüğü durumda opEquals
da true
döndürmelidir.
toString
'in ve opEquals
'un aksine, bu işlevin Object
sınıfından kalıtımla edinilen bir davranışı yoktur. Tanımlanmadan kullanılırsa hata atılır:
auto değişken0 = new Saat(6, 7, 8); auto değişken1 = new Saat(6, 7, 8); assert(değişken0 <= değişken1); // ← Hata atılır
object.Exception: need opCmp for class deneme.Saat
Yukarıda opEquals
için söylenenler bu işlev için de geçerlidir: Sağdaki nesnenin türünün bu nesnenin türüne eşit olmadığı durumda hangisinin daha önce sıralanması gerektiği konusuna bir şekilde karar vermek gerekir.
Bunun en kolayı bu kararı derleyiciye bırakmaktır, çünkü derleyici türler arasında zaten genel bir sıralama belirler. Türler aynı olmadıklarında bu sıralamadan yararlanmanın yolu, typeid
'lerinin opCmp
işlevinden yararlanmaktır:
class Saat { int saat; int dakika; int saniye; override int opCmp(Object o) const { /* Türler aynı olmadıklarında türlerin genel * sıralamasından yararlanıyoruz. */ if (typeid(this) != typeid(o)) { return typeid(this).opCmp(typeid(o)); } const sağdaki = cast(const Saat)o; /* sağdaki'nin null olup olmadığına bakmaya gerek yok * çünkü buraya gelinmişse 'o' ile aynı türdendir. */ if (saat != sağdaki.saat) { return saat - sağdaki.saat; } else if (dakika != sağdaki.dakika) { return dakika - sağdaki.dakika; } else { return saniye - sağdaki.saniye; } } // ... }
Yukarıdaki tanım, nesneleri sıralama amacıyla karşılaştırırken öncelikle türlerinin uyumlu olup olmadıklarına bakıyor. Eğer uyumlu iseler saat bilgisini dikkate alıyor; saatler eşitlerse dakikalara, onlar da eşitlerse saniyelere bakıyor.
Ne yazık ki, bu işlevin bu gibi karşılaştırmalarda daha güzel veya daha etkin bir yazımı yoktur. Eğer daha uygun bulursanız, if-else-if zinciri yerine onun eşdeğeri olan üçlü işleci de kullanabilirsiniz:
override int opCmp(Object o) const { if (typeid(this) != typeid(o)) { return typeid(this).opCmp(typeid(o)); } const sağdaki = cast(const Saat)o; return (saat != sağdaki.saat ? saat - sağdaki.saat : (dakika != sağdaki.dakika ? dakika - sağdaki.dakika : saniye - sağdaki.saniye)); }
Bu işlevi bir alt sınıf için tanımlarken ve karşılaştırmada önemi varsa, üst sınıfını da unutmamak gerekir. Örneğin, aşağıdaki ÇalarSaat.opCmp
sıralama kararında öncelikle üst sınıfından yararlanıyor:
class ÇalarSaat : Saat { override int opCmp(Object o) const { const sağdaki = cast(const ÇalarSaat)o; const int üstSonuç = super.opCmp(o); if (üstSonuç != 0) { return üstSonuç; } else if (alarmSaati != sağdaki.alarmSaati) { return alarmSaati - sağdaki.alarmSaati; } else { return alarmDakikası - sağdaki.alarmDakikası; } } // ... }
Üst sınıfın sıfırdan farklı bir değer döndürmesi durumunda, iki nesnenin sıraları ile ilgili yeterli bilgi edinilmiştir; ve o değer döndürülür. Yukarıdaki kodda, alt sınıfın üyelerine ancak üst sınıf parçaları eşit çıktığında bakılmaktadır.
Artık bu türün nesneleri sıralama karşılaştırmalarında kullanılabilir:
auto çs0 = new ÇalarSaat(8, 0, 0, 6, 30); auto çs1 = new ÇalarSaat(8, 0, 0, 6, 31); assert(çs0 < çs1);
O kodda diğer bütün üyeleri eşit olduğu için, çs0
ve çs1
'in nasıl sıralanacaklarını en son bakılan alarm dakikası belirler.
Bu işlev yalnızca kendi yazdığımız kodlarda kullanılmak için değildir. Programda kullandığımız kütüphaneler ve dil olanakları da bu işlevi çağırabilir. Örneğin bir dizi içindeki nesnelerin sort
ile sıralanmalarında, veya sınıfın bir eşleme tablosunda indeks türü olarak kullanılmasında da perde arkasında bu işlevden yararlanılır.
Dizgi türünden olan üyeler için opCmp
Dizgi türündeki üyeler için opCmp
işlevini eksi, sıfır, veya artı döndürecek şekilde uzun uzun şöyle yazabilirsiniz:
import std.exception; class Öğrenci { string isim; override int opCmp(Object o) const { const sağdaki = cast(Öğrenci)o; enforce(sağdaki); if (isim < sağdaki.isim) { return -1; } else if (isim > sağdaki.isim) { return 1; } else { return 0; } } // ... }
Onun yerine, std.algorithm
modülünde tanımlanmış olan ve aynı karşılaştırmayı daha hızlı olarak gerçekleştiren cmp
işlevini de kullanabilirsiniz:
import std.algorithm; class Öğrenci { string isim; override int opCmp(Object o) const { const sağdaki = cast(Öğrenci)o; enforce(sağdaki); return cmp(isim, sağdaki.isim); } // ... }
Bu türün, kendisiyle uyumsuz olan türlerle sıra karşılaştırılmasında kullanılmasına izin vermediğine dikkat edin. Bu denetimi Object
'ten Öğrenci
'ye tür dönüşümünün başarılı olmasına enforce
ile bakarak sağlıyor.
toHash
Bu işlev, sınıfın eşleme tablolarında indeks türü olarak kullanılabilmesini sağlar. Eşleme tablosunun eleman türü olarak kullanıldığı durumda bir etkisi yoktur.
Bu işlevin eşleme tablolarında indeks türü olarak kullanılabilmesi için opEquals
işlevinin de tanımlanmış olmaları gerekir.
Eşleme tablosu indeks değerleri
Eşleme tabloları eleman erişimini çok hızlı şekilde gerçekleştiren veri yapılarıdır. Üstelik bunu, tabloda ne kadar eleman bulunduğundan bağımsız olarak yapabilirler. (Not: Her şeyin olduğu gibi bu hızın da bir bedeli vardır: elemanları sırasız olarak tutmak zorundadırlar, ve kesinlikle gereken miktardan daha fazla bellek kullanıyor olabilirler.)
Eşleme tablolarının bu hızı, indeks olarak kullanılan türü önce hash denen bir tamsayı değere çevirmelerinden kaynaklanır. Bu tamsayıyı kendilerine ait bir dizinin indeksi olarak kullanırlar.
Bu yöntemin hızdan başka bir yararı, tamsayıya dönüştürülebilen her türün eşleme tablosu indeks türü olarak kullanılabilmesidir.
toHash
, sınıf nesnelerinin bu amaç için indeks değerleri döndürmelerini sağlar.
Bu sayede, pek mantıklı olmasa da, Saat
türünü bile indeks olarak kullanabiliriz:
string[Saat] zamanİsimleri; zamanİsimleri[new Saat(12, 0, 0)] = "öğleni gösteren saat";
Object
'ten kalıtım yoluyla edinilen toHash
işlevi, farklı nesneler için farklı indeks değerleri üretecek şekilde tanımlanmıştır. Bu, opEquals
'un farklı nesnelerin eşit olmadıklarını kabul etmesine benzer.
Yukarıdaki kod Saat
sınıfı için özel bir toHash
işlevi tanımlanmamış olsa bile derlenir; ama istediğimiz gibi çalışmaz. Yukarıdaki tabloya eklenmiş olan Saat
nesnesi ile aynı değere sahip olan, ama ondan farklı bir Saat
nesnesi ile erişmek istesek; doğal olarak tablodaki "öğleni gösteren saat" değerini bulmayı bekleriz:
if (new Saat(12, 0, 0) in zamanİsimleri) { writeln("var"); } else { writeln("yok"); }
Ne yazık ki, oradaki in
işleci false
döndürür; yani bu nesnenin tabloda bulunmadığını belirtir:
yok
Bunun nedeni, yerleştirilirken kullanılan nesne ile erişirken kullanılan nesnenin new
ile ayrı ayrı oluşturulmuş olmalarıdır; yani ikisi farklı nesnelerdir.
Dolayısıyla; Object
'ten kalıtımla edinilen toHash
, eşleme tablolarında indeks değeri olarak kullanılmaya çoğu durumda elverişli değildir. toHash
'i, bir tamsayı indeks döndürecek şekilde bizim yazmamız gerekir.
toHash
için seçilecek üyeler
İndeks değeri, nesnenin üyeleri kullanılarak hesaplanır. Ancak, her üye bu indeks hesabına uygun değildir.
Bunun için seçilecek üyeler, nesneyi diğer nesnelerden ayırt etmeye yarayan üyeler olmalıdır. Örneğin Öğrenci
gibi bir sınıfın isim
ve soyad
üyelerinin ikisi birden nesneleri ayırt etmek için kullanılabilir; çünkü bu iki üyenin her nesnede farklı olduğunu düşünebiliriz. (İsim benzerliklerini gözardı ediyorum.)
Öte yandan, Öğrenci
sınıfının notlar
dizisi uygun değildir; çünkü hem birden fazla nesnede aynı not değerleri bulunabilir; hem de aynı öğrencinin notları zamanla değişebilir.
İndeks değerinin hesaplanması
İndeks değerinin hesabı eşleme tablosunun hızını doğrudan etkiler. Üstelik, her hesap her çeşit veri üzerinde aynı derece etkili değildir. Uygun hesaplama yöntemleri bu kitabın kapsamı dışında kaldığı için bu konunun ayrıntısına girmeyeceğim ve genel bir ilke vermekle yetineceğim: Genel olarak, değerlerinin farklı oldukları kabul edilen nesnelerin farklı indeks değerlerinin olması etkinlik açısından iyidir. Farklı değerli nesnelerin aynı indeks değerini üretmeleri hata değildir; performans açısından istenmeyen bir durumdur.
Saat
nesnelerinin farklı kabul edilebilmeleri için bütün üyelerinin değerlerinin önemli olduğunu düşünebiliriz. Bu yüzden, indeks değeri olarak o üç üyeden yararlanılarak elde edilen bir tamsayı değer kullanılabilir. Eğer indeks değeri olarak gece yarısından kaç saniye ötede olunduğu kullanılırsa, herhangi bir üyesi değişik olan iki nesnenin indeks değerlerinin farklı olacağı garanti edilmiş olur:
class Saat { int saat; int dakika; int saniye; override size_t toHash() const { // Saatte 3600 ve dakikada 60 saniye bulunduğu için: return (3600 * saat) + (60 * dakika) + saniye; } // ... }
Eşleme tablolarında indeks türü olarak Saat
kullanıldığında artık programcı tarafından tanımlanmış olan bu toHash
kullanılır. Bunun sonucunda, yukarıdaki kodda new
ile farklı olarak kurulmuş olan iki nesnenin saat, dakika, ve saniye değerleri aynı olduğundan eşleme tablosunda aynı indeks değeri üretilir.
Programın çıktısı artık beklenen sonucu verir:
var
Önceki işlevlerde olduğu gibi, üst sınıfı unutmamak gerekebilir. Örneğin, ÇalarSaat
'in toHash
işlevi Saat
'inkinden şöyle yararlanabilir:
class ÇalarSaat : Saat { int alarmSaati; int alarmDakikası; override size_t toHash() const { return super.toHash() + alarmSaati + alarmDakikası; } // ... }
Not: Yukarıdaki hesabı bir örnek olarak kabul edin. Tamsayı değerleri toplayarak üretilen indeks değerleri genelde eşleme tablosu performansı açısından iyi değildir.
D; kesirli sayılar, diziler, ve yapı türleri için çoğu duruma uygun olan indeks değeri algoritmaları kullanır. Bu algoritmalardan programcı da yararlanabilir.
Kulağa karmaşık geldiği halde aslında çok kısaca yapmamız gereken; önce typeid
'yi üye ile, sonra da typeid
'nin döndürdüğü nesnenin getHash
üye işlevini üyenin adresi ile çağırmaktır. Hepsinin dönüş değeri, o üyeye uygun bir indeks değeridir. Bu; kesirli sayılar, diziler ve yapılar için hep aynı şekilde yazılır.
Öğrencinin ismini bir string
üyesinde tutan ve eşleme tabloları için indeks değeri olarak bundan yararlanmak isteyen bir sınıfın toHash
işlevi şöyle yazılabilir:
class Öğrenci { string isim; override size_t toHash() const { return typeid(isim).getHash(&isim); } // ... }
Yapılar için toHash
Yapılar değer türleri olduklarından onların indeks değerleri zaten otomatik olarak ve etkin bir algoritmayla hesaplanır. O algoritma nesnenin bütün üyelerini dikkate alır.
Eğer herhangi bir nedenle, örneğin bir öğrenci yapısının not bilgisini dışarıda bırakacak şekilde kendiniz yazmak isterseniz; toHash
'i yapılar için de tanımlayabilirsiniz.
Problemler
- Elimizde renkli noktaları ifade eden bir sınıf olsun:
enum Renk { mavi, yeşil, kırmızı } class Nokta { int x; int y; Renk renk; this(int x, int y, Renk renk) { this.x = x; this.y = y; this.renk = renk; } }
Bu sınıfın
opEquals
işlevini rengi gözardı edecek şekilde yazın. Şu iki nokta, renkleri farklı olduğu halde eşit çıksınlar. Yaniassert
denetimi doğru çıksın:// Renkleri farklı auto maviNokta = new Nokta(1, 2, Renk.mavi); auto yeşilNokta = new Nokta(1, 2, Renk.yeşil); // Yine de eşitler assert(maviNokta == yeşilNokta);
- Aynı sınıf için
opCmp
işlevini önceliklex
'e sonray
'ye bakacak şekilde yazın. Aşağıdakiassert
denetimleri doğru çıksın:auto kırmızıNokta1 = new Nokta(-1, 10, Renk.kırmızı); auto kırmızıNokta2 = new Nokta(-2, 10, Renk.kırmızı); auto kırmızıNokta3 = new Nokta(-2, 7, Renk.kırmızı); assert(kırmızıNokta1 < maviNokta); assert(kırmızıNokta3 < kırmızıNokta2); /* Mavi renk daha önce olduğu halde, renk gözardı * edildiğinden maviNokta yeşilNokta'dan daha önce * olmamalıdır. */ assert(!(maviNokta < yeşilNokta));
Bu sınıfın
opCmp
işlevini de yukarıdaÖğrenci
sınıfında olduğu gibi uyumsuz türlerin karşılaştırılmalarını desteklemeyecek biçimde gerçekleştirebilirsiniz. - Üç noktayı bir araya getiren başka bir sınıf olsun:
class ÜçgenBölge { Nokta[3] noktalar; this(Nokta bir, Nokta iki, Nokta üç) { noktalar = [ bir, iki, üç ]; } }
O sınıf için
toHash
işlevini bütün noktalarını kullanacak biçimde yazın. Yine aşağıdakiassert
'ler doğru çıksın:/* bölge1 ve bölge2, değerleri aynı olan farklı noktalarla * kuruluyorlar. (Hatırlatma: maviNokta ve yeşilNokta * değer olarak eşit kabul ediliyorlardı.) */ auto bölge1 = new ÜçgenBölge(maviNokta, yeşilNokta, kırmızıNokta1); auto bölge2 = new ÜçgenBölge(yeşilNokta, maviNokta, kırmızıNokta1); // Yine de eşitler assert(bölge1 == bölge2); // Bir eşleme tablosu double[ÜçgenBölge] bölgeler; // bölge1 ile indeksleniyor bölgeler[bölge1] = 1.25; // bölge2 ile de aynı veriye erişiliyor assert(bölge2 in bölgeler); assert(bölgeler[bölge2] == 1.25);
toHash
tanımlandığındaopEquals
işlevinin de tanımlanması gerektiğini unutmayın.